2025-05-29 18:26:12 +03:00
|
|
|
import { Command, Element, LinkEditing, Plugin, toWidget, viewToModelPositionOutsideModelElement, Widget } from "ckeditor5";
|
2024-11-09 14:09:50 +02:00
|
|
|
|
|
|
|
export default class ReferenceLink extends Plugin {
|
|
|
|
static get requires() {
|
|
|
|
return [ ReferenceLinkEditing ];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ReferenceLinkCommand extends Command {
|
2025-05-03 17:00:24 +03:00
|
|
|
|
2025-05-29 13:22:38 +03:00
|
|
|
override execute({ href }: { href: string }) {
|
2024-11-09 14:09:50 +02:00
|
|
|
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');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2025-05-29 13:22:38 +03:00
|
|
|
override refresh() {
|
2024-11-09 14:09:50 +02:00
|
|
|
const model = this.editor.model;
|
|
|
|
const selection = model.document.selection;
|
2025-05-03 17:00:24 +03:00
|
|
|
this.isEnabled = selection.focus !== null && model.schema.checkChild(selection.focus.parent as Element, 'reference');
|
2024-11-09 14:09:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ReferenceLinkEditing extends Plugin {
|
|
|
|
static get requires() {
|
2025-05-29 18:26:12 +03:00
|
|
|
return [ Widget, LinkEditing ];
|
2024-11-09 14:09:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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' ) )
|
|
|
|
);
|
2025-05-29 18:26:12 +03:00
|
|
|
|
|
|
|
this.editor.plugins.get("LinkEditing")._registerLinkOpener(() => {
|
|
|
|
// Prevent reference links from being opened in a new browser tab.
|
|
|
|
// This works even if the link is not a reference link, since it is handled by Trilium.
|
|
|
|
return true;
|
|
|
|
});
|
2024-11-09 14:09:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
_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 } ) => {
|
2025-05-03 17:00:24 +03:00
|
|
|
const href = modelItem.getAttribute('href') as string;
|
2024-11-09 14:09:50 +02:00
|
|
|
|
|
|
|
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();
|
2025-05-03 17:00:24 +03:00
|
|
|
const component = glob.getComponentByEl<EditorComponent>(editorEl);
|
2024-11-09 14:09:50 +02:00
|
|
|
|
|
|
|
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 } ) => {
|
2025-05-03 17:00:24 +03:00
|
|
|
const href = modelItem.getAttribute('href') as string;
|
2024-11-09 14:09:50 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
}
|