--- /dev/null
+
+class EntitySelectorPopup {
+
+ constructor(elem) {
+ this.elem = elem;
+ window.EntitySelectorPopup = this;
+
+ this.callback = null;
+ this.selection = null;
+
+ this.selectButton = elem.querySelector('.entity-link-selector-confirm');
+ this.selectButton.addEventListener('click', this.onSelectButtonClick.bind(this));
+
+ window.$events.listen('entity-select-change', this.onSelectionChange.bind(this));
+ window.$events.listen('entity-select-confirm', this.onSelectionConfirm.bind(this));
+ }
+
+ show(callback) {
+ this.callback = callback;
+ this.elem.components.overlay.show();
+ }
+
+ hide() {
+ this.elem.components.overlay.hide();
+ }
+
+ onSelectButtonClick() {
+ this.hide();
+ if (this.selection !== null && this.callback) this.callback(this.selection);
+ }
+
+ onSelectionConfirm(entity) {
+ this.hide();
+ if (this.callback && entity) this.callback(entity);
+ }
+
+ onSelectionChange(entity) {
+ this.selection = entity;
+ if (entity === null) {
+ this.selectButton.setAttribute('disabled', 'true');
+ } else {
+ this.selectButton.removeAttribute('disabled');
+ }
+ }
+}
+
+module.exports = EntitySelectorPopup;
\ No newline at end of file
--- /dev/null
+
+class EntitySelector {
+
+ constructor(elem) {
+ this.elem = elem;
+ this.search = '';
+ this.lastClick = 0;
+
+ let entityTypes = elem.hasAttribute('entity-types') ? elem.getAttribute('entity-types') : 'page,book,chapter';
+ this.searchUrl = window.baseUrl(`/ajax/search/entities?types=${encodeURIComponent(entityTypes)}`);
+
+ this.input = elem.querySelector('[entity-selector-input]');
+ this.searchInput = elem.querySelector('[entity-selector-search]');
+ this.loading = elem.querySelector('[entity-selector-loading]');
+ this.resultsContainer = elem.querySelector('[entity-selector-results]');
+
+ this.elem.addEventListener('click', this.onClick.bind(this));
+
+ let lastSearch = 0;
+ this.searchInput.addEventListener('input', event => {
+ lastSearch = Date.now();
+ this.showLoading();
+ setTimeout(() => {
+ if (Date.now() - lastSearch < 199) return;
+ this.searchEntities(this.searchInput.value);
+ }, 200);
+ });
+ this.searchInput.addEventListener('keydown', event => {
+ if (event.keyCode === 13) event.preventDefault();
+ });
+
+ this.showLoading();
+ this.initialLoad();
+ }
+
+ showLoading() {
+ this.loading.style.display = 'block';
+ this.resultsContainer.style.display = 'none';
+ }
+
+ hideLoading() {
+ this.loading.style.display = 'none';
+ this.resultsContainer.style.display = 'block';
+ }
+
+ initialLoad() {
+ window.$http.get(this.searchUrl).then(resp => {
+ this.resultsContainer.innerHTML = resp.data;
+ this.hideLoading();
+ })
+ }
+
+ searchEntities(searchTerm) {
+ this.input.value = '';
+ let url = this.searchUrl + `&term=${encodeURIComponent(searchTerm)}`;
+ window.$http.get(url).then(resp => {
+ this.resultsContainer.innerHTML = resp.data;
+ this.hideLoading();
+ });
+ }
+
+ isDoubleClick() {
+ let now = Date.now();
+ let answer = now - this.lastClick < 300;
+ this.lastClick = now;
+ return answer;
+ }
+
+ onClick(event) {
+ let t = event.target;
+
+ if (t.matches('.entity-list a')) {
+ event.preventDefault();
+ event.stopPropagation();
+ let item = t.closest('[data-entity-type]');
+ this.selectItem(item);
+ } else if (t.matches('[data-entity-type]')) {
+ this.selectItem(t)
+ }
+
+ }
+
+ selectItem(item) {
+ let isDblClick = this.isDoubleClick();
+ let type = item.getAttribute('data-entity-type');
+ let id = item.getAttribute('data-entity-id');
+ let isSelected = item.classList.contains('selected') || isDblClick;
+
+ this.unselectAll();
+ this.input.value = isSelected ? `${type}:${id}` : '';
+
+ if (!isSelected) window.$events.emit('entity-select-change', null);
+ if (!isDblClick && !isSelected) return;
+
+ let link = item.querySelector('.entity-list-item-link').getAttribute('href');
+ let name = item.querySelector('.entity-list-item-name').textContent;
+ let data = {id: Number(id), name: name, link: link};
+
+ if (isDblClick) window.$events.emit('entity-select-confirm', data);
+ if (isSelected) window.$events.emit('entity-select-change', data);
+ }
+
+ unselectAll() {
+ let selected = this.elem.querySelectorAll('.selected');
+ for (let i = 0, len = selected.length; i < len; i++) {
+ selected[i].classList.remove('selected');
+ selected[i].classList.remove('primary-background');
+ }
+ }
+
+}
+
+module.exports = EntitySelector;
\ No newline at end of file
'notification': require('./notification'),
'chapter-toggle': require('./chapter-toggle'),
'expand-toggle': require('./expand-toggle'),
+ 'entity-selector-popup': require('./entity-selector-popup'),
+ 'entity-selector': require('./entity-selector'),
};
window.components = {};
this.type = elem.getAttribute('notification');
this.textElem = elem.querySelector('span');
this.autohide = this.elem.hasAttribute('data-autohide');
- window.Events.listen(this.type, text => {
+ window.$events.listen(this.type, text => {
this.show(text);
});
elem.addEventListener('click', this.hide.bind(this));
// Show the popup link selector and insert a link when finished
function showLinkSelector() {
let cursorPos = cm.getCursor('from');
- window.showEntityLinkSelector(entity => {
+ window.EntitySelectorPopup.show(entity => {
let selectedText = cm.getSelection() || entity.name;
let newText = `[${selectedText}](${entity.link})`;
cm.focus();
}
}]);
- ngApp.directive('entityLinkSelector', [function($http) {
- return {
- restrict: 'A',
- link: function(scope, element, attrs) {
-
- const selectButton = element.find('.entity-link-selector-confirm');
- let callback = false;
- let entitySelection = null;
-
- // Handle entity selection change, Stores the selected entity locally
- function entitySelectionChange(entity) {
- entitySelection = entity;
- if (entity === null) {
- selectButton.attr('disabled', 'true');
- } else {
- selectButton.removeAttr('disabled');
- }
- }
- events.listen('entity-select-change', entitySelectionChange);
-
- // Handle selection confirm button click
- selectButton.click(event => {
- hide();
- if (entitySelection !== null) callback(entitySelection);
- });
-
- // Show selector interface
- function show() {
- element.fadeIn(240);
- }
-
- // Hide selector interface
- function hide() {
- element.fadeOut(240);
- }
- scope.hide = hide;
-
- // Listen to confirmation of entity selections (doubleclick)
- events.listen('entity-select-confirm', entity => {
- hide();
- callback(entity);
- });
-
- // Show entity selector, Accessible globally, and store the callback
- window.showEntityLinkSelector = function(passedCallback) {
- show();
- callback = passedCallback;
- };
-
- }
- };
- }]);
-
-
- ngApp.directive('entitySelector', ['$http', '$sce', function ($http, $sce) {
- return {
- restrict: 'A',
- scope: true,
- link: function (scope, element, attrs) {
- scope.loading = true;
- scope.entityResults = false;
- scope.search = '';
-
- // Add input for forms
- const input = element.find('[entity-selector-input]').first();
-
- // Detect double click events
- let lastClick = 0;
- function isDoubleClick() {
- let now = Date.now();
- let answer = now - lastClick < 300;
- lastClick = now;
- return answer;
- }
-
- // Listen to entity item clicks
- element.on('click', '.entity-list a', function(event) {
- event.preventDefault();
- event.stopPropagation();
- let item = $(this).closest('[data-entity-type]');
- itemSelect(item, isDoubleClick());
- });
- element.on('click', '[data-entity-type]', function(event) {
- itemSelect($(this), isDoubleClick());
- });
-
- // Select entity action
- function itemSelect(item, doubleClick) {
- let entityType = item.attr('data-entity-type');
- let entityId = item.attr('data-entity-id');
- let isSelected = !item.hasClass('selected') || doubleClick;
- element.find('.selected').removeClass('selected').removeClass('primary-background');
- if (isSelected) item.addClass('selected').addClass('primary-background');
- let newVal = isSelected ? `${entityType}:${entityId}` : '';
- input.val(newVal);
-
- if (!isSelected) {
- events.emit('entity-select-change', null);
- }
-
- if (!doubleClick && !isSelected) return;
-
- let link = item.find('.entity-list-item-link').attr('href');
- let name = item.find('.entity-list-item-name').text();
-
- if (doubleClick) {
- events.emit('entity-select-confirm', {
- id: Number(entityId),
- name: name,
- link: link
- });
- }
-
- if (isSelected) {
- events.emit('entity-select-change', {
- id: Number(entityId),
- name: name,
- link: link
- });
- }
- }
-
- // Get search url with correct types
- function getSearchUrl() {
- let types = (attrs.entityTypes) ? encodeURIComponent(attrs.entityTypes) : encodeURIComponent('page,book,chapter');
- return window.baseUrl(`/ajax/search/entities?types=${types}`);
- }
-
- // Get initial contents
- $http.get(getSearchUrl()).then(resp => {
- scope.entityResults = $sce.trustAsHtml(resp.data);
- scope.loading = false;
- });
-
- // Search when typing
- scope.searchEntities = function() {
- scope.loading = true;
- input.val('');
- let url = getSearchUrl() + '&term=' + encodeURIComponent(scope.search);
- $http.get(url).then(resp => {
- scope.entityResults = $sce.trustAsHtml(resp.data);
- scope.loading = false;
- });
- };
- }
- };
- }]);
-
ngApp.directive('commentReply', [function () {
return {
restrict: 'E',
--- /dev/null
+/**
+ * Polyfills for DOM API's
+ */
+
+if (!Element.prototype.matches) {
+ Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
+}
+
+if (!Element.prototype.closest) {
+ Element.prototype.closest = function (s) {
+ var el = this;
+ var ancestor = this;
+ if (!document.documentElement.contains(el)) return null;
+ do {
+ if (ancestor.matches(s)) return ancestor;
+ ancestor = ancestor.parentElement;
+ } while (ancestor !== null);
+ return null;
+ };
+}
\ No newline at end of file
"use strict";
require("babel-polyfill");
+require('./dom-polyfills');
// Url retrieval function
window.baseUrl = function(path) {
class EventManager {
constructor() {
this.listeners = {};
+ this.stack = [];
}
emit(eventName, eventData) {
+ this.stack.push({name: eventName, data: eventData});
if (typeof this.listeners[eventName] === 'undefined') return this;
let eventsToStart = this.listeners[eventName];
for (let i = 0; i < eventsToStart.length; i++) {
}
}
-window.Events = new EventManager();
+window.$events = new EventManager();
const Vue = require("vue");
const axios = require("axios");
return resp;
}, err => {
if (typeof err.response === "undefined" || typeof err.response.data === "undefined") return Promise.reject(err);
- if (typeof err.response.data.error !== "undefined") window.Events.emit('error', err.response.data.error);
- if (typeof err.response.data.message !== "undefined") window.Events.emit('error', err.response.data.message);
+ if (typeof err.response.data.error !== "undefined") window.$events.emit('error', err.response.data.error);
+ if (typeof err.response.data.message !== "undefined") window.$events.emit('error', err.response.data.message);
});
window.$http = axiosInstance;
Vue.prototype.$http = axiosInstance;
-Vue.prototype.$events = window.Events;
+Vue.prototype.$events = window.$events;
// AngularJS - Create application and load components
// Load in angular specific items
const Directives = require('./directives');
const Controllers = require('./controllers');
-Directives(ngApp, window.Events);
-Controllers(ngApp, window.Events);
+Directives(ngApp, window.$events);
+Controllers(ngApp, window.$events);
//Global jQuery Config & Extensions
file_browser_callback: function (field_name, url, type, win) {
if (type === 'file') {
- window.showEntityLinkSelector(function(entity) {
+ window.EntitySelectorPopup.show(function(entity) {
let originalField = win.document.getElementById(field_name);
originalField.value = entity.link;
$(originalField).closest('.mce-form').find('input').eq(2).val(entity.name);
<div id="entity-selector-wrap">
- <div overlay entity-link-selector>
+ <div overlay entity-selector-popup>
<div class="popup-body small flex-child">
<div class="popup-header primary-background">
<div class="popup-title">{{ trans('entities.entity_select') }}</div>
<div class="form-group">
<div entity-selector class="entity-selector {{$selectorSize or ''}}" entity-types="{{ $entityTypes or 'book,chapter,page' }}">
<input type="hidden" entity-selector-input name="{{$name}}" value="">
- <input type="text" placeholder="{{ trans('common.search') }}" ng-model="search" ng-model-options="{debounce: 200}" ng-change="searchEntities()">
- <div class="text-center loading" ng-show="loading">@include('partials.loading-icon')</div>
- <div ng-show="!loading" ng-bind-html="entityResults"></div>
+ <input type="text" placeholder="{{ trans('common.search') }}" entity-selector-search>
+ <div class="text-center loading" entity-selector-loading>@include('partials.loading-icon')</div>
+ <div entity-selector-results></div>
</div>
</div>
\ No newline at end of file