<commit_before>import Component from '../../common/Component';
import DiscussionListItem from './DiscussionListItem';
import Button from '../../common/components/Button';
import LoadingIndicator from '../../common/components/LoadingIndicator';
import Placeholder from '../../common/components/Placeholder';

/**
 * The `DiscussionList` component displays a list of discussions.
 *
 * ### Props
 *
 * - `params` A map of parameters used to construct a refined parameter object
 *   to send along in the API request to get discussion results.
 * - `state` A DiscussionListState object that represents the discussion lists's state.
 */
export default class DiscussionList extends Component {
  init() {
    this.state = this.props.state;
  }

  view() {
    const state = this.state;

    const params = state.getParams();
    let loading;

    if (state.isLoading()) {
      loading = LoadingIndicator.component();
    } else if (state.moreResults) {
      loading = Button.component({
        children: app.translator.trans('core.forum.discussion_list.load_more_button'),
        className: 'Button',
        onclick: state.loadMore.bind(state),
      });
    }

    if (state.empty()) {
      const text = app.translator.trans('core.forum.discussion_list.empty_text');
      return
{Placeholder.component({ text })}
;
    }

    return (

          {state.discussions.map((discussion) => {
            return (

                {DiscussionListItem.component({ discussion, params })}

            );
          })}

{loading}

    );
  }
}
<commit_msg>Remove documentation for obsolete component prop<commit_after>import Component from '../../common/Component';
import DiscussionListItem from './DiscussionListItem';
import Button from '../../common/components/Button';
import LoadingIndicator from '../../common/components/LoadingIndicator';
import Placeholder from '../../common/components/Placeholder';

/**
 * The `DiscussionList` component displays a list of discussions.
 *
 * ### Props
 *
 * - `state` A DiscussionListState object that represents the discussion lists's state.
 */
export default class DiscussionList extends Component {
  init() {
    this.state = this.props.state;
  }

  view() {
    const state = this.state;

    const params = state.getParams();
    let loading;

    if (state.isLoading()) {
      loading = LoadingIndicator.component();
    } else if (state.moreResults) {
      loading = Button.component({
        children: app.translator.trans('core.forum.discussion_list.load_more_button'),
        className: 'Button',
        onclick: state.loadMore.bind(state),
      });
    }

    if (state.empty()) {
      const text = app.translator.trans('core.forum.discussion_list.empty_text');
      return
{Placeholder.component({ text })}
;
    }

    return (

          {state.discussions.map((discussion) => {
            return (

                {DiscussionListItem.component({ discussion, params })}

            );
          })}

{loading}

    );
  }
}
<|endoftext|>


<commit_before>/**
 * NotificationManager.js
 *
 * Released under LGPL License.
 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class handles the creation of TinyMCE's notifications.
 *
 * @class tinymce.notificationManager
 * @example
 * // Opens a new notification of type "error" with text "An error occurred."
 * tinymce.activeEditor.notificationManager.open({
 *    text: 'An error occurred.',
 *    type: 'error'
 * });
 */
define("tinymce/NotificationManager", [
	"tinymce/ui/Notification"
], function(Notification) {
	return function(editor) {
		var self = this, notifications = [];

		function getLastNotification() {
			if (notifications.length) {
				return notifications[notifications.length - 1];
			}
		}

		self.notifications = notifications;

		function positionNotifications() {
			if (notifications.length > 0) {
				var firstItem = notifications.slice(0, 1)[0];
				var container = editor.inline ? editor.getElement() : editor.getContentAreaContainer();
				firstItem.moveRel(container, 'tc-tc');
				if (notifications.length > 1) {
					for (var i = 1; i < notifications.length; i++) {
						notifications[i].moveRel(notifications[i - 1].getEl(), 'bc-tc');
					}
				}
			}
		}

		editor.on('remove', function() {
			var i = notifications.length;

			while (i--) {
				notifications[i].close();
			}
		});

		editor.on('ResizeEditor ResizeWindow', positionNotifications);

		/**
		 * Opens a new notification.
		 *
		 * @method open
		 * @param {Object} args Optional name/value settings collection contains things like timeout/color/message etc.
		 */
		self.open = function(args) {
			var notif;

			editor.editorManager.setActive(editor);

			notif = new Notification(args);
			notifications.push(notif);

			//If we have a timeout value
			if (args.timeout > 0) {
				notif.timer = setTimeout(function() {
					notif.close();
				}, args.timeout);
			}

			notif.on('close', function() {
				var i = notifications.length;

				if (notif.timer) {
					editor.getWin().clearTimeout(notif.timer);
				}

				while (i--) {
					if (notifications[i] === notif) {
						notifications.splice(i, 1);
					}
				}

				positionNotifications();
			});

			notif.renderTo();

			positionNotifications();

			return notif;
		};

		/**
		 * Closes the top most notification.
		 *
		 * @method close
		 */
		self.close = function() {
			if (getLastNotification()) {
				getLastNotification().close();
			}
		};

		/**
		 * Returns the currently opened notification objects.
		 *
		 * @method getNotifications
		 * @return {Array} Array of the currently opened notifications.
		 */
		self.getNotifications = function() {
			return notifications;
		};
	};
});
<commit_msg>Better position of notifications as appropriate<commit_after>/**
 * NotificationManager.js
 *
 * Released under LGPL License.
 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This class handles the creation of TinyMCE's notifications.
 *
 * @class tinymce.notificationManager
 * @example
 * // Opens a new notification of type "error" with text "An error occurred."
 * tinymce.activeEditor.notificationManager.open({
 *    text: 'An error occurred.',
 *    type: 'error'
 * });
 */
define("tinymce/NotificationManager", [
	"tinymce/ui/Notification",
	"tinymce/util/Delay"
], function(Notification, Delay) {
	return function(editor) {
		var self = this, notifications = [];

		function getLastNotification() {
			if (notifications.length) {
				return notifications[notifications.length - 1];
			}
		}

		self.notifications = notifications;

		function resizeWindowEvent() {
			Delay.requestAnimationFrame(function() {
				prePositionNotifications();
				positionNotifications();
			});
		}

		// Since the viewport will change based on the present notifications, we need to move them all to the
		// top left of the viewport to give an accurate size measurement so we can position them later.
		function prePositionNotifications() {
			for (var i = 0; i < notifications.length; i++) {
				notifications[i].moveTo(0, 0);
			}
		}

		function positionNotifications() {
			if (notifications.length > 0) {
				var firstItem = notifications.slice(0, 1)[0];
				var container = editor.inline ? editor.getElement() : editor.getContentAreaContainer();
				firstItem.moveRel(container, 'tc-tc');
				if (notifications.length > 1) {
					for (var i = 1; i < notifications.length; i++) {
						notifications[i].moveRel(notifications[i - 1].getEl(), 'bc-tc');
					}
				}
			}
		}

		editor.on('remove', function() {
			var i = notifications.length;

			while (i--) {
				notifications[i].close();
			}
		});

		editor.on('ResizeEditor', positionNotifications);
		editor.on('ResizeWindow', resizeWindowEvent);

		/**
		 * Opens a new notification.
		 *
		 * @method open
		 * @param {Object} args Optional name/value settings collection contains things like timeout/color/message etc.
		 */
		self.open = function(args) {
			var notif;

			editor.editorManager.setActive(editor);

			notif = new Notification(args);
			notifications.push(notif);

			//If we have a timeout value
			if (args.timeout > 0) {
				notif.timer = setTimeout(function() {
					notif.close();
				}, args.timeout);
			}

			notif.on('close', function() {
				var i = notifications.length;

				if (notif.timer) {
					editor.getWin().clearTimeout(notif.timer);
				}

				while (i--) {
					if (notifications[i] === notif) {
						notifications.splice(i, 1);
					}
				}

				positionNotifications();
			});

			notif.renderTo();

			positionNotifications();

			return notif;
		};

		/**
		 * Closes the top most notification.
		 *
		 * @method close
		 */
		self.close = function() {
			if (getLastNotification()) {
				getLastNotification().close();
			}
		};

		/**
		 * Returns the currently opened notification objects.
		 *
		 * @method getNotifications
		 * @return {Array} Array of the currently opened notifications.
		 */
		self.getNotifications = function() {
			return notifications;
		};

		//self.positionNotifications = positionNotifications;
	};
});
<|endoftext|>


<commit_before>/**
 * CaretPosition.js
 *
 * Released under LGPL License.
 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This module contains logic for creating caret positions within a document a caretposition
 * is similar to a DOMRange object but it doesn't have two endpoints and is also more lightweight
 * since it's now updated live when the DOM changes.
 *
 * @private
 * @class tinymce.caret.CaretPosition
 * @example
 * var caretPos1 = new CaretPosition(container, offset);
 * var caretPos2 = CaretPosition.fromRangeStart(someRange);
 */
define("tinymce/caret/CaretPosition", [
	"tinymce/util/Fun",
	"tinymce/dom/NodeType",
	"tinymce/dom/DOMUtils",
	"tinymce/dom/RangeUtils",
	"tinymce/caret/CaretCandidate",
	"tinymce/geom/ClientRect",
	"tinymce/text/ExtendingChar"
], function(Fun, NodeType, DOMUtils, RangeUtils, CaretCandidate, ClientRect, ExtendingChar) {
	var isElement = NodeType.isElement,
		isCaretCandidate = CaretCandidate.isCaretCandidate,
		isBlock = NodeType.matchStyleValues('display', 'block table'),
		isFloated = NodeType.matchStyleValues('float', 'left right'),
		isValidElementCaretCandidate = Fun.and(isElement, isCaretCandidate, Fun.negate(isFloated)),
		isNotPre = Fun.negate(NodeType.matchStyleValues('white-space', 'pre pre-line pre-wrap')),
		isText = NodeType.isText,
		isBr = NodeType.isBr,
		nodeIndex = DOMUtils.nodeIndex,
		resolveIndex = RangeUtils.getNode;

	function isWhiteSpace(chr) {
		return chr && /[\r\n\t ]/.test(chr);
	}

	function isHiddenWhiteSpaceRange(range) {
		var container = range.startContainer,
			offset = range.startOffset,
			text;

		if (isWhiteSpace(range.toString()) && isNotPre(container.parentNode)) {
			text = container.data;

			if (isWhiteSpace(text[offset - 1]) || isWhiteSpace(text[offset + 1])) {
				return true;
			}
		}

		return false;
	}

	function getCaretPositionClientRects(caretPosition) {
		var clientRects = [], beforeNode, node;

		// Hack for older WebKit versions that doesn't
		// support getBoundingClientRect on BR elements
		function getBrClientRect(brNode) {
			var doc = brNode.ownerDocument,
				rng = doc.createRange(),
				nbsp = doc.createTextNode('\u00a0'),
				parentNode = brNode.parentNode,
				clientRect;

			parentNode.insertBefore(nbsp, brNode);
			rng.setStart(nbsp, 0);
			rng.setEnd(nbsp, 1);
			clientRect = ClientRect.clone(rng.getBoundingClientRect());
			parentNode.removeChild(nbsp);

			return clientRect;
		}

		function getBoundingClientRect(item) {
			var clientRect, clientRects;

			clientRects = item.getClientRects();
			if (clientRects.length > 0) {
				clientRect = ClientRect.clone(clientRects[0]);
			} else {
				clientRect = ClientRect.clone(item.getBoundingClientRect());
			}

			if (isBr(item) && clientRect.left === 0) {
				return getBrClientRect(item);
			}

			return clientRect;
		}

		function collapseAndInflateWidth(clientRect, toStart) {
			clientRect = ClientRect.collapse(clientRect, toStart);
			clientRect.width = 1;
			clientRect.right = clientRect.left + 1;

			return clientRect;
		}

		function addUniqueAndValidRect(clientRect) {
			if (clientRect.height === 0) {
				return;
			}

			if (clientRects.length > 0) {
				if (ClientRect.isEqual(clientRect, clientRects[clientRects.length - 1])) {
					return;
				}
			}

			clientRects.push(clientRect);
		}

		function addCharacterOffset(container, offset) {
			var range = container.ownerDocument.createRange();

			if (offset < container.data.length) {
				if (ExtendingChar.isExtendingChar(container.data[offset])) {
					return clientRects;
				}
			}

			if (offset > 0) {
				range.setStart(container, offset - 1);
				range.setEnd(container, offset);

				if (!isHiddenWhiteSpaceRange(range)) {
					addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(range), false));
				}
			}

			if (offset < container.data.length) {
				range.setStart(container, offset);
				range.setEnd(container, offset + 1);

				if (!isHiddenWhiteSpaceRange(range)) {
					addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(range), true));
				}
			}
		}

		if (isText(caretPosition.container())) {
			addCharacterOffset(caretPosition.container(), caretPosition.offset());
			return clientRects;
		}

		if (isElement(caretPosition.container())) {
			if (caretPosition.isAtEnd()) {
				node = resolveIndex(caretPosition.container(), caretPosition.offset());
				if (isText(node)) {
					addCharacterOffset(node, node.data.length);
				}

				if (isValidElementCaretCandidate(node) && !isBr(node)) {
					addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(node), false));
				}
			} else {
				node = resolveIndex(caretPosition.container(), caretPosition.offset());
				if (isText(node)) {
					addCharacterOffset(node, 0);
				}

				if (isValidElementCaretCandidate(node) && caretPosition.isAtEnd()) {
					addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(node), false));
					return clientRects;
				}

				beforeNode = resolveIndex(caretPosition.container(), caretPosition.offset() - 1);
				if (isValidElementCaretCandidate(beforeNode) && !isBr(beforeNode)) {
					if (isBlock(beforeNode) || isBlock(node) || !isValidElementCaretCandidate(node)) {
						addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(beforeNode), false));
					}
				}

				if (isValidElementCaretCandidate(node)) {
					addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(node), true));
				}
			}
		}

		return clientRects;
	}

	/**
	 * Represents a location within the document by a container and an offset.
	 *
	 * @constructor
	 * @param {Node} container Container node.
	 * @param {Number} offset Offset within that container node.
	 * @param {Array} clientRects Optional client rects array for the position.
	 */
	function CaretPosition(container, offset, clientRects) {
		function isAtStart() {
			if (isText(container)) {
				return offset === 0;
			}

			return offset === 0;
		}

		function isAtEnd() {
			if (isText(container)) {
				return offset >= container.data.length;
			}

			return offset >= container.childNodes.length;
		}

		function toRange() {
			var range;

			range = container.ownerDocument.createRange();
			range.setStart(container, offset);
			range.setEnd(container, offset);

			return range;
		}

		function getClientRects() {
			if (!clientRects) {
				clientRects = getCaretPositionClientRects(new CaretPosition(container, offset));
			}

			return clientRects;
		}

		function isVisible() {
			return getClientRects().length > 0;
		}

		function isEqual(caretPosition) {
			return caretPosition && container === caretPosition.container() && offset === caretPosition.offset();
		}

		function getNode(before) {
			return resolveIndex(container, before ? offset - 1 : offset);
		}

		return {
			/**
			 * Returns the container node.
			 *
			 * @method container
			 * @return {Node} Container node.
			 */
			container: Fun.constant(container),

			/**
			 * Returns the offset within the container node.
			 *
			 * @method offset
			 * @return {Number} Offset within the container node.
			 */
			offset: Fun.constant(offset),

			/**
			 * Returns a range out of a the caret position.
			 *
			 * @method toRange
			 * @return {DOMRange} range for the caret position.
			 */
			toRange: toRange,

			/**
			 * Returns the client rects for the caret position. Might be multiple rects between
			 * block elements.
			 *
			 * @method getClientRects
			 * @return {Array} Array of client rects.
			 */
			getClientRects: getClientRects,

			/**
			 * Returns true if the caret location is visible/displayed on screen.
			 *
			 * @method isVisible
			 * @return {Boolean} true/false if the position is visible or not.
			 */
			isVisible: isVisible,

			/**
			 * Returns true if the caret location is at the beginning of text node or container.
			 *
			 * @method isVisible
			 * @return {Boolean} true/false if the position is at the beginning.
			 */
			isAtStart: isAtStart,

			/**
			 * Returns true if the caret location is at the end of text node or container.
			 *
			 * @method isVisible
			 * @return {Boolean} true/false if the position is at the end.
			 */
			isAtEnd: isAtEnd,

			/**
			 * Compares the caret position to another caret position. This will only compare the
			 * container and offset not it's visual position.
			 *
			 * @method isEqual
			 * @param {tinymce.caret.CaretPosition} caretPosition Caret position to compare with.
			 * @return {Boolean} true if the caret positions are equal.
			 */
			isEqual: isEqual,

			/**
			 * Returns the closest resolved node from a node index. That means if you have an offset after the
			 * last node in a container it will return that last node.
			 *
			 * @method getNode
			 * @return {Node} Node that is closest to the index.
			 */
			getNode: getNode
		};
	}

	/**
	 * Creates a caret position from the start of a range.
	 *
	 * @method fromRangeStart
	 * @param {DOMRange} range DOM Range to create caret position from.
	 * @return {tinymce.caret.CaretPosition} Caret position from the start of DOM range.
	 */
	CaretPosition.fromRangeStart = function(range) {
		return new CaretPosition(range.startContainer, range.startOffset);
	};

	/**
	 * Creates a caret position from the end of a range.
	 *
	 * @method fromRangeEnd
	 * @param {DOMRange} range DOM Range to create caret position from.
	 * @return {tinymce.caret.CaretPosition} Caret position from the end of DOM range.
	 */
	CaretPosition.fromRangeEnd = function(range) {
		return new CaretPosition(range.endContainer, range.endOffset);
	};

	/**
	 * Creates a caret position from a node and places the offset after it.
	 *
	 * @method after
	 * @param {Node} node Node to get caret position from.
	 * @return {tinymce.caret.CaretPosition} Caret position from the node.
	 */
	CaretPosition.after = function(node) {
		return new CaretPosition(node.parentNode, nodeIndex(node) + 1);
	};

	/**
	 * Creates a caret position from a node and places the offset before it.
	 *
	 * @method before
	 * @param {Node} node Node to get caret position from.
	 * @return {tinymce.caret.CaretPosition} Caret position from the node.
	 */
	CaretPosition.before = function(node) {
		return new CaretPosition(node.parentNode, nodeIndex(node));
	};

	return CaretPosition;
});<commit_msg>Fixed extending character box on WebKit<commit_after>/**
 * CaretPosition.js
 *
 * Released under LGPL License.
 * Copyright (c) 1999-2015 Ephox Corp. All rights reserved
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */

/**
 * This module contains logic for creating caret positions within a document a caretposition
 * is similar to a DOMRange object but it doesn't have two endpoints and is also more lightweight
 * since it's now updated live when the DOM changes.
 *
 * @private
 * @class tinymce.caret.CaretPosition
 * @example
 * var caretPos1 = new CaretPosition(container, offset);
 * var caretPos2 = CaretPosition.fromRangeStart(someRange);
 */
define("tinymce/caret/CaretPosition", [
	"tinymce/util/Fun",
	"tinymce/dom/NodeType",
	"tinymce/dom/DOMUtils",
	"tinymce/dom/RangeUtils",
	"tinymce/caret/CaretCandidate",
	"tinymce/geom/ClientRect",
	"tinymce/text/ExtendingChar"
], function(Fun, NodeType, DOMUtils, RangeUtils, CaretCandidate, ClientRect, ExtendingChar) {
	var isElement = NodeType.isElement,
		isCaretCandidate = CaretCandidate.isCaretCandidate,
		isBlock = NodeType.matchStyleValues('display', 'block table'),
		isFloated = NodeType.matchStyleValues('float', 'left right'),
		isValidElementCaretCandidate = Fun.and(isElement, isCaretCandidate, Fun.negate(isFloated)),
		isNotPre = Fun.negate(NodeType.matchStyleValues('white-space', 'pre pre-line pre-wrap')),
		isText = NodeType.isText,
		isBr = NodeType.isBr,
		nodeIndex = DOMUtils.nodeIndex,
		resolveIndex = RangeUtils.getNode;

	function isWhiteSpace(chr) {
		return chr && /[\r\n\t ]/.test(chr);
	}

	function isHiddenWhiteSpaceRange(range) {
		var container = range.startContainer,
			offset = range.startOffset,
			text;

		if (isWhiteSpace(range.toString()) && isNotPre(container.parentNode)) {
			text = container.data;

			if (isWhiteSpace(text[offset - 1]) || isWhiteSpace(text[offset + 1])) {
				return true;
			}
		}

		return false;
	}

	function getCaretPositionClientRects(caretPosition) {
		var clientRects = [], beforeNode, node;

		// Hack for older WebKit versions that doesn't
		// support getBoundingClientRect on BR elements
		function getBrClientRect(brNode) {
			var doc = brNode.ownerDocument,
				rng = doc.createRange(),
				nbsp = doc.createTextNode('\u00a0'),
				parentNode = brNode.parentNode,
				clientRect;

			parentNode.insertBefore(nbsp, brNode);
			rng.setStart(nbsp, 0);
			rng.setEnd(nbsp, 1);
			clientRect = ClientRect.clone(rng.getBoundingClientRect());
			parentNode.removeChild(nbsp);

			return clientRect;
		}

		function getBoundingClientRect(item) {
			var clientRect, clientRects;

			clientRects = item.getClientRects();
			if (clientRects.length > 0) {
				clientRect = ClientRect.clone(clientRects[0]);
			} else {
				clientRect = ClientRect.clone(item.getBoundingClientRect());
			}

			if (isBr(item) && clientRect.left === 0) {
				return getBrClientRect(item);
			}

			return clientRect;
		}

		function collapseAndInflateWidth(clientRect, toStart) {
			clientRect = ClientRect.collapse(clientRect, toStart);
			clientRect.width = 1;
			clientRect.right = clientRect.left + 1;

			return clientRect;
		}

		function addUniqueAndValidRect(clientRect) {
			if (clientRect.height === 0) {
				return;
			}

			if (clientRects.length > 0) {
				if (ClientRect.isEqual(clientRect, clientRects[clientRects.length - 1])) {
					return;
				}
			}

			clientRects.push(clientRect);
		}

		function addCharacterOffset(container, offset) {
			var range = container.ownerDocument.createRange();

			if (offset < container.data.length) {
				if (ExtendingChar.isExtendingChar(container.data[offset])) {
					return clientRects;
				}

				// WebKit returns two client rects for a position after an extending
				// character a\uxxx|b so expand on "b" and collapse to start of "b" box
				if (ExtendingChar.isExtendingChar(container.data[offset - 1])) {
					range.setStart(container, offset);
					range.setEnd(container, offset + 1);

					if (!isHiddenWhiteSpaceRange(range)) {
						addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(range), false));
						return clientRects;
					}
				}
			}

			if (offset > 0) {
				range.setStart(container, offset - 1);
				range.setEnd(container, offset);

				if (!isHiddenWhiteSpaceRange(range)) {
					addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(range), false));
				}
			}

			if (offset < container.data.length) {
				range.setStart(container, offset);
				range.setEnd(container, offset + 1);

				if (!isHiddenWhiteSpaceRange(range)) {
					addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(range), true));
				}
			}
		}

		if (isText(caretPosition.container())) {
			addCharacterOffset(caretPosition.container(), caretPosition.offset());
			return clientRects;
		}

		if (isElement(caretPosition.container())) {
			if (caretPosition.isAtEnd()) {
				node = resolveIndex(caretPosition.container(), caretPosition.offset());
				if (isText(node)) {
					addCharacterOffset(node, node.data.length);
				}

				if (isValidElementCaretCandidate(node) && !isBr(node)) {
					addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(node), false));
				}
			} else {
				node = resolveIndex(caretPosition.container(), caretPosition.offset());
				if (isText(node)) {
					addCharacterOffset(node, 0);
				}

				if (isValidElementCaretCandidate(node) && caretPosition.isAtEnd()) {
					addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(node), false));
					return clientRects;
				}

				beforeNode = resolveIndex(caretPosition.container(), caretPosition.offset() - 1);
				if (isValidElementCaretCandidate(beforeNode) && !isBr(beforeNode)) {
					if (isBlock(beforeNode) || isBlock(node) || !isValidElementCaretCandidate(node)) {
						addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(beforeNode), false));
					}
				}

				if (isValidElementCaretCandidate(node)) {
					addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect(node), true));
				}
			}
		}

		return clientRects;
	}

	/**
	 * Represents a location within the document by a container and an offset.
	 *
	 * @constructor
	 * @param {Node} container Container node.
	 * @param {Number} offset Offset within that container node.
	 * @param {Array} clientRects Optional client rects array for the position.
	 */
	function CaretPosition(container, offset, clientRects) {
		function isAtStart() {
			if (isText(container)) {
				return offset === 0;
			}

			return offset === 0;
		}

		function isAtEnd() {
			if (isText(container)) {
				return offset >= container.data.length;
			}

			return offset >= container.childNodes.length;
		}

		function toRange() {
			var range;

			range = container.ownerDocument.createRange();
			range.setStart(container, offset);
			range.setEnd(container, offset);

			return range;
		}

		function getClientRects() {
			if (!clientRects) {
				clientRects = getCaretPositionClientRects(new CaretPosition(container, offset));
			}

			return clientRects;
		}

		function isVisible() {
			return getClientRects().length > 0;
		}

		function isEqual(caretPosition) {
			return caretPosition && container === caretPosition.container() && offset === caretPosition.offset();
		}

		function getNode(before) {
			return resolveIndex(container, before ? offset - 1 : offset);
		}

		return {
			/**
			 * Returns the container node.
			 *
			 * @method container
			 * @return {Node} Container node.
			 */
			container: Fun.constant(container),

			/**
			 * Returns the offset within the container node.
			 *
			 * @method offset
			 * @return {Number} Offset within the container node.
			 */
			offset: Fun.constant(offset),

			/**
			 * Returns a range out of a the caret position.
			 *
			 * @method toRange
			 * @return {DOMRange} range for the caret position.
			 */
			toRange: toRange,

			/**
			 * Returns the client rects for the caret position. Might be multiple rects between
			 * block elements.
			 *
			 * @method getClientRects
			 * @return {Array} Array of client rects.
			 */
			getClientRects: getClientRects,

			/**
			 * Returns true if the caret location is visible/displayed on screen.
			 *
			 * @method isVisible
			 * @return {Boolean} true/false if the position is visible or not.
			 */
			isVisible: isVisible,

			/**
			 * Returns true if the caret location is at the beginning of text node or container.
			 *
			 * @method isVisible
			 * @return {Boolean} true/false if the position is at the beginning.
			 */
			isAtStart: isAtStart,

			/**
			 * Returns true if the caret location is at the end of text node or container.
			 *
			 * @method isVisible
			 * @return {Boolean} true/false if the position is at the end.
			 */
			isAtEnd: isAtEnd,

			/**
			 * Compares the caret position to another caret position. This will only compare the
			 * container and offset not it's visual position.
			 *
			 * @method isEqual
			 * @param {tinymce.caret.CaretPosition} caretPosition Caret position to compare with.
			 * @return {Boolean} true if the caret positions are equal.
			 */
			isEqual: isEqual,

			/**
			 * Returns the closest resolved node from a node index. That means if you have an offset after the
			 * last node in a container it will return that last node.
			 *
			 * @method getNode
			 * @return {Node} Node that is closest to the index.
			 */
			getNode: getNode
		};
	}

	/**
	 * Creates a caret position from the start of a range.
	 *
	 * @method fromRangeStart
	 * @param {DOMRange} range DOM Range to create caret position from.
	 * @return {tinymce.caret.CaretPosition} Caret position from the start of DOM range.
	 */
	CaretPosition.fromRangeStart = function(range) {
		return new CaretPosition(range.startContainer, range.startOffset);
	};

	/**
	 * Creates a caret position from the end of a range.
	 *
	 * @method fromRangeEnd
	 * @param {DOMRange} range DOM Range to create caret position from.
	 * @return {tinymce.caret.CaretPosition} Caret position from the end of DOM range.
	 */
	CaretPosition.fromRangeEnd = function(range) {
		return new CaretPosition(range.endContainer, range.endOffset);
	};

	/**
	 * Creates a caret position from a node and places the offset after it.
	 *
	 * @method after
	 * @param {Node} node Node to get caret position from.
	 * @return {tinymce.caret.CaretPosition} Caret position from the node.
	 */
	CaretPosition.after = function(node) {
		return new CaretPosition(node.parentNode, nodeIndex(node) + 1);
	};

	/**
	 * Creates a caret position from a node and places the offset before it.
	 *
	 * @method before
	 * @param {Node} node Node to get caret position from.
	 * @return {tinymce.caret.CaretPosition} Caret position from the node.
	 */
	CaretPosition.before = function(node) {
		return new CaretPosition(node.parentNode, nodeIndex(node));
	};

	return CaretPosition;
});<|endoftext|>


<commit_before>(function () {
    var XMLObj = XML,
        filter = function(data) {
            return (data instanceof String || typeof data === 'string') ?
                data.replace(/<\?xml.*?\?>\s*/, "").replace(/<!--[\s\S]*?-->/g, "") : data;
        };
    this.XML = function (data) {
        return new XMLObj(filter(data));
    };

    var XMLListObj = XMLList;
    this.XMLList = function (data) {
        return new XMLListObj(filter(data));
    };
}());<commit_msg>removing whitespaces from xml strings before passing to the constructor<commit_after>(function () {
    var XMLObj = XML,
        filter = function(data) {
            return (data instanceof String || typeof data === 'string') ?
                data.replace(/<\?xml.*?\?>\s*/, "").replace(/<!--[\s\S]*?-->/g, "").trim() : data;
        };
    this.XML = function (data) {
        return new XMLObj(filter(data));
    };

    var XMLListObj = XMLList;
    this.XMLList = function (data) {
        return new XMLListObj(filter(data));
    };
}());<|endoftext|>


<commit_before>/*
 * LICENCE : CloudUnit is available under the Affero Gnu Public License GPL V3 : https://www.gnu.org/licenses/agpl-3.0.html
 *     but CloudUnit is licensed too under a standard commercial license.
 *     Please contact our sales team if you would like to discuss the specifics of our Enterprise license.
 *     If you are not sure whether the GPL is right for you,
 *     you can always test our software under the GPL and inspect the source code before you contact us
 *     about purchasing a commercial license.
 *
 *     LEGAL TERMS : "CloudUnit" is a registered trademark of Treeptik and can't be used to endorse
 *     or promote products derived from this project without prior written permission from Treeptik.
 *     Products or services derived from this software may not be called "CloudUnit"
 *     nor may "Treeptik" or similar confusing terms appear in their names without prior written permission.
 *     For any questions, contact us : contact@treeptik.fr
 */

(function () {
  'use strict';
  angular.module ( 'webuiApp' )
    .factory ( 'ExplorerService', ExplorerService );

  ExplorerService.$inject = [
    '$resource',
    '$http'
  ];

  function ExplorerService ( $resource, $http ) {

    return {
      buildTree: buildTree,
      //downloadFile: downloadFile,
      deleteFile: deleteFile,
      addDirectory: addDirectory,
      unzipFile: unzipFile,
      editFile: editFile,
      getFile: getFile
    };

    ////////////////////////////////////////////////////////////////////

    // Liste l'arborescence de fichier d'un container
    function buildTree ( containerId, path ) {
      var dir = $resource ( '/file/container/:containerId/path/:path' );
      return dir.query ( {
        containerId: containerId,
        path: path
      } ).$promise;
    }

   function unzipFile ( containerId, applicationName, path, item) {
      var file = $resource ( 'file/unzip/container/:containerId/application/:applicationName/path/:path/fileName/:item',
      {
        containerId: containerId,
        applicationName: applicationName,
        path: path,
        item: item
      },
      { 'update': { method: 'PUT',
          transformResponse: function ( data, headers ) {
            var response = {};
            response.data = data;
            response.headers = headers ();
            return response;
          } }
      });

      return file.update ().$promise;
    }

    function getFile ( containerId, applicationName, path, fileName ) {
      var file = $resource ( '/file/content/container/:containerId/application/:applicationName/path/:path/fileName/:fileName', {
        containerId: containerId,
        applicationName: applicationName,
        path: path,
        fileName: fileName
      }, {
        get: {
          method: 'GET',
          transformResponse: function ( data, headers ) {
            var response = {};
            response.data = data;
            response.headers = headers ();
            return response;
          }
        }
      } );

      return file.get ().$promise;
    }

    function editFile ( containerId, applicationName, path, fileName, fileContent ) {
      console.log(fileContent);
      var data, headers;
            headers = {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Accept': 'application/json, text/plain, */*'
      };
      data = "fileContent=" + fileContent;
      return $http.post('file/content/container/' + containerId + '/application/' + applicationName + '/path/'+path + '/fileName/' + fileName, data, {
        headers: headers
      });

      /*var file = $resource ( 'file/content/container/:containerId/application/:applicationName/path/:path/fileName/:fileName',
      {
        containerId: containerId,
        applicationName: applicationName,
        path: path,
        fileName: fileName
      },{ test: {method:'POST', params: { fileContent: fileContent}}});

      return file.test ( ).$promise;*/
    }

    function deleteFile ( containerId, applicationName, path ) {
      var file = $resource ( '/file/container/:containerId/application/:applicationName/path/:path' );

      return file.delete ( {
        containerId: containerId,
        applicationName: applicationName,
        path: path
      } ).$promise;
    }

    function addDirectory ( containerId, applicationName, path ) {
      var request = $resource ( '/file/container/:containerId/application/:applicationName/path/:path', {
        containerId: containerId,
        applicationName: applicationName,
        path: path
      } );

      return request.save ().$promise;
    }

    /*function downloadFile ( containerId, applicationName, path, fileName ) {
      var file = $resource ( '/file/container/:containerId/application/:applicationName/path/:path/fileName/:fileName', {
        containerId: containerId,
        applicationName: applicationName,
        path: path,
        fileName: fileName
      }, {
        get: {
          method: 'GET',
          transformResponse: function ( data, headers ) {
            var response = {};
            response.data = data;
            response.headers = headers ();
            return response;
          }
        }
      } );

      return file.get ().$promise;

    }*/
  }
}) ();

<commit_msg>Replace  with  in explorer service<commit_after>/*
 * LICENCE : CloudUnit is available under the Affero Gnu Public License GPL V3 : https://www.gnu.org/licenses/agpl-3.0.html
 *     but CloudUnit is licensed too under a standard commercial license.
 *     Please contact our sales team if you would like to discuss the specifics of our Enterprise license.
 *     If you are not sure whether the GPL is right for you,
 *     you can always test our software under the GPL and inspect the source code before you contact us
 *     about purchasing a commercial license.
 *
 *     LEGAL TERMS : "CloudUnit" is a registered trademark of Treeptik and can't be used to endorse
 *     or promote products derived from this project without prior written permission from Treeptik.
 *     Products or services derived from this software may not be called "CloudUnit"
 *     nor may "Treeptik" or similar confusing terms appear in their names without prior written permission.
 *     For any questions, contact us : contact@treeptik.fr
 */

(function () {
  'use strict';
  angular.module ( 'webuiApp' )
    .factory ( 'ExplorerService', ExplorerService );

  ExplorerService.$inject = [
    '$resource'
  ];

  function ExplorerService ( $resource ) {

    return {
      buildTree: buildTree,
      //downloadFile: downloadFile,
      deleteFile: deleteFile,
      addDirectory: addDirectory,
      unzipFile: unzipFile,
      editFile: editFile,
      getFile: getFile
    };

    ////////////////////////////////////////////////////////////////////

    // Liste l'arborescence de fichier d'un container
    function buildTree ( containerId, path ) {
      var dir = $resource ( '/file/container/:containerId/path/:path' );
      return dir.query ( {
        containerId: containerId,
        path: path
      } ).$promise;
    }

   function unzipFile ( containerId, applicationName, path, item) {
      var file = $resource ( 'file/unzip/container/:containerId/application/:applicationName/path/:path/fileName/:item',
      {
        containerId: containerId,
        applicationName: applicationName,
        path: path,
        item: item
      },
      { 'update': { method: 'PUT',
          transformResponse: function ( data, headers ) {
            var response = {};
            response.data = data;
            response.headers = headers ();
            return response;
          } }
      });

      return file.update ().$promise;
    }

    function getFile ( containerId, applicationName, path, fileName ) {
      var file = $resource ( '/file/content/container/:containerId/application/:applicationName/path/:path/fileName/:fileName', {
        containerId: containerId,
        applicationName: applicationName,
        path: path,
        fileName: fileName
      }, {
        get: {
          method: 'GET',
          transformResponse: function ( data, headers ) {
            var response = {};
            response.data = data;
            response.headers = headers ();
            return response;
          }
        }
      } );

      return file.get ().$promise;
    }

    function editFile ( containerId, applicationName, path, fileName, fileContent ) {

      var file = $resource ( 'file/content/container/:containerId/application/:applicationName/path/:path/fileName/:fileName',
      {
        containerId: containerId,
        applicationName: applicationName,
        path: path,
        fileName: fileName
      },{ update: {method:'POST', params: { fileContent: fileContent}}});

      return file.update ( ).$promise;
    }

    function deleteFile ( containerId, applicationName, path ) {
      var file = $resource ( '/file/container/:containerId/application/:applicationName/path/:path' );

      return file.delete ( {
        containerId: containerId,
        applicationName: applicationName,
        path: path
      } ).$promise;
    }

    function addDirectory ( containerId, applicationName, path ) {
      var request = $resource ( '/file/container/:containerId/application/:applicationName/path/:path', {
        containerId: containerId,
        applicationName: applicationName,
        path: path
      } );

      return request.save ().$promise;
    }

    /*function downloadFile ( containerId, applicationName, path, fileName ) {
      var file = $resource ( '/file/container/:containerId/application/:applicationName/path/:path/fileName/:fileName', {
        containerId: containerId,
        applicationName: applicationName,
        path: path,
        fileName: fileName
      }, {
        get: {
          method: 'GET',
          transformResponse: function ( data, headers ) {
            var response = {};
            response.data = data;
            response.headers = headers ();
            return response;
          }
        }
      } );

      return file.get ().$promise;

    }*/
  }
}) ();

<|endoftext|>


<commit_before>importScripts('/js/uncompressed/localforage.min.js');

self.addEventListener('install', (event) => {
  console.log("Installing ServiceWorker...");
  event.waitUntil(preLoad());
  console.log("Installation finished.");
});

self.addEventListener('message', (event) => {
  switch (event.data) {
    case "startSync":
      event.waitUntil(fetchSync(event.source));
      break;

    case "restoreData":
      event.source.postMessage("syncFinished");
      break;

    default:
      break;
  }
});

const fetchSync = (source) => {
  let promiseChain = [];

  promiseChain.push(
    fetch("/feed", {method: 'POST', redirect: 'follow', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({".": "1"}), credentials: 'same-origin'}).then((responseFeed) => {
      return responseFeed.json();
    }).then((jsonFeed) => {
      if (jsonFeed.includes("¯\\_(ツ)_/¯")) {
        return localforage.setItem('feedError', jsonFeed)
      } else {
        return localforage.setItem('feed', jsonFeed)
      }
    })
  );

  promiseChain.push(
    fetch("/dnevnik", {method: 'POST', redirect: 'follow', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({".": "1"}), credentials: 'same-origin'}).then((responseDnevnik) => {
      return responseDnevnik.json();
    }).then((jsonDnevnik) => {
      if (jsonDnevnik.includes("¯\\_(ツ)_/¯")) {
        return localforage.setItem('dnevnikError', jsonDnevnik)
      } else {
        return localforage.setItem('dnevnik', jsonDnevnik)
      }
    })
  );

  promiseChain.push(
    fetch("/stats", {method: 'POST', redirect: 'follow', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({".": "1"}), credentials: 'same-origin'}).then((responseStats) => {
      return responseStats.json();
    }).then((jsonStats) => {
      if (jsonStats.includes("¯\\_(ツ)_/¯")) {
        return localforage.setItem('statsError', jsonStats)
      } else {
        return localforage.setItem('stats', jsonStats)
      }
    })
  );

  return fetch('/up').then(() => {
    return Promise.all(promiseChain).then(() => {
      return localforage.getItem('pushSettings').then((data) => {
        return fetch('/push', {method: 'POST', redirect: 'follow', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({"pushSettings": JSON.stringify(data)}), credentials: 'same-origin'}).then(() => {
          return new Promise((resolve, reject) => {
            resolve(source.postMessage("syncFinished"));
          });
        })
      })
    })
  })
}

self.addEventListener('push', (event) => {
  localforage.getItem("pushData").then((data) => {
    if (!(data === event.data.text())) {
      let title = 'Сводка';
      let options = {
        body: event.data.text(),
        icon: '/images/android-icon-96x96.png',
        badge: '/images/android-icon-96x96.png',
        vibrate: [200, 400],
      };
      localforage.setItem("pushData", event.data.text())
      event.waitUntil(self.registration.showNotification(title, options));
    }
  })
});

self.addEventListener('notificationclick', (event) => {
  event.notification.close();

  event.waitUntil(clients.matchAll({
    type: 'window'
  }).then((clientList) => {
    for (let i = 0; i < clientList.length; i++) {
      let client = clientList[i];
      if (client.url === '/' && 'focus' in client) {
        return client.focus();
      }
    }
    if (clients.openWindow) {
      return clients.openWindow('/');
    }
  }));
});

const preLoad = () => {
  return caches.open('dnevnik-sw').then((cache) => {
  return cache.addAll([
              '/',
              '/main',
              '/css/material.deep_orange-blue.min.css.br',
              '/css/styles-deep_orange.css.br',
              '/css/material.deep_purple-blue.min.css.br',
              '/css/styles-deep_purple.css.br',
              '/css/material.teal-blue.min.css.br',
              '/css/styles-teal.css.br',
              '/css/material.pink-blue.min.css.br',
              '/css/styles-pink.css.br',
              '/css/md_icons.css.br',
              '/css/Roboto.css.br',
              '/js/compressed.js.br',
              '/js/uncompressed/localforage.min.js',
              '/js/material.min.js.br',
              '/config/browserconfig.xml',
              '/config/manifest.json',
              '/fonts/md_icons.woff2',
              '/fonts/Medium/Roboto-Medium.woff2',
              '/fonts/Regular/Roboto-Regular.woff2'
    ]);
  });
};

self.addEventListener('fetch', (event) => {
  let checkFirstEndps = ["/up", "/login", "/main"];

  let isCheckFirst = (element) => {
    return event.request.url === self.location.origin + element;
  }

  if (checkFirstEndps.some(isCheckFirst) || event.request.method == 'POST') {
    event.respondWith(checkResponse(event.request).catch(() => {
      return returnFromCache(event.request);
    }));
  } else {
    event.respondWith(returnFromCache(event.request).catch(() => {
      return checkResponse(event.request);
    }));
  }

  event.waitUntil(addToCache(event.request));
});

const checkResponse = (request) => {
  return new Promise((fulfill, reject) => {
    fetch(request).then((response) => {
      if (response.status !== 404) {
        fulfill(response);
      } else {
        reject();
      }
    }, reject);
  });
};

const addToCache = (request) => {
  return caches.open('dnevnik-sw').then((cache) => {
    return fetch(request).then((response) => {
      return cache.put(request, response);
    });
  });
};

const returnFromCache = (request) => {
  return caches.open('dnevnik-sw').then((cache) => {
    return cache.match(request).then((matching) => {
     if(!matching || matching.status === 404) {
       return cache.match('/main');
     } else {
       return matching;
     }
    });
  });
};

self.addEventListener('activate', (event) => {
  let cacheWhitelist = [];
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames.map((cacheName) => {
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});
<commit_msg>SW activation speedup<commit_after>importScripts('/js/uncompressed/localforage.min.js');

self.addEventListener('install', (event) => {
  console.log("Installing ServiceWorker...");
  event.waitUntil(preLoad());
  console.log("Installation finished.");
  return self.skipWaiting();
});

self.addEventListener('message', (event) => {
  switch (event.data) {
    case "startSync":
      event.waitUntil(fetchSync(event.source));
      break;

    case "restoreData":
      event.source.postMessage("syncFinished");
      break;

    default:
      break;
  }
});

const fetchSync = (source) => {
  let promiseChain = [];

  promiseChain.push(
    fetch("/feed", {method: 'POST', redirect: 'follow', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({".": "1"}), credentials: 'same-origin'}).then((responseFeed) => {
      return responseFeed.json();
    }).then((jsonFeed) => {
      if (jsonFeed.includes("¯\\_(ツ)_/¯")) {
        return localforage.setItem('feedError', jsonFeed)
      } else {
        return localforage.setItem('feed', jsonFeed)
      }
    })
  );

  promiseChain.push(
    fetch("/dnevnik", {method: 'POST', redirect: 'follow', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({".": "1"}), credentials: 'same-origin'}).then((responseDnevnik) => {
      return responseDnevnik.json();
    }).then((jsonDnevnik) => {
      if (jsonDnevnik.includes("¯\\_(ツ)_/¯")) {
        return localforage.setItem('dnevnikError', jsonDnevnik)
      } else {
        return localforage.setItem('dnevnik', jsonDnevnik)
      }
    })
  );

  promiseChain.push(
    fetch("/stats", {method: 'POST', redirect: 'follow', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({".": "1"}), credentials: 'same-origin'}).then((responseStats) => {
      return responseStats.json();
    }).then((jsonStats) => {
      if (jsonStats.includes("¯\\_(ツ)_/¯")) {
        return localforage.setItem('statsError', jsonStats)
      } else {
        return localforage.setItem('stats', jsonStats)
      }
    })
  );

  return fetch('/up').then(() => {
    return Promise.all(promiseChain).then(() => {
      return localforage.getItem('pushSettings').then((data) => {
        return fetch('/push', {method: 'POST', redirect: 'follow', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({"pushSettings": JSON.stringify(data)}), credentials: 'same-origin'}).then(() => {
          return new Promise((resolve, reject) => {
            resolve(source.postMessage("syncFinished"));
          });
        })
      })
    })
  })
}

self.addEventListener('push', (event) => {
  localforage.getItem("pushData").then((data) => {
    if (!(data === event.data.text())) {
      let title = 'Сводка';
      let options = {
        body: event.data.text(),
        icon: '/images/android-icon-96x96.png',
        badge: '/images/android-icon-96x96.png',
        vibrate: [200, 400],
      };
      localforage.setItem("pushData", event.data.text())
      event.waitUntil(self.registration.showNotification(title, options));
    }
  })
});

self.addEventListener('notificationclick', (event) => {
  event.notification.close();

  event.waitUntil(clients.matchAll({
    type: 'window'
  }).then((clientList) => {
    for (let i = 0; i < clientList.length; i++) {
      let client = clientList[i];
      if (client.url === '/' && 'focus' in client) {
        return client.focus();
      }
    }
    if (clients.openWindow) {
      return clients.openWindow('/');
    }
  }));
});

const preLoad = () => {
  return caches.open('dnevnik-sw').then((cache) => {
  return cache.addAll([
              '/',
              '/main',
              '/css/material.deep_orange-blue.min.css.br',
              '/css/styles-deep_orange.css.br',
              '/css/material.deep_purple-blue.min.css.br',
              '/css/styles-deep_purple.css.br',
              '/css/material.teal-blue.min.css.br',
              '/css/styles-teal.css.br',
              '/css/material.pink-blue.min.css.br',
              '/css/styles-pink.css.br',
              '/css/md_icons.css.br',
              '/css/Roboto.css.br',
              '/js/compressed.js.br',
              '/js/uncompressed/localforage.min.js',
              '/js/material.min.js.br',
              '/config/browserconfig.xml',
              '/config/manifest.json',
              '/fonts/md_icons.woff2',
              '/fonts/Medium/Roboto-Medium.woff2',
              '/fonts/Regular/Roboto-Regular.woff2'
    ]);
  });
};

self.addEventListener('fetch', (event) => {
  let checkFirstEndps = ["/up", "/login", "/main"];

  let isCheckFirst = (element) => {
    return event.request.url === self.location.origin + element;
  }

  if (checkFirstEndps.some(isCheckFirst) || event.request.method == 'POST') {
    event.respondWith(checkResponse(event.request).catch(() => {
      return returnFromCache(event.request);
    }));
  } else {
    event.respondWith(returnFromCache(event.request).catch(() => {
      return checkResponse(event.request);
    }));
  }

  event.waitUntil(addToCache(event.request));
});

const checkResponse = (request) => {
  return new Promise((fulfill, reject) => {
    fetch(request).then((response) => {
      if (response.status !== 404) {
        fulfill(response);
      } else {
        reject();
      }
    }, reject);
  });
};

const addToCache = (request) => {
  return caches.open('dnevnik-sw').then((cache) => {
    return fetch(request).then((response) => {
      return cache.put(request, response);
    });
  });
};

const returnFromCache = (request) => {
  return caches.open('dnevnik-sw').then((cache) => {
    return cache.match(request).then((matching) => {
     if(!matching || matching.status === 404) {
       return cache.match('/main');
     } else {
       return matching;
     }
    });
  });
};

self.addEventListener('activate', (event) => {
  let cacheWhitelist = [];
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames.map((cacheName) => {
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
  return self.clients.claim();
});
<|endoftext|>


<commit_before>#!/usr/bin/env node

/**
 * Sweet
 * Simplest Web Engine Ever, The
 *
 * @author Artem Sapegin
 * @copyright 2011 Artem Sapegin (sapegin.ru)
 * @license http://creativecommons.org/licenses/by/3.0/
 */

var fs = require('fs'),
	path = require('path'),
	optparse = require('optparse'),
	fest = require('fest'),
	stylus = require('stylus'),
	colors = require('colors'),
	treewatcher = require('treewatcher');

// Config
try {
	var o = require(path.join(process.cwd(), 'sweet-config.js'));
}
catch (e) {
	error('Cannot open configuration file sweet-config.js.');
}

// Global vars
var compiledTemplates = {},
	isDebug = false;

// Command line options
var switches = [
	['-h', '--help', 'Shows this screen'],
	['-d', '--debug', 'Debug mode'],
	['-w', '--watch', 'Watch for changes in content, templates and styles. (Implies --debug)']
];
var parser = new optparse.OptionParser(switches),
	isBuild = true;

parser.on('help', function() {
	isBuild = false;
	console.log(parser.toString());
});

parser.on('debug', function() {
	isDebug = true;
});

parser.on('watch', function() {
	isBuild = false;
	isDebug = true;
	watch();
});

parser.parse(process.argv);

// Run Forrest run!
if (isBuild) build();

function build(recompile) {
	var recompile = recompile !== false,
		datasets = {},
		sitemap = {},
		commons = {},
		templates = {},
		versions = {};

	var files = getFilesList(o.CONTENT_DIR);
	for (var fileIdx = 0; fileIdx < files.length; fileIdx++) {
		var srcPath = files[fileIdx],
			lang = getFileLanguage(srcPath),
			fileId = lang + '/' + getFileSignificantPath(srcPath);
			resultPath = path.join(o.PUBLISH_DIR, fileId + '.html');

		// Read contents
		var data = getContent(srcPath);
		if (data.error) {
			error('Parse error in content file ' + srcPath + ':\n' + data.error);
			return;
		}

		// Common data file?
		var filename = path.basename(srcPath),
			ext = path.extname(srcPath);
		if (filename.charAt(0) === '.' && (ext === '.js' || ext === '.json')) {
			commons[filename.slice(1).replace(ext, '')] = data;
			continue;
		}

		// Check template
		if (data.template && (recompile || (!recompile && !compiledTemplates[data.template]))) {
			templates[data.template] = true;
		}
		else {
			data.template = o.DEFAULT_TEMPLATE_ID;
		}

		// Additional data
		data.lang = lang;
		data.path = fileId;
		data.url = fileToUrl(srcPath);
		data.uri = fileToUri(srcPath);

		sitemap[fileId] = getSitemapData(data);
		datasets[fileId] = data;
	}

	// File versions
	if (o.FILES) {
		for (var id in o.FILES) {
			var file = o.FILES[id];
			if (!path.existsSync(file[0])) {
				error('Versioned file ' + file[0] + ' not found');
			}
			versions[id] = file[1].replace('{version}', fs.statSync(file.path).mtime.getTime());
		}
	}

	if (recompile) {
		templates[o.DEFAULT_TEMPLATE_ID] = true;
	}
	compileTemplates(templates);
	generateFiles(datasets, sitemap, commons, versions);
}

function watch() {
	console.log('Sweet watching...'.grey);
	watchTemplatesAndContent();
	watchStylesheets();
}

function watchTemplatesAndContent() {
	build();
	watchFolder(o.CONTENT_DIR, function() {
		build(false);
	});
	watchFolder(o.TEMPLATES_DIR, build);
}

function watchStylesheets() {
	if (!o.STYLESHEETS || !o.STYLESHEETS_DIR) {
		return;
	}

	// Create CSS files first time
	for (var ssIdx = 0; ssIdx < o.STYLESHEETS.length; ssIdx++) {
		var ss = o.STYLESHEETS[ssIdx];
		stylusBuild(ss[0], ss[1]);
	}

	watchFolder(o.STYLESHEETS_DIR, updateStylesheets);
}

function watchFolder(dir, callback) {
	var watcher = new treewatcher.Watcher({
		throttle: 50
	});

	watcher.on('change', function(event, path, watcher) {
		console.log('Changes detected in'.grey, colors.blue(path));
		callback();
	});

	watcher.watch(dir, function(err, watcher) {
		if (err) {
			error('Cannnot watch ' + dir)
		}
	});
}

function updateStylesheets() {
	for (var ssIdx = 0; ssIdx < o.STYLESHEETS.length; ssIdx++) {
		var ss = o.STYLESHEETS[ssIdx];
		stylusBuild(ss[0], ss[1]);
	}
}

function getSitemapData(data) {
	var sitemapData = {};
	for (var key in data) {
		if (key !== 'content') {
			sitemapData[key] = data[key];
		}
	}
	return sitemapData;
}

function compileTemplates(templates) {
	for (var templateId in templates) {
		var templatePath = path.join(o.TEMPLATES_DIR, templateId + '.xhtml');
		if (!path.existsSync(templatePath)) {
			error('Template file ' + templatePath + ' not found.');
			return;
		}

		compiledTemplates[templateId] = (new Function('return ' + fest.compile(templatePath)))();
	}
}

function fileToUrl(filepath) {
	return o.URL_PREFIXES[getFileLanguage(filepath)] + getFileUriPart(filepath);
}

function fileToUri(filepath) {
	return o.URI_PREFIXES[getFileLanguage(filepath)] + getFileUriPart(filepath);
}

function getFileUriPart(filepath) {
	return getFileSignificantPath(filepath).replace(/^index$/, '');
}

function getFileSignificantPath(filepath) {
	var basename = path.basename(filepath).replace(path.extname(filepath), '');
	filepath = filepath.replace(path.join(o.CONTENT_DIR, getFileLanguage(filepath)), '');
	filepath = filepath.slice(1);
	filepath = path.dirname(filepath);
	return path.join(filepath, basename).replace('\\', '/');
}

function getFileLanguage(filepath) {
	return path.basename(path.dirname(filepath));
}

function generateFiles(filesData, sitemap, commons, versions) {
	for (var fileId in filesData) {
		var data = filesData[fileId];

		// Special data
		data.map = sitemap;
		data.files = versions;
		data.debug = isDebug;
		for (var key in commons) {
			data[key] = commons[key];
		}

		transform(data.template, data, function(result) {
			var resultPath = path.join(o.PUBLISH_DIR, fileId + '.html');
			mkdirSyncRecursive(path.dirname(resultPath));
			fs.writeFile(resultPath, result, function(err) {
				if (err) {
					error('Cannot write file ' + resultPath + '.');
				}
			});
		});
	}
}

function getContent(file) {
	var content = readUtfFile(file),
		ext = path.extname(file);

	if (ext === '.js' || ext === '.json') {
		return readJsonFile(file);
	}

	var parts = content.split('---');
	if (parts.length !== 2) return {error: 'Head/body delimiter (---) not found.'};

	var head = parts[0],
		body = parts[1],
		result = {content: body.trim()};

	var headLines = head.split('\n');
	for (var lineIdx = 0; lineIdx < headLines.length; lineIdx++) {
		var line = headLines[lineIdx];
		if (line.indexOf(':') === -1) continue;

		var m = line.match(/^\W*(\w+?):\s*(.*)/);
		if (!m) return {error: 'Cannot understand variable on line ' + (lineIdx + 1) + ':\n' + line};

		var key = m[1],
			value = m[2].trim();

		result[key] = value;
	}

	return result;
}

function getFilesList(dir) {
	var lastChar = dir.charAt(dir.length - 1);
	if (lastChar === '/' || lastChar === '\\') {
		dir = dir.slice(0, -1);
	}

	var items = findSync(dir),
		files = [];

	for (var itemIdx = 0; itemIdx < items.length; itemIdx++) {
		var item = items[itemIdx];
		if (!fs.statSync(item).isDirectory()) {
			files.push(path.normalize(item));
		}
	}

	return files;
}

function readJsonFile(filepath) {
	var data = readUtfFile(filepath);
	if (!data) return {};

	try {
		var json = JSON.parse(data);
	}
	catch (e) {
		error('Cannot parse JSON file ' + filepath + '.');
	}
	return json;
}

function readUtfFile(filepath) {
	var data = fs.readFileSync(filepath, 'utf8');
	return data.replace(/^\uFEFF/, '');
}

function error(message) {
	console.error('%s'.red, message);
	process.exit(1);
}

function transform(templateId, json, callback) {
	var template = compiledTemplates[templateId];
	if (!template) {
		error('Template ' + templateId + ' is not invokable.');
	}
	callback(template(json));
}

// https://github.com/ryanmcgrath/wrench-js/blob/master/lib/wrench.js
function mkdirSyncRecursive(dir, mode) {
	try {
		fs.mkdirSync(dir, mode);
	}
	catch (err) {
		if (err.code === 'ENOENT') {
			mkdirSyncRecursive(path.dirname(dir), mode);
			mkdirSyncRecursive(dir, mode);
		} else if (err.code === 'EEXIST') {
			return;
		} else {
			throw err;
		}
	}
}

// https://github.com/substack/node-findit/blob/master/index.js
function findSync(dir, cb) {
	var rootStat = fs.statSync(dir);
	if (!rootStat.isDirectory()) {
		if (cb) cb(dir, rootStat);
		return [dir];
	}

	return fs.readdirSync(dir).reduce(function (files, file) {
		var p = path.join(dir, file);
		var stat = fs.statSync(p);
		if (cb) cb(p, stat);
		files.push(p);

		if (stat.isDirectory()) {
			files.push.apply(files, findSync(p, cb));
		}

		return files;
	}, []);
}

/* Stylus functions */

function stylusBuild(stylpath, csspath) {
	var styl = readUtfFile(stylpath);
	if (!styl) error('Cannot open stylesheet ' + stylpath + '.');

	stylus(styl)
		.set('filename', stylpath)
		.set('compress', isDebug)
		.set('include css', true)
		.render(function(err, css){
			if (err) error('Stylus error.' + '\n\n' + err.message || err.stack);

			fs.writeFile(csspath, css, function(err) {
				if (err) {
					error('Cannot write file ' + csspath + '.');
				}
			});
		});
}
<commit_msg>Fix rebuild with wrong template when changes detected in content.<commit_after>#!/usr/bin/env node

/**
 * Sweet
 * Simplest Web Engine Ever, The
 *
 * @author Artem Sapegin
 * @copyright 2011 Artem Sapegin (sapegin.ru)
 * @license http://creativecommons.org/licenses/by/3.0/
 */

var fs = require('fs'),
	path = require('path'),
	optparse = require('optparse'),
	fest = require('fest'),
	stylus = require('stylus'),
	colors = require('colors'),
	treewatcher = require('treewatcher');

// Config
try {
	var o = require(path.join(process.cwd(), 'sweet-config.js'));
}
catch (e) {
	error('Cannot open configuration file sweet-config.js.');
}

// Global vars
var compiledTemplates = {},
	isDebug = false;

// Command line options
var switches = [
	['-h', '--help', 'Shows this screen'],
	['-d', '--debug', 'Debug mode'],
	['-w', '--watch', 'Watch for changes in content, templates and styles. (Implies --debug)']
];
var parser = new optparse.OptionParser(switches),
	isBuild = true;

parser.on('help', function() {
	isBuild = false;
	console.log(parser.toString());
});

parser.on('debug', function() {
	isDebug = true;
});

parser.on('watch', function() {
	isBuild = false;
	isDebug = true;
	watch();
});

parser.parse(process.argv);

// Run Forrest run!
if (isBuild) build();

function build(recompile) {
	var recompile = recompile !== false,
		datasets = {},
		sitemap = {},
		commons = {},
		templates = {},
		versions = {};

	var files = getFilesList(o.CONTENT_DIR);
	for (var fileIdx = 0; fileIdx < files.length; fileIdx++) {
		var srcPath = files[fileIdx],
			lang = getFileLanguage(srcPath),
			fileId = lang + '/' + getFileSignificantPath(srcPath);
			resultPath = path.join(o.PUBLISH_DIR, fileId + '.html');

		// Read contents
		var data = getContent(srcPath);
		if (data.error) {
			error('Parse error in content file ' + srcPath + ':\n' + data.error);
			return;
		}

		// Common data file?
		var filename = path.basename(srcPath),
			ext = path.extname(srcPath);
		if (filename.charAt(0) === '.' && (ext === '.js' || ext === '.json')) {
			commons[filename.slice(1).replace(ext, '')] = data;
			continue;
		}

		// Check template
		if (data.template) {
			if (recompile || (!recompile && !compiledTemplates[data.template])) {
				templates[data.template] = true;
			}
		}
		else {
			data.template = o.DEFAULT_TEMPLATE_ID;
		}

		// Additional data
		data.lang = lang;
		data.path = fileId;
		data.url = fileToUrl(srcPath);
		data.uri = fileToUri(srcPath);

		sitemap[fileId] = getSitemapData(data);
		datasets[fileId] = data;
	}

	// File versions
	if (o.FILES) {
		for (var id in o.FILES) {
			var file = o.FILES[id];
			if (!path.existsSync(file[0])) {
				error('Versioned file ' + file[0] + ' not found');
			}
			versions[id] = file[1].replace('{version}', fs.statSync(file.path).mtime.getTime());
		}
	}

	if (recompile) {
		templates[o.DEFAULT_TEMPLATE_ID] = true;
	}
	compileTemplates(templates);
	generateFiles(datasets, sitemap, commons, versions);
}

function watch() {
	console.log('Sweet watching...'.grey);
	watchTemplatesAndContent();
	watchStylesheets();
}

function watchTemplatesAndContent() {
	build();
	watchFolder(o.CONTENT_DIR, function() {
		build(false);
	});
	watchFolder(o.TEMPLATES_DIR, build);
}

function watchStylesheets() {
	if (!o.STYLESHEETS || !o.STYLESHEETS_DIR) {
		return;
	}

	// Create CSS files first time
	for (var ssIdx = 0; ssIdx < o.STYLESHEETS.length; ssIdx++) {
		var ss = o.STYLESHEETS[ssIdx];
		stylusBuild(ss[0], ss[1]);
	}

	watchFolder(o.STYLESHEETS_DIR, updateStylesheets);
}

function watchFolder(dir, callback) {
	var watcher = new treewatcher.Watcher({
		throttle: 50
	});

	watcher.on('change', function(event, path, watcher) {
		console.log('Changes detected in'.grey, colors.blue(path));
		callback();
	});

	watcher.watch(dir, function(err, watcher) {
		if (err) {
			error('Cannnot watch ' + dir)
		}
	});
}

function updateStylesheets() {
	for (var ssIdx = 0; ssIdx < o.STYLESHEETS.length; ssIdx++) {
		var ss = o.STYLESHEETS[ssIdx];
		stylusBuild(ss[0], ss[1]);
	}
}

function getSitemapData(data) {
	var sitemapData = {};
	for (var key in data) {
		if (key !== 'content') {
			sitemapData[key] = data[key];
		}
	}
	return sitemapData;
}

function compileTemplates(templates) {
	for (var templateId in templates) {
		var templatePath = path.join(o.TEMPLATES_DIR, templateId + '.xhtml');
		if (!path.existsSync(templatePath)) {
			error('Template file ' + templatePath + ' not found.');
			return;
		}

		compiledTemplates[templateId] = (new Function('return ' + fest.compile(templatePath)))();
	}
}

function fileToUrl(filepath) {
	return o.URL_PREFIXES[getFileLanguage(filepath)] + getFileUriPart(filepath);
}

function fileToUri(filepath) {
	return o.URI_PREFIXES[getFileLanguage(filepath)] + getFileUriPart(filepath);
}

function getFileUriPart(filepath) {
	return getFileSignificantPath(filepath).replace(/^index$/, '');
}

function getFileSignificantPath(filepath) {
	var basename = path.basename(filepath).replace(path.extname(filepath), '');
	filepath = filepath.replace(path.join(o.CONTENT_DIR, getFileLanguage(filepath)), '');
	filepath = filepath.slice(1);
	filepath = path.dirname(filepath);
	return path.join(filepath, basename).replace('\\', '/');
}

function getFileLanguage(filepath) {
	return path.basename(path.dirname(filepath));
}

function generateFiles(filesData, sitemap, commons, versions) {
	for (var fileId in filesData) {
		var data = filesData[fileId];

		// Special data
		data.map = sitemap;
		data.files = versions;
		data.debug = isDebug;
		for (var key in commons) {
			data[key] = commons[key];
		}

		transform(data.template, data, function(result) {
			var resultPath = path.join(o.PUBLISH_DIR, fileId + '.html');
			mkdirSyncRecursive(path.dirname(resultPath));
			fs.writeFile(resultPath, result, function(err) {
				if (err) {
					error('Cannot write file ' + resultPath + '.');
				}
			});
		});
	}
}

function getContent(file) {
	var content = readUtfFile(file),
		ext = path.extname(file);

	if (ext === '.js' || ext === '.json') {
		return readJsonFile(file);
	}

	var parts = content.split('---');
	if (parts.length !== 2) return {error: 'Head/body delimiter (---) not found.'};

	var head = parts[0],
		body = parts[1],
		result = {content: body.trim()};

	var headLines = head.split('\n');
	for (var lineIdx = 0; lineIdx < headLines.length; lineIdx++) {
		var line = headLines[lineIdx];
		if (line.indexOf(':') === -1) continue;

		var m = line.match(/^\W*(\w+?):\s*(.*)/);
		if (!m) return {error: 'Cannot understand variable on line ' + (lineIdx + 1) + ':\n' + line};

		var key = m[1],
			value = m[2].trim();

		result[key] = value;
	}

	return result;
}

function getFilesList(dir) {
	var lastChar = dir.charAt(dir.length - 1);
	if (lastChar === '/' || lastChar === '\\') {
		dir = dir.slice(0, -1);
	}

	var items = findSync(dir),
		files = [];

	for (var itemIdx = 0; itemIdx < items.length; itemIdx++) {
		var item = items[itemIdx];
		if (!fs.statSync(item).isDirectory()) {
			files.push(path.normalize(item));
		}
	}

	return files;
}

function readJsonFile(filepath) {
	var data = readUtfFile(filepath);
	if (!data) return {};

	try {
		var json = JSON.parse(data);
	}
	catch (e) {
		error('Cannot parse JSON file ' + filepath + '.');
	}
	return json;
}

function readUtfFile(filepath) {
	var data = fs.readFileSync(filepath, 'utf8');
	return data.replace(/^\uFEFF/, '');
}

function error(message) {
	console.error('%s'.red, message);
	process.exit(1);
}

function transform(templateId, json, callback) {
	var template = compiledTemplates[templateId];
	if (!template) {
		error('Template ' + templateId + ' is not invokable.');
	}
	callback(template(json));
}

// https://github.com/ryanmcgrath/wrench-js/blob/master/lib/wrench.js
function mkdirSyncRecursive(dir, mode) {
	try {
		fs.mkdirSync(dir, mode);
	}
	catch (err) {
		if (err.code === 'ENOENT') {
			mkdirSyncRecursive(path.dirname(dir), mode);
			mkdirSyncRecursive(dir, mode);
		} else if (err.code === 'EEXIST') {
			return;
		} else {
			throw err;
		}
	}
}

// https://github.com/substack/node-findit/blob/master/index.js
function findSync(dir, cb) {
	var rootStat = fs.statSync(dir);
	if (!rootStat.isDirectory()) {
		if (cb) cb(dir, rootStat);
		return [dir];
	}

	return fs.readdirSync(dir).reduce(function (files, file) {
		var p = path.join(dir, file);
		var stat = fs.statSync(p);
		if (cb) cb(p, stat);
		files.push(p);

		if (stat.isDirectory()) {
			files.push.apply(files, findSync(p, cb));
		}

		return files;
	}, []);
}

/* Stylus functions */

function stylusBuild(stylpath, csspath) {
	var styl = readUtfFile(stylpath);
	if (!styl) error('Cannot open stylesheet ' + stylpath + '.');

	stylus(styl)
		.set('filename', stylpath)
		.set('compress', isDebug)
		.set('include css', true)
		.render(function(err, css){
			if (err) error('Stylus error.' + '\n\n' + err.message || err.stack);

			fs.writeFile(csspath, css, function(err) {
				if (err) {
					error('Cannot write file ' + csspath + '.');
				}
			});
		});
}
<|endoftext|>


<commit_before>/**
 * @file
 * Enable pure swipe functionality attached to elements by ID.
 *
 * @usage
 * It's pretty simple to set up and use. You need a callback function which
 * will be called when the swipe is completed and you need an element to attach
 * the swipe too.
 *
 * You attach the swipe using this function:
 * attachSwipe(
 *   'id-of-element',
 *   number-of-fingers-to-track,
 *   callback-function-name,
 *   prevent-default-click/touch-behaviour);
 *
 * So a normal call would look something like this:
 * attachSwipe('mySwipeBox', 1, myCallback, false);
 *
 * Next you'll need the callback function for the swipe completed event. This
 * function takes 6 parameters:
 *   id (string),
 *   angle (int),
 *   length (int),
 *   direction (string),
 *   fingerCount (int), and
 *   element (element).
 *
 * ID is of course the ID of the element that was swiped on.
 * Angle is the overall degree angle of the swipe.
 * Length is the overall length of the swipe.
 * Direction is the direction of the swipe ('left', 'right', 'up', or 'down')
 * which is calculated from the angle.
 * FingerCount is the number of fingers used for the swipe.
 * Element is the element where the swipe originated.
 *
 * @author
 * Stian Hanger (pdnagilum@gmail.com)
 */

/**
 * @var
 * Array of elements to track swipe events for.
 */
var swipeData = {};

/**
 * Attach swipe events to element with given ID.
 *
 * @param string id
 *   The ID of the element to attach too.
 * @param int fingerCount
 *   The number of fingers to track for this particular event. Use null to
 *   disable tracking of count.
 * @param function callback
 *   Callback function when a swipe event takes place on the element. Arguments
 *   for the callback function are: id (string), and direction (string).
 * @param bool preventDefault
 *   Set whether or not the default behaviour of the touch events should pass
 *   through to event handling.
 */
function attachSwipe(id, fingerCount, callback, preventDefault) {
  var element = document.getElementById(id);

  if (element === null ||
      typeof element === 'undefined')
    throw 'Element with ID "' + id + '" could not be found!';

  if (typeof callback !== 'function')
    throw 'The callback argument must be a function!';

  swipeData[id] = {
    callback: callback,
    startX: 0,
    startY: 0,
    currentX: 0,
    currentY: 0,
    swipeLength: 0,
    minimumSwipeLength: 72,
    fingerCount: 0,
    trackFingerCount: fingerCount,
    preventDefault: preventDefault,
    attached: false
  };

  element.setAttribute('ontouchstart', 'touchStart(event, id);');
  element.setAttribute('ontouchend',   'touchEnd(event, id);');
  element.setAttribute('ontouchmove',  'touchMove(event, id);');

  swipeData[id].attached = true;
}

/**
 * Record the initial touch position.
 *
 * @param event event
 *   The touch even to track.
 * @param string id
 *   The ID of the element to track.
 */
function touchStart(event, id) {
  if (swipeData[id].preventDefault)
    event.preventDefault();

  swipeData[id].fingerCount = event.touches.length;

  var trackEvent = (swipeData[id].trackFingerCount === null ?
    true :
    (swipeData[id].fingerCount === swipeData[id].trackFingerCount));

  if (trackEvent) {
    swipeData[id].startX = event.touches[0].pageX;
    swipeData[id].startY = event.touches[0].pageY;
  }
  else {
    resetTouch(id);
  }
}

/**
 * Calculates the touch direction and calls the callback.
 *
 * @param event event
 *   The touch even to track.
 * @param string id
 *   The ID of the element to track.
 */
function touchEnd(event, id) {
  if (swipeData[id].preventDefault)
    event.preventDefault();

  var trackEvent = (swipeData[id].trackFingerCount === null ?
    true :
    (swipeData[id].fingerCount === swipeData[id].trackFingerCount));

  if (trackEvent &&
      swipeData[id].currentX != 0) {
    swipeData[id].swipeLength = Math.round(Math.sqrt(Math.pow(swipeData[id].currentX - swipeData[id].startX, 2) + Math.pow(swipeData[id].currentY - swipeData[id].startY, 2)));

    if (swipeData[id].swipeLength >= swipeData[id].minimumSwipeLength) {
      var x         = swipeData[id].startX - swipeData[id].currentX,
          y         = swipeData[id].currentY - swipeData[id].startY,
          r         = Math.atan2(y, x),
          angle     = Math.round(r * 180 / Math.PI),
          direction = '';

      if (angle < 0)
        angle = 360 - Math.abs(angle);

      if (angle <= 45 && angle >= 0) { direction = 'left'; }
      else if (angle <= 360 && angle >= 315) { direction = 'left'; }
      else if (angle >= 135 && angle <= 255) { direction = 'right'; }
      else if (angle > 45 && angle < 135) { direction = 'down'; }
      else { direction = 'up'; }

      if (swipeData[id].fingerCount > 0)
        swipeData[id].callback(id, angle, swipeData[id].swipeLength, direction, swipeData[id].fingerCount, event.srcElement);
    }
  }

  resetTouch(id);
}

/**
 * Updates the current position of the touch.
 *
 * @param event event
 *   The touch even to track.
 * @param string id
 *   The ID of the element to track.
 */
function touchMove(event, id) {
  if (swipeData[id].preventDefault)
    event.preventDefault();

  var trackEvent = (swipeData[id].trackFingerCount === null ?
    true :
    (swipeData[id].fingerCount === swipeData[id].trackFingerCount));

  if (trackEvent) {
    swipeData[id].currentX = event.touches[0].pageX;
    swipeData[id].currentY = event.touches[0].pageY;
  }
  else {
    resetTouch(id);
  }
}

/**
 * Resets all touch variables used, making it ready for a new event.
 *
 * @param string id
 *   The ID of the element to reset.
 */
function resetTouch(id) {
  swipeData[id].startX = 0;
  swipeData[id].startY = 0;
  swipeData[id].currentX = 0;
  swipeData[id].currentY = 0;
  swipeData[id].swipeLength = 0;
  swipeData[id].fingerCount = 0;
}
<commit_msg>Update self profilation exposure.<commit_after>/**
 * @file
 * Enable pure swipe functionality attached to elements by ID.
 *
 * @author
 * Stian Hanger (pdnagilum@gmail.com)
 *
 * @usage
 * It's pretty simple to set up and use. You need a callback function which
 * will be called when the swipe is completed and you need an element to attach
 * the swipe too.
 *
 * You attach the swipe using this function:
 * attachSwipe(
 *   'id-of-element',
 *   number-of-fingers-to-track,
 *   callback-function-name,
 *   prevent-default-click/touch-behaviour);
 *
 * So a normal call would look something like this:
 * attachSwipe('mySwipeBox', 1, myCallback, false);
 *
 * Next you'll need the callback function for the swipe completed event. This
 * function takes 6 parameters:
 *   id (string),
 *   angle (int),
 *   length (int),
 *   direction (string),
 *   fingerCount (int), and
 *   element (element).
 *
 * ID is of course the ID of the element that was swiped on.
 * Angle is the overall degree angle of the swipe.
 * Length is the overall length of the swipe.
 * Direction is the direction of the swipe ('left', 'right', 'up', or 'down')
 * which is calculated from the angle.
 * FingerCount is the number of fingers used for the swipe.
 * Element is the element where the swipe originated.
 */

/**
 * @var
 * Array of elements to track swipe events for.
 */
var swipeData = {};

/**
 * Attach swipe events to element with given ID.
 *
 * @param string id
 *   The ID of the element to attach too.
 * @param int fingerCount
 *   The number of fingers to track for this particular event. Use null to
 *   disable tracking of count.
 * @param function callback
 *   Callback function when a swipe event takes place on the element. Arguments
 *   for the callback function are: id (string), and direction (string).
 * @param bool preventDefault
 *   Set whether or not the default behaviour of the touch events should pass
 *   through to event handling.
 */
function attachSwipe(id, fingerCount, callback, preventDefault) {
  var element = document.getElementById(id);

  if (element === null ||
      typeof element === 'undefined')
    throw 'Element with ID "' + id + '" could not be found!';

  if (typeof callback !== 'function')
    throw 'The callback argument must be a function!';

  swipeData[id] = {
    callback: callback,
    startX: 0,
    startY: 0,
    currentX: 0,
    currentY: 0,
    swipeLength: 0,
    minimumSwipeLength: 72,
    fingerCount: 0,
    trackFingerCount: fingerCount,
    preventDefault: preventDefault,
    attached: false
  };

  element.setAttribute('ontouchstart', 'touchStart(event, id);');
  element.setAttribute('ontouchend',   'touchEnd(event, id);');
  element.setAttribute('ontouchmove',  'touchMove(event, id);');

  swipeData[id].attached = true;
}

/**
 * Record the initial touch position.
 *
 * @param event event
 *   The touch even to track.
 * @param string id
 *   The ID of the element to track.
 */
function touchStart(event, id) {
  if (swipeData[id].preventDefault)
    event.preventDefault();

  swipeData[id].fingerCount = event.touches.length;

  var trackEvent = (swipeData[id].trackFingerCount === null ?
    true :
    (swipeData[id].fingerCount === swipeData[id].trackFingerCount));

  if (trackEvent) {
    swipeData[id].startX = event.touches[0].pageX;
    swipeData[id].startY = event.touches[0].pageY;
  }
  else {
    resetTouch(id);
  }
}

/**
 * Calculates the touch direction and calls the callback.
 *
 * @param event event
 *   The touch even to track.
 * @param string id
 *   The ID of the element to track.
 */
function touchEnd(event, id) {
  if (swipeData[id].preventDefault)
    event.preventDefault();

  var trackEvent = (swipeData[id].trackFingerCount === null ?
    true :
    (swipeData[id].fingerCount === swipeData[id].trackFingerCount));

  if (trackEvent &&
      swipeData[id].currentX != 0) {
    swipeData[id].swipeLength = Math.round(Math.sqrt(Math.pow(swipeData[id].currentX - swipeData[id].startX, 2) + Math.pow(swipeData[id].currentY - swipeData[id].startY, 2)));

    if (swipeData[id].swipeLength >= swipeData[id].minimumSwipeLength) {
      var x         = swipeData[id].startX - swipeData[id].currentX,
          y         = swipeData[id].currentY - swipeData[id].startY,
          r         = Math.atan2(y, x),
          angle     = Math.round(r * 180 / Math.PI),
          direction = '';

      if (angle < 0)
        angle = 360 - Math.abs(angle);

      if (angle <= 45 && angle >= 0) { direction = 'left'; }
      else if (angle <= 360 && angle >= 315) { direction = 'left'; }
      else if (angle >= 135 && angle <= 255) { direction = 'right'; }
      else if (angle > 45 && angle < 135) { direction = 'down'; }
      else { direction = 'up'; }

      if (swipeData[id].fingerCount > 0)
        swipeData[id].callback(id, angle, swipeData[id].swipeLength, direction, swipeData[id].fingerCount, event.srcElement);
    }
  }

  resetTouch(id);
}

/**
 * Updates the current position of the touch.
 *
 * @param event event
 *   The touch even to track.
 * @param string id
 *   The ID of the element to track.
 */
function touchMove(event, id) {
  if (swipeData[id].preventDefault)
    event.preventDefault();

  var trackEvent = (swipeData[id].trackFingerCount === null ?
    true :
    (swipeData[id].fingerCount === swipeData[id].trackFingerCount));

  if (trackEvent) {
    swipeData[id].currentX = event.touches[0].pageX;
    swipeData[id].currentY = event.touches[0].pageY;
  }
  else {
    resetTouch(id);
  }
}

/**
 * Resets all touch variables used, making it ready for a new event.
 *
 * @param string id
 *   The ID of the element to reset.
 */
function resetTouch(id) {
  swipeData[id].startX = 0;
  swipeData[id].startY = 0;
  swipeData[id].currentX = 0;
  swipeData[id].currentY = 0;
  swipeData[id].swipeLength = 0;
  swipeData[id].fingerCount = 0;
}
<|endoftext|>