ESLINT: Addressed remaining detected issues

This commit is contained in:
Dan Brown 2023-04-19 15:20:04 +01:00
parent 0519e58fbf
commit da3ae3ba8b
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
41 changed files with 525 additions and 454 deletions

1
package-lock.json generated
View File

@ -18,6 +18,7 @@
"@codemirror/state": "^6.2.0",
"@codemirror/theme-one-dark": "^6.1.1",
"@codemirror/view": "^6.9.4",
"@lezer/highlight": "^1.1.4",
"@ssddanbrown/codemirror-lang-smarty": "^1.0.0",
"@ssddanbrown/codemirror-lang-twig": "^1.0.0",
"codemirror": "^6.0.1",

View File

@ -42,6 +42,7 @@
"@codemirror/state": "^6.2.0",
"@codemirror/theme-one-dark": "^6.1.1",
"@codemirror/view": "^6.9.4",
"@lezer/highlight": "^1.1.4",
"@ssddanbrown/codemirror-lang-smarty": "^1.0.0",
"@ssddanbrown/codemirror-lang-twig": "^1.0.0",
"codemirror": "^6.0.1",
@ -58,48 +59,77 @@
"es2021": true
},
"extends": "airbnb-base",
"ignorePatterns": ["resources/**/*-stub.js"],
"overrides": [
"ignorePatterns": [
"resources/**/*-stub.js"
],
"overrides": [],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {
"indent": ["error", 4],
"arrow-parens": ["error", "as-needed"],
"padded-blocks": ["error", {
"indent": [
"error",
4
],
"arrow-parens": [
"error",
"as-needed"
],
"padded-blocks": [
"error",
{
"blocks": "never",
"classes": "always"
}],
"object-curly-spacing": ["error", "never"],
"space-before-function-paren": ["error", {
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}],
}
],
"object-curly-spacing": [
"error",
"never"
],
"space-before-function-paren": [
"error",
{
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}
],
"import/prefer-default-export": "off",
"no-plusplus": ["error", {
"allowForLoopAfterthoughts": true
}],
"no-plusplus": [
"error",
{
"allowForLoopAfterthoughts": true
}
],
"arrow-body-style": "off",
"no-restricted-syntax": "off",
"no-continue": "off",
"no-console": ["warn", {
"allow": ["error"]
}],
"max-len": ["error", {
"code": 110,
"tabWidth": 4,
"ignoreUrls": true,
"ignoreComments": false,
"ignoreRegExpLiterals": true,
"ignoreStrings": true,
"ignoreTemplateLiterals": true
}],
"no-param-reassign": ["error", {
"props": false
}]
"prefer-destructuring": "off",
"class-methods-use-this": "off",
"no-param-reassign": "off",
"no-console": [
"warn",
{
"allow": [
"error",
"warn"
]
}
],
"no-new": "off",
"max-len": [
"error",
{
"code": 110,
"tabWidth": 4,
"ignoreUrls": true,
"ignoreComments": false,
"ignoreRegExpLiterals": true,
"ignoreStrings": true,
"ignoreTemplateLiterals": true
}
]
}
}
}

View File

@ -1,4 +1,4 @@
import events from './services/events';
import * as events from './services/events';
import * as httpInstance from './services/http';
import Translations from './services/translations';

View File

@ -6,24 +6,36 @@ import {createView} from './views';
import {SimpleEditorInterface} from './simple-editor-interface';
/**
* Highlight pre elements on a page
* Add a button to a CodeMirror instance which copies the contents to the clipboard upon click.
* @param {EditorView} editorView
*/
export function highlight() {
const codeBlocks = document.querySelectorAll('.page-content pre, .comment-box .content pre');
for (const codeBlock of codeBlocks) {
highlightElem(codeBlock);
}
}
function addCopyIcon(editorView) {
const copyIcon = '<svg viewBox="0 0 24 24" width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>';
const checkIcon = '<svg viewBox="0 0 24 24" width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>';
const copyButton = document.createElement('button');
copyButton.setAttribute('type', 'button');
copyButton.classList.add('cm-copy-button');
copyButton.innerHTML = copyIcon;
editorView.dom.appendChild(copyButton);
/**
* Highlight all code blocks within the given parent element
* @param {HTMLElement} parent
*/
export function highlightWithin(parent) {
const codeBlocks = parent.querySelectorAll('pre');
for (const codeBlock of codeBlocks) {
highlightElem(codeBlock);
}
const notifyTime = 620;
const transitionTime = 60;
copyButton.addEventListener('click', () => {
copyTextToClipboard(editorView.state.doc.toString());
copyButton.classList.add('success');
setTimeout(() => {
copyButton.innerHTML = checkIcon;
}, transitionTime / 2);
setTimeout(() => {
copyButton.classList.remove('success');
}, notifyTime);
setTimeout(() => {
copyButton.innerHTML = copyIcon;
}, notifyTime + (transitionTime / 2));
});
}
/**
@ -32,7 +44,7 @@ export function highlightWithin(parent) {
*/
function highlightElem(elem) {
const innerCodeElem = elem.querySelector('code[class^=language-]');
elem.innerHTML = elem.innerHTML.replace(/<br\s*[\/]?>/gi, '\n');
elem.innerHTML = elem.innerHTML.replace(/<br\s*\/?>/gi, '\n');
const content = elem.textContent.trimEnd();
let langName = '';
@ -57,36 +69,24 @@ function highlightElem(elem) {
}
/**
* Add a button to a CodeMirror instance which copies the contents to the clipboard upon click.
* @param {EditorView} editorView
* Highlight all code blocks within the given parent element
* @param {HTMLElement} parent
*/
function addCopyIcon(editorView) {
const copyIcon = '<svg viewBox="0 0 24 24" width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>';
const checkIcon = '<svg viewBox="0 0 24 24" width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>';
const copyButton = document.createElement('button');
copyButton.setAttribute('type', 'button');
copyButton.classList.add('cm-copy-button');
copyButton.innerHTML = copyIcon;
editorView.dom.appendChild(copyButton);
export function highlightWithin(parent) {
const codeBlocks = parent.querySelectorAll('pre');
for (const codeBlock of codeBlocks) {
highlightElem(codeBlock);
}
}
const notifyTime = 620;
const transitionTime = 60;
copyButton.addEventListener('click', event => {
copyTextToClipboard(editorView.state.doc.toString());
copyButton.classList.add('success');
setTimeout(() => {
copyButton.innerHTML = checkIcon;
}, transitionTime / 2);
setTimeout(() => {
copyButton.classList.remove('success');
}, notifyTime);
setTimeout(() => {
copyButton.innerHTML = copyIcon;
}, notifyTime + (transitionTime / 2));
});
/**
* Highlight pre elements on a page
*/
export function highlight() {
const codeBlocks = document.querySelectorAll('.page-content pre, .comment-box .content pre');
for (const codeBlock of codeBlocks) {
highlightElem(codeBlock);
}
}
/**

View File

@ -20,7 +20,7 @@ export class AjaxDeleteRow extends Component {
window.$events.emit('success', resp.data.message);
}
this.row.remove();
}).catch(err => {
}).catch(() => {
this.row.style.opacity = null;
this.row.style.pointerEvents = null;
});

View File

@ -27,7 +27,7 @@ export class Attachments extends Component {
this.startEdit(event.detail.id);
});
this.container.addEventListener('event-emit-select-edit-back', event => {
this.container.addEventListener('event-emit-select-edit-back', () => {
this.stopEdit();
});

View File

@ -25,7 +25,7 @@ export class AutoSuggest extends Component {
setupListeners() {
const navHandler = new KeyboardNavigationHandler(
this.list,
event => {
() => {
this.input.focus();
setTimeout(() => this.hideSuggestions(), 1);
},
@ -104,7 +104,8 @@ export class AutoSuggest extends Component {
*/
displaySuggestions(suggestions) {
if (suggestions.length === 0) {
return this.hideSuggestions();
this.hideSuggestions();
return;
}
// This used to use <button>s but was changed to div elements since Safari would not focus on buttons

View File

@ -45,19 +45,19 @@ const sortOperations = {
*/
const moveActions = {
up: {
active(elem, parent, book) {
active(elem, parent) {
return !(elem.previousElementSibling === null && !parent);
},
run(elem, parent, book) {
run(elem, parent) {
const newSibling = elem.previousElementSibling || parent;
newSibling.insertAdjacentElement('beforebegin', elem);
},
},
down: {
active(elem, parent, book) {
active(elem, parent) {
return !(elem.nextElementSibling === null && !parent);
},
run(elem, parent, book) {
run(elem, parent) {
const newSibling = elem.nextElementSibling || parent;
newSibling.insertAdjacentElement('afterend', elem);
},
@ -81,10 +81,10 @@ const moveActions = {
},
},
next_chapter: {
active(elem, parent, book) {
active(elem, parent) {
return elem.dataset.type === 'page' && this.getNextChapter(elem, parent);
},
run(elem, parent, book) {
run(elem, parent) {
const nextChapter = this.getNextChapter(elem, parent);
nextChapter.querySelector('ul').prepend(elem);
},
@ -92,14 +92,14 @@ const moveActions = {
const topLevel = (parent || elem);
const topItems = Array.from(topLevel.parentElement.children);
const index = topItems.indexOf(topLevel);
return topItems.slice(index + 1).find(elem => elem.dataset.type === 'chapter');
return topItems.slice(index + 1).find(item => item.dataset.type === 'chapter');
},
},
prev_chapter: {
active(elem, parent, book) {
active(elem, parent) {
return elem.dataset.type === 'page' && this.getPrevChapter(elem, parent);
},
run(elem, parent, book) {
run(elem, parent) {
const prevChapter = this.getPrevChapter(elem, parent);
prevChapter.querySelector('ul').append(elem);
},
@ -107,11 +107,11 @@ const moveActions = {
const topLevel = (parent || elem);
const topItems = Array.from(topLevel.parentElement.children);
const index = topItems.indexOf(topLevel);
return topItems.slice(0, index).reverse().find(elem => elem.dataset.type === 'chapter');
return topItems.slice(0, index).reverse().find(item => item.dataset.type === 'chapter');
},
},
book_end: {
active(elem, parent, book) {
active(elem, parent) {
return parent || (parent === null && elem.nextElementSibling);
},
run(elem, parent, book) {
@ -119,7 +119,7 @@ const moveActions = {
},
},
book_start: {
active(elem, parent, book) {
active(elem, parent) {
return parent || (parent === null && elem.previousElementSibling);
},
run(elem, parent, book) {
@ -127,18 +127,18 @@ const moveActions = {
},
},
before_chapter: {
active(elem, parent, book) {
active(elem, parent) {
return parent;
},
run(elem, parent, book) {
run(elem, parent) {
parent.insertAdjacentElement('beforebegin', elem);
},
},
after_chapter: {
active(elem, parent, book) {
active(elem, parent) {
return parent;
},
run(elem, parent, book) {
run(elem, parent) {
parent.insertAdjacentElement('afterend', elem);
},
},
@ -196,7 +196,7 @@ export class BookSort extends Component {
reverse = (lastSort === sort) ? !reverse : false;
let sortFunction = sortOperations[sort];
if (reverse && reversibleTypes.includes(sort)) {
sortFunction = function(a, b) {
sortFunction = function reverseSortOperation(a, b) {
return 0 - sortOperations[sort](a, b);
};
}
@ -260,7 +260,7 @@ export class BookSort extends Component {
animation: 150,
fallbackOnBody: true,
swapThreshold: 0.65,
onSort: event => {
onSort: () => {
this.ensureNoNestedChapters();
this.updateMapInput();
this.updateMoveActionStateForAll();

View File

@ -27,7 +27,11 @@ export class ChapterContents extends Component {
click(event) {
event.preventDefault();
this.isOpen ? this.close() : this.open();
if (this.isOpen) {
this.close();
} else {
this.open();
}
}
}

View File

@ -43,9 +43,9 @@ export class CodeEditor extends Component {
this.languageInputChange(language);
});
onEnterPress(this.languageInput, e => this.save());
this.languageInput.addEventListener('input', e => this.languageInputChange(this.languageInput.value));
onSelect(this.saveButton, e => this.save());
onEnterPress(this.languageInput, () => this.save());
this.languageInput.addEventListener('input', () => this.languageInputChange(this.languageInput.value));
onSelect(this.saveButton, () => this.save());
onChildEvent(this.historyList, 'button', 'click', (event, elem) => {
event.preventDefault();
@ -74,7 +74,8 @@ export class CodeEditor extends Component {
onChildEvent(button.parentElement, '.lang-option-favorite-toggle', 'click', () => {
isFavorite = !isFavorite;
isFavorite ? this.favourites.add(language) : this.favourites.delete(language);
const action = isFavorite ? this.favourites.add : this.favourites.delete;
action(language);
button.setAttribute('data-favourite', isFavorite ? 'true' : 'false');
window.$http.patch('/preferences/update-code-language-favourite', {
@ -173,7 +174,7 @@ export class CodeEditor extends Component {
const historyKeys = Object.keys(this.history).reverse();
this.historyDropDown.classList.toggle('hidden', historyKeys.length === 0);
this.historyList.innerHTML = historyKeys.map(key => {
const localTime = (new Date(parseInt(key))).toLocaleTimeString();
const localTime = (new Date(parseInt(key, 10))).toLocaleTimeString();
return `<li><button type="button" data-time="${key}" class="text-item">${localTime}</button></li>`;
}).join('');
}

View File

@ -25,7 +25,7 @@ export class ConfirmDialog extends Component {
this.sendResult(false);
});
return new Promise((res, rej) => {
return new Promise(res => {
this.res = res;
});
}

View File

@ -41,7 +41,11 @@ export class Dropdown extends Component {
this.menu.style.position = 'fixed';
this.menu.style.width = `${menuOriginalRect.width}px`;
this.menu.style.left = `${menuOriginalRect.left}px`;
heightOffset = dropUpwards ? (window.innerHeight - menuOriginalRect.top - toggleHeight / 2) : menuOriginalRect.top;
if (dropUpwards) {
heightOffset = (window.innerHeight - menuOriginalRect.top - toggleHeight / 2);
} else {
heightOffset = menuOriginalRect.top;
}
}
// Adjust menu to display upwards if near the bottom of the screen
@ -55,8 +59,8 @@ export class Dropdown extends Component {
// Set listener to hide on mouse leave or window click
this.menu.addEventListener('mouseleave', this.hide);
window.addEventListener('click', event => {
if (!this.menu.contains(event.target)) {
window.addEventListener('click', clickEvent => {
if (!this.menu.contains(clickEvent.target)) {
this.hide();
}
});

View File

@ -13,7 +13,7 @@ export class Dropzone extends Component {
this.uploadLimitMessage = this.$opts.uploadLimitMessage;
this.timeoutMessage = this.$opts.timeoutMessage;
const _this = this;
const component = this;
this.dz = new DropZoneLib(this.container, {
addRemoveLinks: true,
dictRemoveFile: this.removeMessage,
@ -23,9 +23,9 @@ export class Dropzone extends Component {
withCredentials: true,
init() {
this.dz = this;
this.dz.on('sending', _this.onSending.bind(_this));
this.dz.on('success', _this.onSuccess.bind(_this));
this.dz.on('error', _this.onError.bind(_this));
this.dz.on('sending', component.onSending.bind(component));
this.dz.on('success', component.onSuccess.bind(component));
this.dz.on('error', component.onError.bind(component));
},
});
}
@ -34,7 +34,7 @@ export class Dropzone extends Component {
const token = window.document.querySelector('meta[name=token]').getAttribute('content');
data.append('_token', token);
xhr.ontimeout = e => {
xhr.ontimeout = () => {
this.dz.emit('complete', file);
this.dz.emit('error', file, this.timeoutMessage);
};

View File

@ -34,7 +34,7 @@ export class EntityPermissions extends Component {
});
// Role select change
this.roleSelect.addEventListener('change', event => {
this.roleSelect.addEventListener('change', () => {
const roleId = this.roleSelect.value;
if (roleId) {
this.addRoleRow(roleId);

View File

@ -31,7 +31,8 @@ export class EntitySearch extends Component {
runSearch() {
const term = this.searchInput.value.trim();
if (term.length === 0) {
return this.clearSearch();
this.clearSearch();
return;
}
this.searchView.classList.remove('hidden');

View File

@ -29,7 +29,7 @@ export class EntitySelector extends Component {
this.elem.addEventListener('click', this.onClick.bind(this));
let lastSearch = 0;
this.searchInput.addEventListener('input', event => {
this.searchInput.addEventListener('input', () => {
lastSearch = Date.now();
this.showLoading();
setTimeout(() => {
@ -43,26 +43,26 @@ export class EntitySelector extends Component {
});
// Keyboard navigation
onChildEvent(this.$el, '[data-entity-type]', 'keydown', (e, el) => {
if (e.ctrlKey && e.code === 'Enter') {
onChildEvent(this.$el, '[data-entity-type]', 'keydown', event => {
if (event.ctrlKey && event.code === 'Enter') {
const form = this.$el.closest('form');
if (form) {
form.submit();
e.preventDefault();
event.preventDefault();
return;
}
}
if (e.code === 'ArrowDown') {
if (event.code === 'ArrowDown') {
this.focusAdjacent(true);
}
if (e.code === 'ArrowUp') {
if (event.code === 'ArrowUp') {
this.focusAdjacent(false);
}
});
this.searchInput.addEventListener('keydown', e => {
if (e.code === 'ArrowDown') {
this.searchInput.addEventListener('keydown', event => {
if (event.code === 'ArrowDown') {
this.focusAdjacent(true);
}
});

View File

@ -3,7 +3,7 @@ import {Component} from './component';
export class ExpandToggle extends Component {
setup(elem) {
setup() {
this.targetSelector = this.$opts.targetSelector;
this.isOpen = this.$opts.isOpen === 'true';
this.updateEndpoint = this.$opts.updateEndpoint;
@ -25,7 +25,8 @@ export class ExpandToggle extends Component {
const matchingElems = document.querySelectorAll(this.targetSelector);
for (const match of matchingElems) {
this.isOpen ? this.close(match) : this.open(match);
const action = this.isOpen ? this.close : this.open;
action(match);
}
this.isOpen = !this.isOpen;

View File

@ -50,20 +50,20 @@ export class ImageManager extends Component {
event.preventDefault();
});
onSelect(this.cancelSearch, event => {
onSelect(this.cancelSearch, () => {
this.resetListView();
this.resetSearchView();
this.loadGallery();
this.cancelSearch.classList.remove('active');
});
this.searchInput.addEventListener('input', event => {
this.searchInput.addEventListener('input', () => {
this.cancelSearch.classList.toggle('active', this.searchInput.value.trim());
});
onChildEvent(this.listContainer, '.load-more', 'click', async event => {
showLoading(event.target);
this.page++;
this.page += 1;
await this.loadGallery();
event.target.remove();
});
@ -71,7 +71,7 @@ export class ImageManager extends Component {
this.listContainer.addEventListener('event-emit-select-image', this.onImageSelectEvent.bind(this));
this.listContainer.addEventListener('error', event => {
event.target.src = baseUrl('loading_error.png');
event.target.src = window.baseUrl('loading_error.png');
}, true);
onSelect(this.selectButton, () => {
@ -81,7 +81,7 @@ export class ImageManager extends Component {
this.hide();
});
onChildEvent(this.formContainer, '#image-manager-delete', 'click', event => {
onChildEvent(this.formContainer, '#image-manager-delete', 'click', () => {
if (this.lastSelected) {
this.loadImageEditForm(this.lastSelected.id, true);
}

View File

@ -1,59 +1,59 @@
export {AddRemoveRows} from './add-remove-rows.js';
export {AjaxDeleteRow} from './ajax-delete-row.js';
export {AjaxForm} from './ajax-form.js';
export {Attachments} from './attachments.js';
export {AttachmentsList} from './attachments-list.js';
export {AutoSuggest} from './auto-suggest.js';
export {AutoSubmit} from './auto-submit.js';
export {BackToTop} from './back-to-top.js';
export {BookSort} from './book-sort.js';
export {ChapterContents} from './chapter-contents.js';
export {CodeEditor} from './code-editor.js';
export {CodeHighlighter} from './code-highlighter.js';
export {CodeTextarea} from './code-textarea.js';
export {Collapsible} from './collapsible.js';
export {AddRemoveRows} from './add-remove-rows';
export {AjaxDeleteRow} from './ajax-delete-row';
export {AjaxForm} from './ajax-form';
export {Attachments} from './attachments';
export {AttachmentsList} from './attachments-list';
export {AutoSuggest} from './auto-suggest';
export {AutoSubmit} from './auto-submit';
export {BackToTop} from './back-to-top';
export {BookSort} from './book-sort';
export {ChapterContents} from './chapter-contents';
export {CodeEditor} from './code-editor';
export {CodeHighlighter} from './code-highlighter';
export {CodeTextarea} from './code-textarea';
export {Collapsible} from './collapsible';
export {ConfirmDialog} from './confirm-dialog';
export {CustomCheckbox} from './custom-checkbox.js';
export {DetailsHighlighter} from './details-highlighter.js';
export {Dropdown} from './dropdown.js';
export {DropdownSearch} from './dropdown-search.js';
export {Dropzone} from './dropzone.js';
export {EditorToolbox} from './editor-toolbox.js';
export {CustomCheckbox} from './custom-checkbox';
export {DetailsHighlighter} from './details-highlighter';
export {Dropdown} from './dropdown';
export {DropdownSearch} from './dropdown-search';
export {Dropzone} from './dropzone';
export {EditorToolbox} from './editor-toolbox';
export {EntityPermissions} from './entity-permissions';
export {EntitySearch} from './entity-search.js';
export {EntitySelector} from './entity-selector.js';
export {EntitySelectorPopup} from './entity-selector-popup.js';
export {EventEmitSelect} from './event-emit-select.js';
export {ExpandToggle} from './expand-toggle.js';
export {GlobalSearch} from './global-search.js';
export {HeaderMobileToggle} from './header-mobile-toggle.js';
export {ImageManager} from './image-manager.js';
export {ImagePicker} from './image-picker.js';
export {ListSortControl} from './list-sort-control.js';
export {MarkdownEditor} from './markdown-editor.js';
export {NewUserPassword} from './new-user-password.js';
export {Notification} from './notification.js';
export {OptionalInput} from './optional-input.js';
export {PageComments} from './page-comments.js';
export {PageDisplay} from './page-display.js';
export {PageEditor} from './page-editor.js';
export {PagePicker} from './page-picker.js';
export {PermissionsTable} from './permissions-table.js';
export {Pointer} from './pointer.js';
export {Popup} from './popup.js';
export {SettingAppColorScheme} from './setting-app-color-scheme.js';
export {SettingColorPicker} from './setting-color-picker.js';
export {SettingHomepageControl} from './setting-homepage-control.js';
export {ShelfSort} from './shelf-sort.js';
export {EntitySearch} from './entity-search';
export {EntitySelector} from './entity-selector';
export {EntitySelectorPopup} from './entity-selector-popup';
export {EventEmitSelect} from './event-emit-select';
export {ExpandToggle} from './expand-toggle';
export {GlobalSearch} from './global-search';
export {HeaderMobileToggle} from './header-mobile-toggle';
export {ImageManager} from './image-manager';
export {ImagePicker} from './image-picker';
export {ListSortControl} from './list-sort-control';
export {MarkdownEditor} from './markdown-editor';
export {NewUserPassword} from './new-user-password';
export {Notification} from './notification';
export {OptionalInput} from './optional-input';
export {PageComments} from './page-comments';
export {PageDisplay} from './page-display';
export {PageEditor} from './page-editor';
export {PagePicker} from './page-picker';
export {PermissionsTable} from './permissions-table';
export {Pointer} from './pointer';
export {Popup} from './popup';
export {SettingAppColorScheme} from './setting-app-color-scheme';
export {SettingColorPicker} from './setting-color-picker';
export {SettingHomepageControl} from './setting-homepage-control';
export {ShelfSort} from './shelf-sort';
export {Shortcuts} from './shortcuts';
export {ShortcutInput} from './shortcut-input';
export {SortableList} from './sortable-list.js';
export {SubmitOnChange} from './submit-on-change.js';
export {Tabs} from './tabs.js';
export {TagManager} from './tag-manager.js';
export {TemplateManager} from './template-manager.js';
export {ToggleSwitch} from './toggle-switch.js';
export {TriLayout} from './tri-layout.js';
export {UserSelect} from './user-select.js';
export {SortableList} from './sortable-list';
export {SubmitOnChange} from './submit-on-change';
export {Tabs} from './tabs';
export {TagManager} from './tag-manager';
export {TemplateManager} from './template-manager';
export {ToggleSwitch} from './toggle-switch';
export {TriLayout} from './tri-layout';
export {UserSelect} from './user-select';
export {WebhookEvents} from './webhook-events';
export {WysiwygEditor} from './wysiwyg-editor.js';
export {WysiwygEditor} from './wysiwyg-editor';

View File

@ -82,7 +82,7 @@ export class MarkdownEditor extends Component {
}
handleDividerDrag() {
this.divider.addEventListener('pointerdown', event => {
this.divider.addEventListener('pointerdown', () => {
const wrapRect = this.elem.getBoundingClientRect();
const moveListener = event => {
const xRel = event.pageX - wrapRect.left;
@ -90,7 +90,7 @@ export class MarkdownEditor extends Component {
this.displayWrap.style.flexBasis = `${100 - xPct}%`;
this.editor.settings.set('editorWidth', xPct);
};
const upListener = event => {
const upListener = () => {
window.removeEventListener('pointermove', moveListener);
window.removeEventListener('pointerup', upListener);
this.display.style.pointerEvents = null;

View File

@ -100,7 +100,7 @@ export class PageComments extends Component {
deleteComment(commentElem) {
const id = commentElem.getAttribute('comment');
this.showLoading(commentElem.querySelector('[comment-content]'));
window.$http.delete(`/comment/${id}`).then(resp => {
window.$http.delete(`/comment/${id}`).then(() => {
commentElem.parentNode.removeChild(commentElem);
window.$events.success(this.deletedText);
this.updateCount();

View File

@ -2,6 +2,33 @@ import * as DOM from '../services/dom';
import {scrollAndHighlightElement} from '../services/util';
import {Component} from './component';
function toggleAnchorHighlighting(elementId, shouldHighlight) {
DOM.forEach(`a[href="#${elementId}"]`, anchor => {
anchor.closest('li').classList.toggle('current-heading', shouldHighlight);
});
}
function headingVisibilityChange(entries) {
for (const entry of entries) {
const isVisible = (entry.intersectionRatio === 1);
toggleAnchorHighlighting(entry.target.id, isVisible);
}
}
function addNavObserver(headings) {
// Setup the intersection observer.
const intersectOpts = {
rootMargin: '0px 0px 0px 0px',
threshold: 1.0,
};
const pageNavObserver = new IntersectionObserver(headingVisibilityChange, intersectOpts);
// observe each heading
for (const heading of headings) {
pageNavObserver.observe(heading);
}
}
export class PageDisplay extends Component {
setup() {
@ -58,33 +85,6 @@ export class PageDisplay extends Component {
if (headings.length > 0 && pageNav !== null) {
addNavObserver(headings);
}
function addNavObserver(headings) {
// Setup the intersection observer.
const intersectOpts = {
rootMargin: '0px 0px 0px 0px',
threshold: 1.0,
};
const pageNavObserver = new IntersectionObserver(headingVisibilityChange, intersectOpts);
// observe each heading
for (const heading of headings) {
pageNavObserver.observe(heading);
}
}
function headingVisibilityChange(entries, observer) {
for (const entry of entries) {
const isVisible = (entry.intersectionRatio === 1);
toggleAnchorHighlighting(entry.target.id, isVisible);
}
}
function toggleAnchorHighlighting(elementId, shouldHighlight) {
DOM.forEach(`a[href="#${elementId}"]`, anchor => {
anchor.closest('li').classList.toggle('current-heading', shouldHighlight);
});
}
}
setupDetailsCodeBlockRefresh() {

View File

@ -59,7 +59,9 @@ export class PageEditor extends Component {
window.$events.listen('editor-save-page', this.savePage.bind(this));
// Listen to content changes from the editor
const onContentChange = () => this.autoSave.pendingChange = true;
const onContentChange = () => {
this.autoSave.pendingChange = true;
};
window.$events.listen('editor-html-change', onContentChange);
window.$events.listen('editor-markdown-change', onContentChange);
@ -80,7 +82,8 @@ export class PageEditor extends Component {
setInitialFocus() {
if (this.hasDefaultTitle) {
return this.titleElem.select();
this.titleElem.select();
return;
}
window.setTimeout(() => {
@ -133,7 +136,9 @@ export class PageEditor extends Component {
try {
const saveKey = `draft-save-fail-${(new Date()).toISOString()}`;
window.localStorage.setItem(saveKey, JSON.stringify(data));
} catch (err) {}
} catch (lsErr) {
console.error(lsErr);
}
window.$events.emit('error', this.autosaveFailText);
}
@ -154,7 +159,8 @@ export class PageEditor extends Component {
try {
response = await window.$http.get(`/ajax/page/${this.pageId}`);
} catch (e) {
return console.error(e);
console.error(e);
return;
}
if (this.autoSave.interval) {

View File

@ -1,5 +1,9 @@
import {Component} from './component';
function toggleElem(elem, show) {
elem.style.display = show ? null : 'none';
}
export class PagePicker extends Component {
setup() {
@ -18,7 +22,7 @@ export class PagePicker extends Component {
this.selectButton.addEventListener('click', this.showPopup.bind(this));
this.display.parentElement.addEventListener('click', this.showPopup.bind(this));
this.resetButton.addEventListener('click', event => {
this.resetButton.addEventListener('click', () => {
this.setValue('', '');
});
}
@ -55,7 +59,3 @@ export class PagePicker extends Component {
}
}
function toggleElem(elem, show) {
elem.style.display = show ? null : 'none';
}

View File

@ -21,7 +21,7 @@ export class Pointer extends Component {
setupListeners() {
// Copy on copy button click
this.button.addEventListener('click', event => {
this.button.addEventListener('click', () => {
copyTextToClipboard(this.input.value);
});
@ -46,7 +46,7 @@ export class Pointer extends Component {
});
// Hide pointer when clicking away
DOM.onEvents(document.body, ['click', 'focus'], event => {
DOM.onEvents(document.body, ['click', 'focus'], () => {
if (!this.showing || this.isSelection) return;
this.hidePointer();
});

View File

@ -26,11 +26,11 @@ export class Popup extends Component {
this.container.addEventListener('click', event => {
if (event.target === this.container && lastMouseDownTarget === this.container) {
return this.hide();
this.hide();
}
});
onSelect(this.hideButtons, e => this.hide());
onSelect(this.hideButtons, () => this.hide());
}
hide(onComplete = null) {

View File

@ -59,7 +59,6 @@ export class SettingAppColorScheme extends Component {
const rgb = this.hexToRgb(hexVal);
const rgbLightVal = `rgba(${[rgb.r, rgb.g, rgb.b, '0.15'].join(',')})`;
console.log(input.name, lightName, hexVal, rgbLightVal);
const lightColorInput = this.container.querySelector(`input[name="${lightName}"][type="hidden"]`);
lightColorInput.value = rgbLightVal;
}

View File

@ -5,13 +5,13 @@ import {Component} from './component';
* @type {Object<string, function(HTMLElement, HTMLElement, HTMLElement)>}
*/
const itemActions = {
move_up(item, shelfBooksList, allBooksList) {
move_up(item) {
const list = item.parentNode;
const index = Array.from(list.children).indexOf(item);
const newIndex = Math.max(index - 1, 0);
list.insertBefore(item, list.children[newIndex] || null);
},
move_down(item, shelfBooksList, allBooksList) {
move_down(item) {
const list = item.parentNode;
const index = Array.from(list.children).indexOf(item);
const newIndex = Math.min(index + 2, list.children.length);
@ -20,7 +20,7 @@ const itemActions = {
remove(item, shelfBooksList, allBooksList) {
allBooksList.appendChild(item);
},
add(item, shelfBooksList, allBooksList) {
add(item, shelfBooksList) {
shelfBooksList.appendChild(item);
},
};
@ -62,7 +62,7 @@ export class ShelfSort extends Component {
}
});
this.bookSearchInput.addEventListener('input', event => {
this.bookSearchInput.addEventListener('input', () => {
this.filterBooksByName(this.bookSearchInput.value);
});
@ -121,10 +121,10 @@ export class ShelfSort extends Component {
const bProp = bookB.dataset[sortProperty].toLowerCase();
if (reverse) {
return aProp < bProp ? (aProp === bProp ? 0 : 1) : -1;
return bProp.localeCompare(aProp);
}
return aProp < bProp ? (aProp === bProp ? 0 : -1) : 1;
return aProp.localeCompare(bProp);
});
for (const book of books) {

View File

@ -33,7 +33,8 @@ export class Shortcuts extends Component {
window.addEventListener('keydown', event => {
if (event.key === '?') {
this.hintsShowing ? this.hideHints() : this.showHints();
const action = this.hintsShowing ? this.hideHints : this.showHints;
action();
}
});
}

View File

@ -36,10 +36,10 @@ export class TemplateManager extends Component {
});
// Search submit button press
this.searchButton.addEventListener('click', event => this.performSearch());
this.searchButton.addEventListener('click', () => this.performSearch());
// Search cancel button press
this.searchCancel.addEventListener('click', event => {
this.searchCancel.addEventListener('click', () => {
this.searchInput.value = '';
this.performSearch();
});

View File

@ -19,7 +19,7 @@ export class TriLayout extends Component {
// Watch layout changes
this.updateLayout();
window.addEventListener('resize', event => {
window.addEventListener('resize', () => {
this.updateLayout();
}, {passive: true});
}

View File

@ -1,4 +1,4 @@
import DrawIO from '../services/drawio';
import * as DrawIO from '../services/drawio';
export class Actions {
@ -140,7 +140,7 @@ export class Actions {
} else {
window.$events.emit('error', this.editor.config.text.imageUploadError);
}
console.log(error);
console.error(error);
}
// Make the editor full screen
@ -165,7 +165,7 @@ export class Actions {
scrollToLine = lineCount;
break;
}
lineCount++;
lineCount += 1;
}
if (scrollToLine === -1) {
@ -258,22 +258,31 @@ export class Actions {
* @param {String} end
*/
wrapSelection(start, end) {
const selectionRange = this.#getSelectionRange();
const selectionText = this.#getSelectionText(selectionRange);
if (!selectionText) return this.#wrapLine(start, end);
const selectRange = this.#getSelectionRange();
const selectionText = this.#getSelectionText(selectRange);
if (!selectionText) {
this.#wrapLine(start, end);
return;
}
let newSelectionText = selectionText;
let newRange;
if (selectionText.startsWith(start) && selectionText.endsWith(end)) {
newSelectionText = selectionText.slice(start.length, selectionText.length - end.length);
newRange = selectionRange.extend(selectionRange.from, selectionRange.to - (start.length + end.length));
newRange = selectRange.extend(selectRange.from, selectRange.to - (start.length + end.length));
} else {
newSelectionText = `${start}${selectionText}${end}`;
newRange = selectionRange.extend(selectionRange.from, selectionRange.to + (start.length + end.length));
newRange = selectRange.extend(selectRange.from, selectRange.to + (start.length + end.length));
}
this.#dispatchChange(selectionRange.from, selectionRange.to, newSelectionText, newRange.anchor, newRange.head);
this.#dispatchChange(
selectRange.from,
selectRange.to,
newSelectionText,
newRange.anchor,
newRange.head,
);
}
replaceLineStartForOrderedList() {
@ -314,7 +323,13 @@ export class Actions {
const newFormat = formats[newFormatIndex];
const newContent = line.text.replace(matches[0], matches[0].replace(format, newFormat));
const lineDiff = newContent.length - line.text.length;
this.#dispatchChange(line.from, line.to, newContent, selectionRange.anchor + lineDiff, selectionRange.head + lineDiff);
this.#dispatchChange(
line.from,
line.to,
newContent,
selectionRange.anchor + lineDiff,
selectionRange.head + lineDiff,
);
}
}
@ -399,7 +414,7 @@ export class Actions {
} catch (err) {
window.$events.emit('error', this.editor.config.text.imageUploadError);
this.#findAndReplaceContent(placeHolderText, '');
console.log(err);
console.error(err);
}
}
@ -432,7 +447,8 @@ export class Actions {
*/
#replaceSelection(newContent, cursorOffset = 0, selectionRange = null) {
selectionRange = selectionRange || this.editor.cm.state.selection.main;
this.#dispatchChange(selectionRange.from, selectionRange.to, newContent, selectionRange.from + cursorOffset);
const selectFrom = selectionRange.from + cursorOffset;
this.#dispatchChange(selectionRange.from, selectionRange.to, newContent, selectFrom);
this.focus();
}
@ -510,6 +526,9 @@ export class Actions {
if (selectFrom) {
tr.selection = {anchor: selectFrom};
if (selectTo) {
tr.selection.head = selectTo;
}
}
this.editor.cm.dispatch(tr);

View File

@ -21,7 +21,9 @@ export async function init(editor) {
const onScrollDebounced = debounce(editor.actions.syncDisplayPosition.bind(editor.actions), 100, false);
let syncActive = editor.settings.get('scrollSync');
editor.settings.onChange('scrollSync', val => syncActive = val);
editor.settings.onChange('scrollSync', val => {
syncActive = val;
});
const domEventHandlers = {
// Handle scroll to sync display view

View File

@ -21,7 +21,7 @@ export class Settings {
listenToInputChanges(inputs) {
for (const input of inputs) {
input.addEventListener('change', event => {
input.addEventListener('change', () => {
const name = input.getAttribute('name').replace('md-', '');
this.set(name, input.checked);
});

View File

@ -7,35 +7,35 @@ function provide(editor) {
const shortcuts = {};
// Insert Image shortcut
shortcuts['Shift-Mod-i'] = cm => editor.actions.insertImage();
shortcuts['Shift-Mod-i'] = () => editor.actions.insertImage();
// Save draft
shortcuts['Mod-s'] = cm => window.$events.emit('editor-save-draft');
shortcuts['Mod-s'] = () => window.$events.emit('editor-save-draft');
// Save page
shortcuts['Mod-Enter'] = cm => window.$events.emit('editor-save-page');
shortcuts['Mod-Enter'] = () => window.$events.emit('editor-save-page');
// Show link selector
shortcuts['Shift-Mod-k'] = cm => editor.actions.showLinkSelector();
shortcuts['Shift-Mod-k'] = () => editor.actions.showLinkSelector();
// Insert Link
shortcuts['Mod-k'] = cm => editor.actions.insertLink();
shortcuts['Mod-k'] = () => editor.actions.insertLink();
// FormatShortcuts
shortcuts['Mod-1'] = cm => editor.actions.replaceLineStart('##');
shortcuts['Mod-2'] = cm => editor.actions.replaceLineStart('###');
shortcuts['Mod-3'] = cm => editor.actions.replaceLineStart('####');
shortcuts['Mod-4'] = cm => editor.actions.replaceLineStart('#####');
shortcuts['Mod-5'] = cm => editor.actions.replaceLineStart('');
shortcuts['Mod-d'] = cm => editor.actions.replaceLineStart('');
shortcuts['Mod-6'] = cm => editor.actions.replaceLineStart('>');
shortcuts['Mod-q'] = cm => editor.actions.replaceLineStart('>');
shortcuts['Mod-7'] = cm => editor.actions.wrapSelection('\n```\n', '\n```');
shortcuts['Mod-8'] = cm => editor.actions.wrapSelection('`', '`');
shortcuts['Shift-Mod-e'] = cm => editor.actions.wrapSelection('`', '`');
shortcuts['Mod-9'] = cm => editor.actions.cycleCalloutTypeAtSelection();
shortcuts['Mod-p'] = cm => editor.actions.replaceLineStart('-');
shortcuts['Mod-o'] = cm => editor.actions.replaceLineStartForOrderedList();
shortcuts['Mod-1'] = () => editor.actions.replaceLineStart('##');
shortcuts['Mod-2'] = () => editor.actions.replaceLineStart('###');
shortcuts['Mod-3'] = () => editor.actions.replaceLineStart('####');
shortcuts['Mod-4'] = () => editor.actions.replaceLineStart('#####');
shortcuts['Mod-5'] = () => editor.actions.replaceLineStart('');
shortcuts['Mod-d'] = () => editor.actions.replaceLineStart('');
shortcuts['Mod-6'] = () => editor.actions.replaceLineStart('>');
shortcuts['Mod-q'] = () => editor.actions.replaceLineStart('>');
shortcuts['Mod-7'] = () => editor.actions.wrapSelection('\n```\n', '\n```');
shortcuts['Mod-8'] = () => editor.actions.wrapSelection('`', '`');
shortcuts['Shift-Mod-e'] = () => editor.actions.wrapSelection('`', '`');
shortcuts['Mod-9'] = () => editor.actions.cycleCalloutTypeAtSelection();
shortcuts['Mod-p'] = () => editor.actions.replaceLineStart('-');
shortcuts['Mod-o'] = () => editor.actions.replaceLineStartForOrderedList();
return shortcuts;
}

View File

@ -5,6 +5,53 @@
*/
const animateStylesCleanupMap = new WeakMap();
/**
* Animate the css styles of an element using FLIP animation techniques.
* Styles must be an object where the keys are style properties, camelcase, and the values
* are an array of two items in the format [initialValue, finalValue]
* @param {Element} element
* @param {Object} styles
* @param {Number} animTime
* @param {Function} onComplete
*/
function animateStyles(element, styles, animTime = 400, onComplete = null) {
const styleNames = Object.keys(styles);
for (const style of styleNames) {
element.style[style] = styles[style][0];
}
const cleanup = () => {
for (const style of styleNames) {
element.style[style] = null;
}
element.style.transition = null;
element.removeEventListener('transitionend', cleanup);
animateStylesCleanupMap.delete(element);
if (onComplete) onComplete();
};
setTimeout(() => {
element.style.transition = `all ease-in-out ${animTime}ms`;
for (const style of styleNames) {
element.style[style] = styles[style][1];
}
element.addEventListener('transitionend', cleanup);
animateStylesCleanupMap.set(element, cleanup);
}, 15);
}
/**
* Run the active cleanup action for the given element.
* @param {Element} element
*/
function cleanupExistingElementAnimation(element) {
if (animateStylesCleanupMap.has(element)) {
const oldCleanup = animateStylesCleanupMap.get(element);
oldCleanup();
}
}
/**
* Fade in the given element.
* @param {Element} element
@ -113,50 +160,3 @@ export function transitionHeight(element, animTime = 400) {
animateStyles(element, animStyles, animTime);
};
}
/**
* Animate the css styles of an element using FLIP animation techniques.
* Styles must be an object where the keys are style properties, camelcase, and the values
* are an array of two items in the format [initialValue, finalValue]
* @param {Element} element
* @param {Object} styles
* @param {Number} animTime
* @param {Function} onComplete
*/
function animateStyles(element, styles, animTime = 400, onComplete = null) {
const styleNames = Object.keys(styles);
for (const style of styleNames) {
element.style[style] = styles[style][0];
}
const cleanup = () => {
for (const style of styleNames) {
element.style[style] = null;
}
element.style.transition = null;
element.removeEventListener('transitionend', cleanup);
animateStylesCleanupMap.delete(element);
if (onComplete) onComplete();
};
setTimeout(() => {
element.style.transition = `all ease-in-out ${animTime}ms`;
for (const style of styleNames) {
element.style[style] = styles[style][1];
}
element.addEventListener('transitionend', cleanup);
animateStylesCleanupMap.set(element, cleanup);
}, 15);
}
/**
* Run the active cleanup action for the given element.
* @param {Element} element
*/
function cleanupExistingElementAnimation(element) {
if (animateStylesCleanupMap.has(element)) {
const oldCleanup = animateStylesCleanupMap.get(element);
oldCleanup();
}
}

View File

@ -19,44 +19,6 @@ const componentModelMap = {};
*/
const elementComponentMap = new WeakMap();
/**
* Initialize a component instance on the given dom element.
* @param {String} name
* @param {Element} element
*/
function initComponent(name, element) {
/** @type {Function<Component>|undefined} * */
const componentModel = componentModelMap[name];
if (componentModel === undefined) return;
// Create our component instance
/** @type {Component} * */
let instance;
try {
instance = new componentModel();
instance.$name = name;
instance.$el = element;
const allRefs = parseRefs(name, element);
instance.$refs = allRefs.refs;
instance.$manyRefs = allRefs.manyRefs;
instance.$opts = parseOpts(name, element);
instance.setup();
} catch (e) {
console.error('Failed to create component', e, name, element);
}
// Add to global listing
if (typeof components[name] === 'undefined') {
components[name] = [];
}
components[name].push(instance);
// Add to element mapping
const elComponents = elementComponentMap.get(element) || {};
elComponents[name] = instance;
elementComponentMap.set(element, elComponents);
}
/**
* Parse out the element references within the given element
* for the given component name.
@ -93,13 +55,13 @@ function parseRefs(name, element) {
/**
* Parse out the element component options.
* @param {String} name
* @param {String} componentName
* @param {Element} element
* @return {Object<String, String>}
*/
function parseOpts(name, element) {
function parseOpts(componentName, element) {
const opts = {};
const prefix = `option:${name}:`;
const prefix = `option:${componentName}:`;
for (const {name, value} of element.attributes) {
if (name.startsWith(prefix)) {
const optName = name.replace(prefix, '');
@ -109,6 +71,44 @@ function parseOpts(name, element) {
return opts;
}
/**
* Initialize a component instance on the given dom element.
* @param {String} name
* @param {Element} element
*/
function initComponent(name, element) {
/** @type {Function<Component>|undefined} * */
const ComponentModel = componentModelMap[name];
if (ComponentModel === undefined) return;
// Create our component instance
/** @type {Component} * */
let instance;
try {
instance = new ComponentModel();
instance.$name = name;
instance.$el = element;
const allRefs = parseRefs(name, element);
instance.$refs = allRefs.refs;
instance.$manyRefs = allRefs.manyRefs;
instance.$opts = parseOpts(name, element);
instance.setup();
} catch (e) {
console.error('Failed to create component', e, name, element);
}
// Add to global listing
if (typeof components[name] === 'undefined') {
components[name] = [];
}
components[name].push(instance);
// Add to element mapping
const elComponents = elementComponentMap.get(element) || {};
elComponents[name] = instance;
elementComponentMap.set(element, elComponents);
}
/**
* Initialize all components found within the given element.
* @param {Element|Document} parentElement

View File

@ -3,50 +3,8 @@ let lastApprovedOrigin;
let onInit; let
onSave;
/**
* Show the draw.io editor.
* @param {String} drawioUrl
* @param {Function} onInitCallback - Must return a promise with the xml to load for the editor.
* @param {Function} onSaveCallback - Is called with the drawing data on save.
*/
function show(drawioUrl, onInitCallback, onSaveCallback) {
onInit = onInitCallback;
onSave = onSaveCallback;
iFrame = document.createElement('iframe');
iFrame.setAttribute('frameborder', '0');
window.addEventListener('message', drawReceive);
iFrame.setAttribute('src', drawioUrl);
iFrame.setAttribute('class', 'fullscreen');
iFrame.style.backgroundColor = '#FFFFFF';
document.body.appendChild(iFrame);
lastApprovedOrigin = (new URL(drawioUrl)).origin;
}
function close() {
drawEventClose();
}
/**
* Receive and handle a message event from the draw.io window.
* @param {MessageEvent} event
*/
function drawReceive(event) {
if (!event.data || event.data.length < 1) return;
if (event.origin !== lastApprovedOrigin) return;
const message = JSON.parse(event.data);
if (message.event === 'init') {
drawEventInit();
} else if (message.event === 'exit') {
drawEventClose();
} else if (message.event === 'save') {
drawEventSave(message);
} else if (message.event === 'export') {
drawEventExport(message);
} else if (message.event === 'configure') {
drawEventConfigure();
}
function drawPostMessage(data) {
iFrame.contentWindow.postMessage(JSON.stringify(data), lastApprovedOrigin);
}
function drawEventExport(message) {
@ -75,15 +33,54 @@ function drawEventConfigure() {
}
function drawEventClose() {
// eslint-disable-next-line no-use-before-define
window.removeEventListener('message', drawReceive);
if (iFrame) document.body.removeChild(iFrame);
}
function drawPostMessage(data) {
iFrame.contentWindow.postMessage(JSON.stringify(data), lastApprovedOrigin);
/**
* Receive and handle a message event from the draw.io window.
* @param {MessageEvent} event
*/
function drawReceive(event) {
if (!event.data || event.data.length < 1) return;
if (event.origin !== lastApprovedOrigin) return;
const message = JSON.parse(event.data);
if (message.event === 'init') {
drawEventInit();
} else if (message.event === 'exit') {
drawEventClose();
} else if (message.event === 'save') {
drawEventSave(message);
} else if (message.event === 'export') {
drawEventExport(message);
} else if (message.event === 'configure') {
drawEventConfigure();
}
}
async function upload(imageData, pageUploadedToId) {
/**
* Show the draw.io editor.
* @param {String} drawioUrl
* @param {Function} onInitCallback - Must return a promise with the xml to load for the editor.
* @param {Function} onSaveCallback - Is called with the drawing data on save.
*/
export function show(drawioUrl, onInitCallback, onSaveCallback) {
onInit = onInitCallback;
onSave = onSaveCallback;
iFrame = document.createElement('iframe');
iFrame.setAttribute('frameborder', '0');
window.addEventListener('message', drawReceive);
iFrame.setAttribute('src', drawioUrl);
iFrame.setAttribute('class', 'fullscreen');
iFrame.style.backgroundColor = '#FFFFFF';
document.body.appendChild(iFrame);
lastApprovedOrigin = (new URL(drawioUrl)).origin;
}
export async function upload(imageData, pageUploadedToId) {
const data = {
image: imageData,
uploaded_to: pageUploadedToId,
@ -92,12 +89,16 @@ async function upload(imageData, pageUploadedToId) {
return resp.data;
}
export function close() {
drawEventClose();
}
/**
* Load an existing image, by fetching it as Base64 from the system.
* @param drawingId
* @returns {Promise<string>}
*/
async function load(drawingId) {
export async function load(drawingId) {
try {
const resp = await window.$http.get(window.baseUrl(`/images/drawio/base64/${drawingId}`));
return `data:image/png;base64,${resp.data.content}`;
@ -109,7 +110,3 @@ async function load(drawingId) {
throw error;
}
}
export default {
show, close, upload, load,
};

View File

@ -6,13 +6,12 @@ const stack = [];
* @param {String} eventName
* @param {*} eventData
*/
function emit(eventName, eventData) {
export function emit(eventName, eventData) {
stack.push({name: eventName, data: eventData});
if (typeof listeners[eventName] === 'undefined') return this;
const eventsToStart = listeners[eventName];
for (let i = 0; i < eventsToStart.length; i++) {
const event = eventsToStart[i];
event(eventData);
const listenersToRun = listeners[eventName] || [];
for (const listener of listenersToRun) {
listener(eventData);
}
}
@ -22,7 +21,7 @@ function emit(eventName, eventData) {
* @param {Function} callback
* @returns {Events}
*/
function listen(eventName, callback) {
export function listen(eventName, callback) {
if (typeof listeners[eventName] === 'undefined') listeners[eventName] = [];
listeners[eventName].push(callback);
}
@ -34,7 +33,7 @@ function listen(eventName, callback) {
* @param {String} eventName
* @param {Object} eventData
*/
function emitPublic(targetElement, eventName, eventData) {
export function emitPublic(targetElement, eventName, eventData) {
const event = new CustomEvent(eventName, {
detail: eventData,
bubbles: true,
@ -43,34 +42,40 @@ function emitPublic(targetElement, eventName, eventData) {
}
/**
* Notify of standard server-provided validation errors.
* @param {Object} error
* Emit a success event with the provided message.
* @param {String} message
*/
function showValidationErrors(error) {
if (!error.status) return;
if (error.status === 422 && error.data) {
const message = Object.values(error.data).flat().join('\n');
emit('error', message);
export function success(message) {
emit('success', message);
}
/**
* Emit an error event with the provided message.
* @param {String} message
*/
export function error(message) {
emit('error', message);
}
/**
* Notify of standard server-provided validation errors.
* @param {Object} responseErr
*/
export function showValidationErrors(responseErr) {
if (!responseErr.status) return;
if (responseErr.status === 422 && responseErr.data) {
const message = Object.values(responseErr.data).flat().join('\n');
error(message);
}
}
/**
* Notify standard server-provided error messages.
* @param {Object} error
* @param {Object} responseErr
*/
function showResponseError(error) {
if (!error.status) return;
if (error.status >= 400 && error.data && error.data.message) {
emit('error', error.data.message);
export function showResponseError(responseErr) {
if (!responseErr.status) return;
if (responseErr.status >= 400 && responseErr.data && responseErr.data.message) {
error(responseErr.data.message);
}
}
export default {
emit,
emitPublic,
listen,
success: msg => emit('success', msg),
error: msg => emit('error', msg),
showValidationErrors,
showResponseError,
};

View File

@ -5,11 +5,7 @@
*/
class Translator {
/**
* Create an instance, Passing in the required translations
* @param translations
*/
constructor(translations) {
constructor() {
this.store = new Map();
this.parseTranslations();
}
@ -27,7 +23,7 @@ class Translator {
}
/**
* Get a translation, Same format as laravel's 'trans' helper
* Get a translation, Same format as Laravel's 'trans' helper
* @param key
* @param replacements
* @returns {*}
@ -38,8 +34,8 @@ class Translator {
}
/**
* Get pluralised text, Dependant on the given count.
* Same format at laravel's 'trans_choice' helper.
* Get pluralised text, Dependent on the given count.
* Same format at Laravel's 'trans_choice' helper.
* @param key
* @param count
* @param replacements
@ -52,7 +48,7 @@ class Translator {
/**
* Parse the given translation and find the correct plural option
* to use. Similar format at laravel's 'trans_choice' helper.
* to use. Similar format at Laravel's 'trans_choice' helper.
* @param {String} translation
* @param {Number} count
* @param {Object} replacements
@ -117,14 +113,17 @@ class Translator {
*/
performReplacements(string, replacements) {
if (!replacements) return string;
const replaceMatches = string.match(/:([\S]+)/g);
const replaceMatches = string.match(/:(\S+)/g);
if (replaceMatches === null) return string;
let updatedString = string;
replaceMatches.forEach(match => {
const key = match.substring(1);
if (typeof replacements[key] === 'undefined') return;
string = string.replace(match, replacements[key]);
updatedString = updatedString.replace(match, replacements[key]);
});
return string;
return updatedString;
}
}

View File

@ -1,4 +1,4 @@
import DrawIO from '../services/drawio';
import * as DrawIO from '../services/drawio';
let pageEditor = null;
let currentNode = null;