import Delta from 'quill-delta'; import { ClassAttributor, Scope } from 'parchment'; import Inline from '../blots/inline.js'; import Quill from '../core/quill.js'; import Module from '../core/module.js'; import { blockDelta } from '../blots/block.js'; import BreakBlot from '../blots/break.js'; import CursorBlot from '../blots/cursor.js'; import TextBlot, { escapeText } from '../blots/text.js'; import CodeBlock, { CodeBlockContainer } from '../formats/code.js'; import { traverse } from './clipboard.js'; const TokenAttributor = new ClassAttributor('code-token', 'hljs', { scope: Scope.INLINE }); class CodeToken extends Inline { static formats(node, scroll) { while (node != null && node !== scroll.domNode) { if (node.classList && node.classList.contains(CodeBlock.className)) { // @ts-expect-error return super.formats(node, scroll); } // @ts-expect-error node = node.parentNode; } return undefined; } constructor(scroll, domNode, value) { // @ts-expect-error super(scroll, domNode, value); TokenAttributor.add(this.domNode, value); } format(format, value) { if (format !== CodeToken.blotName) { super.format(format, value); } else if (value) { TokenAttributor.add(this.domNode, value); } else { TokenAttributor.remove(this.domNode); this.domNode.classList.remove(this.statics.className); } } optimize() { // @ts-expect-error super.optimize(...arguments); if (!TokenAttributor.value(this.domNode)) { this.unwrap(); } } } CodeToken.blotName = 'code-token'; CodeToken.className = 'ql-token'; class SyntaxCodeBlock extends CodeBlock { static create(value) { const domNode = super.create(value); if (typeof value === 'string') { domNode.setAttribute('data-language', value); } return domNode; } static formats(domNode) { // @ts-expect-error return domNode.getAttribute('data-language') || 'plain'; } static register() {} // Syntax module will register format(name, value) { if (name === this.statics.blotName && value) { // @ts-expect-error this.domNode.setAttribute('data-language', value); } else { super.format(name, value); } } replaceWith(name, value) { this.formatAt(0, this.length(), CodeToken.blotName, false); return super.replaceWith(name, value); } } class SyntaxCodeBlockContainer extends CodeBlockContainer { attach() { super.attach(); this.forceNext = false; // @ts-expect-error this.scroll.emitMount(this); } format(name, value) { if (name === SyntaxCodeBlock.blotName) { this.forceNext = true; this.children.forEach(child => { // @ts-expect-error child.format(name, value); }); } } formatAt(index, length, name, value) { if (name === SyntaxCodeBlock.blotName) { this.forceNext = true; } super.formatAt(index, length, name, value); } highlight(highlight) { let forced = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (this.children.head == null) return; const nodes = Array.from(this.domNode.childNodes).filter(node => node !== this.uiNode); const text = `${nodes.map(node => node.textContent).join('\n')}\n`; const language = SyntaxCodeBlock.formats(this.children.head.domNode); if (forced || this.forceNext || this.cachedText !== text) { if (text.trim().length > 0 || this.cachedText == null) { const oldDelta = this.children.reduce((delta, child) => { // @ts-expect-error return delta.concat(blockDelta(child, false)); }, new Delta()); const delta = highlight(text, language); oldDelta.diff(delta).reduce((index, _ref) => { let { retain, attributes } = _ref; // Should be all retains if (!retain) return index; if (attributes) { Object.keys(attributes).forEach(format => { if ([SyntaxCodeBlock.blotName, CodeToken.blotName].includes(format)) { // @ts-expect-error this.formatAt(index, retain, format, attributes[format]); } }); } // @ts-expect-error return index + retain; }, 0); } this.cachedText = text; this.forceNext = false; } } html(index, length) { const [codeBlock] = this.children.find(index); const language = codeBlock ? SyntaxCodeBlock.formats(codeBlock.domNode) : 'plain'; return `
\n${escapeText(this.code(index, length))}\n`;
}
optimize(context) {
super.optimize(context);
if (this.parent != null && this.children.head != null && this.uiNode != null) {
const language = SyntaxCodeBlock.formats(this.children.head.domNode);
// @ts-expect-error
if (language !== this.uiNode.value) {
// @ts-expect-error
this.uiNode.value = language;
}
}
}
}
SyntaxCodeBlockContainer.allowedChildren = [SyntaxCodeBlock];
SyntaxCodeBlock.requiredContainer = SyntaxCodeBlockContainer;
SyntaxCodeBlock.allowedChildren = [CodeToken, CursorBlot, TextBlot, BreakBlot];
const highlight = (lib, language, text) => {
if (typeof lib.versionString === 'string') {
const majorVersion = lib.versionString.split('.')[0];
if (parseInt(majorVersion, 10) >= 11) {
return lib.highlight(text, {
language
}).value;
}
}
return lib.highlight(language, text).value;
};
class Syntax extends Module {
static register() {
Quill.register(CodeToken, true);
Quill.register(SyntaxCodeBlock, true);
Quill.register(SyntaxCodeBlockContainer, true);
}
constructor(quill, options) {
super(quill, options);
if (this.options.hljs == null) {
throw new Error('Syntax module requires highlight.js. Please include the library on the page before Quill.');
}
// @ts-expect-error Fix me later
this.languages = this.options.languages.reduce((memo, _ref2) => {
let {
key
} = _ref2;
memo[key] = true;
return memo;
}, {});
this.highlightBlot = this.highlightBlot.bind(this);
this.initListener();
this.initTimer();
}
initListener() {
this.quill.on(Quill.events.SCROLL_BLOT_MOUNT, blot => {
if (!(blot instanceof SyntaxCodeBlockContainer)) return;
const select = this.quill.root.ownerDocument.createElement('select');
// @ts-expect-error Fix me later
this.options.languages.forEach(_ref3 => {
let {
key,
label
} = _ref3;
const option = select.ownerDocument.createElement('option');
option.textContent = label;
option.setAttribute('value', key);
select.appendChild(option);
});
select.addEventListener('change', () => {
blot.format(SyntaxCodeBlock.blotName, select.value);
this.quill.root.focus(); // Prevent scrolling
this.highlight(blot, true);
});
if (blot.uiNode == null) {
blot.attachUI(select);
if (blot.children.head) {
select.value = SyntaxCodeBlock.formats(blot.children.head.domNode);
}
}
});
}
initTimer() {
let timer = null;
this.quill.on(Quill.events.SCROLL_OPTIMIZE, () => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
this.highlight();
timer = null;
}, this.options.interval);
});
}
highlight() {
let blot = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
let force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (this.quill.selection.composing) return;
this.quill.update(Quill.sources.USER);
const range = this.quill.getSelection();
const blots = blot == null ? this.quill.scroll.descendants(SyntaxCodeBlockContainer) : [blot];
blots.forEach(container => {
container.highlight(this.highlightBlot, force);
});
this.quill.update(Quill.sources.SILENT);
if (range != null) {
this.quill.setSelection(range, Quill.sources.SILENT);
}
}
highlightBlot(text) {
let language = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'plain';
language = this.languages[language] ? language : 'plain';
if (language === 'plain') {
return escapeText(text).split('\n').reduce((delta, line, i) => {
if (i !== 0) {
delta.insert('\n', {
[CodeBlock.blotName]: language
});
}
return delta.insert(line);
}, new Delta());
}
const container = this.quill.root.ownerDocument.createElement('div');
container.classList.add(CodeBlock.className);
container.innerHTML = highlight(this.options.hljs, language, text);
return traverse(this.quill.scroll, container, [(node, delta) => {
// @ts-expect-error
const value = TokenAttributor.value(node);
if (value) {
return delta.compose(new Delta().retain(delta.length(), {
[CodeToken.blotName]: value
}));
}
return delta;
}], [(node, delta) => {
// @ts-expect-error
return node.data.split('\n').reduce((memo, nodeText, i) => {
if (i !== 0) memo.insert('\n', {
[CodeBlock.blotName]: language
});
return memo.insert(nodeText);
}, delta);
}], new WeakMap());
}
}
Syntax.DEFAULTS = {
hljs: (() => {
return window.hljs;
})(),
interval: 1000,
languages: [{
key: 'plain',
label: 'Plain'
}, {
key: 'bash',
label: 'Bash'
}, {
key: 'cpp',
label: 'C++'
}, {
key: 'cs',
label: 'C#'
}, {
key: 'css',
label: 'CSS'
}, {
key: 'diff',
label: 'Diff'
}, {
key: 'xml',
label: 'HTML/XML'
}, {
key: 'java',
label: 'Java'
}, {
key: 'javascript',
label: 'JavaScript'
}, {
key: 'markdown',
label: 'Markdown'
}, {
key: 'php',
label: 'PHP'
}, {
key: 'python',
label: 'Python'
}, {
key: 'ruby',
label: 'Ruby'
}, {
key: 'sql',
label: 'SQL'
}]
};
export { SyntaxCodeBlock as CodeBlock, CodeToken, Syntax as default };
//# sourceMappingURL=syntax.js.map