-
Notifications
You must be signed in to change notification settings - Fork 326
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CodeMirror implementation of GraphNodeComment (#11585)
- Loading branch information
Showing
27 changed files
with
561 additions
and
1,218 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,52 +1,22 @@ | ||
<script setup lang="ts"> | ||
import FloatingSelectionMenu from '@/components/FloatingSelectionMenu.vue' | ||
import { lexicalTheme, useLexical, type LexicalPlugin } from '@/components/lexical' | ||
import LexicalContent from '@/components/lexical/LexicalContent.vue' | ||
import { autoLinkPlugin, useLinkNode } from '@/components/lexical/LinkPlugin' | ||
import LinkToolbar from '@/components/lexical/LinkToolbar.vue' | ||
import { useLexicalStringSync } from '@/components/lexical/sync' | ||
import { registerPlainText } from '@lexical/plain-text' | ||
import { ref, useCssModule, watch, type ComponentInstance } from 'vue' | ||
import { type ComponentInstance, computed, defineAsyncComponent, ref } from 'vue' | ||
import * as Y from 'yjs' | ||
const text = defineModel<string>({ required: true }) | ||
const props = defineProps<{ content: Y.Text | string }>() | ||
const contentElement = ref<ComponentInstance<typeof LexicalContent>>() | ||
const impl = ref<ComponentInstance<typeof LazyPlainTextEditor>>() | ||
const plainText: LexicalPlugin = { | ||
register: registerPlainText, | ||
} | ||
const LazyPlainTextEditor = defineAsyncComponent( | ||
() => import('@/components/PlainTextEditor/PlainTextEditorImpl.vue'), | ||
) | ||
const textSync: LexicalPlugin = { | ||
register: (editor) => { | ||
const { content } = useLexicalStringSync(editor) | ||
watch(text, (newContent) => content.set(newContent), { immediate: true }) | ||
watch(content.editedContent, (newContent) => (text.value = newContent)) | ||
}, | ||
} | ||
const theme = lexicalTheme(useCssModule('lexicalTheme')) | ||
const { editor } = useLexical(contentElement, 'PlainTextEditor', theme, [ | ||
autoLinkPlugin, | ||
plainText, | ||
textSync, | ||
]) | ||
const { urlUnderCursor } = useLinkNode(editor) | ||
defineExpose({ contentElement }) | ||
defineExpose({ | ||
contentElement: computed(() => impl.value?.contentElement), | ||
}) | ||
</script> | ||
|
||
<template> | ||
<LexicalContent ref="contentElement" v-bind="$attrs" /> | ||
<FloatingSelectionMenu :selectionElement="contentElement"> | ||
<LinkToolbar v-if="urlUnderCursor" :url="urlUnderCursor" /> | ||
</FloatingSelectionMenu> | ||
<Suspense> | ||
<LazyPlainTextEditor ref="impl" v-bind="props" class="PlainTextEditor" /> | ||
</Suspense> | ||
</template> | ||
|
||
<style module="lexicalTheme"> | ||
.link { | ||
color: #ddf; | ||
&:hover { | ||
text-decoration: underline; | ||
} | ||
} | ||
</style> |
45 changes: 45 additions & 0 deletions
45
app/gui/src/project-view/components/PlainTextEditor/PlainTextEditorImpl.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<script setup lang="ts"> | ||
import EditorRoot from '@/components/codemirror/EditorRoot.vue' | ||
import { yCollab } from '@/components/codemirror/yCollab' | ||
import { linkifyUrls } from '@/components/PlainTextEditor/linkifyUrls' | ||
import { EditorState } from '@codemirror/state' | ||
import { EditorView } from '@codemirror/view' | ||
import { type ComponentInstance, computed, onMounted, ref, watchEffect } from 'vue' | ||
import { Awareness } from 'y-protocols/awareness' | ||
import { assert } from 'ydoc-shared/util/assert' | ||
import * as Y from 'yjs' | ||
const { content } = defineProps<{ content: Y.Text | string }>() | ||
const editorRoot = ref<ComponentInstance<typeof EditorRoot>>() | ||
const awareness = new Awareness(new Y.Doc()) | ||
const editorView = new EditorView() | ||
function init(content: Y.Text | string) { | ||
const baseExtensions = [linkifyUrls] | ||
if (typeof content === 'string') { | ||
return { doc: content, extensions: baseExtensions } | ||
} else { | ||
assert(content.doc !== null) | ||
const yTextWithDoc: Y.Text & { doc: Y.Doc } = content as any | ||
const doc = content.toString() | ||
const syncExt = yCollab(yTextWithDoc, awareness) | ||
return { doc, extensions: [...baseExtensions, syncExt] } | ||
} | ||
} | ||
watchEffect(() => { | ||
const { doc, extensions } = init(content) | ||
editorView.setState(EditorState.create({ doc, extensions })) | ||
}) | ||
onMounted(() => editorRoot.value?.rootElement?.prepend(editorView.dom)) | ||
defineExpose({ | ||
contentElement: computed(() => editorView.contentDOM), | ||
}) | ||
</script> | ||
|
||
<template> | ||
<EditorRoot ref="editorRoot" /> | ||
</template> |
73 changes: 73 additions & 0 deletions
73
app/gui/src/project-view/components/PlainTextEditor/___tests__/urlLinks.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { linkifyUrls } from '@/components/PlainTextEditor/linkifyUrls' | ||
import { EditorState } from '@codemirror/state' | ||
import { Decoration, EditorView } from '@codemirror/view' | ||
import { expect, test } from 'vitest' | ||
|
||
function decorations<T>( | ||
source: string, | ||
recognize: (from: number, to: number, decoration: Decoration) => T | undefined, | ||
) { | ||
const state = EditorState.create({ | ||
doc: source, | ||
extensions: [linkifyUrls], | ||
}) | ||
const view = new EditorView({ state }) | ||
const decorationSets = state.facet(EditorView.decorations) | ||
const results = [] | ||
for (const decorationSet of decorationSets) { | ||
const resolvedDecorations = | ||
decorationSet instanceof Function ? decorationSet(view) : decorationSet | ||
const cursor = resolvedDecorations.iter() | ||
while (cursor.value != null) { | ||
const recognized = recognize(cursor.from, cursor.to, cursor.value) | ||
if (recognized) results.push(recognized) | ||
cursor.next() | ||
} | ||
} | ||
return results | ||
} | ||
|
||
function links(source: string) { | ||
return decorations(source, (from, to, deco) => { | ||
if (deco.spec.tagName === 'a') { | ||
return { | ||
text: source.substring(from, to), | ||
href: deco.spec.attributes.href, | ||
} | ||
} | ||
}) | ||
} | ||
|
||
// Test that link decorations are created for URLs and emails, with `href` set appropriately. The specific URL and email | ||
// syntaxes recognized are tested separately, in the unit tests for `LINKABLE_URL_REGEX` and `LINKABLE_EMAIL_REGEX`. | ||
test.each([ | ||
{ | ||
text: 'Url: https://www.example.com/index.html', | ||
expectedLinks: [ | ||
{ | ||
text: 'https://www.example.com/index.html', | ||
href: 'https://www.example.com/index.html', | ||
}, | ||
], | ||
}, | ||
{ | ||
text: 'Url: www.example.com', | ||
expectedLinks: [ | ||
{ | ||
text: 'www.example.com', | ||
href: 'https://www.example.com', | ||
}, | ||
], | ||
}, | ||
{ | ||
text: 'Email: [email protected]', | ||
expectedLinks: [ | ||
{ | ||
text: '[email protected]', | ||
href: 'mailto:[email protected]', | ||
}, | ||
], | ||
}, | ||
])('Link decoration: $text', ({ text, expectedLinks }) => { | ||
expect(links(text)).toEqual(expectedLinks) | ||
}) |
Oops, something went wrong.