ghuser1 commited on
Commit
05bbd6f
·
verified ·
1 Parent(s): 127f97c

Update app.js

Browse files
Files changed (1) hide show
  1. app.js +166 -32
app.js CHANGED
@@ -46,6 +46,13 @@ const els = {
46
  cancelPassword: document.querySelector("#cancelPassword"),
47
  submitPassword: document.querySelector("#submitPassword"),
48
  passwordMessage: document.querySelector("#passwordMessage"),
 
 
 
 
 
 
 
49
  folderList: document.querySelector("#folderList"),
50
  newFolder: document.querySelector("#newFolder"),
51
  listTitle: document.querySelector("#listTitle"),
@@ -242,35 +249,11 @@ document.addEventListener("click", (event) => {
242
  els.mobileActionMenu.hidden = true;
243
  });
244
 
245
- els.newFolder.addEventListener("click", async () => {
246
- const name = getUniqueFolderName("新建文件夹");
247
- const folder = {
248
- id: crypto.randomUUID(),
249
- name,
250
- createdAt: Date.now(),
251
- updatedAt: Date.now(),
252
- version: 1
253
- };
254
-
255
- state.folders.push(folder);
256
- state.activeFolder = folder.id;
257
- state.selectedId = null;
258
- state.mobileView = "list";
259
- render();
260
-
261
- try {
262
- const saved = await createFolder(folder);
263
- Object.assign(folder, saved);
264
- setSaveStatus("文件夹已创建", "ok");
265
- renderFolders();
266
- renderEditor();
267
- } catch (error) {
268
- state.folders = state.folders.filter((entry) => entry.id !== folder.id);
269
- state.activeFolder = "all";
270
- setSaveStatus("创建失败", "error");
271
- render();
272
- console.error("Failed to create folder.", error);
273
- }
274
  });
275
 
276
  els.editor.addEventListener("input", (event) => {
@@ -318,6 +301,31 @@ els.changePassword.addEventListener("click", async () => {
318
 
319
  els.cancelPassword.addEventListener("click", hidePasswordGate);
320
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  els.passwordForm.addEventListener("submit", async (event) => {
322
  event.preventDefault();
323
  const currentPassword = els.currentPassword.value;
@@ -444,6 +452,28 @@ function hidePasswordGate() {
444
  els.passwordMessage.textContent = "";
445
  }
446
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
  function saveSelectedNote({ immediate = false } = {}) {
448
  saveUiState();
449
  const note = getSelectedNote();
@@ -536,6 +566,22 @@ async function createFolder(folder) {
536
  return await response.json();
537
  }
538
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
539
  async function removeFolder(folderId) {
540
  const response = await fetch(`/api/folders/${encodeURIComponent(folderId)}`, { method: "DELETE" });
541
  if (!response.ok) throw new Error(`Delete folder API returned ${response.status}`);
@@ -645,7 +691,7 @@ function renderFolders() {
645
  name: folder.name,
646
  icon: "folder",
647
  count: state.notes.filter((note) => note.folder === folder.id).length,
648
- deletable: folder.id !== "notes"
649
  }));
650
 
651
  const smartFolders = [allFolder, recentFolder].filter((folder) => (
@@ -695,7 +741,7 @@ function renderGroupedNotes(notes) {
695
 
696
  function renderFolderItem(folder) {
697
  const item = document.createElement("div");
698
- item.className = `folder-item${folder.deletable ? " can-delete" : ""}`;
699
 
700
  const button = document.createElement("button");
701
  button.className = `folder-row${folder.id === state.activeFolder ? " active" : ""}`;
@@ -708,6 +754,7 @@ function renderFolderItem(folder) {
708
  icon.setAttribute("aria-hidden", "true");
709
 
710
  const name = document.createElement("span");
 
711
  name.textContent = folder.name;
712
 
713
  const count = document.createElement("span");
@@ -726,7 +773,23 @@ function renderFolderItem(folder) {
726
 
727
  item.append(button);
728
 
729
- if (folder.deletable) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
730
  const deleteButton = document.createElement("button");
731
  deleteButton.className = "folder-delete";
732
  deleteButton.type = "button";
@@ -928,6 +991,77 @@ async function deleteFolder(folderId) {
928
  }
929
  }
930
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
931
  function getActiveFolderLabel() {
932
  if (state.activeFolder === "all") return "所有笔记";
933
  if (state.activeFolder === "recent") return "最近编辑";
 
46
  cancelPassword: document.querySelector("#cancelPassword"),
47
  submitPassword: document.querySelector("#submitPassword"),
48
  passwordMessage: document.querySelector("#passwordMessage"),
49
+ folderGate: document.querySelector("#folderGate"),
50
+ folderForm: document.querySelector("#folderForm"),
51
+ folderTitle: document.querySelector("#folderTitle"),
52
+ folderName: document.querySelector("#folderName"),
53
+ cancelFolder: document.querySelector("#cancelFolder"),
54
+ submitFolder: document.querySelector("#submitFolder"),
55
+ folderMessage: document.querySelector("#folderMessage"),
56
  folderList: document.querySelector("#folderList"),
57
  newFolder: document.querySelector("#newFolder"),
58
  listTitle: document.querySelector("#listTitle"),
 
249
  els.mobileActionMenu.hidden = true;
250
  });
251
 
252
+ els.newFolder.addEventListener("click", () => {
253
+ openFolderGate({
254
+ mode: "create",
255
+ name: getUniqueFolderName("新建文件夹")
256
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
  });
258
 
259
  els.editor.addEventListener("input", (event) => {
 
301
 
302
  els.cancelPassword.addEventListener("click", hidePasswordGate);
303
 
304
+ els.cancelFolder.addEventListener("click", hideFolderGate);
305
+
306
+ els.folderForm.addEventListener("submit", async (event) => {
307
+ event.preventDefault();
308
+ const mode = els.folderForm.dataset.mode;
309
+ const folderId = els.folderForm.dataset.folderId;
310
+ const name = els.folderName.value.trim();
311
+ if (!name) {
312
+ els.folderMessage.textContent = "请输入文件夹名称";
313
+ return;
314
+ }
315
+
316
+ els.submitFolder.disabled = true;
317
+ els.folderMessage.textContent = "";
318
+ try {
319
+ if (mode === "rename" && folderId) {
320
+ await submitFolderRename(folderId, name);
321
+ } else {
322
+ await submitFolderCreate(name);
323
+ }
324
+ } finally {
325
+ els.submitFolder.disabled = false;
326
+ }
327
+ });
328
+
329
  els.passwordForm.addEventListener("submit", async (event) => {
330
  event.preventDefault();
331
  const currentPassword = els.currentPassword.value;
 
452
  els.passwordMessage.textContent = "";
453
  }
454
 
455
+ function openFolderGate({ mode, name, folderId = "" }) {
456
+ els.folderForm.dataset.mode = mode;
457
+ els.folderForm.dataset.folderId = folderId;
458
+ els.folderTitle.textContent = mode === "rename" ? "重命名文件夹" : "新建文件夹";
459
+ els.submitFolder.textContent = mode === "rename" ? "保存" : "创建";
460
+ els.folderName.value = name || "";
461
+ els.folderMessage.textContent = "";
462
+ els.folderGate.hidden = false;
463
+ requestAnimationFrame(() => {
464
+ els.folderName.focus();
465
+ els.folderName.select();
466
+ });
467
+ }
468
+
469
+ function hideFolderGate() {
470
+ els.folderGate.hidden = true;
471
+ els.folderForm.reset();
472
+ els.folderForm.dataset.mode = "";
473
+ els.folderForm.dataset.folderId = "";
474
+ els.folderMessage.textContent = "";
475
+ }
476
+
477
  function saveSelectedNote({ immediate = false } = {}) {
478
  saveUiState();
479
  const note = getSelectedNote();
 
566
  return await response.json();
567
  }
568
 
569
+ async function updateFolder(folderId, patch) {
570
+ const response = await fetch(`/api/folders/${encodeURIComponent(folderId)}`, {
571
+ method: "PATCH",
572
+ headers: {
573
+ "content-type": "application/json"
574
+ },
575
+ body: JSON.stringify(patch)
576
+ });
577
+ const payload = await response.json().catch(() => ({}));
578
+ if (response.status === 409 && payload.folder) {
579
+ return { status: "conflict", folder: payload.folder };
580
+ }
581
+ if (!response.ok) throw new Error(`Update folder API returned ${response.status}`);
582
+ return payload;
583
+ }
584
+
585
  async function removeFolder(folderId) {
586
  const response = await fetch(`/api/folders/${encodeURIComponent(folderId)}`, { method: "DELETE" });
587
  if (!response.ok) throw new Error(`Delete folder API returned ${response.status}`);
 
691
  name: folder.name,
692
  icon: "folder",
693
  count: state.notes.filter((note) => note.folder === folder.id).length,
694
+ manageable: folder.id !== "notes"
695
  }));
696
 
697
  const smartFolders = [allFolder, recentFolder].filter((folder) => (
 
741
 
742
  function renderFolderItem(folder) {
743
  const item = document.createElement("div");
744
+ item.className = `folder-item${folder.manageable ? " can-manage" : ""}`;
745
 
746
  const button = document.createElement("button");
747
  button.className = `folder-row${folder.id === state.activeFolder ? " active" : ""}`;
 
754
  icon.setAttribute("aria-hidden", "true");
755
 
756
  const name = document.createElement("span");
757
+ name.className = "folder-name";
758
  name.textContent = folder.name;
759
 
760
  const count = document.createElement("span");
 
773
 
774
  item.append(button);
775
 
776
+ if (folder.manageable) {
777
+ const editButton = document.createElement("button");
778
+ editButton.className = "folder-edit";
779
+ editButton.type = "button";
780
+ editButton.setAttribute("aria-label", `重命名${folder.name}`);
781
+ editButton.title = "重命名文件夹";
782
+ editButton.innerHTML = '<i data-lucide="pencil" aria-hidden="true"></i>';
783
+ editButton.addEventListener("click", (event) => {
784
+ event.stopPropagation();
785
+ openFolderGate({
786
+ mode: "rename",
787
+ folderId: folder.id,
788
+ name: folder.name
789
+ });
790
+ });
791
+ item.append(editButton);
792
+
793
  const deleteButton = document.createElement("button");
794
  deleteButton.className = "folder-delete";
795
  deleteButton.type = "button";
 
991
  }
992
  }
993
 
994
+ async function submitFolderCreate(name) {
995
+ const now = Date.now();
996
+ const previousActiveFolder = state.activeFolder;
997
+ const previousSelectedId = state.selectedId;
998
+ const folder = {
999
+ id: crypto.randomUUID(),
1000
+ name,
1001
+ createdAt: now,
1002
+ updatedAt: now,
1003
+ version: 1
1004
+ };
1005
+
1006
+ state.folders.push(folder);
1007
+ state.activeFolder = folder.id;
1008
+ state.selectedId = null;
1009
+ state.mobileView = "list";
1010
+ render();
1011
+ hideFolderGate();
1012
+
1013
+ try {
1014
+ const saved = await createFolder(folder);
1015
+ Object.assign(folder, saved);
1016
+ setSaveStatus("文件夹已创建", "ok");
1017
+ render();
1018
+ } catch (error) {
1019
+ state.folders = state.folders.filter((entry) => entry.id !== folder.id);
1020
+ state.activeFolder = previousActiveFolder;
1021
+ state.selectedId = previousSelectedId;
1022
+ setSaveStatus("创建失败", "error");
1023
+ render();
1024
+ console.error("Failed to create folder.", error);
1025
+ }
1026
+ }
1027
+
1028
+ async function submitFolderRename(folderId, name) {
1029
+ const folder = state.folders.find((entry) => entry.id === folderId);
1030
+ if (!folder || folder.id === "notes") return;
1031
+
1032
+ const previousName = folder.name;
1033
+ const previousUpdatedAt = folder.updatedAt;
1034
+ const previousVersion = folder.version || 1;
1035
+ folder.name = name;
1036
+ folder.updatedAt = Date.now();
1037
+ folder.version = previousVersion + 1;
1038
+ render();
1039
+ hideFolderGate();
1040
+
1041
+ try {
1042
+ const result = await updateFolder(folder.id, {
1043
+ name: folder.name,
1044
+ updatedAt: folder.updatedAt,
1045
+ version: previousVersion
1046
+ });
1047
+ if (result.status === "conflict" && result.folder) {
1048
+ Object.assign(folder, result.folder);
1049
+ setSaveStatus("文件夹已被其他设备修改", "conflict");
1050
+ } else {
1051
+ Object.assign(folder, result);
1052
+ setSaveStatus("名称已更新", "ok");
1053
+ }
1054
+ render();
1055
+ } catch (error) {
1056
+ folder.name = previousName;
1057
+ folder.updatedAt = previousUpdatedAt;
1058
+ folder.version = previousVersion;
1059
+ setSaveStatus("重命名失败", "error");
1060
+ render();
1061
+ console.error("Failed to rename folder.", error);
1062
+ }
1063
+ }
1064
+
1065
  function getActiveFolderLabel() {
1066
  if (state.activeFolder === "all") return "所有笔记";
1067
  if (state.activeFolder === "recent") return "最近编辑";