<commit_before>var attrs = [
	"source",
	"protocol",
	"authority",
	"userInfo",
	"user",
	"password",
	"host",
	"port",
	"relative",
	"path",
	"directory",
	"file",
	"query",
	"anchor"
];

var URI = exports = Class(function(supr) {
	this.init = function(url, isStrict) {
		if (url instanceof URI) {
			for (var i = 0, attr; attr = attrs[i]; ++i) {
				this['_' + attr] = url['_' + attr];
			}
			return;
		}

		this._isStrict = isStrict;

		var uriData = exports.parse(url, isStrict);
		for (var attr in uriData) {
			this['_' + attr] = uriData[attr];
		};
	}

	for (var i = 0, attr; attr = attrs[i]; ++i) {
		(function(attr) {
			var fNameSuffix = attr.charAt(0).toUpperCase() + attr.slice(1);
			this['get' + fNameSuffix] = function() {
				return this['_' + attr];
			};
			this['set' + fNameSuffix] = function(val) {
				this['_' + attr] = val;
				return this;
			};
		}).call(this, attr);
	};

	this.query = function(key) { return exports.parseQuery(this._query)[key]; }
	this.hash = function(key) { return exports.parseQuery(this._anchor)[key]; }

	this.addHash = function(kvp) {
		var hash = exports.parseQuery(this._anchor);
		for (var i in kvp) { hash[i] = kvp[i]; }
		this._anchor = exports.buildQuery(hash);
		return this;
	}

	this.addQuery = function(kvp) {
		var query = exports.parseQuery(this._query);
		for (var i in kvp) { query[i] = kvp[i]; }
		this._query = exports.buildQuery(query);
		return this;
	}

	this.removeQuery = function(keys) {
		var query = exports.parseQuery(this._query);
		if (isArray(keys)) {
			for (var i = 0, n = keys.length; i < n; ++i) {
				delete query[keys[i]];
			}
		} else {
			delete query[keys];
		}
		this._query = exports.buildQuery(query);
		return this;
	}

	this.toString = this.render = function(onlyBase) {
		// XXX TODO: This is vaguely reasonable, but not complete. fix it...
		var a = this._protocol ? this._protocol + "://" : ""
		var b = this._host ? this._host + ((this._port || 80) == 80 ? "" : ":" + this._port) : "";

		if (onlyBase) {
			return a + b;
		}

		var c = this._path;
		var d = this._query ? '?' + this._query : '';
		var e = this._anchor ? '#' + this._anchor : '';
		return a + b + c + d + e;
	};
});

exports.relativeTo = function(url, base) {
	url = String(url);

	if (/^http(s?):\/\//.test(url)) { return url; }
	if (url.charAt(0) == '/') {
		var baseuri = new exports(base);
		url = baseuri.toString(true) + url;
	} else {
		url = base + url;
	}

	return exports.resolveRelative(url);
}

exports.resolveRelative = function(url) {
	var prevUrl;

	// remove ../ with preceeding folder
	while((prevUrl = url) != (url = url.replace(/(^|\/)([^\/]+)\/\.\.\//g, '/'))) {};

	// remove ./ if it isn't preceeded by a .
	return url.replace(/[^.]\.\//g, '');
}

exports.buildQuery = function(kvp) {
	var pairs = [];
	for (var key in kvp) {
		pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(kvp[key]));
	}
	return pairs.join('&');
}

exports.parseQuery = function(str) {
	var pairs = str.split('&'),
		n = pairs.length,
		data = {};
	for (var i = 0; i < n; ++i) {
		var pair = pairs[i].split('='),
			key = decodeURIComponent(pair[0]);
		if (key) { data[key] = decodeURIComponent(pair[1]); }
	}
	return data;
}

// Regexs are based on parseUri 1.2.2
// Original: (c) Steven Levithan <stevenlevithan.com>
// Original: MIT License

var strictRegex = /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/;
var looseRegex = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
var queryStringRegex = /(?:^|&)([^&=]*)=?([^&]*)/g;

exports.parse = function(str, isStrict) {
	var regex = isStrict ? strictRegex : looseRegex;
	var result = {};
	var match = regex.exec(str);
	for (var i = 0, attr; attr = attrs[i]; ++i) {
		result[attr] = match[i] || "";
	}

	var qs = result['queryKey'] = {};
	result['query'].replace(queryStringRegex, function(check, key, val) {
		if (check) {
			qs[key] = val;
		}
	});

	return result;
}

exports.isSameDomain = function(urlA, urlB) {
	var a = exports.parse(urlA);
	var b = exports.parse(urlB);
	return ((a.port == b.port ) && (a.host == b.host) && (a.protocol == b.protocol));
};
<commit_msg>don't do anything if there isn't a base url<commit_after>var attrs = [
	"source",
	"protocol",
	"authority",
	"userInfo",
	"user",
	"password",
	"host",
	"port",
	"relative",
	"path",
	"directory",
	"file",
	"query",
	"anchor"
];

var URI = exports = Class(function(supr) {
	this.init = function(url, isStrict) {
		if (url instanceof URI) {
			for (var i = 0, attr; attr = attrs[i]; ++i) {
				this['_' + attr] = url['_' + attr];
			}
			return;
		}

		this._isStrict = isStrict;

		var uriData = exports.parse(url, isStrict);
		for (var attr in uriData) {
			this['_' + attr] = uriData[attr];
		};
	}

	for (var i = 0, attr; attr = attrs[i]; ++i) {
		(function(attr) {
			var fNameSuffix = attr.charAt(0).toUpperCase() + attr.slice(1);
			this['get' + fNameSuffix] = function() {
				return this['_' + attr];
			};
			this['set' + fNameSuffix] = function(val) {
				this['_' + attr] = val;
				return this;
			};
		}).call(this, attr);
	};

	this.query = function(key) { return exports.parseQuery(this._query)[key]; }
	this.hash = function(key) { return exports.parseQuery(this._anchor)[key]; }

	this.addHash = function(kvp) {
		var hash = exports.parseQuery(this._anchor);
		for (var i in kvp) { hash[i] = kvp[i]; }
		this._anchor = exports.buildQuery(hash);
		return this;
	}

	this.addQuery = function(kvp) {
		var query = exports.parseQuery(this._query);
		for (var i in kvp) { query[i] = kvp[i]; }
		this._query = exports.buildQuery(query);
		return this;
	}

	this.removeQuery = function(keys) {
		var query = exports.parseQuery(this._query);
		if (isArray(keys)) {
			for (var i = 0, n = keys.length; i < n; ++i) {
				delete query[keys[i]];
			}
		} else {
			delete query[keys];
		}
		this._query = exports.buildQuery(query);
		return this;
	}

	this.toString = this.render = function(onlyBase) {
		// XXX TODO: This is vaguely reasonable, but not complete. fix it...
		var a = this._protocol ? this._protocol + "://" : ""
		var b = this._host ? this._host + ((this._port || 80) == 80 ? "" : ":" + this._port) : "";

		if (onlyBase) {
			return a + b;
		}

		var c = this._path;
		var d = this._query ? '?' + this._query : '';
		var e = this._anchor ? '#' + this._anchor : '';
		return a + b + c + d + e;
	};
});

exports.relativeTo = function(url, base) {
	if (!base) { return url; }

	url = String(url);

	if (/^http(s?):\/\//.test(url)) { return url; }
	if (url.charAt(0) == '/') {
		var baseuri = new exports(base);
		url = baseuri.toString(true) + url;
	} else {
		url = base + url;
	}

	return exports.resolveRelative(url);
}

exports.resolveRelative = function(url) {
	var prevUrl;

	// remove ../ with preceeding folder
	while((prevUrl = url) != (url = url.replace(/(^|\/)([^\/]+)\/\.\.\//g, '/'))) {};

	// remove ./ if it isn't preceeded by a .
	return url.replace(/[^.]\.\//g, '');
}

exports.buildQuery = function(kvp) {
	var pairs = [];
	for (var key in kvp) {
		pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(kvp[key]));
	}
	return pairs.join('&');
}

exports.parseQuery = function(str) {
	var pairs = str.split('&'),
		n = pairs.length,
		data = {};
	for (var i = 0; i < n; ++i) {
		var pair = pairs[i].split('='),
			key = decodeURIComponent(pair[0]);
		if (key) { data[key] = decodeURIComponent(pair[1]); }
	}
	return data;
}

// Regexs are based on parseUri 1.2.2
// Original: (c) Steven Levithan <stevenlevithan.com>
// Original: MIT License

var strictRegex = /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/;
var looseRegex = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
var queryStringRegex = /(?:^|&)([^&=]*)=?([^&]*)/g;

exports.parse = function(str, isStrict) {
	var regex = isStrict ? strictRegex : looseRegex;
	var result = {};
	var match = regex.exec(str);
	for (var i = 0, attr; attr = attrs[i]; ++i) {
		result[attr] = match[i] || "";
	}

	var qs = result['queryKey'] = {};
	result['query'].replace(queryStringRegex, function(check, key, val) {
		if (check) {
			qs[key] = val;
		}
	});

	return result;
}

exports.isSameDomain = function(urlA, urlB) {
	var a = exports.parse(urlA);
	var b = exports.parse(urlB);
	return ((a.port == b.port ) && (a.host == b.host) && (a.protocol == b.protocol));
};
<|endoftext|>


<commit_before>import gql from 'graphql-tag';
import { useEffect } from 'react';
import { t, ngettext, msgid } from 'ttag';
import { useRouter } from 'next/router';
import { useQuery, useLazyQuery, useMutation } from '@apollo/react-hooks';
import Head from 'next/head';
import Link from 'next/link';
import { makeStyles } from '@material-ui/core/styles';
import Box from '@material-ui/core/Box';

import withData from 'lib/apollo';
import useCurrentUser from 'lib/useCurrentUser';
import { usePushToDataLayer } from 'lib/gtm';
import ExpandableText from 'components/ExpandableText';
import AppLayout from 'components/AppLayout';
import ArticleReply from 'components/ArticleReply';
import { Card, CardHeader, CardContent } from 'components/Card';
import EditorName from 'components/EditorName';
import Infos, { TimeInfo } from 'components/Infos';
import {
  SideSection,
  SideSectionHeader,
  SideSectionLinks,
  SideSectionLink,
  SideSectionText,
} from 'components/SideSection';

import { nl2br, ellipsis } from 'lib/text';

const useStyles = makeStyles(theme => ({
  root: {
    display: 'flex',
    padding: '24px 0',
    flexDirection: 'column',
    [theme.breakpoints.up('md')]: {
      flexDirection: 'row',
      alignItems: 'flex-start',
    },
  },
  main: {
    flex: 1,
    minWidth: 0,
    '& > *': {
      marginBottom: 12,
    },
    [theme.breakpoints.up('md')]: {
      flex: 3,
      marginRight: 12,
    },
  },
  articleLink: {
    color: 'inherit',
    textDecoration: 'none',
  },
  infos: {
    marginTop: 12,
  },
}));

const LOAD_REPLY = gql`
  query LoadReplyPage($id: String!) {
    GetReply(id: $id) {
      id
      text
      createdAt
      articleReplies {
        article {
          id
          text
          replyCount
        }
        createdAt
        status
        user {
          id
          name
          level
        }
        ...ArticleReplyData
      }
      similarReplies(orderBy: [{ _score: DESC }]) {
        edges {
          node {
            id
            text
            articleReplies(status: NORMAL) {
              articleId
            }
          }
        }
      }
    }
  }
  ${ArticleReply.fragments.ArticleReplyData}
`;

const LOAD_REPLY_FOR_USER = gql`
  query LoadReplyPageForUser($id: String!) {
    GetReply(id: $id) {
      id
      articleReplies {
        ...ArticleReplyForUser
      }
    }
  }
  ${ArticleReply.fragments.ArticleReplyForUser}
`;

const UPDATE_ARTICLE_REPLY_STATUS = gql`
  mutation UpdateArticleReplyStatus(
    $articleId: String!
    $replyId: String!
    $status: ArticleReplyStatusEnum!
  ) {
    UpdateArticleReplyStatus(
      articleId: $articleId
      replyId: $replyId
      status: $status
    ) {
      articleId
      replyId
      status
    }
  }
`;

/**
 * String wrapper for better i18n.
 *
 * @param {number} count
 * @return {JSX | null} Text that indicates reply count, or null when no other replies.
 */
function getReplyCountElem(count) {
  const otherCount = count - 1;
  if (otherCount <= 0) return null;
  return (

      {ngettext(
        msgid`${otherCount} other reply`,
        `${otherCount} other replies`,
        otherCount
      )}

  );
}

function ReplyPage() {
  const { query } = useRouter();
  const replyVars = { id: query.id };

  const { data, loading } = useQuery(LOAD_REPLY, { variables: replyVars });
  const [
    loadReplyForUser,
    { refetch: refetchReplyForUser, called: replyForUserCalled },
  ] = useLazyQuery(LOAD_REPLY_FOR_USER, {
    variables: replyVars,
    fetchPolicy: 'network-only',
  });

  const [
    updateArticleReplyStatus,
    { loading: updatingArticleReplyStatus },
  ] = useMutation(UPDATE_ARTICLE_REPLY_STATUS);

  const handleDelete = ({ articleId, replyId }) => {
    updateArticleReplyStatus({
      variables: { articleId, replyId, status: 'DELETED' },
    });
  };

  const handleRestore = ({ articleId, replyId }) => {
    updateArticleReplyStatus({
      variables: { articleId, replyId, status: 'NORMAL' },
      refetchQueries: ['LoadArticlePage'],
    });
  };

  const currentUser = useCurrentUser();
  const classes = useStyles();

  // Load user field when currentUser changes
  useEffect(() => {
    if (!replyForUserCalled) {
      loadReplyForUser();
    } else {
      refetchReplyForUser();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentUser]);

  const reply = data?.GetReply;
  usePushToDataLayer(!!reply, { event: 'dataLoaded' });

  if (loading) {
    return (
      <AppLayout>

          <title>{t`Loading`}</title>

          <Card>
            <CardContent>{t`Loading`}...</CardContent>
          </Card>

      </AppLayout>
    );
  }

  if (!reply) {
    return (
      <AppLayout>

          <title>{t`Not found`}</title>

          <Card>
            <CardContent>{t`Reply does not exist`}</CardContent>
          </Card>

      </AppLayout>
    );
  }

  const originalArticleReply = reply.articleReplies.reduce(
    (earliest, articleReply) =>
      articleReply.createdAt < earliest.createdAt ? articleReply : earliest,
    reply.articleReplies[0]
  );
  const isDeleted = originalArticleReply.status === 'DELETED';

  const otherArticleReplies = reply.articleReplies
    .filter(
      ({ article, status }) =>
        article.id !== originalArticleReply.article.id && status === 'NORMAL'
    )
    .sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));

  const similarReplies = reply?.similarReplies?.edges || [];

  return (
    <AppLayout>

        <title>
          {ellipsis(reply.text, { wordCount: 100 })} | {t`Cofacts`}
        </title>

          <Card>
            <CardHeader>{t`This reply`}</CardHeader>
            <CardContent>
              <ArticleReply
                articleReply={originalArticleReply}
                actionText={isDeleted ? t`Restore` : t`Delete`}
                onAction={isDeleted ? handleRestore : handleDelete}
                disabled={updatingArticleReplyStatus}
                linkToReply={false}
              />
            </CardContent>
          </Card>
          <Card>
            <CardHeader>{t`The reply is used in the following messages`}</CardHeader>
            <Link
              href="/article/[id]"
              as={`/article/${originalArticleReply.article.id}`}
            >

                <CardContent>
                  <ExpandableText lineClamp={5}>
                    {nl2br(originalArticleReply.article.text)}
                  </ExpandableText>
                  <Infos className={classes.infos}>
                    {isDeleted && {t`Deleted by its author`}}
                    {getReplyCountElem(originalArticleReply.article.replyCount)}
                    <TimeInfo time={originalArticleReply.createdAt}>
                      {timeAgoStr => t`${timeAgoStr} ago`}
                    </TimeInfo>
                  </Infos>
                </CardContent>

            </Link>
            {otherArticleReplies.map(ar => {
              const editorElem = ar.user ? (
                <EditorName
                  key="editor"
                  editorName={ar.user.name}
                  editorLevel={ar.user.level}
                />
              ) : (
                t`someone`
              );

              return (
                <Link
                  href="/article/[id]"
                  as={`/article/${ar.article.id}`}
                  key={ar.article.id}
                >

                    <CardContent>
                      <ExpandableText>{nl2br(ar.article.text)}</ExpandableText>
                      <Infos className={classes.infos}>
                        <>{t`Added by ${editorElem}`}</>
                        {getReplyCountElem(ar.article.replyCount)}
                        <TimeInfo time={ar.createdAt}>
                          {timeAgoStr => t`${timeAgoStr} ago`}
                        </TimeInfo>
                      </Infos>
                    </CardContent>

                </Link>
              );
            })}
          </Card>

        <SideSection>
          <SideSectionHeader>{t`Similar replies`}</SideSectionHeader>
          {similarReplies.length > 0 ? (
            <SideSectionLinks>
              {similarReplies.map(({ node }) => (
                <Link
                  key={node.id}
                  href="/reply/[id]"
                  as={`/reply/${node.id}`}
                  passHref
                >
                  <SideSectionLink>
                    <SideSectionText>{node.text}</SideSectionText>
                    <Infos className={classes.infos}>
                      {ngettext(
                        msgid`Used in ${node.articleReplies.length} message`,
                        `Used in ${node.articleReplies.length} messages`,
                        node.articleReplies.length
                      )}
                    </Infos>
                  </SideSectionLink>
                </Link>
              ))}
            </SideSectionLinks>
          ) : (
            <Box textAlign="center" pt={4} pb={3}>
              {t`No similar replies found`}
            </Box>
          )}
        </SideSection>

    </AppLayout>
  );
}

export default withData(ReplyPage);
<commit_msg>[reply] fix reply card content and editor name<commit_after>import gql from 'graphql-tag';
import { useEffect } from 'react';
import { t, jt, ngettext, msgid } from 'ttag';
import { useRouter } from 'next/router';
import { useQuery, useLazyQuery, useMutation } from '@apollo/react-hooks';
import Head from 'next/head';
import Link from 'next/link';
import { makeStyles } from '@material-ui/core/styles';
import Box from '@material-ui/core/Box';

import withData from 'lib/apollo';
import useCurrentUser from 'lib/useCurrentUser';
import { usePushToDataLayer } from 'lib/gtm';
import ExpandableText from 'components/ExpandableText';
import AppLayout from 'components/AppLayout';
import ArticleReply from 'components/ArticleReply';
import { Card, CardHeader, CardContent } from 'components/Card';
import EditorName from 'components/EditorName';
import Infos, { TimeInfo } from 'components/Infos';
import {
  SideSection,
  SideSectionHeader,
  SideSectionLinks,
  SideSectionLink,
  SideSectionText,
} from 'components/SideSection';

import { nl2br, ellipsis } from 'lib/text';

const useStyles = makeStyles(theme => ({
  root: {
    display: 'flex',
    padding: '24px 0',
    flexDirection: 'column',
    [theme.breakpoints.up('md')]: {
      flexDirection: 'row',
      alignItems: 'flex-start',
    },
  },
  main: {
    flex: 1,
    minWidth: 0,
    '& > *': {
      marginBottom: 12,
    },
    [theme.breakpoints.up('md')]: {
      flex: 3,
      marginRight: 12,
    },
  },
  articleLink: {
    color: 'inherit',
    textDecoration: 'none',
  },
  infos: {
    marginTop: 12,
  },
}));

const LOAD_REPLY = gql`
  query LoadReplyPage($id: String!) {
    GetReply(id: $id) {
      id
      text
      createdAt
      articleReplies {
        article {
          id
          text
          replyCount
        }
        createdAt
        status
        user {
          id
          name
          level
        }
        ...ArticleReplyData
      }
      similarReplies(orderBy: [{ _score: DESC }]) {
        edges {
          node {
            id
            text
            articleReplies(status: NORMAL) {
              articleId
            }
          }
        }
      }
    }
  }
  ${ArticleReply.fragments.ArticleReplyData}
`;

const LOAD_REPLY_FOR_USER = gql`
  query LoadReplyPageForUser($id: String!) {
    GetReply(id: $id) {
      id
      articleReplies {
        ...ArticleReplyForUser
      }
    }
  }
  ${ArticleReply.fragments.ArticleReplyForUser}
`;

const UPDATE_ARTICLE_REPLY_STATUS = gql`
  mutation UpdateArticleReplyStatus(
    $articleId: String!
    $replyId: String!
    $status: ArticleReplyStatusEnum!
  ) {
    UpdateArticleReplyStatus(
      articleId: $articleId
      replyId: $replyId
      status: $status
    ) {
      articleId
      replyId
      status
    }
  }
`;

/**
 * String wrapper for better i18n.
 *
 * @param {number} count
 * @return {JSX | null} Text that indicates reply count, or null when no other replies.
 */
function getReplyCountElem(count) {
  const otherCount = count - 1;
  if (otherCount <= 0) return null;
  return (

      {ngettext(
        msgid`${otherCount} other reply`,
        `${otherCount} other replies`,
        otherCount
      )}

  );
}

function ReplyPage() {
  const { query } = useRouter();
  const replyVars = { id: query.id };

  const { data, loading } = useQuery(LOAD_REPLY, { variables: replyVars });
  const [
    loadReplyForUser,
    { refetch: refetchReplyForUser, called: replyForUserCalled },
  ] = useLazyQuery(LOAD_REPLY_FOR_USER, {
    variables: replyVars,
    fetchPolicy: 'network-only',
  });

  const [
    updateArticleReplyStatus,
    { loading: updatingArticleReplyStatus },
  ] = useMutation(UPDATE_ARTICLE_REPLY_STATUS);

  const handleDelete = ({ articleId, replyId }) => {
    updateArticleReplyStatus({
      variables: { articleId, replyId, status: 'DELETED' },
    });
  };

  const handleRestore = ({ articleId, replyId }) => {
    updateArticleReplyStatus({
      variables: { articleId, replyId, status: 'NORMAL' },
      refetchQueries: ['LoadArticlePage'],
    });
  };

  const currentUser = useCurrentUser();
  const classes = useStyles();

  // Load user field when currentUser changes
  useEffect(() => {
    if (!replyForUserCalled) {
      loadReplyForUser();
    } else {
      refetchReplyForUser();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentUser]);

  const reply = data?.GetReply;
  usePushToDataLayer(!!reply, { event: 'dataLoaded' });

  if (loading) {
    return (
      <AppLayout>

          <title>{t`Loading`}</title>

          <Card>
            <CardContent>{t`Loading`}...</CardContent>
          </Card>

      </AppLayout>
    );
  }

  if (!reply) {
    return (
      <AppLayout>

          <title>{t`Not found`}</title>

          <Card>
            <CardContent>{t`Reply does not exist`}</CardContent>
          </Card>

      </AppLayout>
    );
  }

  const originalArticleReply = reply.articleReplies.reduce(
    (earliest, articleReply) =>
      articleReply.createdAt < earliest.createdAt ? articleReply : earliest,
    reply.articleReplies[0]
  );
  const isDeleted = originalArticleReply.status === 'DELETED';

  const otherArticleReplies = reply.articleReplies
    .filter(
      ({ article, status }) =>
        article.id !== originalArticleReply.article.id && status === 'NORMAL'
    )
    .sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));

  const similarReplies = reply?.similarReplies?.edges || [];

  return (
    <AppLayout>

        <title>
          {ellipsis(reply.text, { wordCount: 100 })} | {t`Cofacts`}
        </title>

          <Card>
            <CardHeader>{t`This reply`}</CardHeader>
            <CardContent>
              <ArticleReply
                articleReply={originalArticleReply}
                actionText={isDeleted ? t`Restore` : t`Delete`}
                onAction={isDeleted ? handleRestore : handleDelete}
                disabled={updatingArticleReplyStatus}
                linkToReply={false}
              />
            </CardContent>
          </Card>
          <Card>
            <CardHeader>{t`The reply is used in the following messages`}</CardHeader>
            <CardContent>
              <Link
                href="/article/[id]"
                as={`/article/${originalArticleReply.article.id}`}
              >

                  <ExpandableText lineClamp={5}>
                    {nl2br(originalArticleReply.article.text)}
                  </ExpandableText>
                  <Infos className={classes.infos}>
                    {isDeleted && {t`Deleted by its author`}}
                    {getReplyCountElem(originalArticleReply.article.replyCount)}
                    <TimeInfo time={originalArticleReply.createdAt}>
                      {timeAgoStr => t`${timeAgoStr} ago`}
                    </TimeInfo>
                  </Infos>

              </Link>
            </CardContent>
            {otherArticleReplies.map(ar => {
              const editorElem = ar.user ? (
                <EditorName
                  key="editor"
                  editorName={ar.user.name}
                  editorLevel={ar.user.level}
                />
              ) : (
                t`someone`
              );

              return (
                <CardContent key={ar.article.id}>
                  <Link href="/article/[id]" as={`/article/${ar.article.id}`}>

                      <ExpandableText>{nl2br(ar.article.text)}</ExpandableText>
                      <Infos className={classes.infos}>
                        <>{jt`Added by ${editorElem}`}</>
                        {getReplyCountElem(ar.article.replyCount)}
                        <TimeInfo time={ar.createdAt}>
                          {timeAgoStr => t`${timeAgoStr} ago`}
                        </TimeInfo>
                      </Infos>

                  </Link>
                </CardContent>
              );
            })}
          </Card>

        <SideSection>
          <SideSectionHeader>{t`Similar replies`}</SideSectionHeader>
          {similarReplies.length > 0 ? (
            <SideSectionLinks>
              {similarReplies.map(({ node }) => (
                <Link
                  key={node.id}
                  href="/reply/[id]"
                  as={`/reply/${node.id}`}
                  passHref
                >
                  <SideSectionLink>
                    <SideSectionText>{node.text}</SideSectionText>
                    <Infos className={classes.infos}>
                      {ngettext(
                        msgid`Used in ${node.articleReplies.length} message`,
                        `Used in ${node.articleReplies.length} messages`,
                        node.articleReplies.length
                      )}
                    </Infos>
                  </SideSectionLink>
                </Link>
              ))}
            </SideSectionLinks>
          ) : (
            <Box textAlign="center" pt={4} pb={3}>
              {t`No similar replies found`}
            </Box>
          )}
        </SideSection>

    </AppLayout>
  );
}

export default withData(ReplyPage);
<|endoftext|>


<commit_before>/*jslint browser: true, for: true, node: true */
/*global window */

/*!

   hlsjs engine plugin for Flowplayer HTML5

   Copyright (c) 2015, Flowplayer Oy

   Released under the MIT License:
   http://www.opensource.org/licenses/mit-license.php

   Includes hls.min.js
   Copyright (c) 2015 Dailymotion (http://www.dailymotion.com)
   https://github.com/dailymotion/hls.js/blob/master/LICENSE

   requires:
   - Flowplayer HTML5 version 6.x or greater
   - hls.js https://github.com/dailymotion/hls.js
   revision: $GIT_ID$

*/

(function (flowplayer, Hls) {
    "use strict";
    var engineName = "hlsjs",
        hlsconf,
        common = flowplayer.common,
        extend = flowplayer.extend,
        version = flowplayer.version,

        isHlsType = function (typ) {
            return typ.toLowerCase().indexOf("mpegurl") > -1;
        },

        engineImpl = function hlsjsEngine(player, root) {
            var bean = flowplayer.bean,
                videoTag,
                hls,

                pre604 = /^6\.0\.[0-3]$/.test(version),
                bc = common.css(root, 'backgroundColor'),
                // spaces in rgba arg mandatory for recognition
                has_bg = common.css(root, 'backgroundImage') !== "none" ||
                        (bc && bc !== "rgba(0, 0, 0, 0)" && bc !== "transparent"),
                posterCondition = has_bg && !player.conf.splash
                        && (!pre604 || (pre604 && !player.conf.autoplay)),

                posterHack = function (e) {
                    // assert that poster is set regardless of client of
                    // video loading delay
                    setTimeout(function () {
                        var posterClass = "is-poster";

                        if (!player.conf.autoplay || e.type === "stop") {
                            common.addClass(root, posterClass);
                            if (!pre604) {
                                // must set this ourselves
                                player.poster = true;
                            }
                            player.one("resume." + engineName, function () {
                                common.removeClass(root, posterClass);
                                player.off("seek." + engineName);
                                if (player.poster) {
                                    player.poster = false;
                                }
                            });
                        }
                    }, 0);
                },

                engine = {
                    engineName: engineName,

                    pick: function (sources) {
                        var i,
                            source;

                        for (i = 0; i < sources.length; i += 1) {
                            source = sources[i];
                            if (isHlsType(source.type)
                                    && (!source.engine || source.engine === engineName)) {
                                if (typeof source.src === 'string') {
                                    source.src = common.createAbsoluteUrl(source.src);
                                }
                                return source;
                            }
                        }
                    },

                    load: function (video) {
                        var init = !hls,
                            conf = player.conf,
                            hlsClientConf = extend({}, hlsconf),
                            hlsParams = ["autoLevelCapping", "startLevel", "strict"];

                        if (init) {
                            common.removeNode(common.findDirect("video", root)[0]
                                    || common.find(".fp-player > video", root)[0]);
                            videoTag = common.createElement("video", {
                                className: "fp-engine " + engineName + "-engine",
                                autoplay: conf.autoplay
                                    ? "autoplay"
                                    : false
                            });
                            videoTag.setAttribute("x-webkit-airplay", "allow");

                        } else {
                            hls.destroy();
                        }

                        bean.on(videoTag, "play", function () {
                            player.trigger('resume', [player]);
                        });
                        bean.on(videoTag, "pause", function () {
                            player.trigger('pause', [player]);
                        });
                        bean.on(videoTag, "timeupdate", function () {
                            player.trigger('progress', [player, videoTag.currentTime]);
                        });
                        bean.on(videoTag, "loadeddata", function () {
                            video = extend(video, {
                                duration: videoTag.duration,
                                seekable: videoTag.seekable.end(null),
                                width: videoTag.videoWidth,
                                height: videoTag.videoHeight,
                                url: videoTag.currentSrc
                            });
                            player.trigger('ready', [player, video]);
                        });
                        bean.on(videoTag, "seeked", function () {
                            player.trigger('seek', [player, videoTag.currentTime]);
                        });
                        bean.on(videoTag, "progress", function (e) {
                            var ct = videoTag.currentTime,
                                buffer = 0,
                                buffend,
                                buffered,
                                last,
                                i;

                            try {
                                buffered = videoTag.buffered;
                                last = buffered.length - 1;
                                buffend = 0;
                                // cycle through time ranges to obtain buffer
                                // nearest current time
                                if (ct) {
                                    for (i = last; i > -1; i -= 1) {
                                        buffend = buffered.end(i);

                                        if (buffend >= ct) {
                                            buffer = buffend;
                                        }
                                    }
                                }
                            } catch (ignored) {}

                            video.buffer = buffer;
                            player.trigger('buffer', [player, e]);
                        });
                        bean.on(videoTag, "ended", function () {
                            player.trigger('finish', [player]);
                        });
                        bean.on(videoTag, "volumechange", function () {
                            player.trigger('volume', [player, videoTag.volume]);
                        });

                        hlsParams.forEach(function (key) {
                            delete hlsClientConf[key];
                        });
                        hls = new Hls(hlsClientConf);

                        hlsParams.forEach(function (key) {
                            var value = hlsconf[key];

                            switch (key) {
                            case "autoLevelCapping":
                                if (value === false) {
                                    value = -1;
                                }
                                break;
                            case "startLevel":
                                switch (value) {
                                case "auto":
                                    value = -1;
                                    break;
                                case "firstLevel":
                                    value = undefined;
                                    break;
                                }
                                break;
                            }

                            if (value !== undefined) {
                                hls[key] = value;
                            }
                        });

                        hls.on(Hls.Events.ERROR, function (e, data) {
                            var fperr,
                                errobj = {};

                            if (data.fatal || hlsconf.strict) {
                                switch (data.type) {
                                case Hls.ErrorTypes.NETWORK_ERROR:
                                    if (data.frag && data.frag.url) {
                                        errobj.url = data.frag.url;
                                        fperr = 2;
                                    } else {
                                        fperr = 4;
                                    }
                                    break;
                                case Hls.ErrorTypes.MEDIA_ERROR:
                                    fperr = 3;
                                    break;
                                default:
                                    fperr = 5;
                                    break;
                                }
                                errobj.code = fperr;
                                if (fperr > 2) {
                                    errobj.video = extend(video, {
                                        src: video.src,
                                        url: data.url || video.src
                                    });
                                }
                                player.trigger('error', [player, errobj]);
                            }
                            /* TODO: */
                            // log non fatals
                        });

                        if (init) {
                            common.prepend(common.find(".fp-player", root)[0], videoTag);
                        }

                        hls.loadSource(video.src);
                        hls.attachVideo(videoTag);

                        if (videoTag.paused && (video.autoplay || conf.autoplay)) {
                            videoTag.play();
                        }
                    },

                    resume: function () {
                        videoTag.play();
                    },

                    pause: function () {
                        videoTag.pause();
                    },

                    seek: function (time) {
                        videoTag.currentTime = time;
                    },

                    volume: function (level) {
                        if (videoTag) {
                            videoTag.volume = level;
                        }
                    },

                    speed: function (val) {
                        videoTag.playbackRate = val;
                        player.trigger('speed', [player, val]);
                    },

                    unload: function () {
                        // hls conditional probably not needed once
                        // https://github.com/flowplayer/flowplayer/commit/871ff783a8f23aa603e1120f4319d4a892125b0a
                        // is released
                        if (hls) {
                            hls.destroy();
                            hls = 0;
                            bean.off(videoTag);
                            common.removeNode(videoTag);
                            videoTag = 0;
                        }
                    }
                };

            if (posterCondition) {
                player.on("ready." + engineName + " stop." + engineName, posterHack)
                    .one("seek." + engineName, posterHack);
            }

            return engine;
        };

    if (Hls.isSupported() && version.indexOf("5.") !== 0) {
        // only load engine if it can be used
        engineImpl.engineName = engineName; // must be exposed
        engineImpl.canPlay = function (type, conf) {
            var IE11 = navigator.userAgent.indexOf("Trident/7") > -1;

            if (conf[engineName] === false || conf.clip[engineName] === false) {
                // engine disabled for player or clip
                return false;
            }

            // merge hlsjs clip config at earliest opportunity
            hlsconf = extend({}, flowplayer.conf[engineName], conf[engineName], conf.clip[engineName]);

            // support Safari only when hlsjs debugging
            // https://github.com/dailymotion/hls.js/issues/9
            return isHlsType(type) &&
                    (IE11 || !flowplayer.support.browser.safari || hlsconf.debug);
        };

        // put on top of engine stack
        // so hlsjs is tested before html5 video hls and flash hls
        flowplayer.engines.unshift(engineImpl);

    }

}.apply(null, (typeof module === 'object' && module.exports)
    ? [require('flowplayer'), require('hls.js')]
    : [window.flowplayer, window.Hls]));
<commit_msg>Work around hls.js end of video detection issue<commit_after>/*jslint browser: true, for: true, node: true */
/*global window */

/*!

   hlsjs engine plugin for Flowplayer HTML5

   Copyright (c) 2015, Flowplayer Oy

   Released under the MIT License:
   http://www.opensource.org/licenses/mit-license.php

   Includes hls.min.js
   Copyright (c) 2015 Dailymotion (http://www.dailymotion.com)
   https://github.com/dailymotion/hls.js/blob/master/LICENSE

   requires:
   - Flowplayer HTML5 version 6.x or greater
   - hls.js https://github.com/dailymotion/hls.js
   revision: $GIT_ID$

*/

(function (flowplayer, Hls) {
    "use strict";
    var engineName = "hlsjs",
        hlsconf,
        common = flowplayer.common,
        extend = flowplayer.extend,
        version = flowplayer.version,

        isHlsType = function (typ) {
            return typ.toLowerCase().indexOf("mpegurl") > -1;
        },

        engineImpl = function hlsjsEngine(player, root) {
            var bean = flowplayer.bean,
                videoTag,
                hls,

                pre604 = /^6\.0\.[0-3]$/.test(version),
                bc = common.css(root, 'backgroundColor'),
                // spaces in rgba arg mandatory for recognition
                has_bg = common.css(root, 'backgroundImage') !== "none" ||
                        (bc && bc !== "rgba(0, 0, 0, 0)" && bc !== "transparent"),
                posterCondition = has_bg && !player.conf.splash
                        && (!pre604 || (pre604 && !player.conf.autoplay)),

                posterHack = function (e) {
                    // assert that poster is set regardless of client of
                    // video loading delay
                    setTimeout(function () {
                        var posterClass = "is-poster";

                        if (!player.conf.autoplay || e.type === "stop") {
                            common.addClass(root, posterClass);
                            if (!pre604) {
                                // must set this ourselves
                                player.poster = true;
                            }
                            player.one("resume." + engineName, function () {
                                common.removeClass(root, posterClass);
                                player.off("seek." + engineName);
                                if (player.poster) {
                                    player.poster = false;
                                }
                            });
                        }
                    }, 0);
                },

                engine = {
                    engineName: engineName,

                    pick: function (sources) {
                        var i,
                            source;

                        for (i = 0; i < sources.length; i += 1) {
                            source = sources[i];
                            if (isHlsType(source.type)
                                    && (!source.engine || source.engine === engineName)) {
                                if (typeof source.src === 'string') {
                                    source.src = common.createAbsoluteUrl(source.src);
                                }
                                return source;
                            }
                        }
                    },

                    load: function (video) {
                        var init = !hls,
                            conf = player.conf,
                            hlsClientConf = extend({}, hlsconf),
                            hlsParams = ["autoLevelCapping", "startLevel", "strict"];

                        if (init) {
                            common.removeNode(common.findDirect("video", root)[0]
                                    || common.find(".fp-player > video", root)[0]);
                            videoTag = common.createElement("video", {
                                className: "fp-engine " + engineName + "-engine",
                                autoplay: conf.autoplay
                                    ? "autoplay"
                                    : false
                            });
                            videoTag.setAttribute("x-webkit-airplay", "allow");

                        } else {
                            hls.destroy();
                        }

                        bean.on(videoTag, "play", function () {
                            player.trigger('resume', [player]);
                        });
                        bean.on(videoTag, "pause", function () {
                            player.trigger('pause', [player]);
                        });
                        bean.on(videoTag, "timeupdate", function () {
                            // https://github.com/dailymotion/hls.js/issues/83
                            if (!conf.live && videoTag.currentTime >= video.duration - 0.15) {
                                bean.fire(videoTag, 'ended');
                            } else {
                                player.trigger('progress', [player, videoTag.currentTime]);
                            }
                        });
                        bean.on(videoTag, "loadeddata", function () {
                            video = extend(video, {
                                duration: videoTag.duration,
                                seekable: videoTag.seekable.end(null),
                                width: videoTag.videoWidth,
                                height: videoTag.videoHeight,
                                url: videoTag.currentSrc
                            });
                            player.trigger('ready', [player, video]);
                        });
                        bean.on(videoTag, "seeked", function () {
                            player.trigger('seek', [player, videoTag.currentTime]);
                        });
                        bean.on(videoTag, "progress", function (e) {
                            var ct = videoTag.currentTime,
                                buffer = 0,
                                buffend,
                                buffered,
                                last,
                                i;

                            try {
                                buffered = videoTag.buffered;
                                last = buffered.length - 1;
                                buffend = 0;
                                // cycle through time ranges to obtain buffer
                                // nearest current time
                                if (ct) {
                                    for (i = last; i > -1; i -= 1) {
                                        buffend = buffered.end(i);

                                        if (buffend >= ct) {
                                            buffer = buffend;
                                        }
                                    }
                                }
                            } catch (ignored) {}

                            video.buffer = buffer;
                            player.trigger('buffer', [player, e]);
                        });
                        bean.on(videoTag, "ended", function () {
                            player.trigger('finish', [player]);
                        });
                        bean.on(videoTag, "volumechange", function () {
                            player.trigger('volume', [player, videoTag.volume]);
                        });

                        hlsParams.forEach(function (key) {
                            delete hlsClientConf[key];
                        });
                        hls = new Hls(hlsClientConf);

                        hlsParams.forEach(function (key) {
                            var value = hlsconf[key];

                            switch (key) {
                            case "autoLevelCapping":
                                if (value === false) {
                                    value = -1;
                                }
                                break;
                            case "startLevel":
                                switch (value) {
                                case "auto":
                                    value = -1;
                                    break;
                                case "firstLevel":
                                    value = undefined;
                                    break;
                                }
                                break;
                            }

                            if (value !== undefined) {
                                hls[key] = value;
                            }
                        });

                        hls.on(Hls.Events.ERROR, function (e, data) {
                            var fperr,
                                errobj = {};

                            if (data.fatal || hlsconf.strict) {
                                switch (data.type) {
                                case Hls.ErrorTypes.NETWORK_ERROR:
                                    if (data.frag && data.frag.url) {
                                        errobj.url = data.frag.url;
                                        fperr = 2;
                                    } else {
                                        fperr = 4;
                                    }
                                    break;
                                case Hls.ErrorTypes.MEDIA_ERROR:
                                    fperr = 3;
                                    break;
                                default:
                                    fperr = 5;
                                    break;
                                }
                                errobj.code = fperr;
                                if (fperr > 2) {
                                    errobj.video = extend(video, {
                                        src: video.src,
                                        url: data.url || video.src
                                    });
                                }
                                player.trigger('error', [player, errobj]);
                            }
                            /* TODO: */
                            // log non fatals
                        });

                        if (init) {
                            common.prepend(common.find(".fp-player", root)[0], videoTag);
                        }

                        hls.loadSource(video.src);
                        hls.attachVideo(videoTag);

                        if (videoTag.paused && (video.autoplay || conf.autoplay)) {
                            videoTag.play();
                        }
                    },

                    resume: function () {
                        videoTag.play();
                    },

                    pause: function () {
                        videoTag.pause();
                    },

                    seek: function (time) {
                        videoTag.currentTime = time;
                    },

                    volume: function (level) {
                        if (videoTag) {
                            videoTag.volume = level;
                        }
                    },

                    speed: function (val) {
                        videoTag.playbackRate = val;
                        player.trigger('speed', [player, val]);
                    },

                    unload: function () {
                        // hls conditional probably not needed once
                        // https://github.com/flowplayer/flowplayer/commit/871ff783a8f23aa603e1120f4319d4a892125b0a
                        // is released
                        if (hls) {
                            hls.destroy();
                            hls = 0;
                            bean.off(videoTag);
                            common.removeNode(videoTag);
                            videoTag = 0;
                        }
                    }
                };

            if (posterCondition) {
                player.on("ready." + engineName + " stop." + engineName, posterHack)
                    .one("seek." + engineName, posterHack);
            }

            return engine;
        };

    if (Hls.isSupported() && version.indexOf("5.") !== 0) {
        // only load engine if it can be used
        engineImpl.engineName = engineName; // must be exposed
        engineImpl.canPlay = function (type, conf) {
            var IE11 = navigator.userAgent.indexOf("Trident/7") > -1;

            if (conf[engineName] === false || conf.clip[engineName] === false) {
                // engine disabled for player or clip
                return false;
            }

            // merge hlsjs clip config at earliest opportunity
            hlsconf = extend({}, flowplayer.conf[engineName], conf[engineName], conf.clip[engineName]);

            // support Safari only when hlsjs debugging
            // https://github.com/dailymotion/hls.js/issues/9
            return isHlsType(type) &&
                    (IE11 || !flowplayer.support.browser.safari || hlsconf.debug);
        };

        // put on top of engine stack
        // so hlsjs is tested before html5 video hls and flash hls
        flowplayer.engines.unshift(engineImpl);

    }

}.apply(null, (typeof module === 'object' && module.exports)
    ? [require('flowplayer'), require('hls.js')]
    : [window.flowplayer, window.Hls]));
<|endoftext|>


<commit_before>/*jslint browser: true, for: true, node: true */
/*global window */

/*!

   hlsjs engine plugin for Flowplayer HTML5

   Copyright (c) 2015, Flowplayer Oy

   Released under the MIT License:
   http://www.opensource.org/licenses/mit-license.php

   Includes hls.min.js
   Copyright (c) 2015 Dailymotion (http://www.dailymotion.com)
   https://github.com/dailymotion/hls.js/blob/master/LICENSE

   requires:
   - Flowplayer HTML5 version 6.x or greater
   - hls.js https://github.com/dailymotion/hls.js

*/

(function (flowplayer, Hls) {
    "use strict";
    var engineName = "hlsjs",
        hlsconf,
        common = flowplayer.common,
        extend = flowplayer.extend,

        engineImpl = function hlsjsEngine(player, root) {
            var bean = flowplayer.bean,
                videoTag,
                hls,

                engine = {
                    engineName: engineName,

                    pick: function (sources) {
                        var i,
                            source;

                        for (i = 0; i < sources.length; i += 1) {
                            source = sources[i];
                            if (/mpegurl/i.test(source.type) && (!source.engine || source.engine === "hlsjs")) {
                                return source;
                            }
                        }
                    },

                    load: function (video) {
                        var init = !hls,
                            conf = player.conf;

                        if (init) {
                            common.removeNode(common.findDirect("video", root)[0] || common.find(".fp-player > video", root)[0]);
                            videoTag = common.createElement("video", {
                                className: "fp-engine hlsjs-engine",
                                autoplay: conf.autoplay
                                    ? "autoplay"
                                    : false
                            });
                        } else {
                            hls.destroy();
                        }

                        videoTag.setAttribute("x-webkit-airplay", "allow");

                        bean.on(videoTag, "play", function () {
                            player.trigger('resume', [player]);
                        });
                        bean.on(videoTag, "pause", function () {
                            player.trigger('pause', [player]);
                        });
                        bean.on(videoTag, "timeupdate", function () {
                            player.trigger('progress', [player, videoTag.currentTime]);
                        });
                        bean.on(videoTag, "loadeddata", function () {
                            video = extend(video, {
                                duration: videoTag.duration,
                                seekable: videoTag.seekable.end(null),
                                width: videoTag.videoWidth,
                                height: videoTag.videoHeight,
                                url: videoTag.currentSrc
                            });
                            player.trigger('ready', [player, video]);
                        });
                        bean.on(videoTag, "seeked", function () {
                            player.trigger('seek', [player, videoTag.currentTime]);
                        });
                        bean.on(videoTag, "progress", function (e) {
                            var ct = videoTag.currentTime,
                                buffer = 0,
                                buffend,
                                buffered,
                                last,
                                i;

                            try {
                                buffered = videoTag.buffered;
                                last = buffered.length - 1;
                                buffend = 0;
                                // cycle through time ranges to obtain buffer
                                // nearest current time
                                if (ct) {
                                    for (i = last; i > -1; i -= 1) {
                                        buffend = buffered.end(i);

                                        if (buffend >= ct) {
                                            buffer = buffend;
                                        }
                                    }
                                }
                            } catch (ignored) {}

                            video.buffer = buffer;
                            player.trigger('buffer', [player, e]);
                        });
                        bean.on(videoTag, "ended", function () {
                            player.trigger('finish', [player]);
                        });
                        bean.on(videoTag, "volumechange", function () {
                            player.trigger('volume', [player, videoTag.volume]);
                        });

                        hls = new Hls(hlsconf);

                        hls.on(Hls.Events.MSE_ATTACHED, function () {
                            hls.loadSource(video.src);

                            videoTag.load();
                            if (videoTag.paused && (video.autoplay || conf.autoplay)) {
                                videoTag.play();
                            }

                        }).on(Hls.Events.ERROR, function (e, data) {
                            var fperr,
                                errobj = {};

                            if (data.fatal || hlsconf.strict) {
                                switch (data.type) {
                                case Hls.ErrorTypes.NETWORK_ERROR:
                                    if (data.frag && data.frag.url) {
                                        errobj.url = data.frag.url;
                                        fperr = 2;
                                    } else {
                                        fperr = 4;
                                    }
                                    break;
                                case Hls.ErrorTypes.MEDIA_ERROR:
                                    fperr = 3;
                                    break;
                                default:
                                    fperr = 5;
                                    break;
                                }
                                errobj.code = fperr;
                                if (fperr > 2) {
                                    errobj.video = extend(video, {
                                        src: video.src,
                                        url: data.url || video.src
                                    });
                                }
                                player.trigger('error', [player, errobj]);
                            }
                            /* TODO: */
                            // log non fatals
                        });

                        if (init) {
                            common.prepend(common.find(".fp-player", root)[0], videoTag);
                        }
                        hls.attachVideo(videoTag);
                    },

                    resume: function () {
                        videoTag.play();
                    },

                    pause: function () {
                        videoTag.pause();
                    },

                    seek: function (time) {
                        videoTag.currentTime = time;
                    },

                    volume: function (level) {
                        if (videoTag) {
                            videoTag.volume = level;
                        }
                    },

                    speed: function (val) {
                        videoTag.playbackRate = val;
                        player.trigger('speed', [player, val]);
                    },

                    unload: function () {
                        // hls conditional probably not needed once
                        // https://github.com/flowplayer/flowplayer/commit/871ff783a8f23aa603e1120f4319d4a892125b0a
                        // is released
                        if (hls) {
                            hls.destroy();
                            hls = 0;
                            bean.off(videoTag);
                            common.removeNode(videoTag);
                            videoTag = 0;
                        }
                    }
                };

            return engine;
        };

    if (Hls.isSupported()) {
        // only load engine if it can be used
        engineImpl.engineName = engineName; // must be exposed
        engineImpl.canPlay = function (type, conf) {
            var UA = navigator.userAgent,
                IE11 = UA.indexOf("Trident/7") > -1;

            if (conf.hlsjs === false || conf.clip.hlsjs === false) {
                // engine disabled for player or clip
                return false;
            }

            // merge hlsjs clip config at earliest opportunity
            hlsconf = extend({}, conf.hlsjs, conf.clip.hlsjs);

            // support Safari only when hlsjs debugging
            // https://github.com/dailymotion/hls.js/issues/9
            return /mpegurl/i.test(type) &&
                    (IE11 || !flowplayer.support.browser.safari || hlsconf.debug);
        };

        // put on top of engine stack
        // so hlsjs is tested before html5 video hls and flash hls
        flowplayer.engines.unshift(engineImpl);

        // poster hack
        flowplayer(function (api, root) {
            // detect poster condition as in core on boot
            var bc = common.css(root, 'backgroundColor'),
                has_bg = common.css(root, 'backgroundImage') !== "none" ||
                        (bc && bc !== "rgba(0, 0, 0, 0)" && bc !== "transparent"),
                posterCondition = has_bg && !api.conf.splash && !api.conf.autoplay,

                posterHack = function (e) {
                    //if (api.engine.engineName === engineName) {
                    // omitting this condition which would confine the hack to
                    // the hlsjs engine works around
                    // https://github.com/flowplayer/flowplayer/issues/942

                    api.off("seek.hlsjs");

                    if (e.type !== "ready") {
                        // assert that poster is set regardless of client of
                        // video loading delay
                        setTimeout(function () {
                            var posterClass = "is-poster";

                            common.addClass(root, posterClass);
                            api.one("resume", function () {
                                common.removeClass(root, posterClass);
                            });
                        }, 0);
                    }
                };

            if (posterCondition) {
                // setup once at first load
                api.one("load", function (e, api) {
                    // one("seek") is not reliable as it's caught only
                    // with playlists, so will be off'd in posterHack
                    api.on("seek.hlsjs stop.hlsjs", posterHack).one("ready.hlsjs", posterHack);
                });
            }
        });
    }

}.apply(null, (typeof module === 'object' && module.exports)
    ? [require('flowplayer'), require('hls.js')]
    : [window.flowplayer, window.Hls]));
<commit_msg>Fix video loading on Windows<commit_after>/*jslint browser: true, for: true, node: true */
/*global window */

/*!

   hlsjs engine plugin for Flowplayer HTML5

   Copyright (c) 2015, Flowplayer Oy

   Released under the MIT License:
   http://www.opensource.org/licenses/mit-license.php

   Includes hls.min.js
   Copyright (c) 2015 Dailymotion (http://www.dailymotion.com)
   https://github.com/dailymotion/hls.js/blob/master/LICENSE

   requires:
   - Flowplayer HTML5 version 6.x or greater
   - hls.js https://github.com/dailymotion/hls.js

*/

(function (flowplayer, Hls) {
    "use strict";
    var engineName = "hlsjs",
        hlsconf,
        common = flowplayer.common,
        extend = flowplayer.extend,

        engineImpl = function hlsjsEngine(player, root) {
            var bean = flowplayer.bean,
                videoTag,
                hls,

                engine = {
                    engineName: engineName,

                    pick: function (sources) {
                        var i,
                            source;

                        for (i = 0; i < sources.length; i += 1) {
                            source = sources[i];
                            if (/mpegurl/i.test(source.type) && (!source.engine || source.engine === "hlsjs")) {
                                return source;
                            }
                        }
                    },

                    load: function (video) {
                        var init = !hls,
                            conf = player.conf;

                        if (init) {
                            common.removeNode(common.findDirect("video", root)[0] || common.find(".fp-player > video", root)[0]);
                            videoTag = common.createElement("video", {
                                className: "fp-engine hlsjs-engine",
                                autoplay: conf.autoplay
                                    ? "autoplay"
                                    : false
                            });
                        } else {
                            hls.destroy();
                        }

                        videoTag.setAttribute("x-webkit-airplay", "allow");

                        bean.on(videoTag, "play", function () {
                            player.trigger('resume', [player]);
                        });
                        bean.on(videoTag, "pause", function () {
                            player.trigger('pause', [player]);
                        });
                        bean.on(videoTag, "timeupdate", function () {
                            player.trigger('progress', [player, videoTag.currentTime]);
                        });
                        bean.on(videoTag, "loadeddata", function () {
                            video = extend(video, {
                                duration: videoTag.duration,
                                seekable: videoTag.seekable.end(null),
                                width: videoTag.videoWidth,
                                height: videoTag.videoHeight,
                                url: videoTag.currentSrc
                            });
                            player.trigger('ready', [player, video]);

                            if (videoTag.paused && (video.autoplay || conf.autoplay)) {
                                videoTag.play();
                            }
                        });
                        bean.on(videoTag, "seeked", function () {
                            player.trigger('seek', [player, videoTag.currentTime]);
                        });
                        bean.on(videoTag, "progress", function (e) {
                            var ct = videoTag.currentTime,
                                buffer = 0,
                                buffend,
                                buffered,
                                last,
                                i;

                            try {
                                buffered = videoTag.buffered;
                                last = buffered.length - 1;
                                buffend = 0;
                                // cycle through time ranges to obtain buffer
                                // nearest current time
                                if (ct) {
                                    for (i = last; i > -1; i -= 1) {
                                        buffend = buffered.end(i);

                                        if (buffend >= ct) {
                                            buffer = buffend;
                                        }
                                    }
                                }
                            } catch (ignored) {}

                            video.buffer = buffer;
                            player.trigger('buffer', [player, e]);
                        });
                        bean.on(videoTag, "ended", function () {
                            player.trigger('finish', [player]);
                        });
                        bean.on(videoTag, "volumechange", function () {
                            player.trigger('volume', [player, videoTag.volume]);
                        });

                        hls = new Hls(hlsconf);

                        hls.on(Hls.Events.MSE_ATTACHED, function () {
                            hls.loadSource(video.src);

                        }).on(Hls.Events.ERROR, function (e, data) {
                            var fperr,
                                errobj = {};

                            if (data.fatal || hlsconf.strict) {
                                switch (data.type) {
                                case Hls.ErrorTypes.NETWORK_ERROR:
                                    if (data.frag && data.frag.url) {
                                        errobj.url = data.frag.url;
                                        fperr = 2;
                                    } else {
                                        fperr = 4;
                                    }
                                    break;
                                case Hls.ErrorTypes.MEDIA_ERROR:
                                    fperr = 3;
                                    break;
                                default:
                                    fperr = 5;
                                    break;
                                }
                                errobj.code = fperr;
                                if (fperr > 2) {
                                    errobj.video = extend(video, {
                                        src: video.src,
                                        url: data.url || video.src
                                    });
                                }
                                player.trigger('error', [player, errobj]);
                            }
                            /* TODO: */
                            // log non fatals
                        });

                        if (init) {
                            common.prepend(common.find(".fp-player", root)[0], videoTag);
                        }
                        hls.attachVideo(videoTag);
                    },

                    resume: function () {
                        videoTag.play();
                    },

                    pause: function () {
                        videoTag.pause();
                    },

                    seek: function (time) {
                        videoTag.currentTime = time;
                    },

                    volume: function (level) {
                        if (videoTag) {
                            videoTag.volume = level;
                        }
                    },

                    speed: function (val) {
                        videoTag.playbackRate = val;
                        player.trigger('speed', [player, val]);
                    },

                    unload: function () {
                        // hls conditional probably not needed once
                        // https://github.com/flowplayer/flowplayer/commit/871ff783a8f23aa603e1120f4319d4a892125b0a
                        // is released
                        if (hls) {
                            hls.destroy();
                            hls = 0;
                            bean.off(videoTag);
                            common.removeNode(videoTag);
                            videoTag = 0;
                        }
                    }
                };

            return engine;
        };

    if (Hls.isSupported()) {
        // only load engine if it can be used
        engineImpl.engineName = engineName; // must be exposed
        engineImpl.canPlay = function (type, conf) {
            var UA = navigator.userAgent,
                IE11 = UA.indexOf("Trident/7") > -1;

            if (conf.hlsjs === false || conf.clip.hlsjs === false) {
                // engine disabled for player or clip
                return false;
            }

            // merge hlsjs clip config at earliest opportunity
            hlsconf = extend({}, conf.hlsjs, conf.clip.hlsjs);

            // support Safari only when hlsjs debugging
            // https://github.com/dailymotion/hls.js/issues/9
            return /mpegurl/i.test(type) &&
                    (IE11 || !flowplayer.support.browser.safari || hlsconf.debug);
        };

        // put on top of engine stack
        // so hlsjs is tested before html5 video hls and flash hls
        flowplayer.engines.unshift(engineImpl);

        // poster hack
        flowplayer(function (api, root) {
            // detect poster condition as in core on boot
            var bc = common.css(root, 'backgroundColor'),
                has_bg = common.css(root, 'backgroundImage') !== "none" ||
                        (bc && bc !== "rgba(0, 0, 0, 0)" && bc !== "transparent"),
                posterCondition = has_bg && !api.conf.splash && !api.conf.autoplay,

                posterHack = function (e) {
                    //if (api.engine.engineName === engineName) {
                    // omitting this condition which would confine the hack to
                    // the hlsjs engine works around
                    // https://github.com/flowplayer/flowplayer/issues/942

                    api.off("seek.hlsjs");

                    if (e.type !== "ready") {
                        // assert that poster is set regardless of client of
                        // video loading delay
                        setTimeout(function () {
                            var posterClass = "is-poster";

                            common.addClass(root, posterClass);
                            api.one("resume", function () {
                                common.removeClass(root, posterClass);
                            });
                        }, 0);
                    }
                };

            if (posterCondition) {
                // setup once at first load
                api.one("load", function (e, api) {
                    // one("seek") is not reliable as it's caught only
                    // with playlists, so will be off'd in posterHack
                    api.on("seek.hlsjs stop.hlsjs", posterHack).one("ready.hlsjs", posterHack);
                });
            }
        });
    }

}.apply(null, (typeof module === 'object' && module.exports)
    ? [require('flowplayer'), require('hls.js')]
    : [window.flowplayer, window.Hls]));
<|endoftext|>


<commit_before>dojo.provide("dijit.form._FormWidget");

dojo.require("dijit._Widget");
dojo.require("dijit._Templated");

dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated],
{
	/*
	Summary:
		FormElement widgets correspond to native HTML elements such as <input> or <button> or <select>.
		Each FormElement represents a single input value, and has a (possibly hidden) <input> element,
		to which it serializes its input value, so that form submission (either normal submission or via FormBind?)
		works as expected.

		All these widgets should have these attributes just like native HTML input elements.
		You can set them during widget construction, but after that they are read only.

		They also share some common methods.
	*/

	// baseClass: String
	//		Used to add CSS classes like FormElementDisabled
	// TODO: remove this in favor of this.domNode.baseClass?
	baseClass: "",

	// value: String
	//		Corresponds to the native HTML <input> element's attribute.
	value: "",

	// name: String
	//		Name used when submitting form; same as "name" attribute or plain HTML elements
	name: "",

	// id: String
	//		Corresponds to the native HTML <input> element's attribute.
	//		Also becomes the id for the widget.
	id: "",

	// alt: String
	//		Corresponds to the native HTML <input> element's attribute.
	alt: "",

	// type: String
	//		Corresponds to the native HTML <input> element's attribute.
	type: "text",

	// tabIndex: Integer
	//		Order fields are traversed when user hits the tab key
	tabIndex: "0",

	// disabled: Boolean
	//		Should this widget respond to user input?
	//		In markup, this is specified as "disabled='disabled'", or just "disabled".
	disabled: false,

	// intermediateChanges: Boolean
	//              Fires onChange for each value change or only on demand
	intermediateChanges: false,

	setDisabled: function(/*Boolean*/ disabled){
		// summary:
		//		Set disabled state of widget.

		this.domNode.disabled = this.disabled = disabled;
		if(this.focusNode){
			this.focusNode.disabled = disabled;
		}
		dijit.wai.setAttr(this.focusNode || this.domNode, "waiState", "disabled", disabled);
		this._setStateClass();
	},

	_onMouse : function(/*Event*/ event){
		// summary:
		//	Sets _hovering, _active, and baseClass attributes depending on mouse state,
		//	then calls setStateClass() to set appropriate CSS class for this.domNode.
		//
		//	To get a different CSS class for hover, send onmouseover and onmouseout events to this method.
		//	To get a different CSS class while mouse button is depressed, send onmousedown to this method.

		var mouseNode = event.target;

		if(!this.disabled){
			switch(event.type){
				case "mouseover" :
					this._hovering = true;
					var baseClass, node=mouseNode;
					while( !(baseClass=node.getAttribute("baseClass")) && node != this.domNode ){
						node=node.parentNode;
					}
					this.baseClass= baseClass || "dijit"+this.declaredClass.replace(/.*\./g,"");
					break;

				case "mouseout" :
					this._hovering = false;
					this.baseClass=null;
					break;

				case "mousedown" :
					this._active = true;
					// set a global event to handle mouseup, so it fires properly
					//	even if the cursor leaves the button
					var self = this;
					// #2685: use this.connect and disconnect so destroy works properly
					var mouseUpConnector = this.connect(dojo.body(), "onmouseup", function(){
						self._active = false;
						self._setStateClass();
						self.disconnect(mouseUpConnector);
					});
					break;
			}
			this._setStateClass();
		}
	},

	focus: function(){
		dijit.focus(this.focusNode);
	},

	_setStateClass: function(/*String*/ base){
		// summary:
		//	Update the visual state of the widget by changing the css class on the domnode
		//	according to widget state.
		//
		//	State will be one of:
		//		<baseClass>
		//		<baseClass> + "Disabled"	- if the widget is disabled
		//		<baseClass> + "Active"		- if the mouse (or space/enter key?) is being pressed down
		//		<baseClass> + "Hover"		- if the mouse is over the widget
		//		<baseClass> + "Focused"		- if the widget has focus
		//
		//	Note: if you don't want to change the way the widget looks on hover, then don't call
		//	this routine on hover.  Similarly for mousedown --> active
		//
		//	For widgets which can be in a checked state (like checkbox or radio),
		//	in addition to the above classes...
		//		<baseClass> + "Checked"
		//		<baseClass> + "CheckedDisabled"	- if the widget is disabled
		//		<baseClass> + "CheckedActive"		- if the mouse is being pressed down
		//		<baseClass> + "CheckedHover"		- if the mouse is over the widget
		//		<baseClass> + "CheckedFocused"		- if the widget has focus

		// get original class (non state related) specified in template
		var origClass = this._origClass || (this._origClass = (this.styleNode||this.domNode).className);

		// compute list of classname representing the states of the widget
		var base = this.baseClass || this.domNode.getAttribute("baseClass");
		var classes = [ base ];

		function multiply(modifier){
			classes=classes.concat(dojo.map(classes, function(c){ return c+modifier; }));
		}

		if(this.checked){
			multiply("Checked");
		}
		if(this.selected){
			multiply("Selected");
		}

		// Only one of these three can be applied.
		// Active trumps Focused, Focused trumps Hover, and Disabled trumps all.
		if(this.disabled){
			multiply("Disabled");
		}else if(this._active){
			multiply("Active");
		}else if(this._focused){
			multiply("Focused");
		}else if(this._hovering){
			multiply("Hover");
		}

		(this.styleNode || this.domNode).className = origClass + " " + classes.join(" ");
	},

	onChange: function(newValue){
		// summary: callback when value is changed
	},

	postCreate: function(){
		this.setDisabled(this.disabled);
		this._setStateClass();
		this.setValue(this.value, true);
	},

	setValue: function(/*anything*/ newValue, /*Boolean, optional*/ priorityChange){
		// summary: set the value of the widget.
		this._lastValue = newValue;
		dijit.wai.setAttr(this.focusNode || this.domNode, "waiState", "valuenow", this.forWaiValuenow());
		if((this.intermediateChanges || priorityChange) && newValue != this._lastValueReported){
			this._lastValueReported = newValue;
			this.onChange(newValue);
		}
	},

	getValue: function(){
		// summary: get the value of the widget.
		return this._lastValue;
	},

	undo: function(){
		// summary: restore the value to the last value passed to onChange
		this.setValue(this._lastValueReported, false);
	},

	_onKeyPress: function(e){
		if(e.keyCode == dojo.keys.ESCAPE && !e.shiftKey && !e.ctrlKey && !e.altKey){
			var v = this.getValue();
			var lv = this._lastValueReported;
			// Equality comparison of objects such as dates are done by reference so
			// two distinct objects are != even if they have the same data. So use
			// toStrings in case the values are objects.
			if(lv != undefined && v.toString() != lv.toString()){
				this.undo();
				dojo.stopEvent(e);
				return false;
			}
		}
		return true;
	},

	forWaiValuenow: function(){
		// summary: returns a value, reflecting the current state of the widget,
		//		to be used for the ARIA valuenow.
		// 		This method may be overridden by subclasses that want
		// 		to use something other than this.getValue() for valuenow
		return this.getValue();
	},

	resize:function(/*Object*/ contentBox){
		// summary:
		//		Explicitly set this widget's size (in pixels).
		//		Unlike layout containers, this sets the content box size.
		//		Subwidgets may override this function
		//
		// contentBox: Object
		//		{w: int, h: int}
		dojo.contentBox(this.domNode, contentBox);
	}
});
<commit_msg>Fixes #4072: Spinner / Combo / SelectBox can break in IE with some select/mouseup operations. Patch from Sam Foster (CLA on file)<commit_after>dojo.provide("dijit.form._FormWidget");

dojo.require("dijit._Widget");
dojo.require("dijit._Templated");

dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated],
{
	/*
	Summary:
		FormElement widgets correspond to native HTML elements such as <input> or <button> or <select>.
		Each FormElement represents a single input value, and has a (possibly hidden) <input> element,
		to which it serializes its input value, so that form submission (either normal submission or via FormBind?)
		works as expected.

		All these widgets should have these attributes just like native HTML input elements.
		You can set them during widget construction, but after that they are read only.

		They also share some common methods.
	*/

	// baseClass: String
	//		Used to add CSS classes like FormElementDisabled
	// TODO: remove this in favor of this.domNode.baseClass?
	baseClass: "",

	// value: String
	//		Corresponds to the native HTML <input> element's attribute.
	value: "",

	// name: String
	//		Name used when submitting form; same as "name" attribute or plain HTML elements
	name: "",

	// id: String
	//		Corresponds to the native HTML <input> element's attribute.
	//		Also becomes the id for the widget.
	id: "",

	// alt: String
	//		Corresponds to the native HTML <input> element's attribute.
	alt: "",

	// type: String
	//		Corresponds to the native HTML <input> element's attribute.
	type: "text",

	// tabIndex: Integer
	//		Order fields are traversed when user hits the tab key
	tabIndex: "0",

	// disabled: Boolean
	//		Should this widget respond to user input?
	//		In markup, this is specified as "disabled='disabled'", or just "disabled".
	disabled: false,

	// intermediateChanges: Boolean
	//              Fires onChange for each value change or only on demand
	intermediateChanges: false,

	setDisabled: function(/*Boolean*/ disabled){
		// summary:
		//		Set disabled state of widget.

		this.domNode.disabled = this.disabled = disabled;
		if(this.focusNode){
			this.focusNode.disabled = disabled;
		}
		dijit.wai.setAttr(this.focusNode || this.domNode, "waiState", "disabled", disabled);
		this._setStateClass();
	},

	_onMouse : function(/*Event*/ event){
		// summary:
		//	Sets _hovering, _active, and baseClass attributes depending on mouse state,
		//	then calls setStateClass() to set appropriate CSS class for this.domNode.
		//
		//	To get a different CSS class for hover, send onmouseover and onmouseout events to this method.
		//	To get a different CSS class while mouse button is depressed, send onmousedown to this method.

		var mouseNode = event.target;

		if(!this.disabled){
			switch(event.type){
				case "mouseover" :
					this._hovering = true;
					var baseClass, node=mouseNode;
					while( node.nodeType===1 && !(baseClass=node.getAttribute("baseClass")) && node != this.domNode ){
						node=node.parentNode;
					}
					this.baseClass= baseClass || "dijit"+this.declaredClass.replace(/.*\./g,"");
					break;

				case "mouseout" :
					this._hovering = false;
					this.baseClass=null;
					break;

				case "mousedown" :
					this._active = true;
					// set a global event to handle mouseup, so it fires properly
					//	even if the cursor leaves the button
					var self = this;
					// #2685: use this.connect and disconnect so destroy works properly
					var mouseUpConnector = this.connect(dojo.body(), "onmouseup", function(){
						self._active = false;
						self._setStateClass();
						self.disconnect(mouseUpConnector);
					});
					break;
			}
			this._setStateClass();
		}
	},

	focus: function(){
		dijit.focus(this.focusNode);
	},

	_setStateClass: function(/*String*/ base){
		// summary:
		//	Update the visual state of the widget by changing the css class on the domnode
		//	according to widget state.
		//
		//	State will be one of:
		//		<baseClass>
		//		<baseClass> + "Disabled"	- if the widget is disabled
		//		<baseClass> + "Active"		- if the mouse (or space/enter key?) is being pressed down
		//		<baseClass> + "Hover"		- if the mouse is over the widget
		//		<baseClass> + "Focused"		- if the widget has focus
		//
		//	Note: if you don't want to change the way the widget looks on hover, then don't call
		//	this routine on hover.  Similarly for mousedown --> active
		//
		//	For widgets which can be in a checked state (like checkbox or radio),
		//	in addition to the above classes...
		//		<baseClass> + "Checked"
		//		<baseClass> + "CheckedDisabled"	- if the widget is disabled
		//		<baseClass> + "CheckedActive"		- if the mouse is being pressed down
		//		<baseClass> + "CheckedHover"		- if the mouse is over the widget
		//		<baseClass> + "CheckedFocused"		- if the widget has focus

		// get original class (non state related) specified in template
		var origClass = this._origClass || (this._origClass = (this.styleNode||this.domNode).className);

		// compute list of classname representing the states of the widget
		var base = this.baseClass || this.domNode.getAttribute("baseClass");
		var classes = [ base ];

		function multiply(modifier){
			classes=classes.concat(dojo.map(classes, function(c){ return c+modifier; }));
		}

		if(this.checked){
			multiply("Checked");
		}
		if(this.selected){
			multiply("Selected");
		}

		// Only one of these three can be applied.
		// Active trumps Focused, Focused trumps Hover, and Disabled trumps all.
		if(this.disabled){
			multiply("Disabled");
		}else if(this._active){
			multiply("Active");
		}else if(this._focused){
			multiply("Focused");
		}else if(this._hovering){
			multiply("Hover");
		}

		(this.styleNode || this.domNode).className = origClass + " " + classes.join(" ");
	},

	onChange: function(newValue){
		// summary: callback when value is changed
	},

	postCreate: function(){
		this.setDisabled(this.disabled);
		this._setStateClass();
		this.setValue(this.value, true);
	},

	setValue: function(/*anything*/ newValue, /*Boolean, optional*/ priorityChange){
		// summary: set the value of the widget.
		this._lastValue = newValue;
		dijit.wai.setAttr(this.focusNode || this.domNode, "waiState", "valuenow", this.forWaiValuenow());
		if((this.intermediateChanges || priorityChange) && newValue != this._lastValueReported){
			this._lastValueReported = newValue;
			this.onChange(newValue);
		}
	},

	getValue: function(){
		// summary: get the value of the widget.
		return this._lastValue;
	},

	undo: function(){
		// summary: restore the value to the last value passed to onChange
		this.setValue(this._lastValueReported, false);
	},

	_onKeyPress: function(e){
		if(e.keyCode == dojo.keys.ESCAPE && !e.shiftKey && !e.ctrlKey && !e.altKey){
			var v = this.getValue();
			var lv = this._lastValueReported;
			// Equality comparison of objects such as dates are done by reference so
			// two distinct objects are != even if they have the same data. So use
			// toStrings in case the values are objects.
			if(lv != undefined && v.toString() != lv.toString()){
				this.undo();
				dojo.stopEvent(e);
				return false;
			}
		}
		return true;
	},

	forWaiValuenow: function(){
		// summary: returns a value, reflecting the current state of the widget,
		//		to be used for the ARIA valuenow.
		// 		This method may be overridden by subclasses that want
		// 		to use something other than this.getValue() for valuenow
		return this.getValue();
	},

	resize:function(/*Object*/ contentBox){
		// summary:
		//		Explicitly set this widget's size (in pixels).
		//		Unlike layout containers, this sets the content box size.
		//		Subwidgets may override this function
		//
		// contentBox: Object
		//		{w: int, h: int}
		dojo.contentBox(this.domNode, contentBox);
	}
});
<|endoftext|>


<commit_before>

import React, { Component } from "react";
import PropTypes from "prop-types";
import { functor } from "../utils";

class LabelAnnotation extends Component {
	constructor(props) {
		super(props);
		this.handleClick = this.handleClick.bind(this);
	}
	handleClick(e) {
		const { onClick } = this.props;

		if (onClick) {
			const { xScale, yScale, datum } = this.props;
			onClick({ xScale, yScale, datum }, e);
		}
	}
	render() {
		const { className, textAnchor, fontFamily, fontSize, opacity, rotate } = this.props;
		const { xAccessor, xScale, yScale } = this.props;

		const { xPos, yPos, fill, text, tooltip } = helper(this.props, xAccessor, xScale, yScale);

		return (<g className={className}>
			<title>{tooltip}</title>
			<text x={xPos} y={yPos}
				fontFamily={fontFamily} fontSize={fontSize}
				fill={fill}
				opacity={opacity}
				transform={`rotate(${rotate}, ${xPos}, ${yPos})`}
				onClick={this.handleClick}
				textAnchor={textAnchor}>{text}</text>
		</g>);
	}
}

export function helper(props, xAccessor, xScale, yScale) {
	const { x, y, datum, fill, text, tooltip, plotData } = props;

	const xFunc = functor(x);
	const yFunc = functor(y);

	const [xPos, yPos] = [xFunc({ xScale, xAccessor, datum, plotData }), yFunc({ yScale, datum, plotData })];

	return {
		xPos,
		yPos,
		text: functor(text)(datum),
		fill: functor(fill)(datum),
		tooltip: functor(tooltip)(datum),
	};
}

LabelAnnotation.propTypes = {
	className: PropTypes.string,
	text: PropTypes.string,
	textAnchor: PropTypes.string,
	fontFamily: PropTypes.string,
	fontSize: PropTypes.number,
	opacity: PropTypes.number,
	rotate: PropTypes.number,
	onClick: PropTypes.func,
	xAccessor: PropTypes.func,
	xScale: PropTypes.func,
	yScale: PropTypes.func,
	datum: PropTypes.object,
	x: PropTypes.oneOfType([
		PropTypes.number,
		PropTypes.func
	]),
	y: PropTypes.oneOfType([
		PropTypes.number,
		PropTypes.func
	])
};

export const defaultProps = {
	textAnchor: "middle",
	fontFamily: "Helvetica Neue, Helvetica, Arial, sans-serif",
	fontSize: 12,
	fill: "#000000",
	opacity: 1,
	rotate: 0,
	x: ({ xScale, xAccessor, datum }) => xScale(xAccessor(datum)),
};

LabelAnnotation.defaultProps = {
	...defaultProps,
	className: "react-stockcharts-labelannotation",
};

export default LabelAnnotation;
<commit_msg>Update the propTypes of LabelAnnotaion text attr to support both string and func.<commit_after>

import React, { Component } from "react";
import PropTypes from "prop-types";
import { functor } from "../utils";

class LabelAnnotation extends Component {
	constructor(props) {
		super(props);
		this.handleClick = this.handleClick.bind(this);
	}
	handleClick(e) {
		const { onClick } = this.props;

		if (onClick) {
			const { xScale, yScale, datum } = this.props;
			onClick({ xScale, yScale, datum }, e);
		}
	}
	render() {
		const { className, textAnchor, fontFamily, fontSize, opacity, rotate } = this.props;
		const { xAccessor, xScale, yScale } = this.props;

		const { xPos, yPos, fill, text, tooltip } = helper(this.props, xAccessor, xScale, yScale);

		return (<g className={className}>
			<title>{tooltip}</title>
			<text x={xPos} y={yPos}
				fontFamily={fontFamily} fontSize={fontSize}
				fill={fill}
				opacity={opacity}
				transform={`rotate(${rotate}, ${xPos}, ${yPos})`}
				onClick={this.handleClick}
				textAnchor={textAnchor}>{text}</text>
		</g>);
	}
}

export function helper(props, xAccessor, xScale, yScale) {
	const { x, y, datum, fill, text, tooltip, plotData } = props;

	const xFunc = functor(x);
	const yFunc = functor(y);

	const [xPos, yPos] = [xFunc({ xScale, xAccessor, datum, plotData }), yFunc({ yScale, datum, plotData })];

	return {
		xPos,
		yPos,
		text: functor(text)(datum),
		fill: functor(fill)(datum),
		tooltip: functor(tooltip)(datum),
	};
}

LabelAnnotation.propTypes = {
	className: PropTypes.string,
	text: PropTypes.oneOfType([
		PropTypes.string,
		PropTypes.func
	]),
	textAnchor: PropTypes.string,
	fontFamily: PropTypes.string,
	fontSize: PropTypes.number,
	opacity: PropTypes.number,
	rotate: PropTypes.number,
	onClick: PropTypes.func,
	xAccessor: PropTypes.func,
	xScale: PropTypes.func,
	yScale: PropTypes.func,
	datum: PropTypes.object,
	x: PropTypes.oneOfType([
		PropTypes.number,
		PropTypes.func
	]),
	y: PropTypes.oneOfType([
		PropTypes.number,
		PropTypes.func
	])
};

export const defaultProps = {
	textAnchor: "middle",
	fontFamily: "Helvetica Neue, Helvetica, Arial, sans-serif",
	fontSize: 12,
	fill: "#000000",
	opacity: 1,
	rotate: 0,
	x: ({ xScale, xAccessor, datum }) => xScale(xAccessor(datum)),
};

LabelAnnotation.defaultProps = {
	...defaultProps,
	className: "react-stockcharts-labelannotation",
};

export default LabelAnnotation;
<|endoftext|>