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 +20 -0
- package/lib/client.js +89 -4
- package/package.json +1 -1
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 =
|
|
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
|
-
|
|
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.
|
|
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": [
|