dsh-sidebar-row-menu 1.11.0 → 1.11.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 CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.11.1 — 2026-11-08
4
+
5
+ ### 中文摘要
6
+
7
+ - **修好与新版 dsh-sidebar-organizer 的联动:被它收进「更多操作」的头部按钮,现在也出现在
8
+ 本插件自绘的「更多操作」二级菜单里。** 新版 organizer 的收纳行是直接插进宿主弹层的 DOM,
9
+ 不在宿主 Menu 的 `items` 里,所以之前只读 `items` 的子菜单会少掉这些行(例如
10
+ 「IP 防护」)。现在按 organizer 记在 `dsh.sidebar-organizer.layout.v1` 的收纳顺序补行,
11
+ 排在宿主条目之前(与它在原生弹层里的位置一致),点击时再点一次被收起来的原按钮。
12
+
13
+ ### Changed
14
+
15
+ - **Restored the dsh-sidebar-organizer link: header buttons it tucks into the native
16
+ "More actions" popup now show up in this plugin's own "More actions" submenu.** The
17
+ organizer injects those rows straight into the host popup's DOM, so they never appear
18
+ in the host Menu's `items` and a submenu built from `items` alone (e.g. "IP guard")
19
+ lost them. They are now re-derived from the marked header buttons, ordered by
20
+ `dsh.sidebar-organizer.layout.v1`, placed before the host items (matching their position
21
+ in the native popup), and clicking one clicks the tucked original button.
22
+
3
23
  ## 1.11.0 — 2026-11-06
4
24
 
5
25
  ### 中文摘要
package/lib/client.js CHANGED
@@ -120,6 +120,7 @@ var BLANK_MENU_DEFAULTS = {
120
120
  var BLANK_MENU_KEYS = Object.keys(BLANK_MENU_DEFAULTS);
121
121
  var MORE_LABEL_RE = /^(more|more tools|more actions|more options|更多|更多工具|更多操作|更多选项)$/i;
122
122
  var ORGANIZER_MENU_ORDER_KEY = "dsh.sidebar-organizer.host-menu-order.v1";
123
+ var ORGANIZER_LAYOUT_KEY = "dsh.sidebar-organizer.layout.v1";
123
124
  var FOREIGN_ITEMS = {
124
125
  openWith: "session-menu-open-with",
125
126
  deleteSession: "session-menu-delete",
@@ -2724,11 +2725,96 @@ function orderMoreItems(entries) {
2724
2725
  const rank = new Map(saved.map((label, index) => [label, index]));
2725
2726
  return entries.map((entry, index) => ({ entry, index, rank: rank.has(entry.label) ? rank.get(entry.label) : saved.length })).sort((a, b) => a.rank - b.rank || a.index - b.index).map((item) => item.entry);
2726
2727
  }
2728
+ function organizerHiddenOrder() {
2729
+ try {
2730
+ const raw = window.localStorage.getItem(ORGANIZER_LAYOUT_KEY);
2731
+ if (raw === null) return [];
2732
+ const parsed = JSON.parse(raw);
2733
+ const hidden = parsed?.scopes?.header?.hidden;
2734
+ return Array.isArray(hidden) ? hidden.filter((value) => typeof value === "string") : [];
2735
+ } catch {
2736
+ return [];
2737
+ }
2738
+ }
2739
+ function organizerFingerprint(value) {
2740
+ let text = String(value ?? "").trim();
2741
+ if (text.startsWith("host:")) text = text.slice(5);
2742
+ else if (text.startsWith("text:")) text = text.slice(5);
2743
+ const cut = Math.max(text.lastIndexOf(":"), text.lastIndexOf(":"));
2744
+ if (cut > 0 && text.slice(cut + 1).trim() !== "") text = text.slice(0, cut);
2745
+ return text.replace(/\d+/g, "n").toLowerCase().replace(/[^a-z0-9\u4e00-\u9fa5]+/g, "-").replace(/^-+|-+$/g, "");
2746
+ }
2747
+ function organizerTuckedHeaderItems() {
2748
+ const root = activeSessionHeader();
2749
+ if (!root || typeof root.querySelector !== "function") return [];
2750
+ const order = organizerHiddenOrder();
2751
+ const rank = /* @__PURE__ */ new Map();
2752
+ for (const id of order) {
2753
+ const fingerprint = organizerFingerprint(id);
2754
+ if (fingerprint !== "" && !rank.has(fingerprint)) rank.set(fingerprint, rank.size);
2755
+ }
2756
+ const items = [];
2757
+ const seen = /* @__PURE__ */ new Set();
2758
+ for (const slot of HEADER_SLOT_SELECTORS) {
2759
+ const container = root.querySelector(slot.selector);
2760
+ if (!container) continue;
2761
+ for (const unit of Array.from(container.children ?? [])) {
2762
+ const candidates = [];
2763
+ if (typeof unit?.matches === "function" && unit.matches(BLANK_INTERACTIVE)) candidates.push(unit);
2764
+ if (typeof unit?.querySelectorAll === "function") {
2765
+ for (const node of unit.querySelectorAll(BLANK_INTERACTIVE)) {
2766
+ if (node !== unit) candidates.push(node);
2767
+ }
2768
+ }
2769
+ for (const button of candidates) {
2770
+ if (seen.has(button)) continue;
2771
+ if (typeof button?.getAttribute !== "function") continue;
2772
+ if (button.getAttribute("data-dso-scope") !== "header") continue;
2773
+ if (button.getAttribute("data-dso-hidden") !== "1") continue;
2774
+ if (button.disabled === true || button.getAttribute("aria-disabled") === "true") continue;
2775
+ const label = headerButtonLabel(button);
2776
+ if (label === "") continue;
2777
+ seen.add(button);
2778
+ const key = button.getAttribute("data-dso-key") ?? "";
2779
+ const fingerprints = [organizerFingerprint(key), organizerFingerprint(label)].filter((value) => value !== "");
2780
+ const known = fingerprints.map((fingerprint) => rank.get(fingerprint)).filter((value) => value !== void 0);
2781
+ items.push({
2782
+ id: `organizer:${key === "" ? label : key}`,
2783
+ label,
2784
+ icon: headerButtonIcon(button),
2785
+ button,
2786
+ rank: known.length === 0 ? Number.MAX_SAFE_INTEGER : Math.min(...known),
2787
+ order: items.length
2788
+ });
2789
+ }
2790
+ }
2791
+ }
2792
+ return items.sort((a, b) => a.rank - b.rank || a.order - b.order).map(({ id, label, icon, button }) => ({ id, label, icon, button }));
2793
+ }
2794
+ function moreSubmenuItems(menu) {
2795
+ return [...organizerTuckedHeaderItems(), ...orderMoreItems(menu.items)];
2796
+ }
2797
+ function moreSelectHandler(menu, entries) {
2798
+ return (id) => {
2799
+ const entry = entries.find((item) => item.id === id);
2800
+ if (!entry || typeof entry.button?.click !== "function") {
2801
+ menu.onSelect(id);
2802
+ return;
2803
+ }
2804
+ window.setTimeout(() => {
2805
+ try {
2806
+ entry.button.click();
2807
+ } catch (error) {
2808
+ console.warn("[dsh-sidebar-row-menu] tucked header action failed:", error);
2809
+ }
2810
+ }, 0);
2811
+ };
2812
+ }
2727
2813
  function BlankMoreRows({ label, icon, controller, separatorBefore, onClose, children }) {
2728
2814
  const [open, setOpen] = (0, import_react.useState)(false);
2729
2815
  const hostRef = (0, import_react.useRef)(null);
2730
2816
  const { closeSoon, openNow } = useHoverSubmenu(setOpen);
2731
- const entries = orderMoreItems(controller.items);
2817
+ const entries = controller.items;
2732
2818
  useSubmenuDismiss(open, setOpen, hostRef);
2733
2819
  const box = useSubmenuPos(open, hostRef, [entries.length]);
2734
2820
  const pick = (id) => {
@@ -2822,13 +2908,12 @@ function HeaderToolbarMenu({ x, y, items, t, sessionId, onClose }) {
2822
2908
  return;
2823
2909
  }
2824
2910
  if (item.more && item.menu !== null && item.menu !== void 0) {
2911
+ const moreItems = moreSubmenuItems(item.menu);
2825
2912
  rows.push((0, import_jsx_runtime.jsx)(BlankMoreRows, {
2826
2913
  key: `${index}:more`,
2827
2914
  label: item.label,
2828
2915
  icon: item.icon,
2829
- // 宿主弹层的行序由 dsh-sidebar-organizer 用 CSS order 控制,这里的
2830
- // 子菜单是自绘的,必须按它保存的顺序重排才能保持联动。
2831
- controller: { items: orderMoreItems(item.menu.items), onSelect: item.menu.onSelect },
2916
+ controller: { items: moreItems, onSelect: moreSelectHandler(item.menu, moreItems) },
2832
2917
  separatorBefore: takeSeparator("more"),
2833
2918
  onClose,
2834
2919
  children: item.label
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-sidebar-row-menu",
3
- "version": "1.11.0",
3
+ "version": "1.11.1",
4
4
  "type": "module",
5
5
  "description": "Adds open-with and cleanup entries to the DSH sidebar context menus (interleaved on 0.2.2+, self-drawn workspace menu on 0.2.1), plus a conversation-body right-click menu that mirrors the session header toolbar with in-menu open-with and more-actions submenus.",
6
6
  "keywords": [