Skip to content

Commit 892d2f6

Browse files
authored
Merge pull request #2 from derwebcoder/tagnodes
Advanced tag editing
2 parents c0c8fcd + ec9dfdb commit 892d2f6

23 files changed

+838
-151
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ More info coming soon.
1414
- [x] Webapp
1515
- [ ] Todos
1616
- [ ] Reminder
17-
- [ ] Empty state when no spark exist yet
17+
- [x] Empty state when no spark exist yet
1818
- [ ] Add second confirmation to "Delete everything" button

doc/HOW_TO_UPDATE_THE_DB.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
If you want to update the database schema or export / import format, you have to make sure you follow a few steps:
2+
3+
1. Start with adding a new version in `AppDB.ts`. Make sure to define an upgrade transaction.
4+
2. Add a new backup format in `Backup.ts` and a safe guard
5+
3. Add a new import upgrade method in `importJSONWorker.ts`. Don't forget to update the final backup type check (currently line 60)
6+
4. Now you can use the new data
7+
8+
Note: If you add a new table, remember to also implement a `CAREFUL_deleteAllData()` function in the corresponding service and call this from the settings when the user wants to delete all data.

package-lock.json

Lines changed: 51 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@
2323
"@radix-ui/react-slot": "^1.1.0",
2424
"@radix-ui/react-toast": "^1.2.1",
2525
"@tiptap/extension-list-keymap": "^2.6.6",
26+
"@tiptap/extension-mention": "^2.8.0",
2627
"@tiptap/extension-placeholder": "^2.6.6",
2728
"@tiptap/pm": "^2.6.6",
2829
"@tiptap/react": "^2.6.6",
2930
"@tiptap/starter-kit": "^2.6.6",
31+
"@tiptap/suggestion": "^2.8.0",
3032
"@types/wicg-file-system-access": "^2023.10.5",
3133
"astro": "^4.16.1",
3234
"class-variance-authority": "^0.7.0",
@@ -43,6 +45,7 @@
4345
"tailwind-merge": "^2.5.2",
4446
"tailwindcss": "^3.4.10",
4547
"tailwindcss-animate": "^1.0.7",
48+
"tippy.js": "^6.3.7",
4649
"typescript": "^5.5.4",
4750
"vaul": "^1.0.0"
4851
},
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import type { MentionNodeAttrs } from "@tiptap/extension-mention";
2+
import type {
3+
SuggestionKeyDownProps,
4+
SuggestionProps,
5+
} from "@tiptap/suggestion";
6+
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
7+
import type { Tag } from "../../../../interfaces/Tag";
8+
9+
export const NEW_TAG_PREFIX = 'New tag "';
10+
export const getNewTagPhrase = (tag: string) =>
11+
`${NEW_TAG_PREFIX}${tag.toLowerCase()}"`;
12+
13+
export let isUserSelectingTag = false;
14+
15+
type Props = SuggestionProps<{ name: string; hue: number }, MentionNodeAttrs>;
16+
17+
export type TagListRef = {
18+
onKeyDown: (data: SuggestionKeyDownProps) => boolean;
19+
};
20+
21+
export const TagList = forwardRef<TagListRef, Props>((props, ref) => {
22+
const { items } = props;
23+
const [selectedIndex, setSelectedIndex] = useState(1);
24+
25+
useEffect(() => {
26+
isUserSelectingTag = true;
27+
28+
return () => {
29+
isUserSelectingTag = false;
30+
};
31+
}, []);
32+
33+
const selectItem = (index: number) => {
34+
let item = items[index].name;
35+
if (item.startsWith(NEW_TAG_PREFIX)) {
36+
item = item.slice(NEW_TAG_PREFIX.length, item.length - 1);
37+
}
38+
39+
if (item) {
40+
props.command({ id: item.toLowerCase() });
41+
props.editor;
42+
}
43+
};
44+
45+
const upHandler = () => {
46+
setSelectedIndex((selectedIndex + items.length - 1) % items.length);
47+
};
48+
49+
const downHandler = () => {
50+
setSelectedIndex((selectedIndex + 1) % items.length);
51+
};
52+
53+
const enterHandler = () => {
54+
selectItem(selectedIndex);
55+
};
56+
57+
useEffect(() => {
58+
if (items.length <= 1 || !items[0].name.startsWith(NEW_TAG_PREFIX)) {
59+
setSelectedIndex(0);
60+
return;
61+
}
62+
setSelectedIndex(1);
63+
}, [items]);
64+
65+
useImperativeHandle(ref, () => ({
66+
onKeyDown: ({ event }) => {
67+
if (event.key === "ArrowUp") {
68+
upHandler();
69+
return true;
70+
}
71+
72+
if (event.key === "ArrowDown") {
73+
downHandler();
74+
return true;
75+
}
76+
77+
if (event.key === "Enter" || event.key === " ") {
78+
enterHandler();
79+
event.preventDefault();
80+
event.stopPropagation();
81+
return true;
82+
}
83+
84+
return false;
85+
},
86+
}));
87+
88+
return (
89+
<div className="bg-white border border-stone-400 rounded-md shadow flex flex-col gap-1 scroll-auto px-3 py-2 relative">
90+
{items.length ? (
91+
items.map((item, index) => (
92+
<button
93+
type="button"
94+
className={`flex gap-1 text-left py-1 px-3 rounded-md ${index === selectedIndex ? "bg-stone-300" : "hover:bg-stone-200"}`}
95+
key={item.name}
96+
data-key={item.name}
97+
onClick={() => selectItem(index)}
98+
>
99+
<span
100+
className="tag"
101+
style={
102+
{
103+
"--tag-color": `${item.hue}`,
104+
} as React.CSSProperties
105+
}
106+
>
107+
{item.name}
108+
</span>
109+
</button>
110+
))
111+
) : (
112+
<div className="item">No result</div>
113+
)}
114+
</div>
115+
);
116+
});

0 commit comments

Comments
 (0)