diff --git a/gdn-app/src/App.vue b/gdn-app/src/App.vue
index 8d6c0ca..3d13ced 100644
--- a/gdn-app/src/App.vue
+++ b/gdn-app/src/App.vue
@@ -9,9 +9,9 @@ const ui = useUiStore();
window.addEventListener("keypress", (ev) => {
if (document.activeElement !== document.body) return;
- if (ev.key === "Escape") {
+ if (ev.key === "Escape" && ui.mode === "focus") {
const parent = ui.focusPath.parent();
- if (parent) ui.focusPath = parent;
+ if (parent) ui.focusOn(parent);
return;
}
});
diff --git a/gdn-app/src/components/CNote.vue b/gdn-app/src/components/CNote.vue
index 4f77bbd..21fe428 100644
--- a/gdn-app/src/components/CNote.vue
+++ b/gdn-app/src/components/CNote.vue
@@ -11,7 +11,6 @@ import {
RiEditLine,
RiPushpinFill,
RiPushpinLine,
- RiStickyNoteAddLine,
} from "@remixicon/vue";
import { computed, ref, watchEffect } from "vue";
import CNoteButton from "./CNoteButton.vue";
@@ -55,40 +54,24 @@ const children = computed(() => {
});
const hovering = ref(false);
-const mode = ref<"editing" | "creating">();
+const hover = computed(() => hovering.value && !editing.value);
const mayOpen = computed(() => children.value.length > 0);
const open = computed(() => mayOpen.value && ui.isOpen(path));
-const focused = computed(() => ui.focusPath.eq(path));
const pinned = computed(() => ui.isPinned(segment, parentId));
-const hover = computed(() => hovering.value && mode.value !== "editing");
-
-const creating = computed(() => mode.value === "creating");
-const editing = computed(() => mode.value === "editing");
+const focused = computed(() => ui.isFocused(path));
+const editing = computed(() => ui.isEditing(path));
+const insertIndex = computed(() => ui.getInsertIndex(path));
// Ensure we're open if we need to be.
watchEffect(() => {
- if (forceOpen || creating.value) ui.setOpen(path, true);
+ if (forceOpen || editing.value) ui.setOpen(path, true);
});
-// Ensure only one editor is ever open.
-watchEffect(() => {
- if (!focused.value) mode.value = undefined;
-});
-
-// Ensure we're focused when an editor is open.
-watchEffect(() => {
- if (mode.value) focusOnThis();
-});
-
-function focusOnThis(): void {
- ui.focusPath = path;
-}
-
function onClick(): void {
if (!focused.value) {
- focusOnThis();
+ ui.focusOn(path);
return;
}
@@ -102,11 +85,11 @@ function onPinButtonClick(): void {
function onEditButtonClick(): void {
if (!note.value) return;
- mode.value = "editing";
+ ui.edit(path);
}
function onEditEditorClose(): void {
- mode.value = undefined;
+ ui.focus();
}
function onEditEditorFinish(text: string): void {
@@ -115,27 +98,6 @@ function onEditEditorFinish(text: string): void {
onEditEditorClose();
}
-function onCreateButtonClick(): void {
- if (!note.value) return;
- mode.value = "creating";
-}
-
-function onCreateEditorClose(): void {
- mode.value = undefined;
-}
-
-function onCreateEditorFinish(text: string): void {
- if (!note.value) return;
-
- const newNote = notes.createNote(text);
- note.value.children.push(newNote.id);
-
- const lastChild = children.value.at(-1);
- if (lastChild) ui.focusPath = path.concat(lastChild);
-
- onCreateEditorClose();
-}
-
function onMoveButtonClick(): void {
ui.pushAnchorId(segment.id);
}
@@ -190,9 +152,6 @@ function onMoveButtonClick(): void {
-
-
-
@@ -209,21 +168,19 @@ function onMoveButtonClick(): void {
-
-
-
-
-
diff --git a/gdn-app/src/stores/ui.ts b/gdn-app/src/stores/ui.ts
index 6757e29..7f0df87 100644
--- a/gdn-app/src/stores/ui.ts
+++ b/gdn-app/src/stores/ui.ts
@@ -2,42 +2,62 @@ import { Segment, Path as UiPath } from "@/lib/path";
import { defineStore } from "pinia";
import { computed, ref, watchEffect } from "vue";
-export const useUiStore = defineStore("ui", () => {
- const history = ref<
- {
- anchorId: string;
- focusPath: UiPath;
- openPaths: Set;
- }[]
- >([]);
+interface HistoryEntry {
+ anchorId: string;
+ focusPath: UiPath;
+ openPaths: Set;
+}
+type Mode =
+ | { type: "focus" }
+ | { type: "edit"; path: UiPath }
+ | { type: "insert"; path: UiPath; index: number };
+
+export const useUiStore = defineStore("ui", () => {
+ const history = ref([]);
+
+ // Managed by history
const _anchorId = ref();
- const anchorId = computed(() => _anchorId.value);
- const focusPath = ref(new UiPath());
+ const _focusPath = ref(new UiPath());
const openPaths = ref>(new Set());
+ const _mode = ref({ type: "focus" });
const pinned = ref<{ segment: Segment; parentId?: string }>();
- // Ensure all nodes on the focusPath are unfolded.
+ // Ensure the currently focused note is visible.
watchEffect(() => {
- // The node pointed to by the path itself doesn't need to be unfolded.
- for (const ancestor of focusPath.value.ancestors().slice(1)) {
- setOpen(ancestor, true);
+ if (_mode.value.type === "insert") {
+ for (const ancestor of _mode.value.path.ancestors()) {
+ setOpen(ancestor, true);
+ }
+ } else {
+ // The node pointed to by the path itself doesn't need to be unfolded.
+ for (const ancestor of _focusPath.value.ancestors().slice(1)) {
+ setOpen(ancestor, true);
+ }
}
});
+ ///////////////////////////////////
+ // History and anchor management //
+ ///////////////////////////////////
+
+ const anchorId = computed(() => _anchorId.value); // Getter
+
function pushAnchorId(id: string): void {
if (_anchorId.value) {
history.value.push({
anchorId: _anchorId.value,
- focusPath: focusPath.value,
+ focusPath: _focusPath.value,
openPaths: openPaths.value,
});
}
_anchorId.value = id;
- focusPath.value = new UiPath();
+ _focusPath.value = new UiPath();
openPaths.value = new Set();
+
+ _mode.value = { type: "focus" };
}
function popAnchorId(): void {
@@ -47,15 +67,60 @@ export const useUiStore = defineStore("ui", () => {
const entry = history.value.pop();
if (entry) {
_anchorId.value = entry.anchorId;
- focusPath.value = entry.focusPath;
+ _focusPath.value = entry.focusPath;
openPaths.value = entry.openPaths;
} else {
_anchorId.value = undefined;
- focusPath.value = new UiPath();
+ _focusPath.value = new UiPath();
openPaths.value = new Set();
}
+
+ _mode.value = { type: "focus" };
}
+ ///////////////////////////////
+ // Mode and focus management //
+ ///////////////////////////////
+
+ const mode = computed(() => _mode.value.type);
+ const focusPath = computed(() => _focusPath.value);
+
+ function isFocused(path: UiPath): boolean {
+ return _mode.value.type === "focus" && _focusPath.value.eq(path);
+ }
+
+ function isEditing(path: UiPath): boolean {
+ return _mode.value.type === "edit" && _mode.value.path.eq(path);
+ }
+
+ function getInsertIndex(path: UiPath): number | undefined {
+ if (_mode.value.type !== "insert") return;
+ if (!_mode.value.path.eq(path)) return;
+ if (_mode.value.index < 0) return;
+ return _mode.value.index;
+ }
+
+ function focus(): void {
+ _mode.value = { type: "focus" };
+ }
+
+ function focusOn(path: UiPath): void {
+ focus();
+ _focusPath.value = path;
+ }
+
+ function edit(path: UiPath): void {
+ _mode.value = { type: "edit", path };
+ }
+
+ function insertAt(path: UiPath, index: number): void {
+ _mode.value = { type: "insert", path, index };
+ }
+
+ //////////////////
+ // Node folding //
+ //////////////////
+
function isOpen(path: UiPath): boolean {
return openPaths.value.has(path.fmt());
}
@@ -67,7 +132,7 @@ export const useUiStore = defineStore("ui", () => {
openPaths.value.add(path.fmt());
} else if (!value && isOpen(path)) {
// Move the focusPath if necessary
- if (path.isPrefixOf(focusPath.value)) focusPath.value = path;
+ if (path.isPrefixOf(_focusPath.value)) _focusPath.value = path;
openPaths.value.delete(path.fmt());
}
@@ -77,6 +142,10 @@ export const useUiStore = defineStore("ui", () => {
setOpen(path, !isOpen(path));
}
+ /////////////
+ // Pinning //
+ /////////////
+
function isPinned(segment: Segment, parentId?: string): boolean {
if (!pinned.value) return false;
return pinned.value.segment.eq(segment) && pinned.value.parentId === parentId;
@@ -92,9 +161,17 @@ export const useUiStore = defineStore("ui", () => {
return {
anchorId,
- focusPath,
pushAnchorId,
popAnchorId,
+ isFocused,
+ isEditing,
+ getInsertIndex,
+ mode,
+ focusPath,
+ focus,
+ focusOn,
+ edit,
+ insertAt,
isOpen,
setOpen,
toggleOpen,