Last active
August 29, 2022 11:21
-
-
Save zbeyens/dfcf97a8cb2af991d91ca506d500b74a to your computer and use it in GitHub Desktop.
normalized leaf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This gist is using a zustood store