0 ? "sm:group-hover/list-marker:outline-secondary outline-transparent" : "outline-transparent"}`
- }`}
- />
+ {listStyle?.data.value === "ordered" ? (
+ editingNumber ? (
+
setNumberInputValue(e.target.value)}
+ onClick={(e) => e.stopPropagation()}
+ onBlur={handleNumberSave}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") {
+ e.preventDefault();
+ handleNumberSave();
+ } else if (e.key === "Escape") {
+ setEditingNumber(false);
+ }
+ }}
+ autoFocus
+ className="text-secondary font-normal text-right min-w-[2rem] w-[2.2rem] bg-transparent border border-accent focus:outline-none px-1"
+ />
+ ) : (
+
{
+ e.stopPropagation();
+ if (permissions.write && listStyle?.data.value === "ordered") {
+ setNumberInputValue(String(props.listData?.listNumber || 1));
+ setEditingNumber(true);
+ }
+ }}
+ >
+ {props.listData?.listNumber || 1}.
+
+ )
+ ) : (
+
0 ? "sm:group-hover/list-marker:outline-secondary outline-transparent" : "outline-transparent"}`
+ }`}
+ />
+ )}
{checklist && (
,
type: "text",
onSelect: async (rep, props, um) => {
@@ -164,6 +164,32 @@ export const blockCommands: Command[] = [
clearCommandSearchText(entity);
},
},
+ {
+ name: "Ordered List",
+ icon: ,
+ type: "text",
+ onSelect: async (rep, props, um) => {
+ let entity = await createBlockWithType(rep, props, "text");
+ await rep?.mutate.assertFact([
+ {
+ entity,
+ attribute: "block/is-list",
+ data: { value: true, type: "boolean" },
+ },
+ {
+ entity,
+ attribute: "block/list-style",
+ data: { value: "ordered", type: "list-style-union" },
+ },
+ {
+ entity,
+ attribute: "block/list-number",
+ data: { value: 1, type: "number" },
+ },
+ ]);
+ clearCommandSearchText(entity);
+ },
+ },
{
name: "Block Quote",
icon: ,
diff --git a/components/Blocks/MailboxBlock.tsx b/components/Blocks/MailboxBlock.tsx
index 1bc591cd..48ae280e 100644
--- a/components/Blocks/MailboxBlock.tsx
+++ b/components/Blocks/MailboxBlock.tsx
@@ -13,7 +13,7 @@ import { confirmEmailSubscription } from "actions/subscriptions/confirmEmailSubs
import { focusPage } from "src/utils/focusPage";
import { v7 } from "uuid";
import { sendPostToSubscribers } from "actions/subscriptions/sendPostToSubscribers";
-import { getBlocksWithType } from "src/hooks/queries/useBlocks";
+import { getBlocksWithType } from "src/replicache/getBlocks";
import { getBlocksAsHTML } from "src/utils/getBlocksAsHTML";
import { htmlToMarkdown } from "src/htmlMarkdownParsers";
import {
diff --git a/components/Blocks/TextBlock/inputRules.ts b/components/Blocks/TextBlock/inputRules.ts
index f20e9465..5c38b718 100644
--- a/components/Blocks/TextBlock/inputRules.ts
+++ b/components/Blocks/TextBlock/inputRules.ts
@@ -154,11 +154,44 @@ export const inputrules = (
if (propsRef.current.listData) return null;
let tr = state.tr;
tr.delete(0, 2);
- repRef.current?.mutate.assertFact({
- entity: propsRef.current.entityID,
- attribute: "block/is-list",
- data: { type: "boolean", value: true },
- });
+ repRef.current?.mutate.assertFact([
+ {
+ entity: propsRef.current.entityID,
+ attribute: "block/is-list",
+ data: { type: "boolean", value: true },
+ },
+ {
+ entity: propsRef.current.entityID,
+ attribute: "block/list-style",
+ data: { type: "list-style-union", value: "unordered" },
+ },
+ ]);
+ return tr;
+ }),
+
+ // Ordered List - respect the starting number typed
+ new InputRule(/^(\d+)\.\s$/, (state, match) => {
+ if (propsRef.current.listData) return null;
+ let tr = state.tr;
+ tr.delete(0, match[0].length);
+ const startNumber = parseInt(match[1], 10);
+ repRef.current?.mutate.assertFact([
+ {
+ entity: propsRef.current.entityID,
+ attribute: "block/is-list",
+ data: { type: "boolean", value: true },
+ },
+ {
+ entity: propsRef.current.entityID,
+ attribute: "block/list-style",
+ data: { type: "list-style-union", value: "ordered" },
+ },
+ {
+ entity: propsRef.current.entityID,
+ attribute: "block/list-number",
+ data: { type: "number", value: startNumber },
+ },
+ ]);
return tr;
}),
diff --git a/components/Blocks/TextBlock/keymap.ts b/components/Blocks/TextBlock/keymap.ts
index 1012c137..07081388 100644
--- a/components/Blocks/TextBlock/keymap.ts
+++ b/components/Blocks/TextBlock/keymap.ts
@@ -21,7 +21,7 @@ import { focusPage } from "src/utils/focusPage";
import { v7 } from "uuid";
import { scanIndex } from "src/replicache/utils";
import { indent, outdent } from "src/utils/list-operations";
-import { getBlocksWithType } from "src/hooks/queries/useBlocks";
+import { getBlocksWithType } from "src/replicache/getBlocks";
import { isTextBlock } from "src/utils/isTextBlock";
import { UndoManager } from "src/undoManager";
type PropsRef = RefObject<
@@ -369,11 +369,15 @@ const shifttab =
propsRef: RefObject,
repRef: RefObject | null>,
) =>
- () => {
+ async () => {
if (useUIState.getState().selectedBlocks.length > 1) return false;
if (!repRef.current) return false;
if (!repRef.current) return false;
- outdent(propsRef.current, propsRef.current.previousBlock, repRef.current);
+ let { foldedBlocks, toggleFold } = useUIState.getState();
+ await outdent(propsRef.current, propsRef.current.previousBlock, repRef.current, {
+ foldedBlocks,
+ toggleFold,
+ });
return true;
};
@@ -423,12 +427,40 @@ const enter =
y: position.data.position.y + box.height,
},
});
- if (propsRef.current.listData)
+ if (propsRef.current.listData) {
await repRef.current?.mutate.assertFact({
entity: newEntityID,
attribute: "block/is-list",
data: { type: "boolean", value: true },
});
+ // Copy list style for canvas blocks
+ let listStyle = await repRef.current?.query((tx) =>
+ scanIndex(tx).eav(propsRef.current.entityID, "block/list-style"),
+ );
+ if (listStyle?.[0])
+ await repRef.current?.mutate.assertFact({
+ entity: newEntityID,
+ attribute: "block/list-style",
+ data: {
+ type: "list-style-union",
+ value: listStyle[0].data.value,
+ },
+ });
+ // Set list number to next number if this is an ordered list
+ if (listStyle?.[0]) {
+ let listNumber = await repRef.current?.query((tx) =>
+ scanIndex(tx).eav(propsRef.current.entityID, "block/list-number"),
+ );
+ await repRef.current?.mutate.assertFact({
+ entity: newEntityID,
+ attribute: "block/list-number",
+ data: {
+ type: "number",
+ value: (listNumber?.[0]?.data.value || 0) + 1,
+ },
+ });
+ }
+ }
return;
}
if (propsRef.current.listData) {
@@ -499,6 +531,33 @@ const enter =
attribute: "block/is-list",
data: { type: "boolean", value: true },
});
+ // Copy list style (ordered/unordered) to new list item
+ let listStyle = await repRef.current?.query((tx) =>
+ scanIndex(tx).eav(propsRef.current.entityID, "block/list-style"),
+ );
+ if (listStyle?.[0])
+ await repRef.current?.mutate.assertFact({
+ entity: newEntityID,
+ attribute: "block/list-style",
+ data: {
+ type: "list-style-union",
+ value: listStyle[0].data.value,
+ },
+ });
+ // Set list number to next number if this is an ordered list
+ if (listStyle?.[0]) {
+ let listNumber = await repRef.current?.query((tx) =>
+ scanIndex(tx).eav(propsRef.current.entityID, "block/list-number"),
+ );
+ await repRef.current?.mutate.assertFact({
+ entity: newEntityID,
+ attribute: "block/list-number",
+ data: {
+ type: "number",
+ value: (listNumber?.[0]?.data.value || 0) + 1,
+ },
+ });
+ }
let checked = await repRef.current?.query((tx) =>
scanIndex(tx).eav(propsRef.current.entityID, "block/check-list"),
);
diff --git a/components/Blocks/useBlockKeyboardHandlers.ts b/components/Blocks/useBlockKeyboardHandlers.ts
index 18b1f43b..0b2a8793 100644
--- a/components/Blocks/useBlockKeyboardHandlers.ts
+++ b/components/Blocks/useBlockKeyboardHandlers.ts
@@ -91,15 +91,16 @@ type Args = {
const AllowedIfTextBlock = ["Tab"];
-function Tab({ e, props, rep }: Args) {
+async function Tab({ e, props, rep }: Args) {
// if tab or shift tab, indent or outdent
if (useUIState.getState().selectedBlocks.length > 1) return false;
+ let { foldedBlocks, toggleFold } = useUIState.getState();
if (e.shiftKey) {
e.preventDefault();
- outdent(props, props.previousBlock, rep);
+ await outdent(props, props.previousBlock, rep, { foldedBlocks, toggleFold });
} else {
e.preventDefault();
- if (props.previousBlock) indent(props, props.previousBlock, rep);
+ if (props.previousBlock) indent(props, props.previousBlock, rep, { foldedBlocks, toggleFold });
}
}
diff --git a/components/Blocks/useBlockMouseHandlers.ts b/components/Blocks/useBlockMouseHandlers.ts
index eca3cc68..6f99cf46 100644
--- a/components/Blocks/useBlockMouseHandlers.ts
+++ b/components/Blocks/useBlockMouseHandlers.ts
@@ -5,7 +5,7 @@ import { Block } from "./Block";
import { isTextBlock } from "src/utils/isTextBlock";
import { useEntitySetContext } from "components/EntitySetProvider";
import { useReplicache } from "src/replicache";
-import { getBlocksWithType } from "src/hooks/queries/useBlocks";
+import { getBlocksWithType } from "src/replicache/getBlocks";
import { focusBlock } from "src/utils/focusBlock";
import { useIsMobile } from "src/hooks/isMobile";
import { scrollIntoViewIfNeeded } from "src/utils/scrollIntoViewIfNeeded";
diff --git a/components/SelectionManager/index.tsx b/components/SelectionManager/index.tsx
index c822ac0a..81411ac0 100644
--- a/components/SelectionManager/index.tsx
+++ b/components/SelectionManager/index.tsx
@@ -6,7 +6,7 @@ import { scanIndex } from "src/replicache/utils";
import { focusBlock } from "src/utils/focusBlock";
import { useEditorStates } from "src/state/useEditorState";
import { useEntitySetContext } from "../EntitySetProvider";
-import { getBlocksWithType } from "src/hooks/queries/useBlocks";
+import { getBlocksWithType } from "src/replicache/getBlocks";
import { indent, outdent, outdentFull } from "src/utils/list-operations";
import { addShortcut, Shortcut } from "src/shortcuts";
import { elementId } from "src/utils/elementId";
@@ -567,7 +567,8 @@ export function SelectionManager() {
previousBlock = siblings[i - parentoffset];
}
if (!block.listData || !previousBlock.listData) continue;
- outdent(block, previousBlock, rep);
+ let { foldedBlocks, toggleFold } = useUIState.getState();
+ await outdent(block, previousBlock, rep, { foldedBlocks, toggleFold });
}
} else {
for (let i = 0; i < siblings.length; i++) {
@@ -588,7 +589,8 @@ export function SelectionManager() {
previousBlock = siblings[i - parentoffset];
}
if (!block.listData || !previousBlock.listData) continue;
- indent(block, previousBlock, rep);
+ let { foldedBlocks, toggleFold } = useUIState.getState();
+ indent(block, previousBlock, rep, { foldedBlocks, toggleFold });
}
}
}
diff --git a/components/SelectionManager/selectionState.ts b/components/SelectionManager/selectionState.ts
index 7f45ca43..1e929984 100644
--- a/components/SelectionManager/selectionState.ts
+++ b/components/SelectionManager/selectionState.ts
@@ -2,7 +2,7 @@ import { create } from "zustand";
import { Replicache } from "replicache";
import { ReplicacheMutators } from "src/replicache";
import { useUIState } from "src/useUIState";
-import { getBlocksWithType } from "src/hooks/queries/useBlocks";
+import { getBlocksWithType } from "src/replicache/getBlocks";
export const useSelectingMouse = create(() => ({
start: null as null | string,
diff --git a/components/Toolbar/ListToolbar.tsx b/components/Toolbar/ListToolbar.tsx
index 14a1d9cd..cdeb4cee 100644
--- a/components/Toolbar/ListToolbar.tsx
+++ b/components/Toolbar/ListToolbar.tsx
@@ -4,7 +4,7 @@ import { useEntity, useReplicache } from "src/replicache";
import { useUIState } from "src/useUIState";
import { metaKey } from "src/utils/metaKey";
import { ToolbarButton } from ".";
-import { indent, outdent, outdentFull } from "src/utils/list-operations";
+import { indent, outdent, outdentFull, orderListItems, unorderListItems } from "src/utils/list-operations";
import { useEffect } from "react";
import { Props } from "components/Icons/Props";
import { ArrowRightTiny } from "components/Icons/ArrowRightTiny";
@@ -70,6 +70,8 @@ export const ListButton = (props: { setToolbarState: (s: "list") => void }) => {
export const ListToolbar = (props: { onClose: () => void }) => {
let focusedBlock = useUIState((s) => s.focusedEntity);
+ let foldedBlocks = useUIState((s) => s.foldedBlocks);
+ let toggleFold = useUIState((s) => s.toggleFold);
let siblings = useBlocks(
focusedBlock?.entityType === "block" ? focusedBlock.parent : null,
);
@@ -104,9 +106,9 @@ export const ListToolbar = (props: { onClose: () => void }) => {
}
- onClick={() => {
+ onClick={async () => {
if (!rep || !block) return;
- outdent(block, previousBlock, rep);
+ await outdent(block, previousBlock, rep, { foldedBlocks, toggleFold });
}}
>
@@ -126,12 +128,33 @@ export const ListToolbar = (props: { onClose: () => void }) => {
}
onClick={() => {
if (!rep || !block || !previousBlock) return;
- indent(block, previousBlock, rep);
+ indent(block, previousBlock, rep, { foldedBlocks, toggleFold });
}}
>