import { schema } from "components/Blocks/TextBlock/schema";
import { EditorState, TextSelection } from "prosemirror-state";
import { useUIState } from "src/useUIState";
import { ToolbarButton } from ".";
import { useEffect, useState } from "react";
import { Separator } from "components/Layout";
import { MarkType } from "prosemirror-model";
import { setEditorState, useEditorStates } from "src/state/useEditorState";
import { rangeHasMark } from "src/utils/prosemirror/rangeHasMark";
import { Input } from "components/Input";
import { useReplicache } from "src/replicache";
import { CheckTiny } from "components/Icons/CheckTiny";
import { LinkSmall } from "components/Icons/LinkSmall";
export function LinkButton(props: { setToolbarState: (s: "link") => void }) {
let focusedBlock = useUIState((s) => s.focusedEntity);
let focusedEditor = useEditorStates((s) =>
focusedBlock ? s.editorStates[focusedBlock.entityID] : null,
);
let isLink;
if (focusedEditor) {
let { to, from, $cursor } = focusedEditor.editor.selection as TextSelection;
if ($cursor) isLink = !!schema.marks.link.isInSet($cursor.marks());
if (to !== from)
isLink = !!rangeHasMark(
focusedEditor.editor,
schema.marks.link,
from,
to,
);
}
return (
{
e.preventDefault();
props.setToolbarState("link");
}}
disabled={
!focusedEditor || (focusedEditor?.editor.selection.empty && !isLink)
}
tooltipContent={
Inline Link
}
>
);
}
export function InlineLinkToolbar(props: { onClose: () => void }) {
let focusedBlock = useUIState((s) => s.focusedEntity);
let focusedEditor = useEditorStates((s) =>
focusedBlock ? s.editorStates[focusedBlock.entityID] : null,
);
let { undoManager } = useReplicache();
useEffect(() => {
if (focusedEditor) {
let isLink;
let { to, from, $cursor } = focusedEditor.editor
.selection as TextSelection;
if ($cursor) isLink = !!schema.marks.link.isInSet($cursor.marks());
if (to !== from)
isLink = !!rangeHasMark(
focusedEditor.editor,
schema.marks.link,
from,
to,
);
if (isLink) return;
}
if (focusedEditor?.editor.selection.empty) props.onClose();
}, [focusedEditor, props]);
let content = "";
let start: number | null = null;
let end: number | null = null;
if (focusedEditor) {
let { to, from, $cursor } = focusedEditor.editor.selection as TextSelection;
if (to !== from) {
start = from;
end = to;
} else {
let markRange = findMarkRange(focusedEditor.editor, schema.marks.link);
start = markRange.start;
end = markRange.end;
}
if ($cursor) {
let link = $cursor.marks().find((f) => f.type === schema.marks.link);
if (link) {
content = link.attrs.href;
}
}
}
let [linkValue, setLinkValue] = useState(content);
let setLink = () => {
let href =
!linkValue.startsWith("http") &&
!linkValue.startsWith("mailto") &&
!linkValue.startsWith("tel:")
? `https://${linkValue}`
: linkValue;
let editor = focusedEditor?.editor;
if (!editor || start === null || !end || !focusedBlock) return;
let tr = editor.tr;
tr.addMark(start, end, schema.marks.link.create({ href }));
tr.setSelection(TextSelection.create(tr.doc, tr.selection.to));
let oldState = editor;
let newState = editor.apply(tr);
undoManager.add({
undo: () => {
if (!focusedEditor?.view?.hasFocus()) focusedEditor?.view?.focus();
setEditorState(focusedBlock.entityID, {
editor: oldState,
});
},
redo: () => {
if (!focusedEditor?.view?.hasFocus()) focusedEditor?.view?.focus();
setEditorState(focusedBlock.entityID, {
editor: newState,
});
},
});
setEditorState(focusedBlock?.entityID, {
editor: newState,
});
props.onClose();
};
return (
);
}
function findMarkRange(state: EditorState, markType: MarkType) {
const { from, $from } = state.selection;
// Find the start of the mark
let start = from;
let startPos = $from;
while (
startPos.parent.inlineContent &&
startPos.nodeBefore &&
startPos.nodeBefore.marks.some((mark) => mark.type === markType)
) {
start -= startPos.nodeBefore.nodeSize;
startPos = state.doc.resolve(start);
}
// Find the end of the mark
let end = from;
let endPos = $from;
while (
endPos.parent.inlineContent &&
endPos.nodeAfter &&
endPos.nodeAfter.marks.some((mark) => mark.type === markType)
) {
end += endPos.nodeAfter.nodeSize;
endPos = state.doc.resolve(end);
}
return { start, end };
}