forked from coracle/flotilla
add room mentions and clickable room/relay refs
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import {mergeAttributes, Node} from "@tiptap/core"
|
||||
import {RoomReferenceNodeView} from "@app/editor/RoomReferenceNodeView"
|
||||
|
||||
export const RoomReferenceExtension = Node.create({
|
||||
name: "roomref",
|
||||
|
||||
atom: true,
|
||||
|
||||
inline: true,
|
||||
|
||||
group: "inline",
|
||||
|
||||
selectable: true,
|
||||
|
||||
priority: 1000,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
url: {default: undefined},
|
||||
h: {default: undefined},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [{tag: `span[data-type="${this.name}"]`}]
|
||||
},
|
||||
|
||||
renderHTML({HTMLAttributes}) {
|
||||
return ["span", mergeAttributes(HTMLAttributes, {"data-type": this.name}), "~"]
|
||||
},
|
||||
|
||||
renderText({node}) {
|
||||
const url = typeof node.attrs.url === "string" ? node.attrs.url : ""
|
||||
const h = typeof node.attrs.h === "string" ? node.attrs.h : ""
|
||||
|
||||
return `${url}'${h}`
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return RoomReferenceNodeView
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
import type {NodeViewRendererProps} from "@tiptap/core"
|
||||
import {displayRelayUrl} from "@welshman/util"
|
||||
import {deriveRoom} from "@app/core/state"
|
||||
|
||||
export const RoomReferenceNodeView = ({node}: NodeViewRendererProps) => {
|
||||
const dom = document.createElement("span")
|
||||
const url = typeof node.attrs.url === "string" ? node.attrs.url : ""
|
||||
const h = typeof node.attrs.h === "string" ? node.attrs.h : ""
|
||||
const room = deriveRoom(url, h)
|
||||
|
||||
dom.classList.add("tiptap-object")
|
||||
|
||||
const unsubRoom = room.subscribe($room => {
|
||||
dom.textContent = `~${displayRelayUrl(url)} / ${$room.name || h}`
|
||||
})
|
||||
|
||||
return {
|
||||
dom,
|
||||
destroy: () => {
|
||||
unsubRoom()
|
||||
},
|
||||
selectNode() {
|
||||
dom.classList.add("tiptap-active")
|
||||
},
|
||||
deselectNode() {
|
||||
dom.classList.remove("tiptap-active")
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import {displayRelayUrl} from "@welshman/util"
|
||||
import RoomNameWithImage from "@app/components/RoomNameWithImage.svelte"
|
||||
import {splitRoomId} from "@app/core/state"
|
||||
|
||||
type Props = {
|
||||
value: string
|
||||
}
|
||||
|
||||
const {value}: Props = $props()
|
||||
const [url = "", h = ""] = splitRoomId(value)
|
||||
</script>
|
||||
|
||||
<div class="max-w-full overflow-hidden text-ellipsis flex flex-col">
|
||||
<RoomNameWithImage {url} {h} />
|
||||
<span class="text-primary text-sm">{displayRelayUrl(url)}<span> </span></span>
|
||||
</div>
|
||||
+66
-4
@@ -4,7 +4,7 @@ import {get, derived} from "svelte/store"
|
||||
import {Router} from "@welshman/router"
|
||||
import {dec, inc} from "@welshman/lib"
|
||||
import {throttled} from "@welshman/store"
|
||||
import type {PublishedProfile} from "@welshman/util"
|
||||
import type {PublishedProfile, RoomMeta} from "@welshman/util"
|
||||
import {
|
||||
createSearch,
|
||||
profiles,
|
||||
@@ -14,13 +14,27 @@ import {
|
||||
getWotGraph,
|
||||
} from "@welshman/app"
|
||||
import type {FileAttributes} from "@welshman/editor"
|
||||
import {Editor, MentionSuggestion, WelshmanExtension, editorProps} from "@welshman/editor"
|
||||
import {NativeClipboardPasteExtension} from "@app/editor/clipboard"
|
||||
import {
|
||||
Editor,
|
||||
MentionSuggestion,
|
||||
TippySuggestion,
|
||||
WelshmanExtension,
|
||||
editorProps,
|
||||
} from "@welshman/editor"
|
||||
import {escapeHtml} from "@lib/html"
|
||||
import {makeMentionNodeView} from "@app/editor/MentionNodeView"
|
||||
import ProfileSuggestion from "@app/editor/ProfileSuggestion.svelte"
|
||||
import {RoomReferenceExtension} from "@app/editor/RoomReferenceExtension"
|
||||
import RoomSuggestion from "@app/editor/RoomSuggestion.svelte"
|
||||
import {NativeClipboardPasteExtension} from "@app/editor/clipboard"
|
||||
import {uploadFile} from "@app/core/commands"
|
||||
import {deriveSpaceMembers} from "@app/core/state"
|
||||
import {
|
||||
deriveSpaceMembers,
|
||||
makeRoomId,
|
||||
splitRoomId,
|
||||
userSpaceUrls,
|
||||
roomsByUrl,
|
||||
} from "@app/core/state"
|
||||
import {pushToast} from "@app/util/toast"
|
||||
|
||||
export const makeEditor = async ({
|
||||
@@ -83,11 +97,36 @@ export const makeEditor = async ({
|
||||
},
|
||||
)
|
||||
|
||||
const roomReferenceSearch = derived(
|
||||
[throttled(800, userSpaceUrls), throttled(800, roomsByUrl)],
|
||||
([$userSpaceUrls, $roomsByUrl]) => {
|
||||
const roomIdByMeta = new WeakMap<RoomMeta, string>()
|
||||
const options: RoomMeta[] = []
|
||||
|
||||
for (const roomUrl of $userSpaceUrls) {
|
||||
for (const room of $roomsByUrl.get(roomUrl) || []) {
|
||||
roomIdByMeta.set(room, makeRoomId(roomUrl, room.h))
|
||||
options.push(room)
|
||||
}
|
||||
}
|
||||
|
||||
return createSearch(options, {
|
||||
getValue: item => roomIdByMeta.get(item) || item.h,
|
||||
fuseOptions: {
|
||||
keys: ["name", "h"],
|
||||
threshold: 0.3,
|
||||
shouldSort: false,
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
|
||||
const ed = new Editor({
|
||||
content: typeof content === "string" ? escapeHtml(content) : content,
|
||||
editorProps,
|
||||
element: document.createElement("div"),
|
||||
extensions: [
|
||||
RoomReferenceExtension,
|
||||
WelshmanExtension.configure({
|
||||
submit,
|
||||
extensions: {
|
||||
@@ -129,6 +168,29 @@ export const makeEditor = async ({
|
||||
|
||||
mount(ProfileSuggestion, {target, props: {value, url}})
|
||||
|
||||
return target
|
||||
},
|
||||
}),
|
||||
TippySuggestion({
|
||||
char: "~",
|
||||
name: "roomref",
|
||||
editor: (this as any).editor,
|
||||
search: (term: string) => get(roomReferenceSearch).searchValues(term),
|
||||
updateSignal: roomReferenceSearch,
|
||||
select: (id: string, props) => {
|
||||
const [roomUrl, h] = splitRoomId(id)
|
||||
|
||||
if (!roomUrl || !h) {
|
||||
return
|
||||
}
|
||||
|
||||
return props.command({url: roomUrl, h})
|
||||
},
|
||||
createSuggestion: (value: string) => {
|
||||
const target = document.createElement("div")
|
||||
|
||||
mount(RoomSuggestion, {target, props: {value}})
|
||||
|
||||
return target
|
||||
},
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user