mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-06-18 02:12:30 +08:00
ESLINT: Addressed remaining detected issues
This commit is contained in:
1
package-lock.json
generated
1
package-lock.json
generated
@ -18,6 +18,7 @@
|
|||||||
"@codemirror/state": "^6.2.0",
|
"@codemirror/state": "^6.2.0",
|
||||||
"@codemirror/theme-one-dark": "^6.1.1",
|
"@codemirror/theme-one-dark": "^6.1.1",
|
||||||
"@codemirror/view": "^6.9.4",
|
"@codemirror/view": "^6.9.4",
|
||||||
|
"@lezer/highlight": "^1.1.4",
|
||||||
"@ssddanbrown/codemirror-lang-smarty": "^1.0.0",
|
"@ssddanbrown/codemirror-lang-smarty": "^1.0.0",
|
||||||
"@ssddanbrown/codemirror-lang-twig": "^1.0.0",
|
"@ssddanbrown/codemirror-lang-twig": "^1.0.0",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
|
90
package.json
90
package.json
@ -42,6 +42,7 @@
|
|||||||
"@codemirror/state": "^6.2.0",
|
"@codemirror/state": "^6.2.0",
|
||||||
"@codemirror/theme-one-dark": "^6.1.1",
|
"@codemirror/theme-one-dark": "^6.1.1",
|
||||||
"@codemirror/view": "^6.9.4",
|
"@codemirror/view": "^6.9.4",
|
||||||
|
"@lezer/highlight": "^1.1.4",
|
||||||
"@ssddanbrown/codemirror-lang-smarty": "^1.0.0",
|
"@ssddanbrown/codemirror-lang-smarty": "^1.0.0",
|
||||||
"@ssddanbrown/codemirror-lang-twig": "^1.0.0",
|
"@ssddanbrown/codemirror-lang-twig": "^1.0.0",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
@ -58,48 +59,77 @@
|
|||||||
"es2021": true
|
"es2021": true
|
||||||
},
|
},
|
||||||
"extends": "airbnb-base",
|
"extends": "airbnb-base",
|
||||||
"ignorePatterns": ["resources/**/*-stub.js"],
|
"ignorePatterns": [
|
||||||
"overrides": [
|
"resources/**/*-stub.js"
|
||||||
],
|
],
|
||||||
|
"overrides": [],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": "latest",
|
"ecmaVersion": "latest",
|
||||||
"sourceType": "module"
|
"sourceType": "module"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"indent": ["error", 4],
|
"indent": [
|
||||||
"arrow-parens": ["error", "as-needed"],
|
"error",
|
||||||
"padded-blocks": ["error", {
|
4
|
||||||
|
],
|
||||||
|
"arrow-parens": [
|
||||||
|
"error",
|
||||||
|
"as-needed"
|
||||||
|
],
|
||||||
|
"padded-blocks": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
"blocks": "never",
|
"blocks": "never",
|
||||||
"classes": "always"
|
"classes": "always"
|
||||||
}],
|
}
|
||||||
"object-curly-spacing": ["error", "never"],
|
],
|
||||||
"space-before-function-paren": ["error", {
|
"object-curly-spacing": [
|
||||||
"anonymous": "never",
|
"error",
|
||||||
"named": "never",
|
"never"
|
||||||
"asyncArrow": "always"
|
],
|
||||||
}],
|
"space-before-function-paren": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"anonymous": "never",
|
||||||
|
"named": "never",
|
||||||
|
"asyncArrow": "always"
|
||||||
|
}
|
||||||
|
],
|
||||||
"import/prefer-default-export": "off",
|
"import/prefer-default-export": "off",
|
||||||
"no-plusplus": ["error", {
|
"no-plusplus": [
|
||||||
"allowForLoopAfterthoughts": true
|
"error",
|
||||||
}],
|
{
|
||||||
|
"allowForLoopAfterthoughts": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"arrow-body-style": "off",
|
"arrow-body-style": "off",
|
||||||
"no-restricted-syntax": "off",
|
"no-restricted-syntax": "off",
|
||||||
"no-continue": "off",
|
"no-continue": "off",
|
||||||
"no-console": ["warn", {
|
"prefer-destructuring": "off",
|
||||||
"allow": ["error"]
|
"class-methods-use-this": "off",
|
||||||
}],
|
"no-param-reassign": "off",
|
||||||
"max-len": ["error", {
|
"no-console": [
|
||||||
"code": 110,
|
"warn",
|
||||||
"tabWidth": 4,
|
{
|
||||||
"ignoreUrls": true,
|
"allow": [
|
||||||
"ignoreComments": false,
|
"error",
|
||||||
"ignoreRegExpLiterals": true,
|
"warn"
|
||||||
"ignoreStrings": true,
|
]
|
||||||
"ignoreTemplateLiterals": true
|
}
|
||||||
}],
|
],
|
||||||
"no-param-reassign": ["error", {
|
"no-new": "off",
|
||||||
"props": false
|
"max-len": [
|
||||||
}]
|
"error",
|
||||||
|
{
|
||||||
|
"code": 110,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"ignoreUrls": true,
|
||||||
|
"ignoreComments": false,
|
||||||
|
"ignoreRegExpLiterals": true,
|
||||||
|
"ignoreStrings": true,
|
||||||
|
"ignoreTemplateLiterals": true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import events from './services/events';
|
import * as events from './services/events';
|
||||||
import * as httpInstance from './services/http';
|
import * as httpInstance from './services/http';
|
||||||
import Translations from './services/translations';
|
import Translations from './services/translations';
|
||||||
|
|
||||||
|
@ -6,24 +6,36 @@ import {createView} from './views';
|
|||||||
import {SimpleEditorInterface} from './simple-editor-interface';
|
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() {
|
function addCopyIcon(editorView) {
|
||||||
const codeBlocks = document.querySelectorAll('.page-content pre, .comment-box .content pre');
|
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>';
|
||||||
for (const codeBlock of codeBlocks) {
|
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>';
|
||||||
highlightElem(codeBlock);
|
const copyButton = document.createElement('button');
|
||||||
}
|
copyButton.setAttribute('type', 'button');
|
||||||
}
|
copyButton.classList.add('cm-copy-button');
|
||||||
|
copyButton.innerHTML = copyIcon;
|
||||||
|
editorView.dom.appendChild(copyButton);
|
||||||
|
|
||||||
/**
|
const notifyTime = 620;
|
||||||
* Highlight all code blocks within the given parent element
|
const transitionTime = 60;
|
||||||
* @param {HTMLElement} parent
|
copyButton.addEventListener('click', () => {
|
||||||
*/
|
copyTextToClipboard(editorView.state.doc.toString());
|
||||||
export function highlightWithin(parent) {
|
copyButton.classList.add('success');
|
||||||
const codeBlocks = parent.querySelectorAll('pre');
|
|
||||||
for (const codeBlock of codeBlocks) {
|
setTimeout(() => {
|
||||||
highlightElem(codeBlock);
|
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) {
|
function highlightElem(elem) {
|
||||||
const innerCodeElem = elem.querySelector('code[class^=language-]');
|
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();
|
const content = elem.textContent.trimEnd();
|
||||||
|
|
||||||
let langName = '';
|
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.
|
* Highlight all code blocks within the given parent element
|
||||||
* @param {EditorView} editorView
|
* @param {HTMLElement} parent
|
||||||
*/
|
*/
|
||||||
function addCopyIcon(editorView) {
|
export function highlightWithin(parent) {
|
||||||
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 codeBlocks = parent.querySelectorAll('pre');
|
||||||
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>';
|
for (const codeBlock of codeBlocks) {
|
||||||
const copyButton = document.createElement('button');
|
highlightElem(codeBlock);
|
||||||
copyButton.setAttribute('type', 'button');
|
}
|
||||||
copyButton.classList.add('cm-copy-button');
|
}
|
||||||
copyButton.innerHTML = copyIcon;
|
|
||||||
editorView.dom.appendChild(copyButton);
|
|
||||||
|
|
||||||
const notifyTime = 620;
|
/**
|
||||||
const transitionTime = 60;
|
* Highlight pre elements on a page
|
||||||
copyButton.addEventListener('click', event => {
|
*/
|
||||||
copyTextToClipboard(editorView.state.doc.toString());
|
export function highlight() {
|
||||||
copyButton.classList.add('success');
|
const codeBlocks = document.querySelectorAll('.page-content pre, .comment-box .content pre');
|
||||||
|
for (const codeBlock of codeBlocks) {
|
||||||
setTimeout(() => {
|
highlightElem(codeBlock);
|
||||||
copyButton.innerHTML = checkIcon;
|
}
|
||||||
}, transitionTime / 2);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
copyButton.classList.remove('success');
|
|
||||||
}, notifyTime);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
copyButton.innerHTML = copyIcon;
|
|
||||||
}, notifyTime + (transitionTime / 2));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,7 +20,7 @@ export class AjaxDeleteRow extends Component {
|
|||||||
window.$events.emit('success', resp.data.message);
|
window.$events.emit('success', resp.data.message);
|
||||||
}
|
}
|
||||||
this.row.remove();
|
this.row.remove();
|
||||||
}).catch(err => {
|
}).catch(() => {
|
||||||
this.row.style.opacity = null;
|
this.row.style.opacity = null;
|
||||||
this.row.style.pointerEvents = null;
|
this.row.style.pointerEvents = null;
|
||||||
});
|
});
|
||||||
|
@ -27,7 +27,7 @@ export class Attachments extends Component {
|
|||||||
this.startEdit(event.detail.id);
|
this.startEdit(event.detail.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.container.addEventListener('event-emit-select-edit-back', event => {
|
this.container.addEventListener('event-emit-select-edit-back', () => {
|
||||||
this.stopEdit();
|
this.stopEdit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ export class AutoSuggest extends Component {
|
|||||||
setupListeners() {
|
setupListeners() {
|
||||||
const navHandler = new KeyboardNavigationHandler(
|
const navHandler = new KeyboardNavigationHandler(
|
||||||
this.list,
|
this.list,
|
||||||
event => {
|
() => {
|
||||||
this.input.focus();
|
this.input.focus();
|
||||||
setTimeout(() => this.hideSuggestions(), 1);
|
setTimeout(() => this.hideSuggestions(), 1);
|
||||||
},
|
},
|
||||||
@ -104,7 +104,8 @@ export class AutoSuggest extends Component {
|
|||||||
*/
|
*/
|
||||||
displaySuggestions(suggestions) {
|
displaySuggestions(suggestions) {
|
||||||
if (suggestions.length === 0) {
|
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
|
// This used to use <button>s but was changed to div elements since Safari would not focus on buttons
|
||||||
|
@ -45,19 +45,19 @@ const sortOperations = {
|
|||||||
*/
|
*/
|
||||||
const moveActions = {
|
const moveActions = {
|
||||||
up: {
|
up: {
|
||||||
active(elem, parent, book) {
|
active(elem, parent) {
|
||||||
return !(elem.previousElementSibling === null && !parent);
|
return !(elem.previousElementSibling === null && !parent);
|
||||||
},
|
},
|
||||||
run(elem, parent, book) {
|
run(elem, parent) {
|
||||||
const newSibling = elem.previousElementSibling || parent;
|
const newSibling = elem.previousElementSibling || parent;
|
||||||
newSibling.insertAdjacentElement('beforebegin', elem);
|
newSibling.insertAdjacentElement('beforebegin', elem);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
down: {
|
down: {
|
||||||
active(elem, parent, book) {
|
active(elem, parent) {
|
||||||
return !(elem.nextElementSibling === null && !parent);
|
return !(elem.nextElementSibling === null && !parent);
|
||||||
},
|
},
|
||||||
run(elem, parent, book) {
|
run(elem, parent) {
|
||||||
const newSibling = elem.nextElementSibling || parent;
|
const newSibling = elem.nextElementSibling || parent;
|
||||||
newSibling.insertAdjacentElement('afterend', elem);
|
newSibling.insertAdjacentElement('afterend', elem);
|
||||||
},
|
},
|
||||||
@ -81,10 +81,10 @@ const moveActions = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
next_chapter: {
|
next_chapter: {
|
||||||
active(elem, parent, book) {
|
active(elem, parent) {
|
||||||
return elem.dataset.type === 'page' && this.getNextChapter(elem, parent);
|
return elem.dataset.type === 'page' && this.getNextChapter(elem, parent);
|
||||||
},
|
},
|
||||||
run(elem, parent, book) {
|
run(elem, parent) {
|
||||||
const nextChapter = this.getNextChapter(elem, parent);
|
const nextChapter = this.getNextChapter(elem, parent);
|
||||||
nextChapter.querySelector('ul').prepend(elem);
|
nextChapter.querySelector('ul').prepend(elem);
|
||||||
},
|
},
|
||||||
@ -92,14 +92,14 @@ const moveActions = {
|
|||||||
const topLevel = (parent || elem);
|
const topLevel = (parent || elem);
|
||||||
const topItems = Array.from(topLevel.parentElement.children);
|
const topItems = Array.from(topLevel.parentElement.children);
|
||||||
const index = topItems.indexOf(topLevel);
|
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: {
|
prev_chapter: {
|
||||||
active(elem, parent, book) {
|
active(elem, parent) {
|
||||||
return elem.dataset.type === 'page' && this.getPrevChapter(elem, parent);
|
return elem.dataset.type === 'page' && this.getPrevChapter(elem, parent);
|
||||||
},
|
},
|
||||||
run(elem, parent, book) {
|
run(elem, parent) {
|
||||||
const prevChapter = this.getPrevChapter(elem, parent);
|
const prevChapter = this.getPrevChapter(elem, parent);
|
||||||
prevChapter.querySelector('ul').append(elem);
|
prevChapter.querySelector('ul').append(elem);
|
||||||
},
|
},
|
||||||
@ -107,11 +107,11 @@ const moveActions = {
|
|||||||
const topLevel = (parent || elem);
|
const topLevel = (parent || elem);
|
||||||
const topItems = Array.from(topLevel.parentElement.children);
|
const topItems = Array.from(topLevel.parentElement.children);
|
||||||
const index = topItems.indexOf(topLevel);
|
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: {
|
book_end: {
|
||||||
active(elem, parent, book) {
|
active(elem, parent) {
|
||||||
return parent || (parent === null && elem.nextElementSibling);
|
return parent || (parent === null && elem.nextElementSibling);
|
||||||
},
|
},
|
||||||
run(elem, parent, book) {
|
run(elem, parent, book) {
|
||||||
@ -119,7 +119,7 @@ const moveActions = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
book_start: {
|
book_start: {
|
||||||
active(elem, parent, book) {
|
active(elem, parent) {
|
||||||
return parent || (parent === null && elem.previousElementSibling);
|
return parent || (parent === null && elem.previousElementSibling);
|
||||||
},
|
},
|
||||||
run(elem, parent, book) {
|
run(elem, parent, book) {
|
||||||
@ -127,18 +127,18 @@ const moveActions = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
before_chapter: {
|
before_chapter: {
|
||||||
active(elem, parent, book) {
|
active(elem, parent) {
|
||||||
return parent;
|
return parent;
|
||||||
},
|
},
|
||||||
run(elem, parent, book) {
|
run(elem, parent) {
|
||||||
parent.insertAdjacentElement('beforebegin', elem);
|
parent.insertAdjacentElement('beforebegin', elem);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
after_chapter: {
|
after_chapter: {
|
||||||
active(elem, parent, book) {
|
active(elem, parent) {
|
||||||
return parent;
|
return parent;
|
||||||
},
|
},
|
||||||
run(elem, parent, book) {
|
run(elem, parent) {
|
||||||
parent.insertAdjacentElement('afterend', elem);
|
parent.insertAdjacentElement('afterend', elem);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -196,7 +196,7 @@ export class BookSort extends Component {
|
|||||||
reverse = (lastSort === sort) ? !reverse : false;
|
reverse = (lastSort === sort) ? !reverse : false;
|
||||||
let sortFunction = sortOperations[sort];
|
let sortFunction = sortOperations[sort];
|
||||||
if (reverse && reversibleTypes.includes(sort)) {
|
if (reverse && reversibleTypes.includes(sort)) {
|
||||||
sortFunction = function(a, b) {
|
sortFunction = function reverseSortOperation(a, b) {
|
||||||
return 0 - sortOperations[sort](a, b);
|
return 0 - sortOperations[sort](a, b);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -260,7 +260,7 @@ export class BookSort extends Component {
|
|||||||
animation: 150,
|
animation: 150,
|
||||||
fallbackOnBody: true,
|
fallbackOnBody: true,
|
||||||
swapThreshold: 0.65,
|
swapThreshold: 0.65,
|
||||||
onSort: event => {
|
onSort: () => {
|
||||||
this.ensureNoNestedChapters();
|
this.ensureNoNestedChapters();
|
||||||
this.updateMapInput();
|
this.updateMapInput();
|
||||||
this.updateMoveActionStateForAll();
|
this.updateMoveActionStateForAll();
|
||||||
|
@ -27,7 +27,11 @@ export class ChapterContents extends Component {
|
|||||||
|
|
||||||
click(event) {
|
click(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.isOpen ? this.close() : this.open();
|
if (this.isOpen) {
|
||||||
|
this.close();
|
||||||
|
} else {
|
||||||
|
this.open();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -43,9 +43,9 @@ export class CodeEditor extends Component {
|
|||||||
this.languageInputChange(language);
|
this.languageInputChange(language);
|
||||||
});
|
});
|
||||||
|
|
||||||
onEnterPress(this.languageInput, e => this.save());
|
onEnterPress(this.languageInput, () => this.save());
|
||||||
this.languageInput.addEventListener('input', e => this.languageInputChange(this.languageInput.value));
|
this.languageInput.addEventListener('input', () => this.languageInputChange(this.languageInput.value));
|
||||||
onSelect(this.saveButton, e => this.save());
|
onSelect(this.saveButton, () => this.save());
|
||||||
|
|
||||||
onChildEvent(this.historyList, 'button', 'click', (event, elem) => {
|
onChildEvent(this.historyList, 'button', 'click', (event, elem) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -74,7 +74,8 @@ export class CodeEditor extends Component {
|
|||||||
|
|
||||||
onChildEvent(button.parentElement, '.lang-option-favorite-toggle', 'click', () => {
|
onChildEvent(button.parentElement, '.lang-option-favorite-toggle', 'click', () => {
|
||||||
isFavorite = !isFavorite;
|
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');
|
button.setAttribute('data-favourite', isFavorite ? 'true' : 'false');
|
||||||
|
|
||||||
window.$http.patch('/preferences/update-code-language-favourite', {
|
window.$http.patch('/preferences/update-code-language-favourite', {
|
||||||
@ -173,7 +174,7 @@ export class CodeEditor extends Component {
|
|||||||
const historyKeys = Object.keys(this.history).reverse();
|
const historyKeys = Object.keys(this.history).reverse();
|
||||||
this.historyDropDown.classList.toggle('hidden', historyKeys.length === 0);
|
this.historyDropDown.classList.toggle('hidden', historyKeys.length === 0);
|
||||||
this.historyList.innerHTML = historyKeys.map(key => {
|
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>`;
|
return `<li><button type="button" data-time="${key}" class="text-item">${localTime}</button></li>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ export class ConfirmDialog extends Component {
|
|||||||
this.sendResult(false);
|
this.sendResult(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise((res, rej) => {
|
return new Promise(res => {
|
||||||
this.res = res;
|
this.res = res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,11 @@ export class Dropdown extends Component {
|
|||||||
this.menu.style.position = 'fixed';
|
this.menu.style.position = 'fixed';
|
||||||
this.menu.style.width = `${menuOriginalRect.width}px`;
|
this.menu.style.width = `${menuOriginalRect.width}px`;
|
||||||
this.menu.style.left = `${menuOriginalRect.left}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
|
// 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
|
// Set listener to hide on mouse leave or window click
|
||||||
this.menu.addEventListener('mouseleave', this.hide);
|
this.menu.addEventListener('mouseleave', this.hide);
|
||||||
window.addEventListener('click', event => {
|
window.addEventListener('click', clickEvent => {
|
||||||
if (!this.menu.contains(event.target)) {
|
if (!this.menu.contains(clickEvent.target)) {
|
||||||
this.hide();
|
this.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -13,7 +13,7 @@ export class Dropzone extends Component {
|
|||||||
this.uploadLimitMessage = this.$opts.uploadLimitMessage;
|
this.uploadLimitMessage = this.$opts.uploadLimitMessage;
|
||||||
this.timeoutMessage = this.$opts.timeoutMessage;
|
this.timeoutMessage = this.$opts.timeoutMessage;
|
||||||
|
|
||||||
const _this = this;
|
const component = this;
|
||||||
this.dz = new DropZoneLib(this.container, {
|
this.dz = new DropZoneLib(this.container, {
|
||||||
addRemoveLinks: true,
|
addRemoveLinks: true,
|
||||||
dictRemoveFile: this.removeMessage,
|
dictRemoveFile: this.removeMessage,
|
||||||
@ -23,9 +23,9 @@ export class Dropzone extends Component {
|
|||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
init() {
|
init() {
|
||||||
this.dz = this;
|
this.dz = this;
|
||||||
this.dz.on('sending', _this.onSending.bind(_this));
|
this.dz.on('sending', component.onSending.bind(component));
|
||||||
this.dz.on('success', _this.onSuccess.bind(_this));
|
this.dz.on('success', component.onSuccess.bind(component));
|
||||||
this.dz.on('error', _this.onError.bind(_this));
|
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');
|
const token = window.document.querySelector('meta[name=token]').getAttribute('content');
|
||||||
data.append('_token', token);
|
data.append('_token', token);
|
||||||
|
|
||||||
xhr.ontimeout = e => {
|
xhr.ontimeout = () => {
|
||||||
this.dz.emit('complete', file);
|
this.dz.emit('complete', file);
|
||||||
this.dz.emit('error', file, this.timeoutMessage);
|
this.dz.emit('error', file, this.timeoutMessage);
|
||||||
};
|
};
|
||||||
|
@ -34,7 +34,7 @@ export class EntityPermissions extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Role select change
|
// Role select change
|
||||||
this.roleSelect.addEventListener('change', event => {
|
this.roleSelect.addEventListener('change', () => {
|
||||||
const roleId = this.roleSelect.value;
|
const roleId = this.roleSelect.value;
|
||||||
if (roleId) {
|
if (roleId) {
|
||||||
this.addRoleRow(roleId);
|
this.addRoleRow(roleId);
|
||||||
|
@ -31,7 +31,8 @@ export class EntitySearch extends Component {
|
|||||||
runSearch() {
|
runSearch() {
|
||||||
const term = this.searchInput.value.trim();
|
const term = this.searchInput.value.trim();
|
||||||
if (term.length === 0) {
|
if (term.length === 0) {
|
||||||
return this.clearSearch();
|
this.clearSearch();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.searchView.classList.remove('hidden');
|
this.searchView.classList.remove('hidden');
|
||||||
|
@ -29,7 +29,7 @@ export class EntitySelector extends Component {
|
|||||||
this.elem.addEventListener('click', this.onClick.bind(this));
|
this.elem.addEventListener('click', this.onClick.bind(this));
|
||||||
|
|
||||||
let lastSearch = 0;
|
let lastSearch = 0;
|
||||||
this.searchInput.addEventListener('input', event => {
|
this.searchInput.addEventListener('input', () => {
|
||||||
lastSearch = Date.now();
|
lastSearch = Date.now();
|
||||||
this.showLoading();
|
this.showLoading();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -43,26 +43,26 @@ export class EntitySelector extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Keyboard navigation
|
// Keyboard navigation
|
||||||
onChildEvent(this.$el, '[data-entity-type]', 'keydown', (e, el) => {
|
onChildEvent(this.$el, '[data-entity-type]', 'keydown', event => {
|
||||||
if (e.ctrlKey && e.code === 'Enter') {
|
if (event.ctrlKey && event.code === 'Enter') {
|
||||||
const form = this.$el.closest('form');
|
const form = this.$el.closest('form');
|
||||||
if (form) {
|
if (form) {
|
||||||
form.submit();
|
form.submit();
|
||||||
e.preventDefault();
|
event.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.code === 'ArrowDown') {
|
if (event.code === 'ArrowDown') {
|
||||||
this.focusAdjacent(true);
|
this.focusAdjacent(true);
|
||||||
}
|
}
|
||||||
if (e.code === 'ArrowUp') {
|
if (event.code === 'ArrowUp') {
|
||||||
this.focusAdjacent(false);
|
this.focusAdjacent(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.searchInput.addEventListener('keydown', e => {
|
this.searchInput.addEventListener('keydown', event => {
|
||||||
if (e.code === 'ArrowDown') {
|
if (event.code === 'ArrowDown') {
|
||||||
this.focusAdjacent(true);
|
this.focusAdjacent(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,7 @@ import {Component} from './component';
|
|||||||
|
|
||||||
export class ExpandToggle extends Component {
|
export class ExpandToggle extends Component {
|
||||||
|
|
||||||
setup(elem) {
|
setup() {
|
||||||
this.targetSelector = this.$opts.targetSelector;
|
this.targetSelector = this.$opts.targetSelector;
|
||||||
this.isOpen = this.$opts.isOpen === 'true';
|
this.isOpen = this.$opts.isOpen === 'true';
|
||||||
this.updateEndpoint = this.$opts.updateEndpoint;
|
this.updateEndpoint = this.$opts.updateEndpoint;
|
||||||
@ -25,7 +25,8 @@ export class ExpandToggle extends Component {
|
|||||||
|
|
||||||
const matchingElems = document.querySelectorAll(this.targetSelector);
|
const matchingElems = document.querySelectorAll(this.targetSelector);
|
||||||
for (const match of matchingElems) {
|
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;
|
this.isOpen = !this.isOpen;
|
||||||
|
@ -50,20 +50,20 @@ export class ImageManager extends Component {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
||||||
onSelect(this.cancelSearch, event => {
|
onSelect(this.cancelSearch, () => {
|
||||||
this.resetListView();
|
this.resetListView();
|
||||||
this.resetSearchView();
|
this.resetSearchView();
|
||||||
this.loadGallery();
|
this.loadGallery();
|
||||||
this.cancelSearch.classList.remove('active');
|
this.cancelSearch.classList.remove('active');
|
||||||
});
|
});
|
||||||
|
|
||||||
this.searchInput.addEventListener('input', event => {
|
this.searchInput.addEventListener('input', () => {
|
||||||
this.cancelSearch.classList.toggle('active', this.searchInput.value.trim());
|
this.cancelSearch.classList.toggle('active', this.searchInput.value.trim());
|
||||||
});
|
});
|
||||||
|
|
||||||
onChildEvent(this.listContainer, '.load-more', 'click', async event => {
|
onChildEvent(this.listContainer, '.load-more', 'click', async event => {
|
||||||
showLoading(event.target);
|
showLoading(event.target);
|
||||||
this.page++;
|
this.page += 1;
|
||||||
await this.loadGallery();
|
await this.loadGallery();
|
||||||
event.target.remove();
|
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('event-emit-select-image', this.onImageSelectEvent.bind(this));
|
||||||
|
|
||||||
this.listContainer.addEventListener('error', event => {
|
this.listContainer.addEventListener('error', event => {
|
||||||
event.target.src = baseUrl('loading_error.png');
|
event.target.src = window.baseUrl('loading_error.png');
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
onSelect(this.selectButton, () => {
|
onSelect(this.selectButton, () => {
|
||||||
@ -81,7 +81,7 @@ export class ImageManager extends Component {
|
|||||||
this.hide();
|
this.hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
onChildEvent(this.formContainer, '#image-manager-delete', 'click', event => {
|
onChildEvent(this.formContainer, '#image-manager-delete', 'click', () => {
|
||||||
if (this.lastSelected) {
|
if (this.lastSelected) {
|
||||||
this.loadImageEditForm(this.lastSelected.id, true);
|
this.loadImageEditForm(this.lastSelected.id, true);
|
||||||
}
|
}
|
||||||
|
@ -1,59 +1,59 @@
|
|||||||
export {AddRemoveRows} from './add-remove-rows.js';
|
export {AddRemoveRows} from './add-remove-rows';
|
||||||
export {AjaxDeleteRow} from './ajax-delete-row.js';
|
export {AjaxDeleteRow} from './ajax-delete-row';
|
||||||
export {AjaxForm} from './ajax-form.js';
|
export {AjaxForm} from './ajax-form';
|
||||||
export {Attachments} from './attachments.js';
|
export {Attachments} from './attachments';
|
||||||
export {AttachmentsList} from './attachments-list.js';
|
export {AttachmentsList} from './attachments-list';
|
||||||
export {AutoSuggest} from './auto-suggest.js';
|
export {AutoSuggest} from './auto-suggest';
|
||||||
export {AutoSubmit} from './auto-submit.js';
|
export {AutoSubmit} from './auto-submit';
|
||||||
export {BackToTop} from './back-to-top.js';
|
export {BackToTop} from './back-to-top';
|
||||||
export {BookSort} from './book-sort.js';
|
export {BookSort} from './book-sort';
|
||||||
export {ChapterContents} from './chapter-contents.js';
|
export {ChapterContents} from './chapter-contents';
|
||||||
export {CodeEditor} from './code-editor.js';
|
export {CodeEditor} from './code-editor';
|
||||||
export {CodeHighlighter} from './code-highlighter.js';
|
export {CodeHighlighter} from './code-highlighter';
|
||||||
export {CodeTextarea} from './code-textarea.js';
|
export {CodeTextarea} from './code-textarea';
|
||||||
export {Collapsible} from './collapsible.js';
|
export {Collapsible} from './collapsible';
|
||||||
export {ConfirmDialog} from './confirm-dialog';
|
export {ConfirmDialog} from './confirm-dialog';
|
||||||
export {CustomCheckbox} from './custom-checkbox.js';
|
export {CustomCheckbox} from './custom-checkbox';
|
||||||
export {DetailsHighlighter} from './details-highlighter.js';
|
export {DetailsHighlighter} from './details-highlighter';
|
||||||
export {Dropdown} from './dropdown.js';
|
export {Dropdown} from './dropdown';
|
||||||
export {DropdownSearch} from './dropdown-search.js';
|
export {DropdownSearch} from './dropdown-search';
|
||||||
export {Dropzone} from './dropzone.js';
|
export {Dropzone} from './dropzone';
|
||||||
export {EditorToolbox} from './editor-toolbox.js';
|
export {EditorToolbox} from './editor-toolbox';
|
||||||
export {EntityPermissions} from './entity-permissions';
|
export {EntityPermissions} from './entity-permissions';
|
||||||
export {EntitySearch} from './entity-search.js';
|
export {EntitySearch} from './entity-search';
|
||||||
export {EntitySelector} from './entity-selector.js';
|
export {EntitySelector} from './entity-selector';
|
||||||
export {EntitySelectorPopup} from './entity-selector-popup.js';
|
export {EntitySelectorPopup} from './entity-selector-popup';
|
||||||
export {EventEmitSelect} from './event-emit-select.js';
|
export {EventEmitSelect} from './event-emit-select';
|
||||||
export {ExpandToggle} from './expand-toggle.js';
|
export {ExpandToggle} from './expand-toggle';
|
||||||
export {GlobalSearch} from './global-search.js';
|
export {GlobalSearch} from './global-search';
|
||||||
export {HeaderMobileToggle} from './header-mobile-toggle.js';
|
export {HeaderMobileToggle} from './header-mobile-toggle';
|
||||||
export {ImageManager} from './image-manager.js';
|
export {ImageManager} from './image-manager';
|
||||||
export {ImagePicker} from './image-picker.js';
|
export {ImagePicker} from './image-picker';
|
||||||
export {ListSortControl} from './list-sort-control.js';
|
export {ListSortControl} from './list-sort-control';
|
||||||
export {MarkdownEditor} from './markdown-editor.js';
|
export {MarkdownEditor} from './markdown-editor';
|
||||||
export {NewUserPassword} from './new-user-password.js';
|
export {NewUserPassword} from './new-user-password';
|
||||||
export {Notification} from './notification.js';
|
export {Notification} from './notification';
|
||||||
export {OptionalInput} from './optional-input.js';
|
export {OptionalInput} from './optional-input';
|
||||||
export {PageComments} from './page-comments.js';
|
export {PageComments} from './page-comments';
|
||||||
export {PageDisplay} from './page-display.js';
|
export {PageDisplay} from './page-display';
|
||||||
export {PageEditor} from './page-editor.js';
|
export {PageEditor} from './page-editor';
|
||||||
export {PagePicker} from './page-picker.js';
|
export {PagePicker} from './page-picker';
|
||||||
export {PermissionsTable} from './permissions-table.js';
|
export {PermissionsTable} from './permissions-table';
|
||||||
export {Pointer} from './pointer.js';
|
export {Pointer} from './pointer';
|
||||||
export {Popup} from './popup.js';
|
export {Popup} from './popup';
|
||||||
export {SettingAppColorScheme} from './setting-app-color-scheme.js';
|
export {SettingAppColorScheme} from './setting-app-color-scheme';
|
||||||
export {SettingColorPicker} from './setting-color-picker.js';
|
export {SettingColorPicker} from './setting-color-picker';
|
||||||
export {SettingHomepageControl} from './setting-homepage-control.js';
|
export {SettingHomepageControl} from './setting-homepage-control';
|
||||||
export {ShelfSort} from './shelf-sort.js';
|
export {ShelfSort} from './shelf-sort';
|
||||||
export {Shortcuts} from './shortcuts';
|
export {Shortcuts} from './shortcuts';
|
||||||
export {ShortcutInput} from './shortcut-input';
|
export {ShortcutInput} from './shortcut-input';
|
||||||
export {SortableList} from './sortable-list.js';
|
export {SortableList} from './sortable-list';
|
||||||
export {SubmitOnChange} from './submit-on-change.js';
|
export {SubmitOnChange} from './submit-on-change';
|
||||||
export {Tabs} from './tabs.js';
|
export {Tabs} from './tabs';
|
||||||
export {TagManager} from './tag-manager.js';
|
export {TagManager} from './tag-manager';
|
||||||
export {TemplateManager} from './template-manager.js';
|
export {TemplateManager} from './template-manager';
|
||||||
export {ToggleSwitch} from './toggle-switch.js';
|
export {ToggleSwitch} from './toggle-switch';
|
||||||
export {TriLayout} from './tri-layout.js';
|
export {TriLayout} from './tri-layout';
|
||||||
export {UserSelect} from './user-select.js';
|
export {UserSelect} from './user-select';
|
||||||
export {WebhookEvents} from './webhook-events';
|
export {WebhookEvents} from './webhook-events';
|
||||||
export {WysiwygEditor} from './wysiwyg-editor.js';
|
export {WysiwygEditor} from './wysiwyg-editor';
|
||||||
|
@ -82,7 +82,7 @@ export class MarkdownEditor extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleDividerDrag() {
|
handleDividerDrag() {
|
||||||
this.divider.addEventListener('pointerdown', event => {
|
this.divider.addEventListener('pointerdown', () => {
|
||||||
const wrapRect = this.elem.getBoundingClientRect();
|
const wrapRect = this.elem.getBoundingClientRect();
|
||||||
const moveListener = event => {
|
const moveListener = event => {
|
||||||
const xRel = event.pageX - wrapRect.left;
|
const xRel = event.pageX - wrapRect.left;
|
||||||
@ -90,7 +90,7 @@ export class MarkdownEditor extends Component {
|
|||||||
this.displayWrap.style.flexBasis = `${100 - xPct}%`;
|
this.displayWrap.style.flexBasis = `${100 - xPct}%`;
|
||||||
this.editor.settings.set('editorWidth', xPct);
|
this.editor.settings.set('editorWidth', xPct);
|
||||||
};
|
};
|
||||||
const upListener = event => {
|
const upListener = () => {
|
||||||
window.removeEventListener('pointermove', moveListener);
|
window.removeEventListener('pointermove', moveListener);
|
||||||
window.removeEventListener('pointerup', upListener);
|
window.removeEventListener('pointerup', upListener);
|
||||||
this.display.style.pointerEvents = null;
|
this.display.style.pointerEvents = null;
|
||||||
|
@ -100,7 +100,7 @@ export class PageComments extends Component {
|
|||||||
deleteComment(commentElem) {
|
deleteComment(commentElem) {
|
||||||
const id = commentElem.getAttribute('comment');
|
const id = commentElem.getAttribute('comment');
|
||||||
this.showLoading(commentElem.querySelector('[comment-content]'));
|
this.showLoading(commentElem.querySelector('[comment-content]'));
|
||||||
window.$http.delete(`/comment/${id}`).then(resp => {
|
window.$http.delete(`/comment/${id}`).then(() => {
|
||||||
commentElem.parentNode.removeChild(commentElem);
|
commentElem.parentNode.removeChild(commentElem);
|
||||||
window.$events.success(this.deletedText);
|
window.$events.success(this.deletedText);
|
||||||
this.updateCount();
|
this.updateCount();
|
||||||
|
@ -2,6 +2,33 @@ import * as DOM from '../services/dom';
|
|||||||
import {scrollAndHighlightElement} from '../services/util';
|
import {scrollAndHighlightElement} from '../services/util';
|
||||||
import {Component} from './component';
|
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 {
|
export class PageDisplay extends Component {
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
@ -58,33 +85,6 @@ export class PageDisplay extends Component {
|
|||||||
if (headings.length > 0 && pageNav !== null) {
|
if (headings.length > 0 && pageNav !== null) {
|
||||||
addNavObserver(headings);
|
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() {
|
setupDetailsCodeBlockRefresh() {
|
||||||
|
@ -59,7 +59,9 @@ export class PageEditor extends Component {
|
|||||||
window.$events.listen('editor-save-page', this.savePage.bind(this));
|
window.$events.listen('editor-save-page', this.savePage.bind(this));
|
||||||
|
|
||||||
// Listen to content changes from the editor
|
// 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-html-change', onContentChange);
|
||||||
window.$events.listen('editor-markdown-change', onContentChange);
|
window.$events.listen('editor-markdown-change', onContentChange);
|
||||||
|
|
||||||
@ -80,7 +82,8 @@ export class PageEditor extends Component {
|
|||||||
|
|
||||||
setInitialFocus() {
|
setInitialFocus() {
|
||||||
if (this.hasDefaultTitle) {
|
if (this.hasDefaultTitle) {
|
||||||
return this.titleElem.select();
|
this.titleElem.select();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
@ -133,7 +136,9 @@ export class PageEditor extends Component {
|
|||||||
try {
|
try {
|
||||||
const saveKey = `draft-save-fail-${(new Date()).toISOString()}`;
|
const saveKey = `draft-save-fail-${(new Date()).toISOString()}`;
|
||||||
window.localStorage.setItem(saveKey, JSON.stringify(data));
|
window.localStorage.setItem(saveKey, JSON.stringify(data));
|
||||||
} catch (err) {}
|
} catch (lsErr) {
|
||||||
|
console.error(lsErr);
|
||||||
|
}
|
||||||
|
|
||||||
window.$events.emit('error', this.autosaveFailText);
|
window.$events.emit('error', this.autosaveFailText);
|
||||||
}
|
}
|
||||||
@ -154,7 +159,8 @@ export class PageEditor extends Component {
|
|||||||
try {
|
try {
|
||||||
response = await window.$http.get(`/ajax/page/${this.pageId}`);
|
response = await window.$http.get(`/ajax/page/${this.pageId}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return console.error(e);
|
console.error(e);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.autoSave.interval) {
|
if (this.autoSave.interval) {
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import {Component} from './component';
|
import {Component} from './component';
|
||||||
|
|
||||||
|
function toggleElem(elem, show) {
|
||||||
|
elem.style.display = show ? null : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
export class PagePicker extends Component {
|
export class PagePicker extends Component {
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
@ -18,7 +22,7 @@ export class PagePicker extends Component {
|
|||||||
this.selectButton.addEventListener('click', this.showPopup.bind(this));
|
this.selectButton.addEventListener('click', this.showPopup.bind(this));
|
||||||
this.display.parentElement.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('', '');
|
this.setValue('', '');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -55,7 +59,3 @@ export class PagePicker extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleElem(elem, show) {
|
|
||||||
elem.style.display = show ? null : 'none';
|
|
||||||
}
|
|
||||||
|
@ -21,7 +21,7 @@ export class Pointer extends Component {
|
|||||||
|
|
||||||
setupListeners() {
|
setupListeners() {
|
||||||
// Copy on copy button click
|
// Copy on copy button click
|
||||||
this.button.addEventListener('click', event => {
|
this.button.addEventListener('click', () => {
|
||||||
copyTextToClipboard(this.input.value);
|
copyTextToClipboard(this.input.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ export class Pointer extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Hide pointer when clicking away
|
// Hide pointer when clicking away
|
||||||
DOM.onEvents(document.body, ['click', 'focus'], event => {
|
DOM.onEvents(document.body, ['click', 'focus'], () => {
|
||||||
if (!this.showing || this.isSelection) return;
|
if (!this.showing || this.isSelection) return;
|
||||||
this.hidePointer();
|
this.hidePointer();
|
||||||
});
|
});
|
||||||
|
@ -26,11 +26,11 @@ export class Popup extends Component {
|
|||||||
|
|
||||||
this.container.addEventListener('click', event => {
|
this.container.addEventListener('click', event => {
|
||||||
if (event.target === this.container && lastMouseDownTarget === this.container) {
|
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) {
|
hide(onComplete = null) {
|
||||||
|
@ -59,7 +59,6 @@ export class SettingAppColorScheme extends Component {
|
|||||||
const rgb = this.hexToRgb(hexVal);
|
const rgb = this.hexToRgb(hexVal);
|
||||||
const rgbLightVal = `rgba(${[rgb.r, rgb.g, rgb.b, '0.15'].join(',')})`;
|
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"]`);
|
const lightColorInput = this.container.querySelector(`input[name="${lightName}"][type="hidden"]`);
|
||||||
lightColorInput.value = rgbLightVal;
|
lightColorInput.value = rgbLightVal;
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,13 @@ import {Component} from './component';
|
|||||||
* @type {Object<string, function(HTMLElement, HTMLElement, HTMLElement)>}
|
* @type {Object<string, function(HTMLElement, HTMLElement, HTMLElement)>}
|
||||||
*/
|
*/
|
||||||
const itemActions = {
|
const itemActions = {
|
||||||
move_up(item, shelfBooksList, allBooksList) {
|
move_up(item) {
|
||||||
const list = item.parentNode;
|
const list = item.parentNode;
|
||||||
const index = Array.from(list.children).indexOf(item);
|
const index = Array.from(list.children).indexOf(item);
|
||||||
const newIndex = Math.max(index - 1, 0);
|
const newIndex = Math.max(index - 1, 0);
|
||||||
list.insertBefore(item, list.children[newIndex] || null);
|
list.insertBefore(item, list.children[newIndex] || null);
|
||||||
},
|
},
|
||||||
move_down(item, shelfBooksList, allBooksList) {
|
move_down(item) {
|
||||||
const list = item.parentNode;
|
const list = item.parentNode;
|
||||||
const index = Array.from(list.children).indexOf(item);
|
const index = Array.from(list.children).indexOf(item);
|
||||||
const newIndex = Math.min(index + 2, list.children.length);
|
const newIndex = Math.min(index + 2, list.children.length);
|
||||||
@ -20,7 +20,7 @@ const itemActions = {
|
|||||||
remove(item, shelfBooksList, allBooksList) {
|
remove(item, shelfBooksList, allBooksList) {
|
||||||
allBooksList.appendChild(item);
|
allBooksList.appendChild(item);
|
||||||
},
|
},
|
||||||
add(item, shelfBooksList, allBooksList) {
|
add(item, shelfBooksList) {
|
||||||
shelfBooksList.appendChild(item);
|
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);
|
this.filterBooksByName(this.bookSearchInput.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -121,10 +121,10 @@ export class ShelfSort extends Component {
|
|||||||
const bProp = bookB.dataset[sortProperty].toLowerCase();
|
const bProp = bookB.dataset[sortProperty].toLowerCase();
|
||||||
|
|
||||||
if (reverse) {
|
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) {
|
for (const book of books) {
|
||||||
|
@ -33,7 +33,8 @@ export class Shortcuts extends Component {
|
|||||||
|
|
||||||
window.addEventListener('keydown', event => {
|
window.addEventListener('keydown', event => {
|
||||||
if (event.key === '?') {
|
if (event.key === '?') {
|
||||||
this.hintsShowing ? this.hideHints() : this.showHints();
|
const action = this.hintsShowing ? this.hideHints : this.showHints;
|
||||||
|
action();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -36,10 +36,10 @@ export class TemplateManager extends Component {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Search submit button press
|
// Search submit button press
|
||||||
this.searchButton.addEventListener('click', event => this.performSearch());
|
this.searchButton.addEventListener('click', () => this.performSearch());
|
||||||
|
|
||||||
// Search cancel button press
|
// Search cancel button press
|
||||||
this.searchCancel.addEventListener('click', event => {
|
this.searchCancel.addEventListener('click', () => {
|
||||||
this.searchInput.value = '';
|
this.searchInput.value = '';
|
||||||
this.performSearch();
|
this.performSearch();
|
||||||
});
|
});
|
||||||
|
@ -19,7 +19,7 @@ export class TriLayout extends Component {
|
|||||||
|
|
||||||
// Watch layout changes
|
// Watch layout changes
|
||||||
this.updateLayout();
|
this.updateLayout();
|
||||||
window.addEventListener('resize', event => {
|
window.addEventListener('resize', () => {
|
||||||
this.updateLayout();
|
this.updateLayout();
|
||||||
}, {passive: true});
|
}, {passive: true});
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import DrawIO from '../services/drawio';
|
import * as DrawIO from '../services/drawio';
|
||||||
|
|
||||||
export class Actions {
|
export class Actions {
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ export class Actions {
|
|||||||
} else {
|
} else {
|
||||||
window.$events.emit('error', this.editor.config.text.imageUploadError);
|
window.$events.emit('error', this.editor.config.text.imageUploadError);
|
||||||
}
|
}
|
||||||
console.log(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the editor full screen
|
// Make the editor full screen
|
||||||
@ -165,7 +165,7 @@ export class Actions {
|
|||||||
scrollToLine = lineCount;
|
scrollToLine = lineCount;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
lineCount++;
|
lineCount += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scrollToLine === -1) {
|
if (scrollToLine === -1) {
|
||||||
@ -258,22 +258,31 @@ export class Actions {
|
|||||||
* @param {String} end
|
* @param {String} end
|
||||||
*/
|
*/
|
||||||
wrapSelection(start, end) {
|
wrapSelection(start, end) {
|
||||||
const selectionRange = this.#getSelectionRange();
|
const selectRange = this.#getSelectionRange();
|
||||||
const selectionText = this.#getSelectionText(selectionRange);
|
const selectionText = this.#getSelectionText(selectRange);
|
||||||
if (!selectionText) return this.#wrapLine(start, end);
|
if (!selectionText) {
|
||||||
|
this.#wrapLine(start, end);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let newSelectionText = selectionText;
|
let newSelectionText = selectionText;
|
||||||
let newRange;
|
let newRange;
|
||||||
|
|
||||||
if (selectionText.startsWith(start) && selectionText.endsWith(end)) {
|
if (selectionText.startsWith(start) && selectionText.endsWith(end)) {
|
||||||
newSelectionText = selectionText.slice(start.length, selectionText.length - end.length);
|
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 {
|
} else {
|
||||||
newSelectionText = `${start}${selectionText}${end}`;
|
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() {
|
replaceLineStartForOrderedList() {
|
||||||
@ -314,7 +323,13 @@ export class Actions {
|
|||||||
const newFormat = formats[newFormatIndex];
|
const newFormat = formats[newFormatIndex];
|
||||||
const newContent = line.text.replace(matches[0], matches[0].replace(format, newFormat));
|
const newContent = line.text.replace(matches[0], matches[0].replace(format, newFormat));
|
||||||
const lineDiff = newContent.length - line.text.length;
|
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) {
|
} catch (err) {
|
||||||
window.$events.emit('error', this.editor.config.text.imageUploadError);
|
window.$events.emit('error', this.editor.config.text.imageUploadError);
|
||||||
this.#findAndReplaceContent(placeHolderText, '');
|
this.#findAndReplaceContent(placeHolderText, '');
|
||||||
console.log(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -432,7 +447,8 @@ export class Actions {
|
|||||||
*/
|
*/
|
||||||
#replaceSelection(newContent, cursorOffset = 0, selectionRange = null) {
|
#replaceSelection(newContent, cursorOffset = 0, selectionRange = null) {
|
||||||
selectionRange = selectionRange || this.editor.cm.state.selection.main;
|
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();
|
this.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -510,6 +526,9 @@ export class Actions {
|
|||||||
|
|
||||||
if (selectFrom) {
|
if (selectFrom) {
|
||||||
tr.selection = {anchor: selectFrom};
|
tr.selection = {anchor: selectFrom};
|
||||||
|
if (selectTo) {
|
||||||
|
tr.selection.head = selectTo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editor.cm.dispatch(tr);
|
this.editor.cm.dispatch(tr);
|
||||||
|
@ -21,7 +21,9 @@ export async function init(editor) {
|
|||||||
|
|
||||||
const onScrollDebounced = debounce(editor.actions.syncDisplayPosition.bind(editor.actions), 100, false);
|
const onScrollDebounced = debounce(editor.actions.syncDisplayPosition.bind(editor.actions), 100, false);
|
||||||
let syncActive = editor.settings.get('scrollSync');
|
let syncActive = editor.settings.get('scrollSync');
|
||||||
editor.settings.onChange('scrollSync', val => syncActive = val);
|
editor.settings.onChange('scrollSync', val => {
|
||||||
|
syncActive = val;
|
||||||
|
});
|
||||||
|
|
||||||
const domEventHandlers = {
|
const domEventHandlers = {
|
||||||
// Handle scroll to sync display view
|
// Handle scroll to sync display view
|
||||||
|
@ -21,7 +21,7 @@ export class Settings {
|
|||||||
|
|
||||||
listenToInputChanges(inputs) {
|
listenToInputChanges(inputs) {
|
||||||
for (const input of inputs) {
|
for (const input of inputs) {
|
||||||
input.addEventListener('change', event => {
|
input.addEventListener('change', () => {
|
||||||
const name = input.getAttribute('name').replace('md-', '');
|
const name = input.getAttribute('name').replace('md-', '');
|
||||||
this.set(name, input.checked);
|
this.set(name, input.checked);
|
||||||
});
|
});
|
||||||
|
@ -7,35 +7,35 @@ function provide(editor) {
|
|||||||
const shortcuts = {};
|
const shortcuts = {};
|
||||||
|
|
||||||
// Insert Image shortcut
|
// Insert Image shortcut
|
||||||
shortcuts['Shift-Mod-i'] = cm => editor.actions.insertImage();
|
shortcuts['Shift-Mod-i'] = () => editor.actions.insertImage();
|
||||||
|
|
||||||
// Save draft
|
// Save draft
|
||||||
shortcuts['Mod-s'] = cm => window.$events.emit('editor-save-draft');
|
shortcuts['Mod-s'] = () => window.$events.emit('editor-save-draft');
|
||||||
|
|
||||||
// Save page
|
// Save page
|
||||||
shortcuts['Mod-Enter'] = cm => window.$events.emit('editor-save-page');
|
shortcuts['Mod-Enter'] = () => window.$events.emit('editor-save-page');
|
||||||
|
|
||||||
// Show link selector
|
// Show link selector
|
||||||
shortcuts['Shift-Mod-k'] = cm => editor.actions.showLinkSelector();
|
shortcuts['Shift-Mod-k'] = () => editor.actions.showLinkSelector();
|
||||||
|
|
||||||
// Insert Link
|
// Insert Link
|
||||||
shortcuts['Mod-k'] = cm => editor.actions.insertLink();
|
shortcuts['Mod-k'] = () => editor.actions.insertLink();
|
||||||
|
|
||||||
// FormatShortcuts
|
// FormatShortcuts
|
||||||
shortcuts['Mod-1'] = cm => editor.actions.replaceLineStart('##');
|
shortcuts['Mod-1'] = () => editor.actions.replaceLineStart('##');
|
||||||
shortcuts['Mod-2'] = cm => editor.actions.replaceLineStart('###');
|
shortcuts['Mod-2'] = () => editor.actions.replaceLineStart('###');
|
||||||
shortcuts['Mod-3'] = cm => editor.actions.replaceLineStart('####');
|
shortcuts['Mod-3'] = () => editor.actions.replaceLineStart('####');
|
||||||
shortcuts['Mod-4'] = cm => editor.actions.replaceLineStart('#####');
|
shortcuts['Mod-4'] = () => editor.actions.replaceLineStart('#####');
|
||||||
shortcuts['Mod-5'] = cm => editor.actions.replaceLineStart('');
|
shortcuts['Mod-5'] = () => editor.actions.replaceLineStart('');
|
||||||
shortcuts['Mod-d'] = cm => editor.actions.replaceLineStart('');
|
shortcuts['Mod-d'] = () => editor.actions.replaceLineStart('');
|
||||||
shortcuts['Mod-6'] = cm => editor.actions.replaceLineStart('>');
|
shortcuts['Mod-6'] = () => editor.actions.replaceLineStart('>');
|
||||||
shortcuts['Mod-q'] = cm => editor.actions.replaceLineStart('>');
|
shortcuts['Mod-q'] = () => editor.actions.replaceLineStart('>');
|
||||||
shortcuts['Mod-7'] = cm => editor.actions.wrapSelection('\n```\n', '\n```');
|
shortcuts['Mod-7'] = () => editor.actions.wrapSelection('\n```\n', '\n```');
|
||||||
shortcuts['Mod-8'] = cm => editor.actions.wrapSelection('`', '`');
|
shortcuts['Mod-8'] = () => editor.actions.wrapSelection('`', '`');
|
||||||
shortcuts['Shift-Mod-e'] = cm => editor.actions.wrapSelection('`', '`');
|
shortcuts['Shift-Mod-e'] = () => editor.actions.wrapSelection('`', '`');
|
||||||
shortcuts['Mod-9'] = cm => editor.actions.cycleCalloutTypeAtSelection();
|
shortcuts['Mod-9'] = () => editor.actions.cycleCalloutTypeAtSelection();
|
||||||
shortcuts['Mod-p'] = cm => editor.actions.replaceLineStart('-');
|
shortcuts['Mod-p'] = () => editor.actions.replaceLineStart('-');
|
||||||
shortcuts['Mod-o'] = cm => editor.actions.replaceLineStartForOrderedList();
|
shortcuts['Mod-o'] = () => editor.actions.replaceLineStartForOrderedList();
|
||||||
|
|
||||||
return shortcuts;
|
return shortcuts;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,53 @@
|
|||||||
*/
|
*/
|
||||||
const animateStylesCleanupMap = new WeakMap();
|
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.
|
* Fade in the given element.
|
||||||
* @param {Element} element
|
* @param {Element} element
|
||||||
@ -113,50 +160,3 @@ export function transitionHeight(element, animTime = 400) {
|
|||||||
animateStyles(element, animStyles, animTime);
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -19,44 +19,6 @@ const componentModelMap = {};
|
|||||||
*/
|
*/
|
||||||
const elementComponentMap = new WeakMap();
|
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
|
* Parse out the element references within the given element
|
||||||
* for the given component name.
|
* for the given component name.
|
||||||
@ -93,13 +55,13 @@ function parseRefs(name, element) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse out the element component options.
|
* Parse out the element component options.
|
||||||
* @param {String} name
|
* @param {String} componentName
|
||||||
* @param {Element} element
|
* @param {Element} element
|
||||||
* @return {Object<String, String>}
|
* @return {Object<String, String>}
|
||||||
*/
|
*/
|
||||||
function parseOpts(name, element) {
|
function parseOpts(componentName, element) {
|
||||||
const opts = {};
|
const opts = {};
|
||||||
const prefix = `option:${name}:`;
|
const prefix = `option:${componentName}:`;
|
||||||
for (const {name, value} of element.attributes) {
|
for (const {name, value} of element.attributes) {
|
||||||
if (name.startsWith(prefix)) {
|
if (name.startsWith(prefix)) {
|
||||||
const optName = name.replace(prefix, '');
|
const optName = name.replace(prefix, '');
|
||||||
@ -109,6 +71,44 @@ function parseOpts(name, element) {
|
|||||||
return opts;
|
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.
|
* Initialize all components found within the given element.
|
||||||
* @param {Element|Document} parentElement
|
* @param {Element|Document} parentElement
|
||||||
|
@ -3,50 +3,8 @@ let lastApprovedOrigin;
|
|||||||
let onInit; let
|
let onInit; let
|
||||||
onSave;
|
onSave;
|
||||||
|
|
||||||
/**
|
function drawPostMessage(data) {
|
||||||
* Show the draw.io editor.
|
iFrame.contentWindow.postMessage(JSON.stringify(data), lastApprovedOrigin);
|
||||||
* @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 drawEventExport(message) {
|
function drawEventExport(message) {
|
||||||
@ -75,15 +33,54 @@ function drawEventConfigure() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function drawEventClose() {
|
function drawEventClose() {
|
||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
window.removeEventListener('message', drawReceive);
|
window.removeEventListener('message', drawReceive);
|
||||||
if (iFrame) document.body.removeChild(iFrame);
|
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 = {
|
const data = {
|
||||||
image: imageData,
|
image: imageData,
|
||||||
uploaded_to: pageUploadedToId,
|
uploaded_to: pageUploadedToId,
|
||||||
@ -92,12 +89,16 @@ async function upload(imageData, pageUploadedToId) {
|
|||||||
return resp.data;
|
return resp.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function close() {
|
||||||
|
drawEventClose();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load an existing image, by fetching it as Base64 from the system.
|
* Load an existing image, by fetching it as Base64 from the system.
|
||||||
* @param drawingId
|
* @param drawingId
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
*/
|
*/
|
||||||
async function load(drawingId) {
|
export async function load(drawingId) {
|
||||||
try {
|
try {
|
||||||
const resp = await window.$http.get(window.baseUrl(`/images/drawio/base64/${drawingId}`));
|
const resp = await window.$http.get(window.baseUrl(`/images/drawio/base64/${drawingId}`));
|
||||||
return `data:image/png;base64,${resp.data.content}`;
|
return `data:image/png;base64,${resp.data.content}`;
|
||||||
@ -109,7 +110,3 @@ async function load(drawingId) {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
|
||||||
show, close, upload, load,
|
|
||||||
};
|
|
||||||
|
@ -6,13 +6,12 @@ const stack = [];
|
|||||||
* @param {String} eventName
|
* @param {String} eventName
|
||||||
* @param {*} eventData
|
* @param {*} eventData
|
||||||
*/
|
*/
|
||||||
function emit(eventName, eventData) {
|
export function emit(eventName, eventData) {
|
||||||
stack.push({name: eventName, data: eventData});
|
stack.push({name: eventName, data: eventData});
|
||||||
if (typeof listeners[eventName] === 'undefined') return this;
|
|
||||||
const eventsToStart = listeners[eventName];
|
const listenersToRun = listeners[eventName] || [];
|
||||||
for (let i = 0; i < eventsToStart.length; i++) {
|
for (const listener of listenersToRun) {
|
||||||
const event = eventsToStart[i];
|
listener(eventData);
|
||||||
event(eventData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +21,7 @@ function emit(eventName, eventData) {
|
|||||||
* @param {Function} callback
|
* @param {Function} callback
|
||||||
* @returns {Events}
|
* @returns {Events}
|
||||||
*/
|
*/
|
||||||
function listen(eventName, callback) {
|
export function listen(eventName, callback) {
|
||||||
if (typeof listeners[eventName] === 'undefined') listeners[eventName] = [];
|
if (typeof listeners[eventName] === 'undefined') listeners[eventName] = [];
|
||||||
listeners[eventName].push(callback);
|
listeners[eventName].push(callback);
|
||||||
}
|
}
|
||||||
@ -34,7 +33,7 @@ function listen(eventName, callback) {
|
|||||||
* @param {String} eventName
|
* @param {String} eventName
|
||||||
* @param {Object} eventData
|
* @param {Object} eventData
|
||||||
*/
|
*/
|
||||||
function emitPublic(targetElement, eventName, eventData) {
|
export function emitPublic(targetElement, eventName, eventData) {
|
||||||
const event = new CustomEvent(eventName, {
|
const event = new CustomEvent(eventName, {
|
||||||
detail: eventData,
|
detail: eventData,
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
@ -43,34 +42,40 @@ function emitPublic(targetElement, eventName, eventData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify of standard server-provided validation errors.
|
* Emit a success event with the provided message.
|
||||||
* @param {Object} error
|
* @param {String} message
|
||||||
*/
|
*/
|
||||||
function showValidationErrors(error) {
|
export function success(message) {
|
||||||
if (!error.status) return;
|
emit('success', message);
|
||||||
if (error.status === 422 && error.data) {
|
}
|
||||||
const message = Object.values(error.data).flat().join('\n');
|
|
||||||
emit('error', 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.
|
* Notify standard server-provided error messages.
|
||||||
* @param {Object} error
|
* @param {Object} responseErr
|
||||||
*/
|
*/
|
||||||
function showResponseError(error) {
|
export function showResponseError(responseErr) {
|
||||||
if (!error.status) return;
|
if (!responseErr.status) return;
|
||||||
if (error.status >= 400 && error.data && error.data.message) {
|
if (responseErr.status >= 400 && responseErr.data && responseErr.data.message) {
|
||||||
emit('error', error.data.message);
|
error(responseErr.data.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
|
||||||
emit,
|
|
||||||
emitPublic,
|
|
||||||
listen,
|
|
||||||
success: msg => emit('success', msg),
|
|
||||||
error: msg => emit('error', msg),
|
|
||||||
showValidationErrors,
|
|
||||||
showResponseError,
|
|
||||||
};
|
|
||||||
|
@ -5,11 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
class Translator {
|
class Translator {
|
||||||
|
|
||||||
/**
|
constructor() {
|
||||||
* Create an instance, Passing in the required translations
|
|
||||||
* @param translations
|
|
||||||
*/
|
|
||||||
constructor(translations) {
|
|
||||||
this.store = new Map();
|
this.store = new Map();
|
||||||
this.parseTranslations();
|
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 key
|
||||||
* @param replacements
|
* @param replacements
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
@ -38,8 +34,8 @@ class Translator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get pluralised text, Dependant on the given count.
|
* Get pluralised text, Dependent on the given count.
|
||||||
* Same format at laravel's 'trans_choice' helper.
|
* Same format at Laravel's 'trans_choice' helper.
|
||||||
* @param key
|
* @param key
|
||||||
* @param count
|
* @param count
|
||||||
* @param replacements
|
* @param replacements
|
||||||
@ -52,7 +48,7 @@ class Translator {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the given translation and find the correct plural option
|
* 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 {String} translation
|
||||||
* @param {Number} count
|
* @param {Number} count
|
||||||
* @param {Object} replacements
|
* @param {Object} replacements
|
||||||
@ -117,14 +113,17 @@ class Translator {
|
|||||||
*/
|
*/
|
||||||
performReplacements(string, replacements) {
|
performReplacements(string, replacements) {
|
||||||
if (!replacements) return string;
|
if (!replacements) return string;
|
||||||
const replaceMatches = string.match(/:([\S]+)/g);
|
const replaceMatches = string.match(/:(\S+)/g);
|
||||||
if (replaceMatches === null) return string;
|
if (replaceMatches === null) return string;
|
||||||
|
let updatedString = string;
|
||||||
|
|
||||||
replaceMatches.forEach(match => {
|
replaceMatches.forEach(match => {
|
||||||
const key = match.substring(1);
|
const key = match.substring(1);
|
||||||
if (typeof replacements[key] === 'undefined') return;
|
if (typeof replacements[key] === 'undefined') return;
|
||||||
string = string.replace(match, replacements[key]);
|
updatedString = updatedString.replace(match, replacements[key]);
|
||||||
});
|
});
|
||||||
return string;
|
|
||||||
|
return updatedString;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import DrawIO from '../services/drawio';
|
import * as DrawIO from '../services/drawio';
|
||||||
|
|
||||||
let pageEditor = null;
|
let pageEditor = null;
|
||||||
let currentNode = null;
|
let currentNode = null;
|
||||||
|
Reference in New Issue
Block a user