clay-server 3.0.0 → 3.1.0-beta.2

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.
Files changed (35) hide show
  1. package/lib/mates-prompts.js +3 -2
  2. package/lib/notes.js +6 -0
  3. package/lib/project-http.js +3 -6
  4. package/lib/project-session-notes.js +217 -0
  5. package/lib/project.js +84 -18
  6. package/lib/public/app.js +1 -4
  7. package/lib/public/css/icon-strip.css +190 -0
  8. package/lib/public/css/messages.css +104 -2
  9. package/lib/public/css/mobile-nav.css +0 -4
  10. package/lib/public/css/pane.css +27 -0
  11. package/lib/public/css/rewind.css +10 -0
  12. package/lib/public/css/sticky-notes.css +43 -0
  13. package/lib/public/css/title-bar.css +0 -169
  14. package/lib/public/index.html +0 -6
  15. package/lib/public/modules/app-favicon.js +10 -1
  16. package/lib/public/modules/app-home-hub.js +0 -2
  17. package/lib/public/modules/app-messages.js +18 -6
  18. package/lib/public/modules/app-projects.js +30 -43
  19. package/lib/public/modules/app-rendering.js +42 -3
  20. package/lib/public/modules/dom-refs.js +1 -0
  21. package/lib/public/modules/input.js +13 -0
  22. package/lib/public/modules/project-switcher.js +1 -2
  23. package/lib/public/modules/sidebar-mobile.js +6 -19
  24. package/lib/public/modules/sidebar-projects.js +232 -63
  25. package/lib/public/modules/split-pair-ui.js +30 -0
  26. package/lib/public/modules/sticky-note-markdown.js +89 -0
  27. package/lib/public/modules/sticky-notes.js +56 -103
  28. package/lib/sdk-bridge.js +25 -1
  29. package/lib/session-notes-mcp-server.js +39 -0
  30. package/lib/ws-schema.js +1 -0
  31. package/lib/yoke/adapters/codex.js +44 -8
  32. package/lib/yoke/mcp-bridge-server.js +7 -7
  33. package/package.json +1 -1
  34. package/lib/public/modules/branch-switcher.js +0 -178
  35. package/lib/public/modules/worktree-family.js +0 -60
@@ -10,10 +10,9 @@ import { store } from './store.js';
10
10
  import { getWs } from './ws-ref.js';
11
11
  import { closeSidebar } from './sidebar.js';
12
12
  import { showIconTooltip, hideIconTooltip, closeUserCtxMenu, getCurrentDmUserId } from './sidebar-mates.js';
13
- import { switchProject, openAddProjectModal } from './app-projects.js';
13
+ import { switchProject, openAddProjectModal, getCachedProjects } from './app-projects.js';
14
14
  import { showHomeHub } from './app-home-hub.js';
15
15
  import { getPendingTuiAttention } from './app-notifications.js';
16
- import { aggregateFamily } from './worktree-family.js';
17
16
 
18
17
  // --- Project state ---
19
18
  var cachedProjectList = [];
@@ -32,6 +31,9 @@ var emojiPickerEl = null;
32
31
  var draggedSlug = null;
33
32
  var draggedEl = null;
34
33
 
34
+ // --- Worktree folder collapse state (session-local) ---
35
+ var wtCollapsed = {};
36
+
35
37
  var EMOJI_CATEGORIES = [
36
38
  { id: "frequent", icon: "🕐", label: "Frequent", emojis: [
37
39
  "😀","😎","🤓","🧠","💡","🔥","⚡","🚀",
@@ -519,8 +521,17 @@ function showIconCtxMenu(anchorEl, slug, name) {
519
521
  }
520
522
  });
521
523
  menu.appendChild(removeWtItem);
524
+ } else {
525
+ var wtItem = document.createElement("button");
526
+ wtItem.className = "project-ctx-item";
527
+ wtItem.innerHTML = iconHtml("git-branch") + " <span>Add Worktree</span>";
528
+ wtItem.addEventListener("click", function (e) {
529
+ e.stopPropagation();
530
+ closeProjectCtxMenu();
531
+ showWorktreeModal(slug, name || slug);
532
+ });
533
+ menu.appendChild(wtItem);
522
534
  }
523
- // Worktree creation lives in the title-bar branch chip, not here.
524
535
 
525
536
  document.body.appendChild(menu);
526
537
  projectCtxMenu = menu;
@@ -605,7 +616,20 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
605
616
  }
606
617
  }
607
618
 
608
- // Worktree creation lives in the title-bar branch chip, not here.
619
+ var sep2 = document.createElement("div");
620
+ sep2.className = "project-ctx-separator";
621
+ menu.appendChild(sep2);
622
+
623
+ // --- Add Worktree ---
624
+ var wtItem = document.createElement("button");
625
+ wtItem.className = "project-ctx-item";
626
+ wtItem.innerHTML = iconHtml("git-branch") + " <span>Add Worktree</span>";
627
+ wtItem.addEventListener("click", function (e) {
628
+ e.stopPropagation();
629
+ closeProjectCtxMenu();
630
+ showWorktreeModal(slug, name || slug);
631
+ });
632
+ menu.appendChild(wtItem);
609
633
 
610
634
  if (!store.get('permissions') || store.get('permissions').deleteProject !== false) {
611
635
  var sep3 = document.createElement("div");
@@ -926,6 +950,12 @@ function setupDragHandlers(el, slug) {
926
950
  });
927
951
  }
928
952
 
953
+ // --- Worktree folder collapse ---
954
+
955
+ function setWtCollapsed(slug, collapsed) {
956
+ wtCollapsed[slug] = collapsed;
957
+ }
958
+
929
959
  function groupProjects(projects) {
930
960
  var parents = [];
931
961
  var wtByParent = {};
@@ -941,21 +971,6 @@ function groupProjects(projects) {
941
971
  return { parents: parents, wtByParent: wtByParent };
942
972
  }
943
973
 
944
- function aggregateProjectFamily(parent, worktrees) {
945
- var aggregate = aggregateFamily(parent, worktrees);
946
- aggregate.hasFamilyPendingAttention = getPendingTuiAttention(parent.slug) > 0;
947
- aggregate.hasWorktreeUnread = false;
948
- aggregate.hasWorktreePending = false;
949
- for (var i = 0; i < worktrees.length; i++) {
950
- if (getPendingTuiAttention(worktrees[i].slug) > 0) aggregate.hasFamilyPendingAttention = true;
951
- if (worktrees[i].unread > 0) aggregate.hasWorktreeUnread = true;
952
- if (worktrees[i].pendingPermissions > 0 || getPendingTuiAttention(worktrees[i].slug) > 0) {
953
- aggregate.hasWorktreePending = true;
954
- }
955
- }
956
- return aggregate;
957
- }
958
-
959
974
  // --- Icon item creation ---
960
975
 
961
976
  function createIconItem(p, currentSlug) {
@@ -987,7 +1002,7 @@ function createIconItem(p, currentSlug) {
987
1002
 
988
1003
  var projectBadge = document.createElement("span");
989
1004
  projectBadge.className = "icon-strip-project-badge";
990
- if (p.unread > 0 && (!isActive || p.hasWorktreeUnread)) {
1005
+ if (p.unread > 0 && !isActive) {
991
1006
  projectBadge.textContent = p.unread > 99 ? "99+" : String(p.unread);
992
1007
  projectBadge.classList.add("has-unread");
993
1008
  }
@@ -997,8 +1012,7 @@ function createIconItem(p, currentSlug) {
997
1012
  // broadcast — they surface via tui_attention notifications. We shake the
998
1013
  // same icon for either, so the user notices regardless of which engine
999
1014
  // is asking.
1000
- if ((!isActive && (p.pendingPermissions > 0 || p.hasFamilyPendingAttention || getPendingTuiAttention(p.slug) > 0)) ||
1001
- p.hasWorktreePending) {
1015
+ if (!isActive && (p.pendingPermissions > 0 || getPendingTuiAttention(p.slug) > 0)) {
1002
1016
  el.classList.add("has-pending-perm");
1003
1017
  }
1004
1018
 
@@ -1019,7 +1033,7 @@ function createIconItem(p, currentSlug) {
1019
1033
 
1020
1034
  // --- Worktree creation modal ---
1021
1035
 
1022
- export function showWorktreeModal(parentSlug, parentName) {
1036
+ function showWorktreeModal(parentSlug, parentName) {
1023
1037
  var existing = document.getElementById("wt-modal-container");
1024
1038
  if (existing) existing.remove();
1025
1039
 
@@ -1174,17 +1188,164 @@ export function renderIconStrip(projects, currentSlug) {
1174
1188
  for (var i = 0; i < grouped.parents.length; i++) {
1175
1189
  var p = grouped.parents[i];
1176
1190
  var worktrees = grouped.wtByParent[p.slug] || [];
1177
- var familyProject = aggregateProjectFamily(p, worktrees);
1178
- var el = createIconItem(familyProject, currentSlug);
1191
+ var hasWorktrees = worktrees.length > 0;
1192
+
1193
+ if (!hasWorktrees) {
1194
+ var el = createIconItem(p, currentSlug);
1195
+ (function (slug, name, elem) {
1196
+ elem.addEventListener("contextmenu", function (e) {
1197
+ e.preventDefault();
1198
+ e.stopPropagation();
1199
+ showIconCtxMenu(elem, slug, name);
1200
+ });
1201
+ })(p.slug, p.name || p.slug, el);
1202
+ setupDragHandlers(el, p.slug);
1203
+ container.appendChild(el);
1204
+ continue;
1205
+ }
1206
+
1207
+ // Folder group for parent + worktrees
1208
+ var folder = document.createElement("div");
1209
+ folder.className = "icon-strip-group";
1210
+ folder.dataset.parentSlug = p.slug;
1211
+ if (wtCollapsed[p.slug]) folder.classList.add("collapsed");
1212
+
1213
+ if (!p.isProcessing) {
1214
+ for (var wpi = 0; wpi < worktrees.length; wpi++) {
1215
+ if (worktrees[wpi].isProcessing) { p.isProcessing = true; break; }
1216
+ }
1217
+ }
1218
+
1219
+ var header = createIconItem(p, currentSlug);
1220
+ header.classList.add("folder-header");
1179
1221
  (function (slug, name, elem) {
1180
1222
  elem.addEventListener("contextmenu", function (e) {
1181
1223
  e.preventDefault();
1182
1224
  e.stopPropagation();
1183
1225
  showIconCtxMenu(elem, slug, name);
1184
1226
  });
1185
- })(p.slug, p.name || p.slug, el);
1186
- setupDragHandlers(el, p.slug);
1187
- container.appendChild(el);
1227
+ })(p.slug, p.name || p.slug, header);
1228
+ setupDragHandlers(header, p.slug);
1229
+
1230
+ var chevron = document.createElement("span");
1231
+ chevron.className = "icon-strip-group-chevron";
1232
+ chevron.innerHTML = '<i data-lucide="git-branch"></i>';
1233
+ (function (parentSlug, folderEl) {
1234
+ chevron.addEventListener("click", function (e) {
1235
+ e.preventDefault();
1236
+ e.stopPropagation();
1237
+ var nowCollapsed = folderEl.classList.toggle("collapsed");
1238
+ setWtCollapsed(parentSlug, nowCollapsed);
1239
+ });
1240
+ chevron.addEventListener("contextmenu", function (e) {
1241
+ e.preventDefault();
1242
+ e.stopPropagation();
1243
+ });
1244
+ })(p.slug, folder);
1245
+ chevron.setAttribute("data-tip", "Toggle worktrees");
1246
+ header.appendChild(chevron);
1247
+ folder.appendChild(header);
1248
+
1249
+ var itemsContainer = document.createElement("div");
1250
+ itemsContainer.className = "icon-strip-group-items";
1251
+
1252
+ for (var wi = 0; wi < worktrees.length; wi++) {
1253
+ (function (wt) {
1254
+ var wtEl = document.createElement("a");
1255
+ var isWtActive = wt.slug === currentSlug && !currentDmUserId;
1256
+ var isAccessible = wt.worktreeAccessible !== false;
1257
+ wtEl.className = "icon-strip-wt-item" + (isWtActive ? " active" : "") + (!isAccessible ? " wt-disabled" : "");
1258
+ wtEl.href = "/p/" + wt.slug + "/";
1259
+ wtEl.dataset.slug = wt.slug;
1260
+
1261
+ if (wt.icon) {
1262
+ var wtEmoji = document.createElement("span");
1263
+ wtEmoji.className = "wt-branch-abbrev project-emoji";
1264
+ wtEmoji.textContent = wt.icon;
1265
+ parseEmojis(wtEmoji);
1266
+ wtEl.appendChild(wtEmoji);
1267
+ } else {
1268
+ var abbrev = document.createElement("span");
1269
+ abbrev.className = "wt-branch-abbrev";
1270
+ abbrev.textContent = getProjectAbbrev(wt.name);
1271
+ wtEl.appendChild(abbrev);
1272
+ }
1273
+
1274
+ var wtStatus = document.createElement("span");
1275
+ wtStatus.className = "icon-strip-status";
1276
+ if (wt.isProcessing) wtStatus.classList.add("processing");
1277
+ wtEl.appendChild(wtStatus);
1278
+
1279
+ var tooltipText = wt.name;
1280
+ if (!isAccessible) {
1281
+ tooltipText += " (outside project path, cannot be accessed)";
1282
+ }
1283
+ (function (text, elem) {
1284
+ elem.addEventListener("mouseenter", function () { showIconTooltip(elem, text); });
1285
+ elem.addEventListener("mouseleave", hideIconTooltip);
1286
+ })(tooltipText, wtEl);
1287
+
1288
+ if (isAccessible) {
1289
+ (function (slug) {
1290
+ wtEl.addEventListener("click", function (e) {
1291
+ e.preventDefault();
1292
+ if (switchProject) switchProject(slug);
1293
+ });
1294
+ })(wt.slug);
1295
+ } else {
1296
+ wtEl.addEventListener("click", function (e) {
1297
+ e.preventDefault();
1298
+ });
1299
+ }
1300
+
1301
+ if (isAccessible) {
1302
+ (function (slug, name, elem) {
1303
+ elem.addEventListener("contextmenu", function (e) {
1304
+ e.preventDefault();
1305
+ e.stopPropagation();
1306
+ showIconCtxMenu(elem, slug, name);
1307
+ });
1308
+ })(wt.slug, wt.name, wtEl);
1309
+ } else {
1310
+ wtEl.addEventListener("contextmenu", function (e) {
1311
+ e.preventDefault();
1312
+ e.stopPropagation();
1313
+ });
1314
+ }
1315
+
1316
+ if (!isWtActive && (wt.pendingPermissions > 0 || getPendingTuiAttention(wt.slug) > 0)) {
1317
+ wtEl.classList.add("has-pending-perm");
1318
+ }
1319
+
1320
+ itemsContainer.appendChild(wtEl);
1321
+ })(worktrees[wi]);
1322
+ }
1323
+
1324
+ var hasWtPendingPerm = false;
1325
+ for (var wpi2 = 0; wpi2 < worktrees.length; wpi2++) {
1326
+ if (worktrees[wpi2].pendingPermissions > 0 || getPendingTuiAttention(worktrees[wpi2].slug) > 0) {
1327
+ hasWtPendingPerm = true;
1328
+ break;
1329
+ }
1330
+ }
1331
+ if (hasWtPendingPerm) folder.classList.remove("collapsed");
1332
+
1333
+ var addBtn = document.createElement("button");
1334
+ addBtn.className = "icon-strip-group-add";
1335
+ addBtn.textContent = "+";
1336
+ (function (parentSlug, parentName, btn) {
1337
+ btn.addEventListener("click", function (e) {
1338
+ e.preventDefault();
1339
+ e.stopPropagation();
1340
+ showWorktreeModal(parentSlug, parentName);
1341
+ });
1342
+ btn.addEventListener("mouseenter", function () { showIconTooltip(btn, "New worktree"); });
1343
+ btn.addEventListener("mouseleave", hideIconTooltip);
1344
+ })(p.slug, p.name, addBtn);
1345
+ itemsContainer.appendChild(addBtn);
1346
+
1347
+ folder.appendChild(itemsContainer);
1348
+ container.appendChild(folder);
1188
1349
  }
1189
1350
 
1190
1351
  // Update home icon active state
@@ -1212,17 +1373,50 @@ function renderProjectList(projects, currentSlug) {
1212
1373
  for (var i = 0; i < grouped.parents.length; i++) {
1213
1374
  var p = grouped.parents[i];
1214
1375
  var worktrees = grouped.wtByParent[p.slug] || [];
1215
- list.appendChild(createMobileProjectItem(aggregateProjectFamily(p, worktrees), currentSlug));
1376
+
1377
+ if (worktrees.length === 0) {
1378
+ list.appendChild(createMobileProjectItem(p, currentSlug, false));
1379
+ continue;
1380
+ }
1381
+
1382
+ var folderDiv = document.createElement("div");
1383
+ folderDiv.className = "mobile-project-folder";
1384
+ if (wtCollapsed[p.slug]) folderDiv.classList.add("collapsed");
1385
+
1386
+ var headerEl = createMobileProjectItem(p, currentSlug, false);
1387
+ var chevron = document.createElement("span");
1388
+ chevron.className = "mobile-folder-chevron";
1389
+ chevron.innerHTML = "&#9660;";
1390
+ (function (parentSlug, fDiv) {
1391
+ chevron.addEventListener("click", function (e) {
1392
+ e.preventDefault();
1393
+ e.stopPropagation();
1394
+ var nowCollapsed = fDiv.classList.toggle("collapsed");
1395
+ setWtCollapsed(parentSlug, nowCollapsed);
1396
+ });
1397
+ })(p.slug, folderDiv);
1398
+ headerEl.appendChild(chevron);
1399
+ folderDiv.appendChild(headerEl);
1400
+
1401
+ var wtList = document.createElement("div");
1402
+ wtList.className = "mobile-folder-items";
1403
+ for (var wi = 0; wi < worktrees.length; wi++) {
1404
+ var isAccessible = worktrees[wi].worktreeAccessible !== false;
1405
+ var wtItem = createMobileProjectItem(worktrees[wi], currentSlug, true);
1406
+ if (!isAccessible) wtItem.classList.add("wt-disabled");
1407
+ if (!isAccessible) {
1408
+ wtItem.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); });
1409
+ }
1410
+ wtList.appendChild(wtItem);
1411
+ }
1412
+ folderDiv.appendChild(wtList);
1413
+ list.appendChild(folderDiv);
1216
1414
  }
1217
1415
  }
1218
1416
 
1219
- function createMobileProjectItem(p, currentSlug) {
1417
+ function createMobileProjectItem(p, currentSlug, isWorktree) {
1220
1418
  var el = document.createElement("button");
1221
- var isActive = p.slug === currentSlug;
1222
- el.className = "mobile-project-item" + (isActive ? " active" : "");
1223
- if ((!isActive && (p.pendingPermissions > 0 || p.hasFamilyPendingAttention)) || p.hasWorktreePending) {
1224
- el.classList.add("has-pending-perm");
1225
- }
1419
+ el.className = "mobile-project-item" + (p.slug === currentSlug ? " active" : "") + (isWorktree ? " wt-item" : "");
1226
1420
 
1227
1421
  var abbrev = document.createElement("span");
1228
1422
  abbrev.className = "mobile-project-abbrev";
@@ -1245,13 +1439,6 @@ function createMobileProjectItem(p, currentSlug) {
1245
1439
  el.appendChild(dot);
1246
1440
  }
1247
1441
 
1248
- if (p.unread > 0 && (!isActive || p.hasWorktreeUnread)) {
1249
- var badge = document.createElement("span");
1250
- badge.className = "mobile-project-unread";
1251
- badge.textContent = p.unread > 99 ? "99+" : String(p.unread);
1252
- el.appendChild(badge);
1253
- }
1254
-
1255
1442
  el.addEventListener("click", function () {
1256
1443
  if (switchProject) switchProject(p.slug);
1257
1444
  if (closeSidebar) closeSidebar();
@@ -1263,30 +1450,12 @@ function createMobileProjectItem(p, currentSlug) {
1263
1450
  export function getEmojiCategories() { return EMOJI_CATEGORIES; }
1264
1451
 
1265
1452
  export function updateProjectBadge(slug, count) {
1266
- var targetSlug = slug;
1267
- for (var i = 0; i < cachedProjectList.length; i++) {
1268
- if (cachedProjectList[i].slug !== slug) continue;
1269
- cachedProjectList[i].unread = count;
1270
- if (cachedProjectList[i].isWorktree && cachedProjectList[i].parentSlug) {
1271
- targetSlug = cachedProjectList[i].parentSlug;
1272
- }
1273
- break;
1274
- }
1275
-
1276
- var displayCount = count;
1277
- var grouped = groupProjects(cachedProjectList);
1278
- for (var pi = 0; pi < grouped.parents.length; pi++) {
1279
- if (grouped.parents[pi].slug !== targetSlug) continue;
1280
- displayCount = aggregateFamily(grouped.parents[pi], grouped.wtByParent[targetSlug] || []).unread;
1281
- break;
1282
- }
1283
-
1284
- var icon = document.querySelector('.icon-strip-item[data-slug="' + targetSlug + '"]');
1453
+ var icon = document.querySelector('.icon-strip-item[data-slug="' + slug + '"]');
1285
1454
  if (!icon) return;
1286
1455
  var badge = icon.querySelector(".icon-strip-project-badge");
1287
1456
  if (!badge) return;
1288
- if (displayCount > 0) {
1289
- badge.textContent = displayCount > 99 ? "99+" : String(displayCount);
1457
+ if (count > 0) {
1458
+ badge.textContent = count > 99 ? "99+" : String(count);
1290
1459
  badge.classList.add("has-unread");
1291
1460
  } else {
1292
1461
  badge.textContent = "";
@@ -251,11 +251,41 @@ export function handlePairCreated(msg) {
251
251
  return msg.group || null;
252
252
  }
253
253
 
254
+ // --- Worker-pane composer notice -------------------------------------
255
+ // Shown inside a pane iframe while ITS session is processing a delegated
256
+ // turn: typing is allowed (queueing is correct behavior), the bar just
257
+ // makes it visible that a new message joins the Driver's turn.
258
+ var workerNoticeEl = null;
259
+
260
+ export function showWorkerDelegationNotice(driverTitle) {
261
+ if (!store.get('paneMode')) return;
262
+ var inputArea = document.getElementById("input-area");
263
+ if (!inputArea || !inputArea.parentNode) return;
264
+ if (!workerNoticeEl) {
265
+ workerNoticeEl = document.createElement("div");
266
+ workerNoticeEl.className = "pane-delegation-notice";
267
+ inputArea.parentNode.insertBefore(workerNoticeEl, inputArea);
268
+ }
269
+ var who = driverTitle ? "“" + driverTitle + "”" : "the Driver";
270
+ workerNoticeEl.textContent = "Following an instruction from " + who + " — messages you send now join the same turn.";
271
+ workerNoticeEl.classList.add("visible");
272
+ }
273
+
274
+ export function hideWorkerDelegationNotice() {
275
+ if (workerNoticeEl) workerNoticeEl.classList.remove("visible");
276
+ }
277
+
254
278
  export function handleSplitDelegation(msg) {
255
279
  var states = Object.assign({}, store.get('splitDelegations') || {});
256
280
  if (msg.active) states[msg.groupId] = msg;
257
281
  else delete states[msg.groupId];
258
282
  store.set({ splitDelegations: states });
283
+ // Inside the worker's own pane, mirror the delegation into the composer
284
+ // notice (the delegated user_message shows it too; this covers the end).
285
+ if (store.get('paneMode') && msg.to === store.get('activeSessionId')) {
286
+ if (msg.active) showWorkerDelegationNotice(null);
287
+ else hideWorkerDelegationNotice();
288
+ }
259
289
  }
260
290
 
261
291
  function sendSetPair(groupId, driverId) {
@@ -0,0 +1,89 @@
1
+ // Markdown helpers for sticky-note titles, handoff bodies, and checklists.
2
+
3
+ export function getTitle(text) {
4
+ if (!text) return "";
5
+ var idx = text.indexOf("\n");
6
+ return idx === -1 ? text : text.substring(0, idx);
7
+ }
8
+
9
+ function formatInline(text) {
10
+ var escaped = text
11
+ .replace(/&/g, "&amp;")
12
+ .replace(/</g, "&lt;")
13
+ .replace(/>/g, "&gt;");
14
+ return escaped
15
+ .replace(/(https?:\/\/[^\s<]+)/g, '<a href="$1" target="_blank" rel="noopener">$1</a>')
16
+ .replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
17
+ .replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "<em>$1</em>")
18
+ .replace(/`([^`]+)`/g, "<code>$1</code>")
19
+ .replace(/~~(.+?)~~/g, "<del>$1</del>")
20
+ .replace(/^(?:-\s*)?\[[xX]\]/gm, '<span class="sn-check checked" role="checkbox" aria-checked="true" tabindex="0" contenteditable="false">✓</span>')
21
+ .replace(/^(?:-\s*)?\[ \]/gm, '<span class="sn-check" role="checkbox" aria-checked="false" tabindex="0" contenteditable="false">☐</span>')
22
+ .replace(/\n/g, "<br>");
23
+ }
24
+
25
+ export function renderMiniMarkdown(text) {
26
+ if (!text) return "";
27
+ var lines = text.split("\n");
28
+ var title = lines[0];
29
+ var body = lines.slice(1).join("\n");
30
+ var html = '<div class="sn-title">' + formatInline(title) + "</div>";
31
+ if (body.trim()) html += formatInline(body);
32
+ return html;
33
+ }
34
+
35
+ function childrenToMarkdown(element) {
36
+ var result = "";
37
+ for (var i = 0; i < element.childNodes.length; i++) {
38
+ result += nodeToMarkdown(element.childNodes[i]);
39
+ }
40
+ return result;
41
+ }
42
+
43
+ function nodeToMarkdown(node) {
44
+ if (node.nodeType === 3) return node.textContent;
45
+ if (node.nodeType !== 1) return "";
46
+
47
+ var tag = node.tagName;
48
+ var inner = childrenToMarkdown(node);
49
+ switch (tag) {
50
+ case "STRONG": case "B": return "**" + inner + "**";
51
+ case "EM": case "I": return "*" + inner + "*";
52
+ case "DEL": case "S": case "STRIKE": return "~~" + inner + "~~";
53
+ case "CODE": return "`" + inner + "`";
54
+ case "BR": return "\n";
55
+ case "DIV":
56
+ if (node.classList.contains("sn-title")) return inner;
57
+ if (node.classList.contains("sn-placeholder")) return "";
58
+ return "\n" + inner;
59
+ case "P": return "\n" + inner;
60
+ case "A": return node.getAttribute("href") || inner;
61
+ case "SPAN":
62
+ if (node.classList.contains("sn-check")) {
63
+ return node.classList.contains("checked") ? "- [x]" : "- [ ]";
64
+ }
65
+ if (node.classList.contains("sn-placeholder")) return "";
66
+ return inner;
67
+ default: return inner;
68
+ }
69
+ }
70
+
71
+ export function extractMarkdown(rendered) {
72
+ var titleEl = rendered.querySelector(".sn-title");
73
+ if (titleEl) {
74
+ var titleMarkdown = childrenToMarkdown(titleEl);
75
+ var rest = "";
76
+ var afterTitle = false;
77
+ for (var i = 0; i < rendered.childNodes.length; i++) {
78
+ var child = rendered.childNodes[i];
79
+ if (child === titleEl) {
80
+ afterTitle = true;
81
+ continue;
82
+ }
83
+ if (afterTitle) rest += nodeToMarkdown(child);
84
+ }
85
+ if (rest && rest.charAt(0) === "\n") rest = rest.substring(1);
86
+ return titleMarkdown + (rest ? "\n" + rest : "");
87
+ }
88
+ return childrenToMarkdown(rendered).replace(/^\n+/, "");
89
+ }