diff --git a/app/[leaflet_id]/actions/HelpButton.tsx b/app/[leaflet_id]/actions/HelpButton.tsx
index d20d2f04..28690a7d 100644
--- a/app/[leaflet_id]/actions/HelpButton.tsx
+++ b/app/[leaflet_id]/actions/HelpButton.tsx
@@ -58,6 +58,30 @@ export const HelpButton = (props: { noShortcuts?: boolean }) => {
keys={[metaKey(), isMac() ? "Ctrl" : "Meta", "X"]}
/>
+
+
+
+
+
+
{/* shift + up/down arrows (or click + drag): select multiple blocks */}
diff --git a/components/SelectionManager/index.tsx b/components/SelectionManager/index.tsx
index 5fd8253e..773403c1 100644
--- a/components/SelectionManager/index.tsx
+++ b/components/SelectionManager/index.tsx
@@ -87,6 +87,154 @@ export function SelectionManager() {
}
},
},
+ {
+ metaKey: true,
+ altKey: true,
+ key: ["1", "¡"],
+ handler: async () => {
+ let [sortedBlocks] = await getSortedSelectionBound();
+ for (let block of sortedBlocks) {
+ await rep?.mutate.assertFact({
+ entity: block.value,
+ attribute: "block/heading-level",
+ data: { type: "number", value: 1 },
+ });
+ await rep?.mutate.assertFact({
+ entity: block.value,
+ attribute: "block/type",
+ data: { type: "block-type-union", value: "heading" },
+ });
+ }
+ },
+ },
+ {
+ metaKey: true,
+ altKey: true,
+ key: ["2", "™"],
+ handler: async () => {
+ let [sortedBlocks] = await getSortedSelectionBound();
+ for (let block of sortedBlocks) {
+ await rep?.mutate.assertFact({
+ entity: block.value,
+ attribute: "block/heading-level",
+ data: { type: "number", value: 2 },
+ });
+ await rep?.mutate.assertFact({
+ entity: block.value,
+ attribute: "block/type",
+ data: { type: "block-type-union", value: "heading" },
+ });
+ }
+ },
+ },
+ {
+ metaKey: true,
+ altKey: true,
+ key: ["3", "£"],
+ handler: async () => {
+ let [sortedBlocks] = await getSortedSelectionBound();
+ for (let block of sortedBlocks) {
+ await rep?.mutate.assertFact({
+ entity: block.value,
+ attribute: "block/heading-level",
+ data: { type: "number", value: 3 },
+ });
+ await rep?.mutate.assertFact({
+ entity: block.value,
+ attribute: "block/type",
+ data: { type: "block-type-union", value: "heading" },
+ });
+ }
+ },
+ },
+ {
+ metaKey: true,
+ altKey: true,
+ key: ["p", "π"],
+ handler: async () => {
+ let [sortedBlocks] = await getSortedSelectionBound();
+ for (let block of sortedBlocks) {
+ // Convert to text block
+ await rep?.mutate.assertFact({
+ entity: block.value,
+ attribute: "block/type",
+ data: { type: "block-type-union", value: "text" },
+ });
+ // Remove heading level if exists
+ let headingLevel = await rep?.query((tx) =>
+ scanIndex(tx).eav(block.value, "block/heading-level"),
+ );
+ if (headingLevel?.[0]) {
+ await rep?.mutate.retractFact({ factID: headingLevel[0].id });
+ }
+ // Remove text-size to make it default
+ let textSizeFact = await rep?.query((tx) =>
+ scanIndex(tx).eav(block.value, "block/text-size"),
+ );
+ if (textSizeFact?.[0]) {
+ await rep?.mutate.retractFact({ factID: textSizeFact[0].id });
+ }
+ }
+ },
+ },
+ {
+ metaKey: true,
+ altKey: true,
+ key: ["+", "≠"],
+ handler: async () => {
+ let [sortedBlocks] = await getSortedSelectionBound();
+ for (let block of sortedBlocks) {
+ // Convert to text block
+ await rep?.mutate.assertFact({
+ entity: block.value,
+ attribute: "block/type",
+ data: { type: "block-type-union", value: "text" },
+ });
+ // Remove heading level if exists
+ let headingLevel = await rep?.query((tx) =>
+ scanIndex(tx).eav(block.value, "block/heading-level"),
+ );
+ if (headingLevel?.[0]) {
+ await rep?.mutate.retractFact({ factID: headingLevel[0].id });
+ }
+ // Set text size to large
+ await rep?.mutate.assertFact({
+ entity: block.value,
+ attribute: "block/text-size",
+ data: { type: "text-size-union", value: "large" },
+ });
+ }
+ },
+ },
+ {
+ metaKey: true,
+ altKey: true,
+ key: ["-", "–"],
+ handler: async () => {
+ let [sortedBlocks] = await getSortedSelectionBound();
+ for (let block of sortedBlocks) {
+ // Convert to text block
+ await rep?.mutate.assertFact({
+ entity: block.value,
+ attribute: "block/type",
+ data: { type: "block-type-union", value: "text" },
+ });
+ // Remove heading level if exists
+ let headingLevel = await rep?.query((tx) =>
+ scanIndex(tx).eav(block.value, "block/heading-level"),
+ );
+ if (headingLevel?.[0]) {
+ await rep?.mutate.retractFact({ factID: headingLevel[0].id });
+ }
+ // Set text size to small
+ await rep?.mutate.assertFact({
+ entity: block.value,
+ attribute: "block/text-size",
+ data: { type: "text-size-union", value: "small" },
+ });
+ }
+ },
+ },
{
metaKey: true,
shift: true,
@@ -685,7 +833,6 @@ function getContentEditableParent(e: Node | null): Node | null {
return null;
}
-
function toggleMarkInBlocks(blocks: string[], mark: MarkType, attrs?: any) {
let everyBlockHasMark = blocks.reduce((acc, block) => {
let editor = useEditorStates.getState().editorStates[block];