Remove svelte from editor
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import {HardBreak, type HardBreakOptions} from "@tiptap/extension-hard-break"
|
||||
|
||||
export interface BreakOrSubmitOptions extends HardBreakOptions {
|
||||
/** Handler for when enter is pressed. */
|
||||
submit: () => void
|
||||
|
||||
/** Whether to call `submit` on unmodified Enter */
|
||||
aggressive?: boolean
|
||||
}
|
||||
|
||||
export const BreakOrSubmit = HardBreak.extend<BreakOrSubmitOptions>({
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
"Shift-Enter": () => this.editor.commands.setHardBreak(),
|
||||
"Mod-Enter": () => {
|
||||
this.options.submit()
|
||||
|
||||
return true
|
||||
},
|
||||
Enter: () => {
|
||||
if (this.options.aggressive) {
|
||||
this.options.submit()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,75 @@
|
||||
import {InputRule, mergeAttributes, Node, PasteRule} from "@tiptap/core"
|
||||
import type {CodeOptions} from "@tiptap/extension-code"
|
||||
|
||||
const inputRegex = /(?:^|\s)(`(?!\s+`)((?:[^`]+))`(?!\s+`))$/
|
||||
|
||||
const pasteRegex = /(?:^|\s)(`(?!\s+`)((?:[^`]+))`(?!\s+`))/g
|
||||
|
||||
export type CodeInlineOptions = object
|
||||
|
||||
export const CodeInline = Node.create<CodeOptions>({
|
||||
name: "codeInline",
|
||||
content: "text*",
|
||||
marks: "",
|
||||
group: "inline",
|
||||
inline: true,
|
||||
code: true,
|
||||
defining: true,
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
parseHTML() {
|
||||
return [{tag: "code"}]
|
||||
},
|
||||
renderHTML({HTMLAttributes}) {
|
||||
return ["code", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
|
||||
},
|
||||
addKeyboardShortcuts() {
|
||||
return {
|
||||
// remove code block when at start of document or code block is empty
|
||||
Backspace: () => {
|
||||
const {empty, $anchor, $from} = this.editor.state.selection
|
||||
|
||||
const isAtEnd = $from.parentOffset === $from.parent.nodeSize - 2
|
||||
|
||||
if (!empty || $anchor.parent.type.name !== this.name) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (isAtEnd) {
|
||||
const {tr} = this.editor.state
|
||||
tr.delete($from.start(), $from.end() + 1)
|
||||
this.editor.view.dispatch(tr)
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
}
|
||||
},
|
||||
addInputRules() {
|
||||
return [
|
||||
new InputRule({
|
||||
find: inputRegex,
|
||||
handler: ({state, range, match}) => {
|
||||
const textNode = state.schema.text(match[2])
|
||||
const codeNode = this.type.create(null, textNode)
|
||||
state.tr.replaceWith(range.from, range.to, codeNode).insertText(" ")
|
||||
},
|
||||
}),
|
||||
]
|
||||
},
|
||||
addPasteRules() {
|
||||
return [
|
||||
new PasteRule({
|
||||
find: pasteRegex,
|
||||
handler: ({state, range, match}) => {
|
||||
const textNode = state.schema.text(match[2])
|
||||
const codeNode = this.type.create(null, textNode)
|
||||
state.tr.replaceWith(range.from, range.to, codeNode).insertText(" ")
|
||||
},
|
||||
}),
|
||||
]
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,227 @@
|
||||
import type {StampedEvent, SignedEvent} from "@welshman/util"
|
||||
import {deepMergeLeft} from "@welshman/lib"
|
||||
import type {Extensions, AnyExtension} from "@tiptap/core"
|
||||
import {CodeBlock} from "@tiptap/extension-code-block"
|
||||
import type {CodeBlockOptions} from "@tiptap/extension-code-block"
|
||||
import {Document} from "@tiptap/extension-document"
|
||||
import {Dropcursor} from "@tiptap/extension-dropcursor"
|
||||
import type {DropcursorOptions} from "@tiptap/extension-dropcursor"
|
||||
import {Gapcursor} from "@tiptap/extension-gapcursor"
|
||||
import {History} from "@tiptap/extension-history"
|
||||
import type {HistoryOptions} from "@tiptap/extension-history"
|
||||
import {Paragraph} from "@tiptap/extension-paragraph"
|
||||
import type {ParagraphOptions} from "@tiptap/extension-paragraph"
|
||||
import {Text} from "@tiptap/extension-text"
|
||||
import {Placeholder} from "@tiptap/extension-placeholder"
|
||||
import type {PlaceholderOptions} from "@tiptap/extension-placeholder"
|
||||
import type {
|
||||
NostrOptions,
|
||||
FileUploadOptions,
|
||||
ImageOptions,
|
||||
LinkOptions,
|
||||
NSecRejectOptions,
|
||||
} from "nostr-editor"
|
||||
import {
|
||||
NostrExtension,
|
||||
Bolt11Extension,
|
||||
FileUploadExtension,
|
||||
ImageExtension,
|
||||
LinkExtension,
|
||||
NAddrExtension,
|
||||
NEventExtension,
|
||||
NProfileExtension,
|
||||
TagExtension,
|
||||
VideoExtension,
|
||||
NSecRejectExtension,
|
||||
} from "nostr-editor"
|
||||
import {WordCount} from "./WordCount.js"
|
||||
import {CodeInline, type CodeInlineOptions} from "./CodeInline.js"
|
||||
import {BreakOrSubmit, type BreakOrSubmitOptions} from "./BreakOrSubmit.js"
|
||||
import {MentionNodeView, Bolt11NodeView, MediaNodeView, EventNodeView} from "../nodeviews/index.js"
|
||||
|
||||
export type ChildExtensionOptions<C = any, E = any> =
|
||||
| false
|
||||
| {
|
||||
extend?: Partial<E>
|
||||
config?: Partial<C>
|
||||
}
|
||||
|
||||
export type EmptyOptions = object
|
||||
|
||||
export type WelshmanExtensionOptions = {
|
||||
bolt11?: false
|
||||
breakOrSubmit?: ChildExtensionOptions<BreakOrSubmitOptions>
|
||||
codeInline?: ChildExtensionOptions<CodeInlineOptions>
|
||||
codeBlock?: ChildExtensionOptions<CodeBlockOptions>
|
||||
document?: false
|
||||
dropcursor?: ChildExtensionOptions<DropcursorOptions>
|
||||
fileUpload?: ChildExtensionOptions<FileUploadOptions>
|
||||
gapcursor?: false
|
||||
history?: ChildExtensionOptions<HistoryOptions>
|
||||
image?: ChildExtensionOptions<ImageOptions>
|
||||
link?: ChildExtensionOptions<LinkOptions>
|
||||
naddr?: ChildExtensionOptions<EmptyOptions>
|
||||
nevent?: ChildExtensionOptions<EmptyOptions>
|
||||
nprofile?: ChildExtensionOptions<EmptyOptions>
|
||||
nsecReject?: ChildExtensionOptions<NSecRejectOptions>
|
||||
paragraph?: ChildExtensionOptions<ParagraphOptions>
|
||||
placeholder?: ChildExtensionOptions<PlaceholderOptions>
|
||||
tag?: false
|
||||
text?: false
|
||||
video?: false
|
||||
wordCount?: false
|
||||
}
|
||||
|
||||
export interface WelshmanOptions extends NostrOptions {
|
||||
submit?: () => void
|
||||
sign?: (event: StampedEvent) => Promise<SignedEvent>
|
||||
defaultUploadUrl?: string
|
||||
defaultUploadType?: "nip96" | "blossom"
|
||||
extensions?: WelshmanExtensionOptions
|
||||
}
|
||||
|
||||
export const WelshmanExtension = NostrExtension.extend<WelshmanOptions>({
|
||||
// Return an empty object or else options can't be passed
|
||||
addOptions() {
|
||||
return {}
|
||||
},
|
||||
|
||||
addExtensions() {
|
||||
const {
|
||||
sign,
|
||||
submit,
|
||||
defaultUploadUrl = "https://nostr.build",
|
||||
defaultUploadType = "nip96",
|
||||
} = this.options
|
||||
|
||||
if (!sign) throw new Error("sign is a required argument to WelshmanExtension")
|
||||
if (!submit) throw new Error("submit is a required argument to WelshmanExtension")
|
||||
|
||||
const extensionOptions = deepMergeLeft(this.options.extensions || {}, {
|
||||
codeInline: {
|
||||
extend: {
|
||||
renderText: (props: any) => "`" + props.node.textContent + "`",
|
||||
},
|
||||
},
|
||||
codeBlock: {
|
||||
extend: {
|
||||
renderText: (props: any) => "```" + props.node.textContent + "```",
|
||||
},
|
||||
},
|
||||
bolt11: {
|
||||
config: {
|
||||
inline: true,
|
||||
group: "inline",
|
||||
},
|
||||
extend: {
|
||||
addNodeView: () => Bolt11NodeView,
|
||||
},
|
||||
},
|
||||
image: {
|
||||
config: {
|
||||
inline: true,
|
||||
group: "inline",
|
||||
defaultUploadUrl,
|
||||
defaultUploadType,
|
||||
},
|
||||
extend: {
|
||||
addNodeView: () => MediaNodeView,
|
||||
},
|
||||
},
|
||||
video: {
|
||||
config: {
|
||||
inline: true,
|
||||
group: "inline",
|
||||
defaultUploadUrl,
|
||||
defaultUploadType,
|
||||
},
|
||||
extend: {
|
||||
addNodeView: () => MediaNodeView,
|
||||
},
|
||||
},
|
||||
nevent: {
|
||||
config: {
|
||||
inline: true,
|
||||
group: "inline",
|
||||
},
|
||||
extend: {
|
||||
addNodeView: () => EventNodeView,
|
||||
},
|
||||
},
|
||||
naddr: {
|
||||
config: {
|
||||
inline: true,
|
||||
group: "inline",
|
||||
},
|
||||
extend: {
|
||||
addNodeView: () => EventNodeView,
|
||||
},
|
||||
},
|
||||
nprofile: {
|
||||
extend: {
|
||||
addNodeView: () => MentionNodeView,
|
||||
},
|
||||
},
|
||||
breakOrSubmit: {
|
||||
config: {
|
||||
submit,
|
||||
},
|
||||
},
|
||||
fileUpload: {
|
||||
config: {
|
||||
sign,
|
||||
immediateUpload: true,
|
||||
allowedMimeTypes: [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/gif",
|
||||
"image/webp",
|
||||
"video/mp4",
|
||||
"video/mpeg",
|
||||
"video/webm",
|
||||
],
|
||||
},
|
||||
},
|
||||
}) as WelshmanExtensionOptions
|
||||
|
||||
const extensions: Extensions = []
|
||||
|
||||
const addExtension = (extension: AnyExtension, options?: ChildExtensionOptions | false) => {
|
||||
if (options === false) return
|
||||
|
||||
if (options?.extend) {
|
||||
extension = extension.extend(options.extend)
|
||||
}
|
||||
|
||||
if (options?.config) {
|
||||
extension = extension.configure(options.config)
|
||||
}
|
||||
|
||||
extensions.push(extension)
|
||||
}
|
||||
|
||||
addExtension(Document, extensionOptions.document)
|
||||
addExtension(Text, extensionOptions.text)
|
||||
addExtension(Paragraph, extensionOptions.paragraph)
|
||||
addExtension(History, extensionOptions.history)
|
||||
addExtension(CodeBlock, extensionOptions.codeBlock)
|
||||
addExtension(CodeInline, extensionOptions.codeInline)
|
||||
addExtension(Dropcursor, extensionOptions.dropcursor)
|
||||
addExtension(FileUploadExtension, extensionOptions.fileUpload)
|
||||
addExtension(Gapcursor, extensionOptions.gapcursor)
|
||||
addExtension(BreakOrSubmit, extensionOptions.breakOrSubmit)
|
||||
addExtension(ImageExtension, extensionOptions.image)
|
||||
addExtension(LinkExtension, extensionOptions.link)
|
||||
addExtension(NAddrExtension, extensionOptions.naddr)
|
||||
addExtension(NEventExtension, extensionOptions.nevent)
|
||||
addExtension(NProfileExtension, extensionOptions.nprofile)
|
||||
addExtension(NSecRejectExtension, extensionOptions.nsecReject)
|
||||
addExtension(Placeholder, extensionOptions.placeholder)
|
||||
addExtension(TagExtension, extensionOptions.tag)
|
||||
addExtension(VideoExtension, extensionOptions.video)
|
||||
addExtension(Bolt11Extension, extensionOptions.bolt11)
|
||||
addExtension(WordCount, extensionOptions.wordCount)
|
||||
|
||||
return extensions
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
import {Extension} from "@tiptap/core"
|
||||
|
||||
export const WordCount = Extension.create({
|
||||
name: "wordCount",
|
||||
|
||||
addStorage() {
|
||||
return {
|
||||
words: 0,
|
||||
chars: 0,
|
||||
}
|
||||
},
|
||||
|
||||
onUpdate() {
|
||||
const text = this.editor.state.doc.textContent
|
||||
|
||||
this.storage.words = text.split(/\s+/).filter(word => word.length > 0).length
|
||||
this.storage.chars = text.length
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./BreakOrSubmit.js"
|
||||
export * from "./CodeInline.js"
|
||||
export * from "./Welshman.js"
|
||||
export * from "./WordCount.js"
|
||||
Reference in New Issue
Block a user