chore(ckeditor5/plugins): integrate reference link

This commit is contained in:
Elian Doran
2025-05-03 17:00:24 +03:00
parent a54d8ed811
commit 2dcd37001f
7 changed files with 45 additions and 25 deletions

View File

@@ -1,14 +1,22 @@
import "ckeditor5";
declare global {
interface Component {
triggerCommand(command: string): void;
}
interface EditorComponent extends Component {
loadReferenceLinkTitle($el: JQuery<HTMLElement>, href: string): Promise<void>;
}
var glob: {
getComponentByEl(el: unknown): {
triggerCommand(command: string): void;
};
getComponentByEl<T extends Component>(el: unknown): T;
getActiveContextNote(): {
noteId: string;
};
getHeaders(): Promise<Record<string, string>>;
getReferenceLinkTitle(href: string): Promise<string>;
getReferenceLinkTitleSync(href: string): string;
}
}

View File

@@ -5,11 +5,13 @@ import UploadimagePlugin from "./plugins/uploadimage.js";
import ItalicAsEmPlugin from "./plugins/italic_as_em.js";
import StrikethroughAsDel from "./plugins/strikethrough_as_del.js";
import InternalLinkPlugin from "./plugins/internallink.js";
import ReferenceLink from "./plugins/referencelink.js";
const TRILIUM_PLUGINS: typeof Plugin[] = [
CutToNotePlugin,
ItalicAsEmPlugin,
StrikethroughAsDel,
ReferenceLink,
UploadimagePlugin,
InternalLinkPlugin
];
@@ -71,7 +73,6 @@ export const COMMON_PLUGINS: typeof Plugin[] = [
// MarkdownImportPlugin,
// MentionCustomization,
// IncludeNote,
// ReferenceLink,
// indentBlockShortcutPlugin,
// removeFormatLinksPlugin,
PageBreak,

View File

@@ -1,7 +1,13 @@
import { ButtonView, Plugin } from 'ckeditor5';
import internalLinkIcon from '../icons/trilium.svg?raw';
import ReferenceLink from './referencelink';
export default class InternalLinkPlugin extends Plugin {
static get requires() {
return [ ReferenceLink ];
}
init() {
const editor = this.editor;

View File

@@ -0,0 +1,139 @@
import { Command, Element, Plugin, toWidget, viewToModelPositionOutsideModelElement, Widget } from "ckeditor5";
export default class ReferenceLink extends Plugin {
static get requires() {
return [ ReferenceLinkEditing ];
}
}
class ReferenceLinkCommand extends Command {
execute({ href }: { href: string }) {
if (!href?.trim()) {
return;
}
const editor = this.editor;
// make sure the referenced note is in cache before adding the reference element
glob.getReferenceLinkTitle(href).then(() => {
editor.model.change(writer => {
const placeholder = writer.createElement('reference', {href});
// ... and insert it into the document.
editor.model.insertContent(placeholder);
// Put the selection on the inserted element.
writer.setSelection(placeholder, 'after');
});
});
}
refresh() {
const model = this.editor.model;
const selection = model.document.selection;
this.isEnabled = selection.focus !== null && model.schema.checkChild(selection.focus.parent as Element, 'reference');
}
}
class ReferenceLinkEditing extends Plugin {
static get requires() {
return [ Widget ];
}
init() {
this._defineSchema();
this._defineConverters();
this.editor.commands.add( 'referenceLink', new ReferenceLinkCommand( this.editor ) );
this.editor.editing.mapper.on(
'viewToModelPosition',
viewToModelPositionOutsideModelElement( this.editor.model,
viewElement => viewElement.hasClass( 'reference-link' ) )
);
}
_defineSchema() {
const schema = this.editor.model.schema;
schema.register( 'reference', {
// Allow wherever a text is allowed:
allowWhere: '$text',
isInline: true,
// The inline widget is self-contained, so it cannot be split by the caret, and it can be selected:
isObject: true,
allowAttributes: [ 'href', 'uploadId', 'uploadStatus' ]
} );
}
_defineConverters() {
const editor = this.editor;
const conversion = editor.conversion;
conversion.for( 'upcast' ).elementToElement( {
view: {
name: 'a',
classes: [ 'reference-link' ]
},
model: ( viewElement, { writer: modelWriter } ) => {
const href = viewElement.getAttribute('href');
return modelWriter.createElement( 'reference', { href } );
}
} );
conversion.for( 'editingDowncast' ).elementToElement( {
model: 'reference',
view: ( modelItem, { writer: viewWriter } ) => {
const href = modelItem.getAttribute('href') as string;
const referenceLinkView = viewWriter.createContainerElement( 'a', {
href,
class: 'reference-link'
},
{
renderUnsafeAttributes: [ 'href' ]
} );
const noteTitleView = viewWriter.createUIElement('span', {}, function( domDocument ) {
const domElement = this.toDomElement( domDocument );
const editorEl = editor.editing.view.getDomRoot();
const component = glob.getComponentByEl<EditorComponent>(editorEl);
component.loadReferenceLinkTitle($(domElement), href);
return domElement;
});
viewWriter.insert( viewWriter.createPositionAt( referenceLinkView, 0 ), noteTitleView );
// Enable widget handling on a reference element inside the editing view.
return toWidget( referenceLinkView, viewWriter );
}
} );
conversion.for( 'dataDowncast' ).elementToElement( {
model: 'reference',
view: ( modelItem, { writer: viewWriter } ) => {
const href = modelItem.getAttribute('href') as string;
const referenceLinkView = viewWriter.createContainerElement( 'a', {
href: href,
class: 'reference-link'
} );
const title = glob.getReferenceLinkTitleSync(href);
const innerText = viewWriter.createText(title);
viewWriter.insert(viewWriter.createPositionAt(referenceLinkView, 0), innerText);
return referenceLinkView;
}
} );
}
}