mirror of
https://github.com/zadam/trilium.git
synced 2026-01-07 16:02:13 +01:00
Compare commits
59 Commits
feature/tr
...
renovate/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42f3968cc0 | ||
|
|
7f2cc885fe | ||
|
|
19a365a370 | ||
|
|
9a50da328e | ||
|
|
181e36a7c1 | ||
|
|
178508d245 | ||
|
|
d132d084cf | ||
|
|
458398f2ca | ||
|
|
7a6cc4f51e | ||
|
|
f4ccce7de5 | ||
|
|
f8b5417d6c | ||
|
|
87ab41c80c | ||
|
|
d2391f94c0 | ||
|
|
050ddb8c55 | ||
|
|
bc23e0984a | ||
|
|
07de353207 | ||
|
|
c02491d2e6 | ||
|
|
a6ede8f905 | ||
|
|
22941a9ce0 | ||
|
|
633a09d414 | ||
|
|
29f0881c5a | ||
|
|
60debca37b | ||
|
|
30ea81d0fb | ||
|
|
b1d92c4fe6 | ||
|
|
70f46de2d8 | ||
|
|
f1b2d0b870 | ||
|
|
8a385972fc | ||
|
|
28dd85c1d1 | ||
|
|
827c8e0e72 | ||
|
|
162c076a14 | ||
|
|
9386465de7 | ||
|
|
acca22f3a1 | ||
|
|
f8d84814e0 | ||
|
|
c46cf41842 | ||
|
|
64ab1c4116 | ||
|
|
a6de1041c7 | ||
|
|
c8d34e65ea | ||
|
|
51db729546 | ||
|
|
d2052ad236 | ||
|
|
9c4301467f | ||
|
|
e7355dc0e4 | ||
|
|
4110fec94f | ||
|
|
d5e601eae9 | ||
|
|
4f044c4a57 | ||
|
|
5821c350e1 | ||
|
|
edba8188fe | ||
|
|
1471a72633 | ||
|
|
56834cb88a | ||
|
|
a0f16f9184 | ||
|
|
de80eb4806 | ||
|
|
48a4b81fbe | ||
|
|
e225794f72 | ||
|
|
4eef30f8b5 | ||
|
|
569b09609d | ||
|
|
39838c25c2 | ||
|
|
49e90c08a9 | ||
|
|
e777b06fb8 | ||
|
|
497ec2ac74 | ||
|
|
c5d282d203 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -51,4 +51,4 @@ upload
|
||||
# docs
|
||||
site/
|
||||
apps/*/coverage
|
||||
scripts/translation/.language*.json
|
||||
scripts/translation/.language*.json
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "17.0.1",
|
||||
"mermaid": "11.12.2",
|
||||
"mind-elixir": "5.4.0",
|
||||
"mind-elixir": "5.5.0",
|
||||
"normalize.css": "8.0.1",
|
||||
"panzoom": "9.4.3",
|
||||
"preact": "10.28.1",
|
||||
|
||||
@@ -153,7 +153,7 @@ const TPL = /*html*/`
|
||||
const MAX_SEARCH_RESULTS_IN_TREE = 100;
|
||||
|
||||
// this has to be hanged on the actual elements to effectively intercept and stop click event
|
||||
const cancelClickPropagation: (e: JQuery.ClickEvent | MouseEvent) => void = (e) => e.stopPropagation();
|
||||
const cancelClickPropagation: (e: JQuery.ClickEvent) => void = (e) => e.stopPropagation();
|
||||
|
||||
// TODO: Fix once we remove Node.js API from public
|
||||
type Timeout = NodeJS.Timeout | string | number | undefined;
|
||||
@@ -353,7 +353,6 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
this.$tree.fancytree({
|
||||
titlesTabbable: true,
|
||||
keyboard: true,
|
||||
toggleEffect: false,
|
||||
extensions: ["dnd5", "clones", "filter"],
|
||||
source: treeData,
|
||||
scrollOfs: {
|
||||
@@ -599,7 +598,102 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
clones: {
|
||||
highlightActiveClones: true
|
||||
},
|
||||
enhanceTitle: buildEnhanceTitle(),
|
||||
async enhanceTitle (
|
||||
event: Event,
|
||||
data: {
|
||||
node: Fancytree.FancytreeNode;
|
||||
noteId: string;
|
||||
}
|
||||
) {
|
||||
const node = data.node;
|
||||
|
||||
if (!node.data.noteId) {
|
||||
// if there's "non-note" node, then don't enhance
|
||||
// this can happen for e.g. "Load error!" node
|
||||
return;
|
||||
}
|
||||
|
||||
const note = await froca.getNote(node.data.noteId, true);
|
||||
|
||||
if (!note) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeNoteContext = appContext.tabManager.getActiveContext();
|
||||
|
||||
const $span = $(node.span);
|
||||
|
||||
$span.find(".tree-item-button").remove();
|
||||
$span.find(".note-indicator-icon").remove();
|
||||
|
||||
const isHoistedNote = activeNoteContext && activeNoteContext.hoistedNoteId === note.noteId && note.noteId !== "root";
|
||||
|
||||
if (note.hasLabel("workspace") && !isHoistedNote) {
|
||||
const $enterWorkspaceButton = $(`<span class="tree-item-button tn-icon enter-workspace-button bx bx-door-open" title="${t("note_tree.hoist-this-note-workspace")}"></span>`).on(
|
||||
"click",
|
||||
cancelClickPropagation
|
||||
);
|
||||
|
||||
$span.append($enterWorkspaceButton);
|
||||
}
|
||||
|
||||
if (note.type === "search") {
|
||||
const $refreshSearchButton = $(`<span class="tree-item-button tn-icon refresh-search-button bx bx-refresh" title="${t("note_tree.refresh-saved-search-results")}"></span>`).on(
|
||||
"click",
|
||||
cancelClickPropagation
|
||||
);
|
||||
|
||||
$span.append($refreshSearchButton);
|
||||
}
|
||||
|
||||
// TODO: Deduplicate with server's notes.ts#getAndValidateParent
|
||||
if (!["search", "launcher"].includes(note.type)
|
||||
&& !note.isOptions()
|
||||
&& !note.isLaunchBarConfig()
|
||||
&& !note.noteId.startsWith("_help")
|
||||
) {
|
||||
const $createChildNoteButton = $(`<span class="tree-item-button tn-icon add-note-button bx bx-plus" title="${t("note_tree.create-child-note")}"></span>`).on(
|
||||
"click",
|
||||
cancelClickPropagation
|
||||
);
|
||||
|
||||
$span.append($createChildNoteButton);
|
||||
}
|
||||
|
||||
if (isHoistedNote) {
|
||||
const $unhoistButton = $(`<span class="tree-item-button tn-icon unhoist-button bx bx-door-open" title="${t("note_tree.unhoist")}"></span>`).on("click", cancelClickPropagation);
|
||||
|
||||
$span.append($unhoistButton);
|
||||
}
|
||||
|
||||
// Add clone indicator with tooltip if note has multiple parents
|
||||
const parentNotes = note.getParentNotes();
|
||||
const realParents = parentNotes.filter(
|
||||
(parent) => !["_share", "_lbBookmarks"].includes(parent.noteId) && parent.type !== "search"
|
||||
);
|
||||
|
||||
if (realParents.length > 1) {
|
||||
const parentTitles = realParents.map((p) => p.title).join(", ");
|
||||
const tooltipText = realParents.length === 2
|
||||
? t("note_tree.clone-indicator-tooltip-single", { parent: realParents[1].title })
|
||||
: t("note_tree.clone-indicator-tooltip", { count: realParents.length, parents: parentTitles });
|
||||
|
||||
const $cloneIndicator = $(`<span class="note-indicator-icon clone-indicator"></span>`);
|
||||
$cloneIndicator.attr("title", tooltipText);
|
||||
$span.find(".fancytree-title").append($cloneIndicator);
|
||||
}
|
||||
|
||||
// Add shared indicator with tooltip if note is shared
|
||||
if (note.isShared()) {
|
||||
const shareId = note.getOwnedLabelValue("shareAlias") || note.noteId;
|
||||
const shareUrl = `${location.origin}${location.pathname}share/${shareId}`;
|
||||
const tooltipText = t("note_tree.shared-indicator-tooltip-with-url", { url: shareUrl });
|
||||
|
||||
const $sharedIndicator = $(`<span class="note-indicator-icon shared-indicator"></span>`);
|
||||
$sharedIndicator.attr("title", tooltipText);
|
||||
$span.find(".fancytree-title").append($sharedIndicator);
|
||||
}
|
||||
},
|
||||
// this is done to automatically lazy load all expanded notes after tree load
|
||||
loadChildren: (event, data) => {
|
||||
data.node.visit((subNode) => {
|
||||
@@ -1788,100 +1882,3 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
function buildEnhanceTitle() {
|
||||
const createChildTemplate = document.createElement("span");
|
||||
createChildTemplate.className = "tree-item-button tn-icon add-note-button bx bx-plus";
|
||||
createChildTemplate.title = t("note_tree.create-child-note");
|
||||
createChildTemplate.addEventListener("click", cancelClickPropagation);
|
||||
|
||||
return async function enhanceTitle(event: Event,
|
||||
data: {
|
||||
node: Fancytree.FancytreeNode;
|
||||
noteId: string;
|
||||
}) {
|
||||
const node = data.node;
|
||||
|
||||
if (!node.data.noteId) {
|
||||
// if there's "non-note" node, then don't enhance
|
||||
// this can happen for e.g. "Load error!" node
|
||||
return;
|
||||
}
|
||||
|
||||
const note = froca.getNoteFromCache(node.data.noteId);
|
||||
if (!note) return;
|
||||
|
||||
const activeNoteContext = appContext.tabManager.getActiveContext();
|
||||
|
||||
const $span = $(node.span);
|
||||
|
||||
$span.find(".tree-item-button").remove();
|
||||
$span.find(".note-indicator-icon").remove();
|
||||
|
||||
const isHoistedNote = activeNoteContext && activeNoteContext.hoistedNoteId === note.noteId && note.noteId !== "root";
|
||||
|
||||
if (note.hasLabel("workspace") && !isHoistedNote) {
|
||||
const $enterWorkspaceButton = $(`<span class="tree-item-button tn-icon enter-workspace-button bx bx-door-open" title="${t("note_tree.hoist-this-note-workspace")}"></span>`).on(
|
||||
"click",
|
||||
cancelClickPropagation
|
||||
);
|
||||
|
||||
$span.append($enterWorkspaceButton);
|
||||
}
|
||||
|
||||
if (note.type === "search") {
|
||||
const $refreshSearchButton = $(`<span class="tree-item-button tn-icon refresh-search-button bx bx-refresh" title="${t("note_tree.refresh-saved-search-results")}"></span>`).on(
|
||||
"click",
|
||||
cancelClickPropagation
|
||||
);
|
||||
|
||||
$span.append($refreshSearchButton);
|
||||
}
|
||||
|
||||
// TODO: Deduplicate with server's notes.ts#getAndValidateParent
|
||||
if (!["search", "launcher"].includes(note.type)
|
||||
&& !note.isOptions()
|
||||
&& !note.isLaunchBarConfig()
|
||||
&& !note.noteId.startsWith("_help")
|
||||
) {
|
||||
node.span.append(createChildTemplate.cloneNode());
|
||||
}
|
||||
|
||||
if (isHoistedNote) {
|
||||
const $unhoistButton = $(`<span class="tree-item-button tn-icon unhoist-button bx bx-door-open" title="${t("note_tree.unhoist")}"></span>`).on("click", cancelClickPropagation);
|
||||
|
||||
$span.append($unhoistButton);
|
||||
}
|
||||
|
||||
// Add clone indicator with tooltip if note has multiple parents
|
||||
const parentNotes = note.getParentNotes();
|
||||
const realParents: FNote[] = [];
|
||||
for (const parent of parentNotes) {
|
||||
if (parent.noteId !== "_share" && parent.noteId !== "_lbBookmarks" && parent.type !== "search") {
|
||||
realParents.push(parent);
|
||||
}
|
||||
}
|
||||
|
||||
if (realParents.length > 1) {
|
||||
const parentTitles = realParents.map((p) => p.title).join(", ");
|
||||
const tooltipText = realParents.length === 2
|
||||
? t("note_tree.clone-indicator-tooltip-single", { parent: realParents[1].title })
|
||||
: t("note_tree.clone-indicator-tooltip", { count: realParents.length, parents: parentTitles });
|
||||
|
||||
const $cloneIndicator = $(`<span class="note-indicator-icon clone-indicator"></span>`);
|
||||
$cloneIndicator.attr("title", tooltipText);
|
||||
$span.find(".fancytree-title").append($cloneIndicator);
|
||||
}
|
||||
|
||||
// Add shared indicator with tooltip if note is shared
|
||||
if (note.isShared()) {
|
||||
const shareId = note.getOwnedLabelValue("shareAlias") || note.noteId;
|
||||
const shareUrl = `${location.origin}${location.pathname}share/${shareId}`;
|
||||
const tooltipText = t("note_tree.shared-indicator-tooltip-with-url", { url: shareUrl });
|
||||
|
||||
const $sharedIndicator = $(`<span class="note-indicator-icon shared-indicator"></span>`);
|
||||
$sharedIndicator.attr("title", tooltipText);
|
||||
$span.find(".fancytree-title").append($sharedIndicator);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function SqlResults() {
|
||||
{t("sql_result.no_rows")}
|
||||
</Alert>
|
||||
) : (
|
||||
<div class="sql-console-result-container">
|
||||
<div className="sql-console-result-container selectable-text">
|
||||
{results?.map(rows => {
|
||||
// inserts, updates
|
||||
if (typeof rows === "object" && !Array.isArray(rows)) {
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
"jiti": "2.6.1",
|
||||
"jsonc-eslint-parser": "2.4.2",
|
||||
"react-refresh": "0.18.0",
|
||||
"rollup-plugin-webpack-stats": "2.1.8",
|
||||
"rollup-plugin-webpack-stats": "2.1.9",
|
||||
"tslib": "2.8.1",
|
||||
"tsx": "4.21.0",
|
||||
"typescript": "~5.9.0",
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@ckeditor/ckeditor5-icons": "47.3.0"
|
||||
"@ckeditor/ckeditor5-icons": "47.3.0",
|
||||
"mathlive": "0.108.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import ckeditor from './../theme/icons/math.svg?raw';
|
||||
import './augmentation.js';
|
||||
import "../theme/mathform.css";
|
||||
import 'mathlive';
|
||||
import 'mathlive/fonts.css';
|
||||
import 'mathlive/static.css';
|
||||
|
||||
export { default as Math } from './math.js';
|
||||
export { default as MathUI } from './mathui.js';
|
||||
|
||||
@@ -55,9 +55,9 @@ export default class MathUI extends Plugin {
|
||||
|
||||
this._balloon.showStack( 'main' );
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
this.formView?.mathInputView.fieldView.element?.focus();
|
||||
});
|
||||
requestAnimationFrame( () => {
|
||||
this.formView?.mathInputView.focus();
|
||||
} );
|
||||
}
|
||||
|
||||
private _createFormView() {
|
||||
@@ -71,31 +71,37 @@ export default class MathUI extends Plugin {
|
||||
throw new CKEditorError( 'math-command' );
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const mathConfig = editor.config.get( 'math' )!;
|
||||
|
||||
const formView = new MainFormView(
|
||||
editor.locale,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
mathConfig.engine!,
|
||||
mathConfig.lazyLoad,
|
||||
{
|
||||
engine: mathConfig.engine!,
|
||||
lazyLoad: mathConfig.lazyLoad,
|
||||
previewUid: this._previewUid,
|
||||
previewClassName: mathConfig.previewClassName!,
|
||||
katexRenderOptions: mathConfig.katexRenderOptions!
|
||||
},
|
||||
mathConfig.enablePreview,
|
||||
this._previewUid,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
mathConfig.previewClassName!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
mathConfig.popupClassName!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
mathConfig.katexRenderOptions!
|
||||
mathConfig.popupClassName!
|
||||
);
|
||||
|
||||
formView.mathInputView.bind( 'value' ).to( mathCommand, 'value' );
|
||||
formView.displayButtonView.bind( 'isOn' ).to( mathCommand, 'display' );
|
||||
|
||||
// Form elements should be read-only when corresponding commands are disabled.
|
||||
formView.mathInputView.bind( 'isReadOnly' ).to( mathCommand, 'isEnabled', value => !value );
|
||||
formView.saveButtonView.bind( 'isEnabled' ).to( mathCommand );
|
||||
formView.displayButtonView.bind( 'isEnabled' ).to( mathCommand );
|
||||
formView.mathInputView.bind( 'isReadOnly' ).to( mathCommand, 'isEnabled', ( value: boolean ) => !value );
|
||||
formView.saveButtonView.bind( 'isEnabled' ).to(
|
||||
mathCommand,
|
||||
'isEnabled',
|
||||
formView.mathInputView,
|
||||
'value',
|
||||
( commandEnabled, equation ) => {
|
||||
const normalizedEquation = ( equation ?? '' ).trim();
|
||||
return commandEnabled && normalizedEquation.length > 0;
|
||||
}
|
||||
);
|
||||
formView.displayButtonView.bind( 'isEnabled' ).to( mathCommand, 'isEnabled' );
|
||||
|
||||
// Listen to submit button click
|
||||
this.listenTo( formView, 'submit', () => {
|
||||
@@ -115,24 +121,12 @@ export default class MathUI extends Plugin {
|
||||
} );
|
||||
|
||||
// Allow pressing Enter to submit changes, and use Shift+Enter to insert a new line
|
||||
formView.keystrokes.set('enter', (data, cancel) => {
|
||||
if (!data.shiftKey) {
|
||||
formView.fire('submit');
|
||||
formView.keystrokes.set( 'enter', ( data, cancel ) => {
|
||||
if ( !data.shiftKey ) {
|
||||
formView.fire( 'submit' );
|
||||
cancel();
|
||||
}
|
||||
});
|
||||
|
||||
// Allow the textarea to be resizable
|
||||
formView.mathInputView.fieldView.once('render', () => {
|
||||
const textarea = formView.mathInputView.fieldView.element;
|
||||
if (!textarea) return;
|
||||
Object.assign(textarea.style, {
|
||||
resize: 'both',
|
||||
height: '100px',
|
||||
width: '400px',
|
||||
minWidth: '100%',
|
||||
});
|
||||
});
|
||||
} );
|
||||
|
||||
return formView;
|
||||
}
|
||||
@@ -162,14 +156,12 @@ export default class MathUI extends Plugin {
|
||||
} );
|
||||
|
||||
if ( this._balloon.visibleView === this.formView ) {
|
||||
this.formView.mathInputView.fieldView.element?.select();
|
||||
this.formView.mathInputView.focus();
|
||||
}
|
||||
|
||||
// Show preview element
|
||||
const previewEl = document.getElementById( this._previewUid );
|
||||
if ( previewEl && this.formView.previewEnabled ) {
|
||||
// Force refresh preview
|
||||
this.formView.mathView?.updateMath();
|
||||
if ( previewEl && this.formView.mathView ) {
|
||||
this.formView.mathView.updateMath();
|
||||
}
|
||||
|
||||
this.formView.equation = mathCommand.value ?? '';
|
||||
@@ -206,8 +198,10 @@ export default class MathUI extends Plugin {
|
||||
|
||||
private _removeFormView() {
|
||||
if ( this._isFormInPanel && this.formView ) {
|
||||
this.formView.saveButtonView.focus();
|
||||
// Hide virtual keyboard before removing the form
|
||||
this.formView.hideKeyboard();
|
||||
|
||||
this.formView.saveButtonView.focus();
|
||||
this._balloon.remove( this.formView );
|
||||
|
||||
// Hide preview element
|
||||
|
||||
@@ -1,91 +1,59 @@
|
||||
import { ButtonView, createLabeledTextarea, FocusCycler, LabelView, LabeledFieldView, submitHandler, SwitchButtonView, View, ViewCollection, type TextareaView, type FocusableView, Locale, FocusTracker, KeystrokeHandler } from 'ckeditor5';
|
||||
import { ButtonView, FocusCycler, FocusTracker, KeystrokeHandler, LabelView, submitHandler, SwitchButtonView, View, ViewCollection, type FocusableView, type Locale } from 'ckeditor5';
|
||||
import IconCheck from "@ckeditor/ckeditor5-icons/theme/icons/check.svg?raw";
|
||||
import IconCancel from "@ckeditor/ckeditor5-icons/theme/icons/cancel.svg?raw";
|
||||
import { extractDelimiters, hasDelimiters } from '../utils.js';
|
||||
import MathView from './mathview.js';
|
||||
import MathView, { type MathViewOptions } from './mathview.js';
|
||||
import MathInputView from './mathinputview.js';
|
||||
import '../../theme/mathform.css';
|
||||
import type { KatexOptions } from '../typings-external.js';
|
||||
|
||||
class MathInputView extends LabeledFieldView<TextareaView> {
|
||||
public value: null | string = null;
|
||||
public isReadOnly = false;
|
||||
|
||||
constructor( locale: Locale ) {
|
||||
super( locale, createLabeledTextarea );
|
||||
}
|
||||
}
|
||||
|
||||
export default class MainFormView extends View {
|
||||
public saveButtonView: ButtonView;
|
||||
public mathInputView: MathInputView;
|
||||
public displayButtonView: SwitchButtonView;
|
||||
public cancelButtonView: ButtonView;
|
||||
public previewEnabled: boolean;
|
||||
public previewLabel?: LabelView;
|
||||
public displayButtonView: SwitchButtonView;
|
||||
|
||||
public mathInputView: MathInputView;
|
||||
public mathView?: MathView;
|
||||
public override locale: Locale = new Locale();
|
||||
public lazyLoad: undefined | ( () => Promise<void> );
|
||||
|
||||
public focusTracker = new FocusTracker();
|
||||
public keystrokes = new KeystrokeHandler();
|
||||
private _focusables = new ViewCollection<FocusableView>();
|
||||
private _focusCycler: FocusCycler;
|
||||
|
||||
constructor(
|
||||
locale: Locale,
|
||||
engine:
|
||||
| 'mathjax'
|
||||
| 'katex'
|
||||
| ( (
|
||||
equation: string,
|
||||
element: HTMLElement,
|
||||
display: boolean,
|
||||
) => void ),
|
||||
lazyLoad: undefined | ( () => Promise<void> ),
|
||||
mathViewOptions: MathViewOptions,
|
||||
previewEnabled = false,
|
||||
previewUid: string,
|
||||
previewClassName: Array<string>,
|
||||
popupClassName: Array<string>,
|
||||
katexRenderOptions: KatexOptions
|
||||
popupClassName: Array<string> = []
|
||||
) {
|
||||
super( locale );
|
||||
|
||||
const t = locale.t;
|
||||
|
||||
// Submit button
|
||||
this.saveButtonView = this._createButton( t( 'Save' ), IconCheck, 'ck-button-save', null );
|
||||
this.saveButtonView.type = 'submit';
|
||||
// Create views
|
||||
this.mathInputView = new MathInputView( locale );
|
||||
this.saveButtonView = this._createButton( t( 'Save' ), IconCheck, 'ck-button-save', 'submit' );
|
||||
this.cancelButtonView = this._createButton( t( 'Cancel' ), IconCancel, 'ck-button-cancel' );
|
||||
this.cancelButtonView.delegate( 'execute' ).to( this, 'cancel' );
|
||||
this.displayButtonView = this._createDisplayButton( t );
|
||||
|
||||
// Equation input
|
||||
this.mathInputView = this._createMathInput();
|
||||
// Build children
|
||||
|
||||
// Display button
|
||||
this.displayButtonView = this._createDisplayButton();
|
||||
const children: Array<View> = [
|
||||
this.mathInputView,
|
||||
this.displayButtonView
|
||||
];
|
||||
|
||||
// Cancel button
|
||||
this.cancelButtonView = this._createButton( t( 'Cancel' ), IconCancel, 'ck-button-cancel', 'cancel' );
|
||||
if ( previewEnabled ) {
|
||||
const previewLabel = new LabelView( locale );
|
||||
previewLabel.text = t( 'Equation preview' );
|
||||
|
||||
this.previewEnabled = previewEnabled;
|
||||
|
||||
let children = [];
|
||||
if ( this.previewEnabled ) {
|
||||
// Preview label
|
||||
this.previewLabel = new LabelView( locale );
|
||||
this.previewLabel.text = t( 'Equation preview' );
|
||||
|
||||
// Math element
|
||||
this.mathView = new MathView( engine, lazyLoad, locale, previewUid, previewClassName, katexRenderOptions );
|
||||
this.mathView = new MathView( locale, mathViewOptions );
|
||||
this.mathView.bind( 'display' ).to( this.displayButtonView, 'isOn' );
|
||||
|
||||
children = [
|
||||
this.mathInputView,
|
||||
this.displayButtonView,
|
||||
this.previewLabel,
|
||||
this.mathView
|
||||
];
|
||||
} else {
|
||||
children = [
|
||||
this.mathInputView,
|
||||
this.displayButtonView
|
||||
];
|
||||
children.push( previewLabel, this.mathView );
|
||||
}
|
||||
|
||||
// Add UI elements to template
|
||||
this._setupSync( previewEnabled );
|
||||
|
||||
this.setTemplate( {
|
||||
tag: 'form',
|
||||
attributes: {
|
||||
@@ -107,10 +75,30 @@ export default class MainFormView extends View {
|
||||
},
|
||||
children
|
||||
},
|
||||
this.saveButtonView,
|
||||
this.cancelButtonView
|
||||
{
|
||||
tag: 'div',
|
||||
attributes: {
|
||||
class: [
|
||||
'ck-math-button-row'
|
||||
]
|
||||
},
|
||||
children: [
|
||||
this.saveButtonView,
|
||||
this.cancelButtonView
|
||||
]
|
||||
}
|
||||
]
|
||||
} );
|
||||
|
||||
this._focusCycler = new FocusCycler( {
|
||||
focusables: this._focusables,
|
||||
focusTracker: this.focusTracker,
|
||||
keystrokeHandler: this.keystrokes,
|
||||
actions: {
|
||||
focusPrevious: 'shift + tab',
|
||||
focusNext: 'tab'
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
public override render(): void {
|
||||
@@ -121,103 +109,73 @@ export default class MainFormView extends View {
|
||||
view: this
|
||||
} );
|
||||
|
||||
// Register form elements to focusable elements
|
||||
const childViews = [
|
||||
this.mathInputView,
|
||||
const focusableViews = [
|
||||
this.mathInputView.latexTextAreaView,
|
||||
this.displayButtonView,
|
||||
this.saveButtonView,
|
||||
this.cancelButtonView
|
||||
];
|
||||
|
||||
childViews.forEach( v => {
|
||||
focusableViews.forEach( v => {
|
||||
this._focusables.add( v );
|
||||
if ( v.element ) {
|
||||
this._focusables.add( v );
|
||||
this.focusTracker.add( v.element );
|
||||
}
|
||||
} );
|
||||
|
||||
// Listen to keypresses inside form element
|
||||
this.mathInputView.on( 'mathfieldReady', () => {
|
||||
const mathfieldView = this.mathInputView.mathFieldFocusableView;
|
||||
if ( mathfieldView.element ) {
|
||||
if ( this._focusables.has( mathfieldView ) ) {
|
||||
this._focusables.remove( mathfieldView );
|
||||
}
|
||||
this._focusables.add( mathfieldView, 0 );
|
||||
this.focusTracker.add( mathfieldView.element );
|
||||
}
|
||||
} );
|
||||
|
||||
if ( this.element ) {
|
||||
this.keystrokes.listenTo( this.element );
|
||||
}
|
||||
}
|
||||
|
||||
public get equation(): string {
|
||||
return this.mathInputView.value ?? '';
|
||||
}
|
||||
|
||||
public set equation( equation: string ) {
|
||||
const norm = equation.trim();
|
||||
this.mathInputView.value = norm.length ? norm : null;
|
||||
if ( this.mathView ) {
|
||||
this.mathView.value = norm;
|
||||
}
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this._focusCycler.focusFirst();
|
||||
}
|
||||
|
||||
public get equation(): string {
|
||||
return this.mathInputView.fieldView.element?.value ?? '';
|
||||
}
|
||||
private _setupSync( previewEnabled: boolean ): void {
|
||||
this.mathInputView.on( 'change:value', () => {
|
||||
let eq = ( this.mathInputView.value ?? '' ).trim();
|
||||
|
||||
public set equation( equation: string ) {
|
||||
if ( this.mathInputView.fieldView.element ) {
|
||||
this.mathInputView.fieldView.element.value = equation;
|
||||
}
|
||||
if ( this.previewEnabled && this.mathView ) {
|
||||
this.mathView.value = equation;
|
||||
}
|
||||
}
|
||||
if ( hasDelimiters( eq ) ) {
|
||||
const params = extractDelimiters( eq );
|
||||
eq = params.equation;
|
||||
this.displayButtonView.isOn = params.display;
|
||||
|
||||
public focusTracker: FocusTracker = new FocusTracker();
|
||||
public keystrokes: KeystrokeHandler = new KeystrokeHandler();
|
||||
private _focusables = new ViewCollection<FocusableView>();
|
||||
private _focusCycler: FocusCycler = new FocusCycler( {
|
||||
focusables: this._focusables,
|
||||
focusTracker: this.focusTracker,
|
||||
keystrokeHandler: this.keystrokes,
|
||||
actions: {
|
||||
focusPrevious: 'shift + tab',
|
||||
focusNext: 'tab'
|
||||
}
|
||||
} );
|
||||
|
||||
private _createMathInput() {
|
||||
const t = this.locale.t;
|
||||
|
||||
// Create equation input
|
||||
const mathInput = new MathInputView( this.locale );
|
||||
const fieldView = mathInput.fieldView;
|
||||
mathInput.infoText = t( 'Insert equation in TeX format.' );
|
||||
|
||||
const onInput = () => {
|
||||
if ( fieldView.element != null ) {
|
||||
let equationInput = fieldView.element.value.trim();
|
||||
|
||||
// If input has delimiters
|
||||
if ( hasDelimiters( equationInput ) ) {
|
||||
// Get equation without delimiters
|
||||
const params = extractDelimiters( equationInput );
|
||||
|
||||
// Remove delimiters from input field
|
||||
fieldView.element.value = params.equation;
|
||||
|
||||
equationInput = params.equation;
|
||||
|
||||
// update display button and preview
|
||||
this.displayButtonView.isOn = params.display;
|
||||
if ( this.mathInputView.value !== eq ) {
|
||||
this.mathInputView.value = eq.length ? eq : null;
|
||||
}
|
||||
if ( this.previewEnabled && this.mathView ) {
|
||||
// Update preview view
|
||||
this.mathView.value = equationInput;
|
||||
}
|
||||
|
||||
this.saveButtonView.isEnabled = !!equationInput;
|
||||
}
|
||||
};
|
||||
|
||||
fieldView.on( 'render', onInput );
|
||||
fieldView.on( 'input', onInput );
|
||||
|
||||
return mathInput;
|
||||
if ( previewEnabled && this.mathView && this.mathView.value !== eq ) {
|
||||
this.mathView.value = eq;
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
private _createButton(
|
||||
label: string,
|
||||
icon: string,
|
||||
className: string,
|
||||
eventName: string | null
|
||||
) {
|
||||
private _createButton( label: string, icon: string, className: string, type?: 'submit' | 'button' ): ButtonView {
|
||||
const button = new ButtonView( this.locale );
|
||||
|
||||
button.set( {
|
||||
@@ -232,16 +190,14 @@ export default class MainFormView extends View {
|
||||
}
|
||||
} );
|
||||
|
||||
if ( eventName ) {
|
||||
button.delegate( 'execute' ).to( this, eventName );
|
||||
if ( type ) {
|
||||
button.type = type;
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private _createDisplayButton() {
|
||||
const t = this.locale.t;
|
||||
|
||||
private _createDisplayButton( t: ( str: string ) => string ): SwitchButtonView {
|
||||
const switchButton = new SwitchButtonView( this.locale );
|
||||
|
||||
switchButton.set( {
|
||||
@@ -256,15 +212,13 @@ export default class MainFormView extends View {
|
||||
} );
|
||||
|
||||
switchButton.on( 'execute', () => {
|
||||
// Toggle state
|
||||
switchButton.isOn = !switchButton.isOn;
|
||||
|
||||
if ( this.previewEnabled && this.mathView ) {
|
||||
// Update preview view
|
||||
this.mathView.display = switchButton.isOn;
|
||||
}
|
||||
} );
|
||||
|
||||
return switchButton;
|
||||
}
|
||||
|
||||
public hideKeyboard(): void {
|
||||
this.mathInputView.hideKeyboard();
|
||||
}
|
||||
}
|
||||
|
||||
268
packages/ckeditor5-math/src/ui/mathinputview.ts
Normal file
268
packages/ckeditor5-math/src/ui/mathinputview.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
// Math input widget: wraps a MathLive <math-field> and a LaTeX textarea
|
||||
// and keeps them in sync for the CKEditor 5 math dialog.
|
||||
import { View, type Locale, type FocusableView } from 'ckeditor5';
|
||||
import 'mathlive/fonts.css'; // Auto-bundles offline fonts
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
mathVirtualKeyboard?: {
|
||||
visible: boolean;
|
||||
show: () => void;
|
||||
hide: () => void;
|
||||
addEventListener: ( event: string, cb: () => void ) => void;
|
||||
removeEventListener: ( event: string, cb: () => void ) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface MathFieldElement extends HTMLElement {
|
||||
value: string;
|
||||
readOnly: boolean;
|
||||
mathVirtualKeyboardPolicy: string;
|
||||
inlineShortcuts?: Record<string, string>;
|
||||
setValue?: ( value: string, options?: { silenceNotifications?: boolean } ) => void;
|
||||
}
|
||||
|
||||
// Wrapper for the MathLive element to make it focusable in CKEditor's UI system
|
||||
export class MathFieldFocusableView extends View implements FocusableView {
|
||||
public declare element: HTMLElement | null;
|
||||
private _view: MathInputView;
|
||||
constructor( locale: Locale, view: MathInputView ) {
|
||||
super( locale );
|
||||
this._view = view;
|
||||
}
|
||||
public focus(): void {
|
||||
this._view.mathfield?.focus();
|
||||
}
|
||||
public setElement( el: HTMLElement ): void {
|
||||
this.element = el;
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper for the LaTeX textarea to make it focusable in CKEditor's UI system
|
||||
export class LatexTextAreaView extends View implements FocusableView {
|
||||
declare public element: HTMLTextAreaElement;
|
||||
constructor( locale: Locale ) {
|
||||
super( locale );
|
||||
this.setTemplate( { tag: 'textarea', attributes: {
|
||||
class: [ 'ck', 'ck-textarea', 'ck-latex-textarea' ], spellcheck: 'false', tabindex: 0
|
||||
} } );
|
||||
}
|
||||
public focus(): void {
|
||||
this.element?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Main view class for the math input
|
||||
export default class MathInputView extends View {
|
||||
public declare value: string | null;
|
||||
public declare isReadOnly: boolean;
|
||||
public mathfield: MathFieldElement | null = null;
|
||||
public readonly latexTextAreaView: LatexTextAreaView;
|
||||
public readonly mathFieldFocusableView: MathFieldFocusableView;
|
||||
private _destroyed = false;
|
||||
private _vkGeometryHandler?: () => void;
|
||||
private _updating = false;
|
||||
private static _configured = false;
|
||||
|
||||
constructor( locale: Locale ) {
|
||||
super( locale );
|
||||
this.latexTextAreaView = new LatexTextAreaView( locale );
|
||||
this.mathFieldFocusableView = new MathFieldFocusableView( locale, this );
|
||||
this.set( 'value', null );
|
||||
this.set( 'isReadOnly', false );
|
||||
this.setTemplate( {
|
||||
tag: 'div', attributes: { class: [ 'ck', 'ck-math-input' ] },
|
||||
children: [
|
||||
{ tag: 'div', attributes: { class: [ 'ck-mathlive-container' ] } },
|
||||
{ tag: 'label', attributes: { class: [ 'ck-latex-label' ] }, children: [ locale.t( 'LaTeX' ) ] },
|
||||
{ tag: 'div', attributes: { class: [ 'ck-latex-wrapper' ] }, children: [ this.latexTextAreaView ] }
|
||||
]
|
||||
} );
|
||||
}
|
||||
|
||||
public override render(): void {
|
||||
super.render();
|
||||
const textarea = this.latexTextAreaView.element;
|
||||
|
||||
// Sync changes from the LaTeX textarea to the mathfield and model
|
||||
this.listenTo( textarea, 'input', () => {
|
||||
if ( this._updating ) {
|
||||
return;
|
||||
}
|
||||
this._updating = true;
|
||||
const val = textarea.value;
|
||||
this.value = val || null;
|
||||
if ( this.mathfield ) {
|
||||
if ( val === '' ) {
|
||||
this.mathfield.remove();
|
||||
this.mathfield = null;
|
||||
this._initMathField( false );
|
||||
} else if ( this.mathfield.value.trim() !== val.trim() ) {
|
||||
this._setMathfieldValue( val );
|
||||
}
|
||||
}
|
||||
this._updating = false;
|
||||
} );
|
||||
|
||||
// Sync changes from the model (this.value) to the UI elements
|
||||
this.on( 'change:value', ( _e, _n, val ) => {
|
||||
if ( this._updating ) {
|
||||
return;
|
||||
}
|
||||
this._updating = true;
|
||||
const newVal = val ?? '';
|
||||
if ( textarea.value !== newVal ) {
|
||||
textarea.value = newVal;
|
||||
}
|
||||
if ( this.mathfield ) {
|
||||
if ( this.mathfield.value.trim() !== newVal.trim() ) {
|
||||
this._setMathfieldValue( newVal );
|
||||
}
|
||||
} else if ( newVal !== '' ) {
|
||||
this._initMathField( false );
|
||||
}
|
||||
this._updating = false;
|
||||
} );
|
||||
|
||||
// Handle read-only state changes
|
||||
this.on( 'change:isReadOnly', ( _e, _n, val ) => {
|
||||
textarea.readOnly = val;
|
||||
if ( this.mathfield ) {
|
||||
this.mathfield.readOnly = val;
|
||||
}
|
||||
} );
|
||||
|
||||
// Handle virtual keyboard geometry changes
|
||||
const vk = window.mathVirtualKeyboard;
|
||||
if ( vk && !this._vkGeometryHandler ) {
|
||||
this._vkGeometryHandler = () => {
|
||||
if ( vk.visible && this.mathfield ) {
|
||||
this.mathfield.focus();
|
||||
}
|
||||
};
|
||||
vk.addEventListener( 'geometrychange', this._vkGeometryHandler );
|
||||
}
|
||||
|
||||
const initial = this.value ?? '';
|
||||
if ( textarea.value !== initial ) {
|
||||
textarea.value = initial;
|
||||
}
|
||||
this._loadMathLive();
|
||||
}
|
||||
|
||||
// Loads the MathLive library dynamically
|
||||
private async _loadMathLive(): Promise<void> {
|
||||
try {
|
||||
await import( 'mathlive' );
|
||||
await customElements.whenDefined( 'math-field' );
|
||||
if ( this._destroyed ) {
|
||||
return;
|
||||
}
|
||||
if ( !MathInputView._configured ) {
|
||||
const MathfieldClass = customElements.get( 'math-field' ) as any;
|
||||
if ( MathfieldClass ) {
|
||||
MathfieldClass.soundsDirectory = null;
|
||||
MathfieldClass.plonkSound = null;
|
||||
MathInputView._configured = true;
|
||||
}
|
||||
}
|
||||
if ( this.element && !this._destroyed ) {
|
||||
this._initMathField( true );
|
||||
}
|
||||
} catch {
|
||||
const c = this.element?.querySelector( '.ck-mathlive-container' );
|
||||
if ( c ) {
|
||||
c.textContent = 'Math editor unavailable';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes the <math-field> element
|
||||
private _initMathField( shouldFocus: boolean ): void {
|
||||
const container = this.element?.querySelector( '.ck-mathlive-container' );
|
||||
if ( !container ) {
|
||||
return;
|
||||
}
|
||||
if ( this.mathfield ) {
|
||||
this._setMathfieldValue( this.value ?? '' );
|
||||
return;
|
||||
}
|
||||
const mf = document.createElement( 'math-field' ) as MathFieldElement;
|
||||
mf.mathVirtualKeyboardPolicy = 'auto';
|
||||
mf.setAttribute( 'tabindex', '0' );
|
||||
mf.value = this.value ?? '';
|
||||
mf.readOnly = this.isReadOnly;
|
||||
container.appendChild( mf );
|
||||
// Set shortcuts after mounting (accessing inlineShortcuts requires mounted element)
|
||||
try {
|
||||
if ( mf.inlineShortcuts ) {
|
||||
mf.inlineShortcuts = { ...mf.inlineShortcuts, dx: 'dx', dy: 'dy', dt: 'dt' };
|
||||
}
|
||||
} catch {
|
||||
// Inline shortcut configuration is optional; ignore failures to avoid breaking the math field.
|
||||
}
|
||||
mf.addEventListener( 'keydown', ev => {
|
||||
if ( ev.key === 'Tab' ) {
|
||||
if ( ev.shiftKey ) {
|
||||
ev.preventDefault();
|
||||
} else {
|
||||
ev.preventDefault();
|
||||
ev.stopImmediatePropagation();
|
||||
this.latexTextAreaView.focus();
|
||||
}
|
||||
}
|
||||
}, { capture: true } );
|
||||
mf.addEventListener( 'input', () => {
|
||||
if ( this._updating ) {
|
||||
return;
|
||||
}
|
||||
this._updating = true;
|
||||
const textarea = this.latexTextAreaView.element;
|
||||
if ( textarea.value.trim() !== mf.value.trim() ) {
|
||||
textarea.value = mf.value;
|
||||
}
|
||||
this.value = mf.value || null;
|
||||
this._updating = false;
|
||||
} );
|
||||
this.mathfield = mf;
|
||||
this.mathFieldFocusableView.setElement( mf );
|
||||
this.fire( 'mathfieldReady' );
|
||||
if ( shouldFocus ) {
|
||||
requestAnimationFrame( () => mf.focus() );
|
||||
}
|
||||
}
|
||||
|
||||
// Updates the mathfield value without triggering loops
|
||||
private _setMathfieldValue( value: string ): void {
|
||||
if ( !this.mathfield ) {
|
||||
return;
|
||||
}
|
||||
if ( this.mathfield.setValue ) {
|
||||
this.mathfield.setValue( value, { silenceNotifications: true } );
|
||||
} else {
|
||||
this.mathfield.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public hideKeyboard(): void {
|
||||
window.mathVirtualKeyboard?.hide();
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.mathfield?.focus();
|
||||
}
|
||||
|
||||
public override destroy(): void {
|
||||
this._destroyed = true;
|
||||
const vk = window.mathVirtualKeyboard;
|
||||
if ( vk && this._vkGeometryHandler ) {
|
||||
vk.removeEventListener( 'geometrychange', this._vkGeometryHandler );
|
||||
this._vkGeometryHandler = undefined;
|
||||
}
|
||||
this.hideKeyboard();
|
||||
this.mathfield?.remove();
|
||||
this.mathfield = null;
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
@@ -2,44 +2,44 @@ import { View, type Locale } from 'ckeditor5';
|
||||
import type { KatexOptions } from '../typings-external.js';
|
||||
import { renderEquation } from '../utils.js';
|
||||
|
||||
/**
|
||||
* Configuration options for the MathView.
|
||||
*/
|
||||
export interface MathViewOptions {
|
||||
engine: 'mathjax' | 'katex' | ( ( equation: string, element: HTMLElement, display: boolean ) => void );
|
||||
lazyLoad: undefined | ( () => Promise<void> );
|
||||
previewUid: string;
|
||||
previewClassName: Array<string>;
|
||||
katexRenderOptions: KatexOptions;
|
||||
}
|
||||
|
||||
export default class MathView extends View {
|
||||
/**
|
||||
* The LaTeX equation value to render.
|
||||
* @observable
|
||||
*/
|
||||
public declare value: string;
|
||||
|
||||
/**
|
||||
* Whether to render in display mode (centered) or inline.
|
||||
* @observable
|
||||
*/
|
||||
public declare display: boolean;
|
||||
public previewUid: string;
|
||||
public previewClassName: Array<string>;
|
||||
public katexRenderOptions: KatexOptions;
|
||||
public engine:
|
||||
| 'mathjax'
|
||||
| 'katex'
|
||||
| ( ( equation: string, element: HTMLElement, display: boolean ) => void );
|
||||
public lazyLoad: undefined | ( () => Promise<void> );
|
||||
|
||||
constructor(
|
||||
engine:
|
||||
| 'mathjax'
|
||||
| 'katex'
|
||||
| ( (
|
||||
equation: string,
|
||||
element: HTMLElement,
|
||||
display: boolean,
|
||||
) => void ),
|
||||
lazyLoad: undefined | ( () => Promise<void> ),
|
||||
locale: Locale,
|
||||
previewUid: string,
|
||||
previewClassName: Array<string>,
|
||||
katexRenderOptions: KatexOptions
|
||||
) {
|
||||
/**
|
||||
* Configuration options passed during initialization.
|
||||
*/
|
||||
private options: MathViewOptions;
|
||||
|
||||
constructor( locale: Locale, options: MathViewOptions ) {
|
||||
super( locale );
|
||||
|
||||
this.engine = engine;
|
||||
this.lazyLoad = lazyLoad;
|
||||
this.previewUid = previewUid;
|
||||
this.katexRenderOptions = katexRenderOptions;
|
||||
this.previewClassName = previewClassName;
|
||||
this.options = options;
|
||||
|
||||
this.set( 'value', '' );
|
||||
this.set( 'display', false );
|
||||
|
||||
// Update rendering when state changes.
|
||||
// Checking isRendered prevents errors during initialization.
|
||||
this.on( 'change', () => {
|
||||
if ( this.isRendered ) {
|
||||
this.updateMath();
|
||||
@@ -55,19 +55,39 @@ export default class MathView extends View {
|
||||
}
|
||||
|
||||
public updateMath(): void {
|
||||
if ( this.element ) {
|
||||
void renderEquation(
|
||||
this.value,
|
||||
this.element,
|
||||
this.engine,
|
||||
this.lazyLoad,
|
||||
this.display,
|
||||
true,
|
||||
this.previewUid,
|
||||
this.previewClassName,
|
||||
this.katexRenderOptions
|
||||
);
|
||||
if ( !this.element ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle empty equations
|
||||
if ( !this.value || !this.value.trim() ) {
|
||||
this.element.textContent = '';
|
||||
this.element.classList.remove( 'ck-math-render-error' );
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear previous render
|
||||
this.element.textContent = '';
|
||||
this.element.classList.remove( 'ck-math-render-error' );
|
||||
|
||||
renderEquation(
|
||||
this.value,
|
||||
this.element,
|
||||
this.options.engine,
|
||||
this.options.lazyLoad,
|
||||
this.display,
|
||||
true, // isPreview
|
||||
this.options.previewUid,
|
||||
this.options.previewClassName,
|
||||
this.options.katexRenderOptions
|
||||
).catch( error => {
|
||||
console.error( 'Math rendering failed:', error );
|
||||
|
||||
if ( this.element ) {
|
||||
this.element.textContent = 'Error rendering equation';
|
||||
this.element.classList.add( 'ck-math-render-error' );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
public override render(): void {
|
||||
|
||||
@@ -3,6 +3,20 @@ import Math from '../src/math';
|
||||
import AutoformatMath from '../src/autoformatmath';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
// Suppress MathLive errors during async cleanup in tests
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('unhandledrejection', event => {
|
||||
if (event.reason?.message?.includes('options') || event.reason?.message?.includes('mathlive')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
window.addEventListener('error', event => {
|
||||
if (event.message?.includes('options') || event.message?.includes('mathlive')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe( 'CKEditor5 Math DLL', () => {
|
||||
it( 'exports Math', () => {
|
||||
expect( MathDll ).to.equal( Math );
|
||||
|
||||
@@ -2,6 +2,20 @@ import { ClassicEditor, type EditorConfig } from 'ckeditor5';
|
||||
import MathUI from '../src/mathui';
|
||||
import { describe, beforeEach, it, afterEach, expect } from "vitest";
|
||||
|
||||
// Suppress MathLive errors during async cleanup
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('unhandledrejection', event => {
|
||||
if (event.reason?.message?.includes('options') || event.reason?.message?.includes('mathlive')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
window.addEventListener('error', event => {
|
||||
if (event.message?.includes('options') || event.message?.includes('mathlive')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe( 'Lazy load', () => {
|
||||
let editorElement: HTMLDivElement;
|
||||
let editor: ClassicEditor;
|
||||
@@ -24,11 +38,14 @@ describe( 'Lazy load', () => {
|
||||
beforeEach( () => {
|
||||
editorElement = document.createElement( 'div' );
|
||||
document.body.appendChild( editorElement );
|
||||
|
||||
lazyLoadInvoked = false;
|
||||
} );
|
||||
|
||||
afterEach( () => {
|
||||
afterEach( async () => {
|
||||
if ( mathUIFeature?.formView ) {
|
||||
mathUIFeature._hideUI();
|
||||
}
|
||||
await new Promise( resolve => setTimeout( resolve, 50 ) );
|
||||
editorElement.remove();
|
||||
return editor.destroy();
|
||||
} );
|
||||
@@ -37,6 +54,7 @@ describe( 'Lazy load', () => {
|
||||
await buildEditor( {
|
||||
math: {
|
||||
engine: 'katex',
|
||||
enablePreview: true,
|
||||
lazyLoad: async () => {
|
||||
lazyLoadInvoked = true;
|
||||
}
|
||||
@@ -44,6 +62,15 @@ describe( 'Lazy load', () => {
|
||||
} );
|
||||
|
||||
mathUIFeature._showUI();
|
||||
|
||||
// Trigger render with a non-empty value to bypass empty check optimization
|
||||
if ( mathUIFeature.formView ) {
|
||||
mathUIFeature.formView.equation = 'x^2';
|
||||
}
|
||||
|
||||
// Wait for async rendering and lazy loading
|
||||
await new Promise( resolve => setTimeout( resolve, 100 ) );
|
||||
|
||||
expect( lazyLoadInvoked ).to.be.true;
|
||||
} );
|
||||
} );
|
||||
|
||||
@@ -410,7 +410,7 @@ describe( 'MathUI', () => {
|
||||
it( 'should bind mainFormView.mathInputView#value to math command value', () => {
|
||||
const command = editor.commands.get( 'math' );
|
||||
|
||||
expect( formView!.mathInputView.value ).to.null;
|
||||
expect( formView!.mathInputView.value ).to.be.null;
|
||||
|
||||
command!.value = 'x^2';
|
||||
expect( formView!.mathInputView.value ).to.equal( 'x^2' );
|
||||
@@ -419,10 +419,18 @@ describe( 'MathUI', () => {
|
||||
it( 'should execute math command on mainFormView#submit event', () => {
|
||||
const executeSpy = vi.spyOn( editor, 'execute' );
|
||||
|
||||
formView!.mathInputView.fieldView.element!.value = 'x^2';
|
||||
formView!.mathInputView.value = 'x^2';
|
||||
formView!.fire( 'submit' );
|
||||
|
||||
expect(executeSpy.mock.lastCall?.slice(0, 2)).toMatchObject(['math', 'x^2']);
|
||||
expect( executeSpy.mock.lastCall?.slice( 0, 2 ) ).toMatchObject( [ 'math', 'x^2' ] );
|
||||
} );
|
||||
|
||||
it( 'should update equation value when mathInputView changes', () => {
|
||||
formView!.mathInputView.value = 'x^2';
|
||||
expect( formView!.equation ).to.equal( 'x^2' );
|
||||
|
||||
formView!.mathInputView.value = '\\frac{1}{2}';
|
||||
expect( formView!.equation ).to.equal( '\\frac{1}{2}' );
|
||||
} );
|
||||
|
||||
it( 'should hide the balloon on mainFormView#cancel if math command does not have a value', () => {
|
||||
|
||||
@@ -1,35 +1,220 @@
|
||||
/**
|
||||
* Math Equation Editor Dialog Styles - Compact & Readable
|
||||
*/
|
||||
|
||||
/* === Z-INDEX: MathLive UI above CKEditor === */
|
||||
.ML__keyboard, .ML__popover, .ML__menu, .ML__suggestions, .ML__autocomplete,
|
||||
.ML__tooltip, .ML__sr-only, [data-ml-root], #mathlive-suggestion-popover,
|
||||
.mathlive-suggestions-popover, [data-ml-tooltip], .ML__base {
|
||||
z-index: calc(var(--ck-z-panel) + 1000) !important;
|
||||
}
|
||||
.ML__tooltip, [role="tooltip"], .ML__popover[role="tooltip"], .popover, [data-ml-tooltip] {
|
||||
z-index: calc(var(--ck-z-panel) + 2000) !important;
|
||||
position: fixed !important;
|
||||
}
|
||||
.ck.ck-balloon-panel, .ck.ck-balloon-panel .ck-balloon-panel__content {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* === MAIN DIALOG === */
|
||||
.ck.ck-math-form {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
padding: var(--ck-spacing-standard);
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
flex-wrap: wrap;
|
||||
|
||||
& .ck-math-view {
|
||||
flex-basis: 100%;
|
||||
|
||||
& .ck-labeled-view {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
& .ck-label {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
& .ck-button {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--ck-spacing-standard);
|
||||
box-sizing: border-box;
|
||||
max-width: 80vw;
|
||||
max-height: 80vh;
|
||||
overflow: visible;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.ck-math-tex.ck-placeholder::before {
|
||||
display: none !important;
|
||||
/* Scrollable content - vertical scroll, horizontal visible for tooltips */
|
||||
.ck-math-view {
|
||||
overflow-y: auto;
|
||||
overflow-x: visible;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
gap: var(--ck-spacing-standard);
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ck.ck-toolbar-container {
|
||||
z-index: calc(var(--ck-z-panel) + 2);
|
||||
/* === MATH INPUT === */
|
||||
.ck.ck-math-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--ck-spacing-standard);
|
||||
width: fit-content;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* === MATHLIVE EDITOR === */
|
||||
.ck.ck-math-input .ck-mathlive-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 50px;
|
||||
padding: var(--ck-spacing-small);
|
||||
border: 1px solid var(--ck-color-input-border);
|
||||
border-radius: var(--ck-border-radius);
|
||||
background: var(--ck-color-input-background) !important;
|
||||
transition: border-color 120ms ease;
|
||||
overflow: visible !important;
|
||||
clip-path: none !important;
|
||||
}
|
||||
.ck.ck-math-input .ck-mathlive-container:focus-within {
|
||||
border-color: var(--ck-color-focus-border);
|
||||
}
|
||||
|
||||
/* Position keyboard & menu buttons */
|
||||
.ck-mathlive-container math-field::part(virtual-keyboard-toggle),
|
||||
.ck-mathlive-container math-field::part(menu-toggle) {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
}
|
||||
.ck-mathlive-container math-field::part(virtual-keyboard-toggle) { right: 40px; }
|
||||
.ck-mathlive-container math-field::part(menu-toggle) {
|
||||
right: 8px;
|
||||
display: flex !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
/* Math field element */
|
||||
.ck.ck-math-form math-field {
|
||||
display: block !important;
|
||||
width: 100%;
|
||||
font-size: 1.5em;
|
||||
background: transparent !important;
|
||||
color: var(--ck-color-input-text);
|
||||
border: none !important;
|
||||
padding: 0;
|
||||
outline: none !important;
|
||||
--selection-background-color: rgba(33, 150, 243, 0.2);
|
||||
--selection-color: inherit;
|
||||
--contains-highlight-background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* === LATEX TEXTAREA === */
|
||||
.ck.ck-math-input .ck-latex-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: fit-content;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
padding: var(--ck-spacing-small);
|
||||
border: 1px solid var(--ck-color-input-border);
|
||||
border-radius: var(--ck-border-radius);
|
||||
background: var(--ck-color-input-background) !important;
|
||||
transition: border-color 120ms ease;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.ck.ck-math-input .ck-latex-wrapper:focus-within {
|
||||
border-color: var(--ck-color-focus-border);
|
||||
}
|
||||
.ck.ck-math-input .ck-latex-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--ck-color-text);
|
||||
opacity: 0.8;
|
||||
margin: 0 0 var(--ck-spacing-small) 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.ck.ck-math-input .ck-latex-textarea {
|
||||
width: fit-content;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
min-height: 60px;
|
||||
max-height: calc(80vh - 300px);
|
||||
resize: both;
|
||||
overflow: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.95em;
|
||||
background: transparent !important;
|
||||
color: var(--ck-color-input-text);
|
||||
border: none !important;
|
||||
padding: 0;
|
||||
outline: none !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* === DISPLAY TOGGLE === */
|
||||
.ck-button-display-toggle {
|
||||
align-self: flex-start;
|
||||
padding: var(--ck-spacing-small) var(--ck-spacing-standard);
|
||||
background: var(--ck-color-input-background);
|
||||
color: var(--ck-color-text);
|
||||
border: 1px solid var(--ck-color-input-border);
|
||||
border-radius: var(--ck-border-radius);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.ck-button-display-toggle:hover { background: var(--ck-color-focus-border); }
|
||||
|
||||
/* === PREVIEW === */
|
||||
.ck-math-preview,
|
||||
.ck.ck-math-preview {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
max-height: none !important;
|
||||
height: auto !important;
|
||||
padding: var(--ck-spacing-small);
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
display: block;
|
||||
text-align: left;
|
||||
overflow-x: auto !important;
|
||||
overflow-y: visible !important;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Center equation when in display mode */
|
||||
.ck-math-preview[data-display="true"],
|
||||
.ck.ck-math-preview[data-display="true"] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ck-math-preview.ck-error, .ck-math-render-error {
|
||||
border-color: var(--ck-color-error-text);
|
||||
background: var(--ck-color-base-background);
|
||||
color: var(--ck-color-error-text);
|
||||
}
|
||||
|
||||
/* === BUTTONS === */
|
||||
.ck-math-button-row {
|
||||
display: flex;
|
||||
gap: var(--ck-spacing-standard);
|
||||
justify-content: flex-end;
|
||||
margin-top: var(--ck-spacing-standard);
|
||||
}
|
||||
.ck-button-save, .ck-button-cancel {
|
||||
padding: var(--ck-spacing-small) var(--ck-spacing-standard);
|
||||
border: 1px solid var(--ck-color-input-border);
|
||||
border-radius: var(--ck-border-radius);
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
}
|
||||
.ck-button-save {
|
||||
background: var(--ck-color-focus-border);
|
||||
color: white;
|
||||
}
|
||||
.ck-button-cancel {
|
||||
background: var(--ck-color-input-background);
|
||||
color: var(--ck-color-text);
|
||||
}
|
||||
.ck-button-save:hover { opacity: 0.9; }
|
||||
.ck-button-cancel:hover { background: var(--ck-color-base-background); }
|
||||
|
||||
/* === OVERFLOW FIX: Allow tooltips to escape === */
|
||||
.ck.ck-balloon-panel,
|
||||
.ck.ck-balloon-panel .ck-balloon-panel__content,
|
||||
.ck.ck-math-form,
|
||||
.ck-math-view,
|
||||
.ck.ck-math-input,
|
||||
.ck.ck-math-input .ck-mathlive-container {
|
||||
overflow: visible !important;
|
||||
clip-path: none !important;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ export default defineConfig( {
|
||||
include: [
|
||||
'tests/**/*.[jt]s'
|
||||
],
|
||||
exclude: [
|
||||
'tests/setup.ts'
|
||||
],
|
||||
globals: true,
|
||||
watch: false,
|
||||
coverage: {
|
||||
|
||||
68
pnpm-lock.yaml
generated
68
pnpm-lock.yaml
generated
@@ -104,8 +104,8 @@ importers:
|
||||
specifier: 0.18.0
|
||||
version: 0.18.0
|
||||
rollup-plugin-webpack-stats:
|
||||
specifier: 2.1.8
|
||||
version: 2.1.8(rolldown@1.0.0-beta.29)(rollup@4.52.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))
|
||||
specifier: 2.1.9
|
||||
version: 2.1.9(rolldown@1.0.0-beta.29)(rollup@4.52.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))
|
||||
tslib:
|
||||
specifier: 2.8.1
|
||||
version: 2.8.1
|
||||
@@ -186,7 +186,7 @@ importers:
|
||||
version: 0.2.0(mermaid@11.12.2)
|
||||
'@mind-elixir/node-menu':
|
||||
specifier: 5.0.1
|
||||
version: 5.0.1(mind-elixir@5.4.0)
|
||||
version: 5.0.1(mind-elixir@5.5.0)
|
||||
'@popperjs/core':
|
||||
specifier: 2.11.8
|
||||
version: 2.11.8
|
||||
@@ -278,8 +278,8 @@ importers:
|
||||
specifier: 11.12.2
|
||||
version: 11.12.2
|
||||
mind-elixir:
|
||||
specifier: 5.4.0
|
||||
version: 5.4.0
|
||||
specifier: 5.5.0
|
||||
version: 5.5.0
|
||||
normalize.css:
|
||||
specifier: 8.0.1
|
||||
version: 8.0.1
|
||||
@@ -1073,6 +1073,9 @@ importers:
|
||||
'@ckeditor/ckeditor5-icons':
|
||||
specifier: 47.3.0
|
||||
version: 47.3.0
|
||||
mathlive:
|
||||
specifier: 0.108.2
|
||||
version: 0.108.2
|
||||
devDependencies:
|
||||
'@ckeditor/ckeditor5-dev-build-tools':
|
||||
specifier: 54.2.3
|
||||
@@ -2129,6 +2132,10 @@ packages:
|
||||
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
|
||||
engines: {node: '>=0.1.90'}
|
||||
|
||||
'@cortex-js/compute-engine@0.30.2':
|
||||
resolution: {integrity: sha512-Zx+iisk9WWdbxjm8EYsneIBszvjfUs7BHNwf1jBtSINIgfWGpHrTTq9vW0J59iGCFt6bOFxbmWyxNMRSmksHMA==}
|
||||
engines: {node: '>=21.7.3', npm: '>=10.5.0'}
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -7076,6 +7083,10 @@ packages:
|
||||
compare-versions@6.1.1:
|
||||
resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==}
|
||||
|
||||
complex-esm@2.1.1-esm1:
|
||||
resolution: {integrity: sha512-IShBEWHILB9s7MnfyevqNGxV0A1cfcSnewL/4uPFiSxkcQL4Mm3FxJ0pXMtCXuWLjYz3lRRyk6OfkeDZcjD6nw==}
|
||||
engines: {node: '>=16.14.2', npm: '>=8.5.0'}
|
||||
|
||||
component-emitter@1.3.1:
|
||||
resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==}
|
||||
|
||||
@@ -10269,6 +10280,9 @@ packages:
|
||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
mathlive@0.108.2:
|
||||
resolution: {integrity: sha512-GIZkfprGTxrbHckOvwo92ZmOOxdD018BHDzlrEwYUU+pzR5KabhqI1s43lxe/vqXdF5RLiQKgDcuk5jxEjhkYg==}
|
||||
|
||||
mathml-tag-names@2.1.3:
|
||||
resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==}
|
||||
|
||||
@@ -10508,8 +10522,8 @@ packages:
|
||||
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
mind-elixir@5.4.0:
|
||||
resolution: {integrity: sha512-yxXajDWoSF6id8b2LKxlhXidxH/v6mx4JV+isrtsZ62RGCMsRbjUMFO9xOfTVH8vyxWhsbCkiAP6/i5hqbyk6w==}
|
||||
mind-elixir@5.5.0:
|
||||
resolution: {integrity: sha512-a/bOTp3wJrK/vTm2/Vn5+9kYL0fNqxWvm8SsVojJO/tltLPPU8yMPzFCZHzGRz1Aoj6bpLxN+ExfIbc28nrNxQ==}
|
||||
|
||||
mini-css-extract-plugin@2.9.4:
|
||||
resolution: {integrity: sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==}
|
||||
@@ -12389,8 +12403,8 @@ packages:
|
||||
resolution: {integrity: sha512-EsoOi8moHN6CAYyTZipxDDVTJn0j2nBCWor4wRU45RQ8ER2qREDykXLr3Ulz6hBh6oBKCFTQIjo21i0FXNo/IA==}
|
||||
hasBin: true
|
||||
|
||||
rollup-plugin-stats@1.5.3:
|
||||
resolution: {integrity: sha512-0IYVGhsFTjcddpqcElzU7Mi4vmDLihCCTH5QgCCgWpNY1VKMXVoEpxmCmGjivtJKLzI6t5QIicsPBC93UWWN2g==}
|
||||
rollup-plugin-stats@1.5.4:
|
||||
resolution: {integrity: sha512-b1hYagYLTyr8mCVUb7e1x9fjxOXFyeWmV9hIr7vYqq/agN+WDaGNzz+KmM3GAx0KGGI2qllOL+zAUi/l39s/Sg==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
rolldown: ^1.0.0-beta.0
|
||||
@@ -12416,8 +12430,8 @@ packages:
|
||||
peerDependencies:
|
||||
rollup: ^3.0.0||^4.0.0
|
||||
|
||||
rollup-plugin-webpack-stats@2.1.8:
|
||||
resolution: {integrity: sha512-agc1OE+QwG3sGeTSdruh16DkxPb6QkgR7I3gntPDFHMXsK1bR2ADHUVod1eoE+epAOqiv3idx/hcSqZAI3a1yg==}
|
||||
rollup-plugin-webpack-stats@2.1.9:
|
||||
resolution: {integrity: sha512-ft1vdp3xPjE+zw8A22yCToo5cpymoWCjNDefWNO1awywsDrSDoRJhkoZTENkhJwmfh6oe5ztpGu7PfnJOMXc2g==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
rolldown: ^1.0.0-beta.0
|
||||
@@ -15361,8 +15375,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-core': 47.3.0
|
||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||
ckeditor5: 47.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
|
||||
dependencies:
|
||||
@@ -16093,8 +16105,6 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-ui': 47.3.0
|
||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||
ckeditor5: 47.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-restricted-editing@47.3.0':
|
||||
dependencies:
|
||||
@@ -16292,6 +16302,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-icons': 47.3.0
|
||||
'@ckeditor/ckeditor5-ui': 47.3.0
|
||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-upload@47.3.0':
|
||||
dependencies:
|
||||
@@ -16499,6 +16511,11 @@ snapshots:
|
||||
|
||||
'@colors/colors@1.5.0': {}
|
||||
|
||||
'@cortex-js/compute-engine@0.30.2':
|
||||
dependencies:
|
||||
complex-esm: 2.1.1-esm1
|
||||
decimal.js: 10.6.0
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.9
|
||||
@@ -18315,9 +18332,9 @@ snapshots:
|
||||
|
||||
'@microsoft/tsdoc@0.15.1': {}
|
||||
|
||||
'@mind-elixir/node-menu@5.0.1(mind-elixir@5.4.0)':
|
||||
'@mind-elixir/node-menu@5.0.1(mind-elixir@5.5.0)':
|
||||
dependencies:
|
||||
mind-elixir: 5.4.0
|
||||
mind-elixir: 5.5.0
|
||||
|
||||
'@mixmark-io/domino@2.2.0': {}
|
||||
|
||||
@@ -22305,6 +22322,8 @@ snapshots:
|
||||
|
||||
compare-versions@6.1.1: {}
|
||||
|
||||
complex-esm@2.1.1-esm1: {}
|
||||
|
||||
component-emitter@1.3.1: {}
|
||||
|
||||
compress-commons@6.0.2:
|
||||
@@ -22991,8 +23010,7 @@ snapshots:
|
||||
|
||||
decimal.js@10.5.0: {}
|
||||
|
||||
decimal.js@10.6.0:
|
||||
optional: true
|
||||
decimal.js@10.6.0: {}
|
||||
|
||||
decko@1.2.0: {}
|
||||
|
||||
@@ -26338,6 +26356,10 @@ snapshots:
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
mathlive@0.108.2:
|
||||
dependencies:
|
||||
'@cortex-js/compute-engine': 0.30.2
|
||||
|
||||
mathml-tag-names@2.1.3: {}
|
||||
|
||||
mdast-util-find-and-replace@3.0.2:
|
||||
@@ -26765,7 +26787,7 @@ snapshots:
|
||||
|
||||
mimic-response@3.1.0: {}
|
||||
|
||||
mind-elixir@5.4.0: {}
|
||||
mind-elixir@5.5.0: {}
|
||||
|
||||
mini-css-extract-plugin@2.9.4(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.2)):
|
||||
dependencies:
|
||||
@@ -28808,7 +28830,7 @@ snapshots:
|
||||
'@rolldown/binding-win32-x64-msvc': 1.0.0-beta.29
|
||||
optional: true
|
||||
|
||||
rollup-plugin-stats@1.5.3(rolldown@1.0.0-beta.29)(rollup@4.52.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)):
|
||||
rollup-plugin-stats@1.5.4(rolldown@1.0.0-beta.29)(rollup@4.52.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)):
|
||||
optionalDependencies:
|
||||
rolldown: 1.0.0-beta.29
|
||||
rollup: 4.52.0
|
||||
@@ -28841,9 +28863,9 @@ snapshots:
|
||||
'@rollup/pluginutils': 5.1.4(rollup@4.52.0)
|
||||
rollup: 4.52.0
|
||||
|
||||
rollup-plugin-webpack-stats@2.1.8(rolldown@1.0.0-beta.29)(rollup@4.52.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)):
|
||||
rollup-plugin-webpack-stats@2.1.9(rolldown@1.0.0-beta.29)(rollup@4.52.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)):
|
||||
dependencies:
|
||||
rollup-plugin-stats: 1.5.3(rolldown@1.0.0-beta.29)(rollup@4.52.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))
|
||||
rollup-plugin-stats: 1.5.4(rolldown@1.0.0-beta.29)(rollup@4.52.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))
|
||||
optionalDependencies:
|
||||
rolldown: 1.0.0-beta.29
|
||||
rollup: 4.52.0
|
||||
|
||||
Reference in New Issue
Block a user