Skip to content

Instantly share code, notes, and snippets.

@zbeyens
Last active August 29, 2022 11:21
Show Gist options
  • Save zbeyens/dfcf97a8cb2af991d91ca506d500b74a to your computer and use it in GitHub Desktop.
Save zbeyens/dfcf97a8cb2af991d91ca506d500b74a to your computer and use it in GitHub Desktop.
normalized leaf
export const MARK_THREAD = 'thread'
export const getThreadKey = (id: string) => MARK_THREAD + '_' + id
export const isThreadKey = (key: string) => key.startsWith(MARK_THREAD + '_')
export const createThreadPlugin = createPluginFactory({
key: MARK_THREAD,
isLeaf: true,
component: ThreadLeaf,
withOverrides: editor => {
const { normalizeNode, insertBreak } = editor
editor.insertBreak = () => {
removeThreadMark(editor)
insertBreak()
}
editor.normalizeNode = entry => {
const [node, path] = entry
// Unset MARK_THREAD prop when there is no threads
if (node[MARK_THREAD]) {
if (getThreadCount(node as any) < 1) {
Transforms.unsetNodes(editor, MARK_THREAD, { at: path })
return
}
}
normalizeNode(entry)
}
return editor
},
})
export const getThreadCount = (node: ThreadText) => {
let threadCount = 0
Object.keys(node).forEach(key => {
if (isThreadKey(key)) threadCount++
})
return threadCount
}
export const getThreadKeys = (node: ThreadText) => {
const keys: string[] = []
Object.keys(node).forEach(key => {
if (isThreadKey(key)) keys.push(key)
})
return keys
}
export const addThreadMark = (editor: TEditor, _value?: ThreadType) => {
const value: ThreadType | undefined = _value ? { ..._value } : undefined
const id = value?.id ?? cuid()
// default thread
const thread: ThreadType = value ?? {
id: id,
// props
}
Editor.withoutNormalizing(editor, () => {
const key = getThreadKey(id)
// add thread prop to inline elements
const entries = getNodes(editor, {
// TODO
})
Array.from(entries).forEach(([node, path]) => {
setNodes(
editor,
{
[key]: thread,
},
{ at: path },
)
})
editor.addMark(MARK_THREAD, true)
// add thread mark to leaves
editor.addMark(key, thread)
ReactEditor.deselect(editor)
})
return thread
}
export const removeThreadMark = (editor: TEditor) => {
const nodeEntry = findThreadNode(editor)
if (!nodeEntry) return
const keys = getThreadKeys(nodeEntry[0])
withoutNormalizing(editor, () => {
keys.forEach(key => {
editor.removeMark(key)
})
editor.removeMark(MARK_THREAD)
})
}
export const setThreadValue = (editor: TEditor, value: ThreadType) => {
const activeThreadConfig = editorSelectors.activeThreadConfig()
if (!activeThreadConfig) return
const { id } = activeThreadConfig
const entries = getThreadNodes(editor, id)
const key = getThreadKey(id)
Editor.withoutNormalizing(editor, () => {
entries.forEach(([, path]) => {
Transforms.setNodes(
editor,
{
[key]: value,
},
{ at: path },
)
})
})
}
export const unsetThreadNode = (editor: MyEditor, { id, at: _at }: { id: string; at?: Path }) => {
const threadKey = getThreadKey(id)
const at = _at ?? editor.selection?.focus.path
if (!at) return
const selectedNode = getNode(editor, at)
if (!selectedNode?.[threadKey]) {
// Thread blocks
unsetNodes(editor, threadKey as any, {
match: n => isBlock(editor, n),
mode: 'all',
})
return
}
// current node
const paths: Path[] = [at]
// next siblings
let currPath = at
while (true) {
const nextPath = Path.next(currPath)
const node = getNode(editor, nextPath)
if (node?.[threadKey]) {
paths.push(nextPath)
} else {
break
}
currPath = nextPath
}
// prev siblings
currPath = at
while (true) {
const nextPath = getPreviousPath(currPath)
if (!nextPath) break
const node = getNode(editor, nextPath)
if (node?.[threadKey]) {
paths.push(nextPath)
} else {
break
}
currPath = nextPath
}
withoutNormalizing(editor, () => {
paths.forEach(path => {
unsetNodes<MyThreadNode>(editor, threadKey, {
at: path,
})
})
})
}
export const unsetThreadNodes = (editor: TEditor, id: string) => {
const entries = getThreadNodes(editor, id)
Editor.withoutNormalizing(editor, () => {
entries.forEach(([node, path]) => {
unsetThreadNode(editor, { id, at: path })
})
})
}
export const findThreadNode = (editor: TEditor, options?: FindNodeOptions) => {
return findNode(editor, {
match: n => n[MARK_THREAD],
...options,
})
}
export const findThreadNodeById = (editor: TEditor, id: string) => {
return findNode(editor, {
at: [],
match: n => n[getThreadKey(id)],
})
}
export const getThreadNodes = (editor: TEditor, id: string) => {
return Array.from(
getNodes(editor, {
at: [],
match: n => n[getThreadKey(id)],
}),
)
}
export const ThreadLeaf = props => {
const { attributes, children, leaf } = props
const [threads, setThreads] = useState<ThreadType[]>([])
const { bg, border } = useThreadColors(leaf)
const thread = null
Object.keys(leaf).some(key => {
if (isThreadKey(key)) {
thread = leaf[key]
return true
}
})
return (
<span
{...attributes}
onMouseDown={() => {
editorActions.activeThread(thread)
}}
onMouseEnter={() => {
editorActions.hoverThreadId(thread.id)
}}
onMouseLeave={() => {
editorActions.hoverThreadId('')
}}
// TODO: styles
>
{children}
</span>
)
}
@zbeyens
Copy link
Author

zbeyens commented Apr 23, 2022

This gist is using a zustood store

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment