Get link extension working better

This commit is contained in:
Jon Staab
2024-09-23 15:18:39 -07:00
parent ad4944d512
commit d7dba6c61a
4 changed files with 109 additions and 69 deletions
+1 -1
View File
@@ -16,7 +16,7 @@
let editor: Readable<Editor> let editor: Readable<Editor>
onMount(() => { onMount(() => {
editor = createEditor(getViewOptions(root.content)) editor = createEditor(getViewOptions(root))
}) })
</script> </script>
+40 -7
View File
@@ -1,14 +1,11 @@
import {Node, nodePasteRule, type PasteRuleMatch} from "@tiptap/core" import {last} from '@welshman/lib'
import {Node, InputRule, nodePasteRule, type PasteRuleMatch} from "@tiptap/core"
import type {Node as ProsemirrorNode} from "@tiptap/pm/model" import type {Node as ProsemirrorNode} from "@tiptap/pm/model"
import type {MarkdownSerializerState} from "prosemirror-markdown" import type {MarkdownSerializerState} from "prosemirror-markdown"
import {createPasteRuleMatch, createInputRuleMatch} from './util'
export const LINK_REGEX = export const LINK_REGEX =
/^([a-z\+:]{2,30}:\/\/)?[^<>\(\)\s]+\.[a-z]{2,6}[^\s]*[^<>"'\.!?,:\s\)\(]*/gi /([a-z\+:]{2,30}:\/\/)?[^<>\(\)\s]+\.[a-z]{2,6}[^\s]*[^<>"'\.!?,:\s\)\(]*/gi
export const createPasteRuleMatch = <T extends Record<string, unknown>>(
match: RegExpMatchArray,
data: T,
): PasteRuleMatch => ({index: match.index!, replaceWith: match[2], text: match[0], match, data})
export interface LinkAttributes { export interface LinkAttributes {
url: string url: string
@@ -65,6 +62,42 @@ export const LinkExtension = Node.create({
}, },
} }
}, },
addInputRules() {
return [
new InputRule({
find: text => {
const match = last(Array.from(text.matchAll(LINK_REGEX)))
if (match && text.length === match.index + match[0].length + 1) {
return {
index: match.index!,
text: match[0],
data: {
url: match[0],
},
}
}
return null
},
handler: ({state, range, match}) => {
const {tr} = state
if (match[0]) {
try {
tr.insert(range.from - 1, this.type.create(match.data))
.delete(tr.mapping.map(range.from - 1), tr.mapping.map(range.to))
.insert(tr.mapping.map(range.to), this.editor.schema.text(last(Array.from(match.input!))))
} catch (e) {
// If the node was already linkified, the above code breaks for whatever reason
}
}
tr.scrollIntoView()
},
}),
]
},
addPasteRules() { addPasteRules() {
return [ return [
nodePasteRule({ nodePasteRule({
+61 -59
View File
@@ -78,68 +78,70 @@ export const getEditorOptions = ({
loading, loading,
getPubkeyHints, getPubkeyHints,
submitOnEnter, submitOnEnter,
}: EditorOptions) => { }: EditorOptions) => ({
return { content: "",
content: "", autofocus: true,
autofocus: true, extensions: [
extensions: [ Code,
Code, CodeBlock,
CodeBlock, Document,
Document, Dropcursor,
Dropcursor, Gapcursor,
Gapcursor, History,
History, Paragraph,
Paragraph, Text,
Text, submitOnEnter ? getModifiedHardBreakExtension() : HardBreakExtension,
submitOnEnter ? getModifiedHardBreakExtension() : HardBreakExtension, LinkExtension.extend({
LinkExtension.extend({ addNodeView: () => SvelteNodeViewRenderer(EditLink),
addNodeView: () => SvelteNodeViewRenderer(EditLink), }),
}), Bolt11Extension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(EditBolt11)})),
Bolt11Extension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(EditBolt11)})), NProfileExtension.extend({
NProfileExtension.extend({ addNodeView: () => SvelteNodeViewRenderer(EditMention),
addNodeView: () => SvelteNodeViewRenderer(EditMention), addProseMirrorPlugins() {
addProseMirrorPlugins() { return [
return [ createSuggestions({
createSuggestions({ char: "@",
char: "@", name: "nprofile",
name: "nprofile", editor: this.editor,
editor: this.editor, search: profileSearch,
search: profileSearch, select: (pubkey: string, props: any) => {
select: (pubkey: string, props: any) => { const relays = getPubkeyHints(pubkey)
const relays = getPubkeyHints(pubkey) const nprofile = nprofileEncode({pubkey, relays})
const nprofile = nprofileEncode({pubkey, relays})
return props.command({pubkey, nprofile, relays}) return props.command({pubkey, nprofile, relays})
}, },
suggestionComponent: SuggestionProfile, suggestionComponent: SuggestionProfile,
suggestionsComponent: Suggestions, suggestionsComponent: Suggestions,
}), }),
] ]
}, },
}), }),
NEventExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(EditEvent)})), NEventExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(EditEvent)})),
NAddrExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(EditEvent)})), NAddrExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(EditEvent)})),
ImageExtension.extend( ImageExtension.extend(
asInline({addNodeView: () => SvelteNodeViewRenderer(EditImage)}), asInline({addNodeView: () => SvelteNodeViewRenderer(EditImage)}),
).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}), ).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}),
VideoExtension.extend( VideoExtension.extend(
asInline({addNodeView: () => SvelteNodeViewRenderer(EditVideo)}), asInline({addNodeView: () => SvelteNodeViewRenderer(EditVideo)}),
).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}), ).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}),
FileUploadExtension.configure({ FileUploadExtension.configure({
immediateUpload: false, immediateUpload: false,
sign: (event: StampedEvent) => { sign: (event: StampedEvent) => {
loading.set(true) loading.set(true)
return signer.get()!.sign(event) return signer.get()!.sign(event)
}, },
onComplete: () => { onComplete: () => {
loading.set(false) loading.set(false)
submit() submit()
}, },
}), }),
], ],
onTransaction() {
// @ts-ignore
console.log(this.getJSON())
} }
} })
type ViewOptions = { type ViewOptions = {
content: string content: string
+7 -2
View File
@@ -1,4 +1,4 @@
import type {JSONContent, PasteRuleMatch} from "@tiptap/core" import type {JSONContent, PasteRuleMatch, InputRuleMatch} from "@tiptap/core"
import {Editor} from "@tiptap/core" import {Editor} from "@tiptap/core"
import {choice} from "@welshman/lib" import {choice} from "@welshman/lib"
@@ -8,10 +8,15 @@ export const asInline = (extend: Record<string, any>) => ({
...extend, ...extend,
}) })
export const createInputRuleMatch = <T extends Record<string, unknown>>(
match: RegExpMatchArray,
data: T,
): InputRuleMatch => ({index: match.index!, text: match[0], match, data})
export const createPasteRuleMatch = <T extends Record<string, unknown>>( export const createPasteRuleMatch = <T extends Record<string, unknown>>(
match: RegExpMatchArray, match: RegExpMatchArray,
data: T, data: T,
): PasteRuleMatch => ({index: match.index!, replaceWith: match[2], text: match[0], match, data}) ): PasteRuleMatch => ({index: match.index!, text: match[0], match, data})
export const findNodes = (type: string, json: JSONContent) => { export const findNodes = (type: string, json: JSONContent) => {
const results: JSONContent[] = [] const results: JSONContent[] = []