dsh-quicknote 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +18 -12
- package/README.zh.md +16 -10
- package/icon.svg +2 -2
- package/lib/client.js +1010 -137
- package/lib/index.js +112 -36
- package/llms.txt +2 -0
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -74,10 +74,13 @@ function registerRoutes(ctx, store) {
|
|
|
74
74
|
{
|
|
75
75
|
path: `${API}/notes/create`,
|
|
76
76
|
methods: ["POST"],
|
|
77
|
-
fetch: wrap(async (request) =>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
fetch: wrap(async (request) => {
|
|
78
|
+
const input = await body(request);
|
|
79
|
+
return json(200, {
|
|
80
|
+
ok: true,
|
|
81
|
+
note: await store.add(input.text, input.origin)
|
|
82
|
+
});
|
|
83
|
+
})
|
|
81
84
|
},
|
|
82
85
|
{
|
|
83
86
|
path: `${API}/notes/update`,
|
|
@@ -99,8 +102,23 @@ function registerRoutes(ctx, store) {
|
|
|
99
102
|
methods: ["POST"],
|
|
100
103
|
fetch: wrap(async (request) => json(200, {
|
|
101
104
|
ok: true,
|
|
102
|
-
|
|
105
|
+
note: await store.remove((await body(request)).id)
|
|
103
106
|
}))
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
path: `${API}/notes/restore`,
|
|
110
|
+
methods: ["POST"],
|
|
111
|
+
fetch: wrap(async (request) => {
|
|
112
|
+
try {
|
|
113
|
+
return json(200, {
|
|
114
|
+
ok: true,
|
|
115
|
+
note: await store.restore((await body(request)).note)
|
|
116
|
+
});
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (String(error?.message) === "invalid-note") throw new HttpError(400, "invalid-note");
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
})
|
|
104
122
|
}
|
|
105
123
|
];
|
|
106
124
|
for (const route of routes) scope.effect(() => registry.register({
|
|
@@ -113,9 +131,43 @@ function registerRoutes(ctx, store) {
|
|
|
113
131
|
}
|
|
114
132
|
// src/host/store.ts
|
|
115
133
|
const MAX_TEXT = 2e4;
|
|
134
|
+
function parseNote(value) {
|
|
135
|
+
if (value === null || typeof value !== "object") return null;
|
|
136
|
+
const raw = value;
|
|
137
|
+
if (typeof raw.id !== "string" || raw.id === "" || raw.id.length > 128) return null;
|
|
138
|
+
if (typeof raw.text !== "string") return null;
|
|
139
|
+
const text = raw.text.trim();
|
|
140
|
+
if (text === "" || text.length > MAX_TEXT) return null;
|
|
141
|
+
const createdAt = typeof raw.createdAt === "number" && Number.isFinite(raw.createdAt) ? raw.createdAt : Date.now();
|
|
142
|
+
const note = {
|
|
143
|
+
id: raw.id,
|
|
144
|
+
text,
|
|
145
|
+
done: raw.done === true,
|
|
146
|
+
createdAt
|
|
147
|
+
};
|
|
148
|
+
const origin = parseOrigin(raw.origin);
|
|
149
|
+
if (origin) note.origin = origin;
|
|
150
|
+
return note;
|
|
151
|
+
}
|
|
152
|
+
function parseOrigin(value) {
|
|
153
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
154
|
+
const sessionId = String(value.sessionId ?? "");
|
|
155
|
+
const title = String(value.title ?? "");
|
|
156
|
+
if (sessionId === "" && title === "") return void 0;
|
|
157
|
+
return {
|
|
158
|
+
sessionId,
|
|
159
|
+
title
|
|
160
|
+
};
|
|
161
|
+
}
|
|
116
162
|
/** A small durable store: one JSON file, atomic whole-file writes, newest first. */
|
|
117
163
|
function createStore(file) {
|
|
118
164
|
let cache = null;
|
|
165
|
+
let tail = Promise.resolve();
|
|
166
|
+
function serialized(run) {
|
|
167
|
+
const result = tail.then(run);
|
|
168
|
+
tail = result.then(() => void 0, () => void 0);
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
119
171
|
async function load() {
|
|
120
172
|
if (cache) return cache;
|
|
121
173
|
try {
|
|
@@ -128,46 +180,70 @@ function createStore(file) {
|
|
|
128
180
|
}
|
|
129
181
|
async function save(notes) {
|
|
130
182
|
await mkdir(dirname(file), { recursive: true });
|
|
131
|
-
const temporary = `${file}.${process.pid}.tmp`;
|
|
183
|
+
const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
132
184
|
await writeFile(temporary, `${JSON.stringify({ notes }, null, 2)}\n`, { mode: 384 });
|
|
133
185
|
await rename(temporary, file);
|
|
134
186
|
cache = notes;
|
|
135
187
|
}
|
|
136
188
|
return {
|
|
137
189
|
file,
|
|
138
|
-
|
|
139
|
-
return [...await load()];
|
|
190
|
+
list() {
|
|
191
|
+
return serialized(async () => [...await load()]);
|
|
192
|
+
},
|
|
193
|
+
add(text, origin) {
|
|
194
|
+
return serialized(async () => {
|
|
195
|
+
const value = String(text ?? "").trim();
|
|
196
|
+
if (value === "") throw new Error("empty-note");
|
|
197
|
+
if (value.length > MAX_TEXT) throw new Error("note-too-long");
|
|
198
|
+
const notes = await load();
|
|
199
|
+
const createdAt = Math.max(Date.now(), (notes[0]?.createdAt ?? 0) + 1);
|
|
200
|
+
const note = {
|
|
201
|
+
id: randomUUID(),
|
|
202
|
+
text: value,
|
|
203
|
+
done: false,
|
|
204
|
+
createdAt
|
|
205
|
+
};
|
|
206
|
+
const cleanOrigin = parseOrigin(origin);
|
|
207
|
+
if (cleanOrigin) note.origin = cleanOrigin;
|
|
208
|
+
await save([note, ...notes]);
|
|
209
|
+
return note;
|
|
210
|
+
});
|
|
140
211
|
},
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
212
|
+
update(id, done) {
|
|
213
|
+
return serialized(async () => {
|
|
214
|
+
const notes = await load();
|
|
215
|
+
const current = notes.find((note) => note.id === id);
|
|
216
|
+
if (!current) return null;
|
|
217
|
+
const updated = {
|
|
218
|
+
...current,
|
|
219
|
+
done
|
|
220
|
+
};
|
|
221
|
+
await save(notes.map((note) => note.id === id ? updated : note));
|
|
222
|
+
return updated;
|
|
223
|
+
});
|
|
153
224
|
},
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
};
|
|
162
|
-
await save(notes.map((note) => note.id === id ? updated : note));
|
|
163
|
-
return updated;
|
|
225
|
+
remove(id) {
|
|
226
|
+
return serialized(async () => {
|
|
227
|
+
const notes = await load();
|
|
228
|
+
const removed = notes.find((note) => note.id === id) ?? null;
|
|
229
|
+
if (!removed) return null;
|
|
230
|
+
await save(notes.filter((note) => note.id !== id));
|
|
231
|
+
return removed;
|
|
232
|
+
});
|
|
164
233
|
},
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
234
|
+
restore(value) {
|
|
235
|
+
return serialized(async () => {
|
|
236
|
+
const note = parseNote(value);
|
|
237
|
+
if (!note) throw new Error("invalid-note");
|
|
238
|
+
const notes = await load();
|
|
239
|
+
const existing = notes.find((item) => item.id === note.id);
|
|
240
|
+
if (existing) return existing;
|
|
241
|
+
const index = notes.findIndex((item) => item.createdAt <= note.createdAt);
|
|
242
|
+
const next = [...notes];
|
|
243
|
+
next.splice(index === -1 ? notes.length : index, 0, note);
|
|
244
|
+
await save(next);
|
|
245
|
+
return note;
|
|
246
|
+
});
|
|
171
247
|
}
|
|
172
248
|
};
|
|
173
249
|
}
|
package/llms.txt
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-quicknote",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Capture a note from the DSH composer, keep it out of the way, and manage the list on one page.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dsh-kit contributors",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"type": "git",
|
|
72
72
|
"url": "git+https://github.com/dsh-kit/dsh-quicknote.git"
|
|
73
73
|
},
|
|
74
|
-
"homepage": "https://
|
|
74
|
+
"homepage": "https://dsh-kit.github.io/dsh-quicknote/",
|
|
75
75
|
"bugs": {
|
|
76
76
|
"url": "https://github.com/dsh-kit/dsh-quicknote/issues"
|
|
77
77
|
}
|