contexted link styling

This commit is contained in:
2023-04-30 14:57:43 +02:00
parent 787b5a4cb8
commit 2ff849219b
5 changed files with 83 additions and 33 deletions

View File

@@ -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 { data(): CKEditorComponentData {
return { return {
@@ -131,7 +139,7 @@ export default defineComponent({
editorConfig.initialData = this.modelValue editorConfig.initialData = this.modelValue
} }
this.editor this.editor
.create(this.$el, editorConfig) .create(this.$el, editorConfig)
.then((editor) => { .then((editor) => {
// Save the reference to the instance for further use. // Save the reference to the instance for further use.
@@ -209,6 +217,11 @@ export default defineComponent({
editor.editing.view.document.on('blur', (evt) => { editor.editing.view.document.on('blur', (evt) => {
this.$emit('blur', evt, editor) this.$emit('blur', evt, editor)
}) })
// Custom event
editor.editing.view.document.on('click', (evt, data) => {
this.$emit('click', { evt, data }, editor)
})
}, },
}, },

View File

@@ -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',
})
}
}

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import CKEditor from '@/components/CKEditor' import CKEditor from '@/ckeditor/CKEditor'
import BalloonEditor from '@ckeditor/ckeditor5-editor-balloon/src/ballooneditor' import BalloonEditor from '@ckeditor/ckeditor5-editor-balloon/src/ballooneditor'
import EssentialsPlugin from '@ckeditor/ckeditor5-essentials/src/essentials' import EssentialsPlugin from '@ckeditor/ckeditor5-essentials/src/essentials'
import BoldPlugin from '@ckeditor/ckeditor5-basic-styles/src/bold' import BoldPlugin from '@ckeditor/ckeditor5-basic-styles/src/bold'
@@ -11,16 +11,18 @@ import HeadingPlugin from '@ckeditor/ckeditor5-heading/src/heading'
import ParagraphPlugin from '@ckeditor/ckeditor5-paragraph/src/paragraph' import ParagraphPlugin from '@ckeditor/ckeditor5-paragraph/src/paragraph'
import ListPlugin from '@ckeditor/ckeditor5-list/src/list' import ListPlugin from '@ckeditor/ckeditor5-list/src/list'
import AutoformatPlugin from '@ckeditor/ckeditor5-autoformat/src/autoformat' import AutoformatPlugin from '@ckeditor/ckeditor5-autoformat/src/autoformat'
import ContextedPlugin from '@/ckeditor/ContextedPlugin'
import { mdToHtml } from '@/utils/markdown' import { mdToHtml } from '@/utils/markdown'
import { getNoteById } from '@/composables/useNotes' import { activeNote, getNoteByTitle } from '@/composables/useNotes'
const props = defineProps<{ const props = defineProps<{
note: Note note: Note
}>() }>()
const html = mdToHtml(props.note.content)
const editor = BalloonEditor const editor = BalloonEditor
const editorData = mdToHtml(props.note.content, 'c@', getNoteById) const editorData = html
const editorConfig = { const editorConfig = {
plugins: [ plugins: [
EssentialsPlugin, EssentialsPlugin,
@@ -33,6 +35,7 @@ const editorConfig = {
ParagraphPlugin, ParagraphPlugin,
ListPlugin, ListPlugin,
AutoformatPlugin, AutoformatPlugin,
ContextedPlugin,
], ],
toolbar: { toolbar: {
@@ -50,6 +53,12 @@ const editorConfig = {
], ],
}, },
} }
const handleClick = ({ data }: { data: any }) => {
const noteTitle = data.domTarget.textContent as string
const note = getNoteByTitle(noteTitle)
if (note) activeNote.value = note
}
</script> </script>
<template> <template>
<div> <div>
@@ -58,6 +67,7 @@ const editorConfig = {
:editor="editor" :editor="editor"
v-model="editorData" v-model="editorData"
:config="editorConfig" :config="editorConfig"
@click="handleClick"
></CKEditor> ></CKEditor>
</div> </div>
</template> </template>
@@ -68,4 +78,7 @@ const editorConfig = {
outline: none !important; outline: none !important;
box-shadow: none !important; box-shadow: none !important;
} }
.ck-content a[data-contexted-link='true'] {
@apply cursor-pointer font-semibold text-primary hover:bg-gray-200;
}
</style> </style>

View File

@@ -4,8 +4,6 @@ import { mdToHtml } from '@/utils/markdown'
import { getAllMatches } from '@/utils/helpers' import { getAllMatches } from '@/utils/helpers'
import shortid from 'shortid' import shortid from 'shortid'
const contextedPrefix = 'c@'
const baseNotes = reactive<{ [noteId: string]: BaseNote }>({}) const baseNotes = reactive<{ [noteId: string]: BaseNote }>({})
export const notes = computed<Note[]>(() => { export const notes = computed<Note[]>(() => {
@@ -40,10 +38,14 @@ export const getNoteById = (noteId: string) => {
return notes.value.find((note) => note.id === noteId) 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[] => { export const findNotes = (query: string): Note[] => {
const removeMdFromText = (mdText: string): string => { const removeMdFromText = (mdText: string): string => {
const div = document.createElement('div') const div = document.createElement('div')
div.innerHTML = mdToHtml(mdText, contextedPrefix, getNoteById) div.innerHTML = mdToHtml(mdText)
const textWithoutMd = div.textContent || div.innerText || '' const textWithoutMd = div.textContent || div.innerText || ''
return textWithoutMd return textWithoutMd
} }

View File

@@ -1,39 +1,16 @@
import { marked } from 'marked' import { marked } from 'marked'
import DOMPurify from 'dompurify' import DOMPurify from 'dompurify'
export function mdToHtml( export function mdToHtml(mdText: string) {
mdText: string,
contextedPrefix: string,
getNoteById: (id: string) => Note | undefined
) {
const renderer = new marked.Renderer() 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 = `<a data-contexted-link="${noteId}" title="${note?.title}"${contextedHref}>${text}</a>`
return note?.title ? contextedLink : text
} else {
return `<a target="_blank" href="${href}">${text}</a>`
}
}
const html = DOMPurify.sanitize(marked.parse(mdText, { renderer })) const html = DOMPurify.sanitize(marked.parse(mdText, { renderer }))
const re = /(\[\[)(.*?)(\]\])/g const re = /(\[\[)(.*?)(\]\])/g
const doc = new DOMParser().parseFromString(html, 'text/html') const doc = new DOMParser().parseFromString(html, 'text/html')
doc.querySelectorAll('p, b, u, i, li, h1, h2, h3').forEach((element) => { 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) => { element.innerHTML = element.innerHTML.replace(re, (_, p1, p2, p3) => {
// const { id: noteId } = getters.getNoteByTitle(p2) || {}
return `${p1}<a data-contexted-link="true">${p2}</a>${p3}` return `${p1}<a data-contexted-link="true">${p2}</a>${p3}`
}) })
// }
}) })
return doc.body.innerHTML return doc.body.innerHTML
} }