forked from coracle/flotilla
Get link extension working better
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
let editor: Readable<Editor>
|
||||
|
||||
onMount(() => {
|
||||
editor = createEditor(getViewOptions(root.content))
|
||||
editor = createEditor(getViewOptions(root))
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -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 {MarkdownSerializerState} from "prosemirror-markdown"
|
||||
import {createPasteRuleMatch, createInputRuleMatch} from './util'
|
||||
|
||||
export const LINK_REGEX =
|
||||
/^([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})
|
||||
/([a-z\+:]{2,30}:\/\/)?[^<>\(\)\s]+\.[a-z]{2,6}[^\s]*[^<>"'\.!?,:\s\)\(]*/gi
|
||||
|
||||
export interface LinkAttributes {
|
||||
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() {
|
||||
return [
|
||||
nodePasteRule({
|
||||
|
||||
+61
-59
@@ -78,68 +78,70 @@ export const getEditorOptions = ({
|
||||
loading,
|
||||
getPubkeyHints,
|
||||
submitOnEnter,
|
||||
}: EditorOptions) => {
|
||||
return {
|
||||
content: "",
|
||||
autofocus: true,
|
||||
extensions: [
|
||||
Code,
|
||||
CodeBlock,
|
||||
Document,
|
||||
Dropcursor,
|
||||
Gapcursor,
|
||||
History,
|
||||
Paragraph,
|
||||
Text,
|
||||
submitOnEnter ? getModifiedHardBreakExtension() : HardBreakExtension,
|
||||
LinkExtension.extend({
|
||||
addNodeView: () => SvelteNodeViewRenderer(EditLink),
|
||||
}),
|
||||
Bolt11Extension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(EditBolt11)})),
|
||||
NProfileExtension.extend({
|
||||
addNodeView: () => SvelteNodeViewRenderer(EditMention),
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
createSuggestions({
|
||||
char: "@",
|
||||
name: "nprofile",
|
||||
editor: this.editor,
|
||||
search: profileSearch,
|
||||
select: (pubkey: string, props: any) => {
|
||||
const relays = getPubkeyHints(pubkey)
|
||||
const nprofile = nprofileEncode({pubkey, relays})
|
||||
}: EditorOptions) => ({
|
||||
content: "",
|
||||
autofocus: true,
|
||||
extensions: [
|
||||
Code,
|
||||
CodeBlock,
|
||||
Document,
|
||||
Dropcursor,
|
||||
Gapcursor,
|
||||
History,
|
||||
Paragraph,
|
||||
Text,
|
||||
submitOnEnter ? getModifiedHardBreakExtension() : HardBreakExtension,
|
||||
LinkExtension.extend({
|
||||
addNodeView: () => SvelteNodeViewRenderer(EditLink),
|
||||
}),
|
||||
Bolt11Extension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(EditBolt11)})),
|
||||
NProfileExtension.extend({
|
||||
addNodeView: () => SvelteNodeViewRenderer(EditMention),
|
||||
addProseMirrorPlugins() {
|
||||
return [
|
||||
createSuggestions({
|
||||
char: "@",
|
||||
name: "nprofile",
|
||||
editor: this.editor,
|
||||
search: profileSearch,
|
||||
select: (pubkey: string, props: any) => {
|
||||
const relays = getPubkeyHints(pubkey)
|
||||
const nprofile = nprofileEncode({pubkey, relays})
|
||||
|
||||
return props.command({pubkey, nprofile, relays})
|
||||
},
|
||||
suggestionComponent: SuggestionProfile,
|
||||
suggestionsComponent: Suggestions,
|
||||
}),
|
||||
]
|
||||
},
|
||||
}),
|
||||
NEventExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(EditEvent)})),
|
||||
NAddrExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(EditEvent)})),
|
||||
ImageExtension.extend(
|
||||
asInline({addNodeView: () => SvelteNodeViewRenderer(EditImage)}),
|
||||
).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}),
|
||||
VideoExtension.extend(
|
||||
asInline({addNodeView: () => SvelteNodeViewRenderer(EditVideo)}),
|
||||
).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}),
|
||||
FileUploadExtension.configure({
|
||||
immediateUpload: false,
|
||||
sign: (event: StampedEvent) => {
|
||||
loading.set(true)
|
||||
return props.command({pubkey, nprofile, relays})
|
||||
},
|
||||
suggestionComponent: SuggestionProfile,
|
||||
suggestionsComponent: Suggestions,
|
||||
}),
|
||||
]
|
||||
},
|
||||
}),
|
||||
NEventExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(EditEvent)})),
|
||||
NAddrExtension.extend(asInline({addNodeView: () => SvelteNodeViewRenderer(EditEvent)})),
|
||||
ImageExtension.extend(
|
||||
asInline({addNodeView: () => SvelteNodeViewRenderer(EditImage)}),
|
||||
).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}),
|
||||
VideoExtension.extend(
|
||||
asInline({addNodeView: () => SvelteNodeViewRenderer(EditVideo)}),
|
||||
).configure({defaultUploadUrl: "https://nostr.build", defaultUploadType: "nip96"}),
|
||||
FileUploadExtension.configure({
|
||||
immediateUpload: false,
|
||||
sign: (event: StampedEvent) => {
|
||||
loading.set(true)
|
||||
|
||||
return signer.get()!.sign(event)
|
||||
},
|
||||
onComplete: () => {
|
||||
loading.set(false)
|
||||
submit()
|
||||
},
|
||||
}),
|
||||
],
|
||||
return signer.get()!.sign(event)
|
||||
},
|
||||
onComplete: () => {
|
||||
loading.set(false)
|
||||
submit()
|
||||
},
|
||||
}),
|
||||
],
|
||||
onTransaction() {
|
||||
// @ts-ignore
|
||||
console.log(this.getJSON())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
type ViewOptions = {
|
||||
content: string
|
||||
|
||||
@@ -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 {choice} from "@welshman/lib"
|
||||
|
||||
@@ -8,10 +8,15 @@ export const asInline = (extend: Record<string, any>) => ({
|
||||
...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>>(
|
||||
match: RegExpMatchArray,
|
||||
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) => {
|
||||
const results: JSONContent[] = []
|
||||
|
||||
Reference in New Issue
Block a user