diff --git a/src/components/CKEditor.ts b/src/ckeditor/CKEditor.ts similarity index 96% rename from src/components/CKEditor.ts rename to src/ckeditor/CKEditor.ts index e4d337c..1e770a1 100644 --- a/src/components/CKEditor.ts +++ b/src/ckeditor/CKEditor.ts @@ -56,7 +56,15 @@ export default defineComponent({ }, }, - emits: ['ready', 'destroy', 'blur', 'focus', 'input', 'update:modelValue'], + emits: [ + 'ready', + 'destroy', + 'blur', + 'focus', + 'input', + 'update:modelValue', + 'click', + ], data(): CKEditorComponentData { return { @@ -131,7 +139,7 @@ export default defineComponent({ editorConfig.initialData = this.modelValue } - this.editor + this.editor .create(this.$el, editorConfig) .then((editor) => { // Save the reference to the instance for further use. @@ -209,6 +217,11 @@ export default defineComponent({ editor.editing.view.document.on('blur', (evt) => { this.$emit('blur', evt, editor) }) + + // Custom event + editor.editing.view.document.on('click', (evt, data) => { + this.$emit('click', { evt, data }, editor) + }) }, }, diff --git a/src/ckeditor/ContextedPlugin.ts b/src/ckeditor/ContextedPlugin.ts new file mode 100644 index 0000000..924c1ce --- /dev/null +++ b/src/ckeditor/ContextedPlugin.ts @@ -0,0 +1,45 @@ +import Plugin from '@ckeditor/ckeditor5-core/src/plugin' + +export default class ContextedLinkEditing extends Plugin { + init() { + this._defineSchema() // ADDED + this._defineConverters() // ADDED + } + _defineSchema() { + // ADDED + const schema = this.editor.model.schema + + // Extend the text node's schema to accept the abbreviation attribute. + schema.extend('$text', { + allowAttributes: ['abbreviation', 'contextedLink'], + }) + } + _defineConverters() { + // ADDED + const conversion = this.editor.conversion + + // Conversion from a model attribute to a view element. + conversion.for('downcast').attributeToElement({ + model: 'contextedLink', + // Callback function provides access to the model attribute value + // and the DowncastWriter. + view: (modelAttributeValue, conversionApi) => { + const { writer } = conversionApi + + return writer.createAttributeElement('a', { + 'data-contexted-link': modelAttributeValue, + }) + }, + }) + conversion.for('upcast').elementToAttribute({ + view: { + name: 'a', + key: 'data-contexted-link', + }, + model: { + key: 'contextedLink', + }, + converterPriority: 'high', + }) + } +} diff --git a/src/components/NoteEditor.vue b/src/components/NoteEditor.vue index 2648e67..09e0051 100644 --- a/src/components/NoteEditor.vue +++ b/src/components/NoteEditor.vue @@ -1,5 +1,5 @@ @@ -68,4 +78,7 @@ const editorConfig = { outline: none !important; box-shadow: none !important; } +.ck-content a[data-contexted-link='true'] { + @apply cursor-pointer font-semibold text-primary hover:bg-gray-200; +} diff --git a/src/composables/useNotes.ts b/src/composables/useNotes.ts index 4d15645..42fd5c6 100644 --- a/src/composables/useNotes.ts +++ b/src/composables/useNotes.ts @@ -4,8 +4,6 @@ import { mdToHtml } from '@/utils/markdown' import { getAllMatches } from '@/utils/helpers' import shortid from 'shortid' -const contextedPrefix = 'c@' - const baseNotes = reactive<{ [noteId: string]: BaseNote }>({}) export const notes = computed(() => { @@ -40,10 +38,14 @@ export const getNoteById = (noteId: string) => { return notes.value.find((note) => note.id === noteId) } +export const getNoteByTitle = (title: string) => { + return notes.value.find((note) => note.title === title) +} + export const findNotes = (query: string): Note[] => { const removeMdFromText = (mdText: string): string => { const div = document.createElement('div') - div.innerHTML = mdToHtml(mdText, contextedPrefix, getNoteById) + div.innerHTML = mdToHtml(mdText) const textWithoutMd = div.textContent || div.innerText || '' return textWithoutMd } diff --git a/src/utils/markdown.ts b/src/utils/markdown.ts index 9c53c3c..3cabb92 100644 --- a/src/utils/markdown.ts +++ b/src/utils/markdown.ts @@ -1,39 +1,16 @@ import { marked } from 'marked' import DOMPurify from 'dompurify' -export function mdToHtml( - mdText: string, - contextedPrefix: string, - getNoteById: (id: string) => Note | undefined -) { +export function mdToHtml(mdText: string) { const renderer = new marked.Renderer() - renderer.link = (href, _, text) => { - const isContextedLink = href?.startsWith(contextedPrefix) - if (isContextedLink) { - const re = new RegExp(`${contextedPrefix}([^]+)`) - const match = re.exec(href || '') - const contextedLinkOptions = match ? match[1].split(';') : [] - const noteId = contextedLinkOptions[0] || '' - const note = getNoteById(noteId) - const contextedHref = '' - const contextedLink = `${text}` - return note?.title ? contextedLink : text - } else { - return `${text}` - } - } - const html = DOMPurify.sanitize(marked.parse(mdText, { renderer })) const re = /(\[\[)(.*?)(\]\])/g const doc = new DOMParser().parseFromString(html, 'text/html') doc.querySelectorAll('p, b, u, i, li, h1, h2, h3').forEach((element) => { - // if (element.childElementCount === 0) { element.innerHTML = element.innerHTML.replace(re, (_, p1, p2, p3) => { - // const { id: noteId } = getters.getNoteByTitle(p2) || {} return `${p1}${p2}${p3}` }) - // } }) return doc.body.innerHTML }