);
}
export function BaseTextBlock(props: BlockProps & { className?: string }) {
let headingLevel = useEntity(props.entityID, "block/heading-level");
let alignment =
useEntity(props.entityID, "block/text-alignment")?.data.value || "left";
let rep = useReplicache();
let selected = useUIState(
(s) => !!s.selectedBlocks.find((b) => b.value === props.entityID),
);
let focused = useUIState((s) => s.focusedEntity?.entityID === props.entityID);
let alignmentClass = {
left: "text-left",
right: "text-right",
center: "text-center",
justify: "text-justify",
}[alignment];
let editorState = useEditorStates(
(s) => s.editorStates[props.entityID],
)?.editor;
const {
viewRef,
mentionOpen,
mentionCoords,
openMentionAutocomplete,
handleMentionSelect,
handleMentionOpenChange,
} = useMentionState(props.entityID);
let { mountRef, actionTimeout } = useMountProsemirror({
props,
openMentionAutocomplete,
});
return (
<>
{
if (
["***", "---", "___"].includes(
editorState?.doc.textContent.trim() || "",
)
) {
await rep.rep?.mutate.assertFact({
entity: props.entityID,
attribute: "block/type",
data: { type: "block-type-union", value: "horizontal-rule" },
});
}
if (actionTimeout.current) {
rep.undoManager.endGroup();
window.clearTimeout(actionTimeout.current);
actionTimeout.current = null;
}
}}
onFocus={() => {
handleMentionOpenChange(false);
setTimeout(() => {
useUIState.getState().setSelectedBlock(props);
useUIState.setState(() => ({
focusedEntity: {
entityType: "block",
entityID: props.entityID,
parent: props.parent,
},
}));
}, 5);
}}
id={elementId.block(props.entityID).text}
// unless we break *only* on urls, this is better than tailwind 'break-all'
// b/c break-all can cause breaks in the middle of words, but break-word still
// forces break if a single text string (e.g. a url) spans more than a full line
style={{ wordBreak: "break-word" }}
className={`
${alignmentClass}
grow resize-none align-top whitespace-pre-wrap bg-transparent
outline-hidden
${props.type === "heading" ? HeadingStyle[headingLevel?.data.value || 1] : ""}
${props.className}`}
ref={mountRef}
/>
{focused && (
)}
{editorState?.doc.textContent.length === 0 &&
props.previousBlock === null &&
props.nextBlock === null ? (
// if this is the only block on the page and is empty or is a canvas, show placeholder
) : editorState?.doc.textContent.length === 0 && focused ? (
// if not the only block on page but is the block is empty and selected, but NOT multiselected show add button
) : null}
{editorState?.doc.textContent.startsWith("/") && selected && (
)}
>
);
}
const BlockifyLink = (props: {
entityID: string;
editorState: EditorState | undefined;
}) => {
let [loading, setLoading] = useState(false);
let { editorState } = props;
let rep = useReplicache();
let smoker = useSmoker();
let isLocked = useEntity(props.entityID, "block/is-locked");
let focused = useUIState((s) => s.focusedEntity?.entityID === props.entityID);
let isBlueskyPost =
editorState?.doc.textContent.includes("bsky.app/") &&
editorState?.doc.textContent.includes("post");
// only if the line stats with http or https and doesn't have other content
// if its bluesky, change text to embed post
if (
!isLocked &&
focused &&
editorState &&
betterIsUrl(editorState.doc.textContent) &&
!editorState.doc.textContent.includes(" ")
) {
return (
);
} else return null;
};
const CommandOptions = (props: BlockProps & { className?: string }) => {
let rep = useReplicache();
let entity_set = useEntitySetContext();
let { data: pub } = useLeafletPublicationData();
return (