@logictan/dsh-easyrewrite 2.7.1 → 2.7.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 (2) hide show
  1. package/lib/client.js +316 -413
  2. package/package.json +12 -1
package/lib/client.js CHANGED
@@ -10,6 +10,7 @@ window.__ModuleLoader__.load({
10
10
  factory: function (require) {
11
11
  var React = require("react");
12
12
  var Primitives = require("@deepseek-ai/dsh-client-ui-primitives");
13
+ var UI = require("@logictan/dsh-plugins-shared-client");
13
14
 
14
15
  /**
15
16
  * 取第一个可用的图标导出。
@@ -1851,19 +1852,7 @@ window.__ModuleLoader__.load({
1851
1852
  /** 设置卡片:注册进 plugins.row.config(设置 → 插件 → 插件配置)。
1852
1853
  * 插件页对每个配置项渲染两次:view==='summary' 取行标题下的一行摘要,view==='page' 取完整表单。 */
1853
1854
  function EasyRewriteSettingsCard(props) {
1854
- /**
1855
- * 是否由外部容器提供卡片外壳。
1856
- *
1857
- * 0.1.7 的插件页(plugins.item)自己画行外壳、标题取自插槽 label,卡片再画一层头部
1858
- * 就成了嵌套折叠;旧插槽 settings.plugin.item 没有外壳,卡片必须自画。由挂载方经
1859
- * inject 面传入,默认 false,保持旧行为。
1860
- */
1861
- var embedded = !!(props && props.embedded);
1862
1855
  var L = useUILocaleDict();
1863
- // embedded 时头部不渲染,但「展开时检查更新」这类 side effect 仍要照跑,故初始即展开。
1864
- var openState = React.useState(embedded);
1865
- var open = openState[0];
1866
- var setOpen = openState[1];
1867
1856
  // 控件状态(初始化自 localStorage)
1868
1857
  var sRewrite = React.useState(rewriteOnClick());
1869
1858
  var rewrite = sRewrite[0];
@@ -1948,10 +1937,10 @@ window.__ModuleLoader__.load({
1948
1937
  }
1949
1938
  }).catch(function () { setUpdateUpdating(false); setUpdateMsg(L.updateFailed); });
1950
1939
  }
1951
- // 展开折叠时无感触发一次检查(5 分钟节流,防频繁展开刷请求);
1952
- // 每日检查开关开启时,即使不展开也会按天自动检测
1940
+ // 卡片加载时无感触发一次检查(5 分钟节流,防频繁刷新刷请求);
1941
+ // 每日检查开关开启时,即使不打开也会按天自动检测
1953
1942
  React.useEffect(function () {
1954
- if (!open) return;
1943
+ if (props && props.view === "summary") return;
1955
1944
  var now = Date.now();
1956
1945
  var lastCheck = 0;
1957
1946
  try { lastCheck = parseInt(localStorage.getItem("dsh-easyrewrite:lastUpdateCheckTs") || "0", 10) || 0; } catch (e) { /* ignore */ }
@@ -1959,7 +1948,7 @@ window.__ModuleLoader__.load({
1959
1948
  try { localStorage.setItem("dsh-easyrewrite:lastUpdateCheckTs", String(now)); } catch (e) { /* ignore */ }
1960
1949
  if (!updateVer) setUpdateVer("…");
1961
1950
  doCheckUpdate();
1962
- }, [open]);
1951
+ }, [props && props.view]);
1963
1952
  // 每日检测:开关开启时每天首次触发一次(不依赖展开)
1964
1953
  React.useEffect(function () {
1965
1954
  var autoOn = getBool("dsh-easyrewrite:autoCheckUpdate", false);
@@ -2025,417 +2014,331 @@ window.__ModuleLoader__.load({
2025
2014
  var origImg = sOrigImg[0];
2026
2015
  var setOrigImg = sOrigImg[1];
2027
2016
 
2028
- // 官方 PluginCard 同款:卡片/展开态/头部/标题/描述/箭头/内容区
2029
- var cardStyle = {
2030
- border: "1px solid var(--dsw-alias-border-l2)",
2031
- background: "var(--dsw-alias-bg-layer-3)",
2032
- borderRadius: "12px",
2033
- listStyle: "none",
2034
- transition: "border-color .16s, background .16s"
2035
- };
2036
- var cardOpenStyle = { background: "var(--dsw-alias-bg-layer-2)", borderColor: "var(--dsw-alias-label-dimmed)" };
2037
- var headStyle = {
2038
- appearance: "none",
2039
- width: "100%",
2040
- font: "inherit",
2041
- color: "inherit",
2042
- textAlign: "left",
2043
- cursor: "pointer",
2044
- background: "transparent",
2045
- border: "none",
2046
- borderRadius: "12px",
2047
- display: "flex",
2048
- alignItems: "center",
2049
- gap: "12px",
2050
- padding: "14px 16px"
2051
- };
2052
- var headTextStyle = { display: "flex", flexDirection: "column", flex: "1", minWidth: "0", gap: "4px" };
2053
- var titleStyle = { color: "var(--dsw-alias-label-primary)", fontSize: "15px", fontWeight: 600, lineHeight: "1.4" };
2054
- var subStyle = { color: "var(--dsw-alias-label-tertiary)", fontSize: "13px", lineHeight: "1.5" };
2055
- var chevronStyle = { color: "var(--dsw-alias-label-tertiary)", flex: "none", transition: "transform .16s", transform: "rotate(90deg)" };
2056
- var chevronOpenStyle = { transform: "rotate(270deg)" };
2057
- var bodyStyle = {
2058
- borderTop: "1px solid var(--dsw-alias-border-l2)",
2059
- margin: "0 16px",
2060
- paddingTop: "14px",
2061
- paddingBottom: "8px",
2062
- display: "flex",
2063
- flexDirection: "column",
2064
- gap: "16px"
2065
- };
2066
- // 大项分组:组内 10px,组间 16px(bodyStyle gap);每级缩进 4 空格(16px)
2067
- var sectionStyle = { display: "flex", flexDirection: "column", gap: "10px", paddingLeft: "16px" };
2068
- var groupTitleStyle = {
2069
- fontSize: "14px",
2070
- fontWeight: 600,
2071
- color: "var(--dsw-alias-label-primary)",
2072
- letterSpacing: "0.02em",
2073
- lineHeight: "1.6"
2074
- };
2075
- var rowStyle = { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "12px", paddingLeft: "16px" };
2076
- var labelStyle = { fontSize: "13px", color: "var(--dsw-alias-label-primary)" };
2077
- var hintStyle = { fontSize: "11px", color: "var(--dsw-alias-label-tertiary)" };
2078
- var groupStyle = { display: "flex", flexDirection: "column", gap: "6px", paddingLeft: "16px" };
2079
- var inputStyle = {
2080
- width: "90px",
2081
- border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.3))",
2082
- borderRadius: "8px",
2083
- padding: "4px 8px",
2084
- fontSize: "13px",
2085
- background: "var(--dsw-alias-bg-base)",
2086
- color: "var(--dsw-alias-label-primary)"
2087
- };
2088
- var disabledInputStyle = Object.assign({}, inputStyle, { opacity: 0.45, cursor: "not-allowed" });
2089
- function switchRow(label, value, onChange, extraHint, disabled) {
2090
- // 圆形勾选框:选中 = 白底黑勾(与确认胶囊同设计语言);未选中 = 灰色圆环
2091
- var checkSize = 20;
2092
- var checkStyle = {
2093
- width: checkSize,
2094
- height: checkSize,
2095
- borderRadius: "50%",
2096
- border: "2px solid " + (value ? "#ffffff" : "var(--dsw-alias-border-l2, rgba(128,128,128,0.45))"),
2097
- background: value ? "#ffffff" : "transparent",
2098
- display: "flex",
2099
- alignItems: "center",
2100
- justifyContent: "center",
2101
- color: value ? "#000000" : "transparent",
2102
- fontSize: "13px",
2103
- lineHeight: "13px",
2104
- cursor: "pointer",
2105
- flex: "none",
2106
- margin: "0 2px",
2107
- transition: "border-color .15s, background .15s, color .15s",
2108
- userSelect: "none",
2109
- boxSizing: "border-box"
2110
- };
2111
- return React.createElement("div", {
2112
- style: Object.assign({}, rowStyle, disabled ? { opacity: 0.45, cursor: "not-allowed" } : null),
2113
- "data-dsh-easyrewrite": "switch-row",
2114
- title: disabled ? (extraHint || "") : undefined
2115
- },
2116
- React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "2px", flex: "1", minWidth: "0" } },
2117
- React.createElement("span", { style: labelStyle }, label),
2118
- extraHint ? React.createElement("span", { style: hintStyle }, extraHint) : null
2119
- ),
2120
- React.createElement("div", {
2121
- role: "checkbox",
2122
- "aria-checked": !!value,
2123
- "aria-disabled": disabled || undefined,
2124
- style: Object.assign({}, checkStyle, disabled ? { cursor: "not-allowed" } : null),
2125
- onClick: function (e) { e.stopPropagation(); if (disabled) return; onChange(!value); }
2126
- }, value ? React.createElement(IconCheckOutline, null) : null)
2017
+ if (props && props.view === "summary") return L.subtitle;
2018
+
2019
+ function sectionHead(title) {
2020
+ return React.createElement(
2021
+ "div",
2022
+ { key: "head-" + title, className: "dsp-card-title", style: { fontSize: "14px", fontWeight: 600, marginTop: "8px" } },
2023
+ title,
2127
2024
  );
2128
2025
  }
2129
- // Apple 风格分段控件:灰色药丸长条 + 白色小药丸高亮当前项(滑动过渡,主题自适应)
2130
- function segmentedGroup(options, value, onChange) {
2131
- var n = options.length;
2132
- var idx = 0;
2133
- for (var i = 0; i < n; i++) { if (options[i][0] === value) { idx = i; break; } }
2134
- var segStyle = {
2135
- position: "relative",
2136
- display: "flex",
2137
- background: "var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,0.1))",
2138
- borderRadius: "999px",
2139
- padding: "2px"
2140
- };
2141
- var thumbStyle = {
2142
- position: "absolute",
2143
- top: "2px",
2144
- left: "calc(" + idx + " * (100% - 4px) / " + n + ")",
2145
- width: "calc((100% - 4px) / " + n + ")",
2146
- height: "calc(100% - 4px)",
2147
- background: "var(--dsw-alias-bg-layer-3, #ffffff)",
2148
- borderRadius: "999px",
2149
- boxShadow: "0 1px 2px rgba(0, 0, 0, 0.12)",
2150
- transition: "left .18s ease",
2151
- zIndex: 1
2152
- };
2153
- return React.createElement("div", { style: segStyle, role: "radiogroup" },
2154
- React.createElement("div", { style: thumbStyle, "data-dsh-easyrewrite": "seg-thumb" }),
2155
- options.map(function (opt) {
2156
- var sel = value === opt[0];
2157
- return React.createElement("div", {
2158
- key: opt[0],
2159
- role: "radio",
2160
- "aria-checked": sel,
2161
- style: {
2162
- position: "relative",
2163
- zIndex: 2,
2164
- flex: "1",
2165
- padding: "3px 10px",
2166
- textAlign: "center",
2167
- fontSize: "13px",
2168
- lineHeight: "20px",
2169
- cursor: "pointer",
2170
- borderRadius: "999px",
2171
- color: sel ? "var(--dsw-alias-label-primary)" : "var(--dsw-alias-label-secondary)",
2172
- transition: "color .15s",
2173
- whiteSpace: "nowrap",
2174
- userSelect: "none"
2175
- },
2176
- onClick: function () { onChange(opt[0]); }
2177
- }, opt[1]);
2178
- })
2026
+
2027
+ function sectionDivider(key) {
2028
+ return React.createElement("hr", {
2029
+ key: key,
2030
+ style: { border: "none", borderTop: "1px solid var(--dsw-alias-border-l2)", margin: "8px 0" },
2031
+ });
2032
+ }
2033
+
2034
+ function renderSwitch(label, value, onChange, extraHint, disabled) {
2035
+ return React.createElement(
2036
+ UI.Field,
2037
+ {
2038
+ key: label,
2039
+ inline: true,
2040
+ label: label,
2041
+ hint: extraHint || undefined,
2042
+ },
2043
+ React.createElement(UI.Switch, {
2044
+ checked: !!value,
2045
+ disabled: !!disabled,
2046
+ label: label,
2047
+ onCheckedChange: onChange,
2048
+ }),
2179
2049
  );
2180
2050
  }
2181
2051
 
2182
- // 配置主体:两种挂法共用。
2183
- //
2184
- // 0.1.7 起卡片挂进 plugins.item,页面已经给了行外壳(标题来自插槽 label、描述、
2185
- // 展开由页面管),卡片只该交出主体。自带 <li> + 可点头部会变成页面里多出来的一层
2186
- // 折叠 —— 列表上一个下拉,点进去还有一个。旧插槽 settings.plugin.item 没有外壳,
2187
- // 那种挂法仍旧要卡片自画,所以分流在函数末尾按 embedded 做。
2188
- var cardBody = React.createElement("div", { style: bodyStyle },
2189
- // —— 编辑 ——
2190
- React.createElement("div", { style: sectionStyle },
2191
- React.createElement("span", { style: groupTitleStyle }, L.sectionEdit),
2192
- switchRow(L.rewrite, rewrite, function (v) { setRewrite(v); setBool("dsh-easyrewrite:rewriteOnClick", v); }),
2193
- switchRow(L.editOffShowRecall, showRecall, function (v) {
2194
- setShowRecall(v); setBool("dsh-easyrewrite:editOffShowRecall", v);
2195
- }, rewrite ? L.lockedHint : null, rewrite),
2196
- // 编辑宽度:三固定 + 自定义(固定时输入框禁用置灰)
2197
- React.createElement("div", { style: groupStyle },
2198
- React.createElement("span", { style: labelStyle }, L.editWidth),
2199
- segmentedGroup([["compact", L.wCompact], ["standard", L.wStandard], ["extended", L.wExtended], ["custom", L.wCustom]], widthMode, function (v) { setWidthMode(v); setSetting("dsh-easyrewrite:editWidth", v); }),
2200
- React.createElement("div", { style: rowStyle },
2201
- React.createElement("span", { style: labelStyle }, L.customWidth),
2202
- React.createElement("input", {
2203
- type: "number",
2204
- min: 100,
2205
- max: 1200,
2206
- style: widthMode === "custom" ? inputStyle : disabledInputStyle,
2207
- disabled: widthMode !== "custom",
2208
- placeholder: L.placeholderCustom,
2209
- value: customW,
2210
- onChange: function (e) {
2211
- setCustomW(e.target.value);
2212
- setSetting("dsh-easyrewrite:editWidthCustom", e.target.value);
2213
- }
2214
- })
2215
- )
2216
- )
2052
+ var children = [
2053
+ // —— 编辑 ——
2054
+ sectionHead(L.sectionEdit),
2055
+ renderSwitch(L.rewrite, rewrite, function (v) { setRewrite(v); setBool("dsh-easyrewrite:rewriteOnClick", v); }),
2056
+ renderSwitch(L.editOffShowRecall, showRecall, function (v) {
2057
+ setShowRecall(v); setBool("dsh-easyrewrite:editOffShowRecall", v);
2058
+ }, rewrite ? L.lockedHint : null, rewrite),
2059
+ React.createElement(
2060
+ UI.Field,
2061
+ {
2062
+ key: "edit-width",
2063
+ label: L.editWidth,
2064
+ },
2065
+ React.createElement(UI.Segmented, {
2066
+ label: L.editWidth,
2067
+ value: widthMode,
2068
+ options: [
2069
+ { value: "compact", label: L.wCompact },
2070
+ { value: "standard", label: L.wStandard },
2071
+ { value: "extended", label: L.wExtended },
2072
+ { value: "custom", label: L.wCustom },
2073
+ ],
2074
+ onChange: function (v) { setWidthMode(v); setSetting("dsh-easyrewrite:editWidth", v); },
2075
+ }),
2076
+ ),
2077
+ React.createElement(
2078
+ UI.Field,
2079
+ {
2080
+ key: "custom-width",
2081
+ inline: true,
2082
+ label: L.customWidth,
2083
+ },
2084
+ React.createElement(UI.NumberField, {
2085
+ min: 100,
2086
+ max: 1200,
2087
+ disabled: widthMode !== "custom",
2088
+ placeholder: L.placeholderCustom,
2089
+ value: customW,
2090
+ onValueChange: function (v) {
2091
+ setCustomW(v);
2092
+ setSetting("dsh-easyrewrite:editWidthCustom", v);
2093
+ },
2094
+ }),
2095
+ ),
2096
+
2097
+ sectionDivider("div-recall"),
2098
+
2099
+ // —— 撤回 ——
2100
+ sectionHead(L.sectionRecall),
2101
+ renderSwitch(L.recallConfirm, confirmCapsule, function (v) { setConfirmCapsule(v); setBool("dsh-easyrewrite:recallConfirm", v); }),
2102
+ renderSwitch(L.showOriginalImages, origImg, function (v) { setOrigImg(v); setBool("dsh-easyrewrite:showOriginalImages", v); }),
2103
+ React.createElement(
2104
+ UI.Field,
2105
+ {
2106
+ key: "visual-mode",
2107
+ label: L.visualMode,
2108
+ },
2109
+ React.createElement(UI.Segmented, {
2110
+ label: L.visualMode,
2111
+ value: visual,
2112
+ options: [
2113
+ { value: "simple", label: L.visualSimple },
2114
+ { value: "minimal", label: L.visualMinimal },
2115
+ { value: "info", label: L.visualInfo },
2116
+ ],
2117
+ onChange: function (v) { setVisual(v); setSetting("dsh-easyrewrite:visualMode", v); },
2118
+ }),
2119
+ ),
2120
+ renderSwitch(L.statOnlyUser, statOnly, function (v) { setStatOnly(v); setBool("dsh-easyrewrite:statOnlyUser", v); }),
2121
+ renderSwitch(L.hotkeyEnable, hotkeyOn, function (v) {
2122
+ setHotkeyOn(v);
2123
+ setHotkeyEnabledSetting(v);
2124
+ if (!v) setRecording(false);
2125
+ }, L.hotkeyHint),
2126
+ React.createElement(
2127
+ UI.Field,
2128
+ {
2129
+ key: "hotkey-field",
2130
+ inline: true,
2131
+ label: L.hotkey + ":" + (hotkey ? formatHotkey(hotkey) : L.hotkeyNone),
2132
+ hint: recording ? L.hotkeyRecording : (hotkey ? undefined : L.hotkeyHint),
2133
+ error: hotkeyInvalid ? L.hotkeyInvalid : undefined,
2134
+ },
2135
+ React.createElement(
2136
+ UI.Button,
2137
+ {
2138
+ variant: "secondary",
2139
+ size: "sm",
2140
+ disabled: !hotkeyOn,
2141
+ onClick: function () { setHotkeyInvalid(false); setRecording(!recording); },
2142
+ },
2143
+ recording ? L.hotkeyRecording : L.hotkeyRecord,
2217
2144
  ),
2218
- // —— 撤回 ——
2219
- React.createElement("div", { style: sectionStyle },
2220
- React.createElement("span", { style: groupTitleStyle }, L.sectionRecall),
2221
- switchRow(L.recallConfirm, confirmCapsule, function (v) { setConfirmCapsule(v); setBool("dsh-easyrewrite:recallConfirm", v); }),
2222
- switchRow(L.showOriginalImages, origImg, function (v) { setOrigImg(v); setBool("dsh-easyrewrite:showOriginalImages", v); }),
2223
- // 视觉模式
2224
- React.createElement("div", { style: groupStyle },
2225
- React.createElement("span", { style: labelStyle }, L.visualMode),
2226
- segmentedGroup([["simple", L.visualSimple], ["minimal", L.visualMinimal], ["info", L.visualInfo]], visual, function (v) { setVisual(v); setSetting("dsh-easyrewrite:visualMode", v); })
2227
- ),
2228
- switchRow(L.statOnlyUser, statOnly, function (v) { setStatOnly(v); setBool("dsh-easyrewrite:statOnlyUser", v); }),
2229
- // 撤回快捷键:总开关(Beta,默认关——避免与其他插件快捷键打架)
2230
- switchRow(L.hotkeyEnable, hotkeyOn, function (v) {
2231
- setHotkeyOn(v);
2232
- setHotkeyEnabledSetting(v);
2233
- if (!v) setRecording(false);
2234
- }, L.hotkeyHint),
2235
- // 键位行:始终显示;总开关关闭时录制按钮禁用置灰
2236
- React.createElement("div", { style: rowStyle },
2237
- React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "2px" } },
2238
- React.createElement("span", { style: labelStyle }, L.hotkey + ":" + (hotkey ? formatHotkey(hotkey) : L.hotkeyNone)),
2239
- React.createElement("span", { style: hintStyle }, recording ? L.hotkeyRecording : (hotkey ? null : L.hotkeyHint)),
2240
- hotkeyInvalid ? React.createElement("span", { style: { fontSize: "11px", color: "var(--dsw-alias-label-error, #d9534f)" } }, L.hotkeyInvalid) : null
2145
+ ),
2146
+
2147
+ sectionDivider("div-composer"),
2148
+
2149
+ // —— 回填 ——
2150
+ sectionHead(L.sectionComposer),
2151
+ React.createElement(
2152
+ UI.Field,
2153
+ {
2154
+ key: "conflict-mode",
2155
+ label: L.conflictMode,
2156
+ hint: conflict === "merge" ? L.conflictMerge : L.conflictOverwrite,
2157
+ },
2158
+ React.createElement(UI.Segmented, {
2159
+ label: L.conflictMode,
2160
+ value: conflict,
2161
+ options: [
2162
+ { value: "overwrite", label: L.conflictOverwriteShort },
2163
+ { value: "merge", label: L.conflictMergeShort },
2164
+ ],
2165
+ onChange: function (v) { setConflict(v); setSetting("dsh-easyrewrite:conflictMode", v); },
2166
+ }),
2167
+ ),
2168
+
2169
+ sectionDivider("div-versions"),
2170
+
2171
+ // —— 版本 ——
2172
+ sectionHead(L.sectionVersions),
2173
+ React.createElement(
2174
+ "div",
2175
+ { key: "versions-group", style: { display: "flex", flexDirection: "column", gap: "6px" } },
2176
+ (function () {
2177
+ var families = listVersionFamilies(props.ctxSessions);
2178
+ if (families.length === 0) return React.createElement("span", { className: "dsp-field-hint" }, L.versionFamilyNone);
2179
+ var snap = null;
2180
+ try { snap = props.ctxSessions.list.getSnapshot(); } catch (e) { /* ignore */ }
2181
+ var metaOf = function (vid) { return (snap && snap.byId && snap.byId[vid]) || {}; };
2182
+ var famName = function (fam) {
2183
+ var best = null, bt = -1;
2184
+ fam.versions.forEach(function (vid) {
2185
+ var m = metaOf(vid); var tt = m.updatedAt || 0;
2186
+ if (tt >= bt) { bt = tt; best = m.title || null; }
2187
+ });
2188
+ return best || (fam.versions.length + " " + L.versionCount);
2189
+ };
2190
+ var fmtStamp = function (tsv) {
2191
+ if (!tsv) return "—";
2192
+ var dd = new Date(tsv);
2193
+ var p2 = function (x) { return (x < 10 ? "0" : "") + x; };
2194
+ return dd.getFullYear() + "-" + p2(dd.getMonth() + 1) + "-" + p2(dd.getDate()) + " " + p2(dd.getHours()) + ":" + p2(dd.getMinutes());
2195
+ };
2196
+ var chev = function (openState) {
2197
+ return React.createElement("svg", {
2198
+ width: "16", height: "16", viewBox: "0 0 24 24",
2199
+ style: { flex: "none", display: "block", color: "var(--dsw-alias-label-tertiary)", transform: openState ? "rotate(90deg)" : "rotate(0deg)", transition: "transform .12s" }
2200
+ }, React.createElement("path", { d: "M9 6l6 6-6 6", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }));
2201
+ };
2202
+ var rowHead = { display: "flex", alignItems: "center", gap: "8px", padding: "4px 8px", borderRadius: "6px" };
2203
+ var hdrRow = { display: "flex", alignItems: "center", gap: "6px", cursor: "pointer", padding: "4px 0" };
2204
+ return React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px" } },
2205
+ React.createElement("div", { style: hdrRow, onClick: function () { setHistOpen(!histOpen); } },
2206
+ chev(histOpen),
2207
+ React.createElement("span", { style: { fontSize: "13px", color: "var(--dsw-alias-label-secondary)" } }, L.versionHistory),
2208
+ React.createElement("span", { style: { marginLeft: "auto" } }, React.createElement(UI.Tag, { mono: true }, families.length + ""))
2241
2209
  ),
2242
- React.createElement("button", {
2243
- type: "button",
2244
- disabled: !hotkeyOn,
2245
- style: {
2246
- appearance: "none",
2247
- border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.3))",
2248
- background: "transparent",
2249
- color: "var(--dsw-alias-label-secondary)",
2250
- borderRadius: "6px",
2251
- padding: "3px 10px",
2252
- fontSize: "12px",
2253
- cursor: hotkeyOn ? "pointer" : "not-allowed",
2254
- fontFamily: "inherit",
2255
- flex: "none",
2256
- opacity: hotkeyOn ? 1 : 0.45
2257
- },
2258
- onClick: function () { setHotkeyInvalid(false); setRecording(!recording); }
2259
- }, recording ? L.hotkeyRecording : L.hotkeyRecord)
2260
- )
2261
- ),
2262
- // —— 回填 ——
2263
- React.createElement("div", { style: sectionStyle },
2264
- React.createElement("span", { style: groupTitleStyle }, L.sectionComposer),
2265
- // 回填冲突模式
2266
- React.createElement("div", { style: groupStyle },
2267
- React.createElement("span", { style: labelStyle }, L.conflictMode),
2268
- segmentedGroup([["overwrite", L.conflictOverwriteShort], ["merge", L.conflictMergeShort]], conflict, function (v) { setConflict(v); setSetting("dsh-easyrewrite:conflictMode", v); }),
2269
- React.createElement("span", { style: hintStyle }, conflict === "merge" ? L.conflictMerge : L.conflictOverwrite)
2270
- )
2271
- ),
2272
- // —— 版本 ——
2273
- React.createElement("div", { style: sectionStyle },
2274
- React.createElement("span", { style: groupTitleStyle }, L.sectionVersions),
2275
- // 版本历史(三层折叠):总开关默认收起 → 按对话名的家族折叠项 → 条目显最后对话时间
2276
- // (官方无归档恢复入口,此处保留恢复并打开;review #6 后家族来自官方 lineage)
2277
- React.createElement("div", { style: groupStyle },
2278
- (function () {
2279
- var families = listVersionFamilies(props.ctxSessions);
2280
- if (families.length === 0) return React.createElement("span", { style: hintStyle }, L.versionFamilyNone);
2281
- var snap = null;
2282
- try { snap = props.ctxSessions.list.getSnapshot(); } catch (e) { /* ignore */ }
2283
- var metaOf = function (vid) { return (snap && snap.byId && snap.byId[vid]) || {}; };
2284
- var famName = function (fam) {
2285
- var best = null, bt = -1;
2286
- fam.versions.forEach(function (vid) {
2287
- var m = metaOf(vid); var tt = m.updatedAt || 0;
2288
- if (tt >= bt) { bt = tt; best = m.title || null; }
2289
- });
2290
- return best || (fam.versions.length + " " + L.versionCount);
2291
- };
2292
- var fmtStamp = function (tsv) {
2293
- if (!tsv) return "—";
2294
- var dd = new Date(tsv);
2295
- var p2 = function (x) { return (x < 10 ? "0" : "") + x; };
2296
- return dd.getFullYear() + "-" + p2(dd.getMonth() + 1) + "-" + p2(dd.getDate()) + " " + p2(dd.getHours()) + ":" + p2(dd.getMinutes());
2297
- };
2298
- // 折叠箭头:SVG 描边(官方图标同风格),像素级对齐、无字距
2299
- var chev = function (openState) {
2300
- return React.createElement("svg", {
2301
- width: "18", height: "18", viewBox: "0 0 24 24",
2302
- style: { flex: "none", display: "block", color: "var(--dsw-alias-label-tertiary)", transform: openState ? "rotate(90deg)" : "rotate(0deg)", transition: "transform .12s" }
2303
- }, React.createElement("path", { d: "M9 6l6 6-6 6", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }));
2304
- };
2305
- var rowHead = { display: "flex", alignItems: "center", gap: "6px", padding: "5px 4px", cursor: "pointer", borderRadius: "6px" };
2306
- var hdrRow = { display: "flex", alignItems: "center", cursor: "pointer" }; // 无 gap:SVG 箭头紧贴文字
2307
- // 层1:版本历史总折叠(默认收起)
2308
- return React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "2px" } },
2309
- React.createElement("div", { style: hdrRow, onClick: function () { setHistOpen(!histOpen); } },
2310
- chev(histOpen),
2311
- React.createElement("span", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary)" } }, L.versionHistory),
2312
- React.createElement("span", { style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary)", marginLeft: "auto" } }, families.length + "")
2210
+ histOpen ? families.map(function (fam) {
2211
+ var famOpen = !!famOpenMap[fam.rootId];
2212
+ return React.createElement("div", { key: fam.rootId, style: { display: "flex", flexDirection: "column", gap: "2px", padding: "2px 0 2px 14px", borderTop: "1px solid var(--dsw-alias-border-l2)" } },
2213
+ React.createElement("div", { style: hdrRow, onClick: function () { var nm = {}; for (var ok2 in famOpenMap) nm[ok2] = famOpenMap[ok2]; nm[fam.rootId] = !famOpen; setFamOpenMap(nm); } },
2214
+ chev(famOpen),
2215
+ React.createElement("span", { style: { fontSize: "13px", color: "var(--dsw-alias-label-primary)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: "1" } }, famName(fam)),
2216
+ React.createElement("span", { style: { marginLeft: "auto" } }, React.createElement(UI.Tag, null, fam.versions.length + " " + L.versionCount))
2313
2217
  ),
2314
- histOpen ? families.map(function (fam) {
2315
- var open = !!famOpenMap[fam.rootId];
2316
- return React.createElement("div", { key: fam.rootId, style: { display: "flex", flexDirection: "column", gap: "2px", padding: "2px 0 2px 12px", borderTop: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.14))" } },
2317
- React.createElement("div", { style: hdrRow, onClick: function () { var nm = {}; for (var ok2 in famOpenMap) nm[ok2] = famOpenMap[ok2]; nm[fam.rootId] = !open; setFamOpenMap(nm); } },
2318
- chev(open),
2319
- React.createElement("span", { style: { fontSize: "12px", color: "var(--dsw-alias-label-primary)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: "1" } }, famName(fam)),
2320
- React.createElement("span", { style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary)", flex: "none", marginLeft: "auto" } }, fam.versions.length + " " + L.versionCount)
2321
- ),
2322
- open ? fam.versions.map(function (vid, vi) {
2323
- return React.createElement("div", { key: vid, style: rowHead },
2324
- React.createElement("span", { style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary)", fontVariantNumeric: "tabular-nums", flex: "none", width: "26px" } }, "v" + (vi + 1)),
2325
- React.createElement("span", { style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary)", fontVariantNumeric: "tabular-nums", flex: "1" } }, fmtStamp(metaOf(vid).updatedAt)),
2326
- React.createElement("button", {
2327
- type: "button",
2328
- style: { appearance: "none", border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.3))", background: "transparent", color: "var(--dsw-alias-label-secondary)", borderRadius: "6px", padding: "2px 8px", fontSize: "12px", cursor: "pointer", fontFamily: "inherit", flex: "none" },
2329
- onClick: function (ev) {
2330
- ev.stopPropagation();
2331
- fetch("/bubble/unarchive", {
2332
- method: "POST",
2333
- headers: { "content-type": "application/json" },
2334
- body: JSON.stringify({ sessionId: vid }),
2335
- keepalive: true
2336
- }).then(function () {
2337
- if (typeof props.openSession === "function") props.openSession(vid);
2338
- }).catch(function () { /* ignore */ });
2339
- }
2340
- }, L.versionRestoreOpen)
2341
- );
2342
- }) : null
2218
+ famOpen ? fam.versions.map(function (vid, vi) {
2219
+ return React.createElement("div", { key: vid, style: rowHead },
2220
+ React.createElement("span", { style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary)", fontVariantNumeric: "tabular-nums", flex: "none", width: "28px" } }, "v" + (vi + 1)),
2221
+ React.createElement("span", { style: { fontSize: "12px", color: "var(--dsw-alias-label-tertiary)", fontVariantNumeric: "tabular-nums", flex: "1" } }, fmtStamp(metaOf(vid).updatedAt)),
2222
+ React.createElement(UI.Button, {
2223
+ variant: "secondary",
2224
+ size: "sm",
2225
+ onClick: function (ev) {
2226
+ ev.stopPropagation();
2227
+ fetch("/bubble/unarchive", {
2228
+ method: "POST",
2229
+ headers: { "content-type": "application/json" },
2230
+ body: JSON.stringify({ sessionId: vid }),
2231
+ keepalive: true
2232
+ }).then(function () {
2233
+ if (typeof props.openSession === "function") props.openSession(vid);
2234
+ }).catch(function () { /* ignore */ });
2235
+ }
2236
+ }, L.versionRestoreOpen)
2343
2237
  );
2344
2238
  }) : null
2345
2239
  );
2346
- })()
2347
- )
2240
+ }) : null
2241
+ );
2242
+ })()
2243
+ ),
2244
+
2245
+ sectionDivider("div-update"),
2246
+
2247
+ // —— 更新 ——
2248
+ sectionHead(L.sectionUpdate),
2249
+ React.createElement(
2250
+ "div",
2251
+ { key: "updates-group", style: { display: "flex", flexDirection: "column", gap: "10px" } },
2252
+ React.createElement(
2253
+ UI.Field,
2254
+ {
2255
+ key: "check-update-field",
2256
+ inline: true,
2257
+ label: L.currentVersion + ":" + (updateVer || "…"),
2258
+ hint: updateMsg || undefined,
2259
+ },
2260
+ React.createElement(UI.Button, {
2261
+ variant: "secondary",
2262
+ size: "sm",
2263
+ loading: updateChecking,
2264
+ onClick: function () { doCheckUpdate(); },
2265
+ }, updateChecking ? L.checking : L.checkUpdate),
2348
2266
  ),
2349
- // —— 更新 ——
2350
- React.createElement("div", { style: sectionStyle },
2351
- React.createElement("span", { style: groupTitleStyle }, L.sectionUpdate),
2352
- // 更新检查与执行(精简版:手动检查 + 每日检测可选 + 有新版提示)
2353
- React.createElement("div", { style: groupStyle },
2354
- React.createElement("span", { style: labelStyle }, L.currentVersion + ":" + (updateVer || "…")),
2355
- (function () {
2356
- if (!dshVer) return null;
2357
- if (cmpDsh(dshVer, MIN_DSH_VERSION) < 0) return React.createElement("div", { key: "dsh-low", style: { display: "flex", flexDirection: "column", gap: "4px", padding: "6px 8px", background: "var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,0.1))", borderRadius: "8px" } },
2358
- React.createElement("span", { style: { fontSize: "12px", lineHeight: "18px", color: "var(--dsw-alias-label-primary)" } }, L.dshLowWarning.replace("{cur}", dshVer).replace("{min}", MIN_DSH_VERSION)),
2359
- React.createElement("div", { style: { display: "flex", alignItems: "center", gap: "6px" } },
2360
- React.createElement("code", { style: { fontSize: "11px", color: "var(--dsw-alias-label-secondary)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: "1" } }, "npm install -g @deepseek-ai/dsh@" + MIN_DSH_VERSION),
2361
- React.createElement("button", { type: "button", style: { appearance: "none", border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.3))", background: "transparent", color: "var(--dsw-alias-label-secondary)", borderRadius: "6px", padding: "2px 8px", fontSize: "11px", cursor: "pointer", fontFamily: "inherit", flex: "none" }, onClick: function () { try { legacyCopy("npm install -g @deepseek-ai/dsh@" + MIN_DSH_VERSION); } catch (eC1) { /* ignore */ } } }, L.copyUpgradeCmd)
2362
- )
2363
- );
2364
- if (cmpDsh(MAX_TESTED_DSH_VERSION, dshVer) < 0) return React.createElement("div", { key: "dsh-new", style: { padding: "6px 8px", fontSize: "12px", lineHeight: "18px", color: "var(--dsw-alias-label-secondary)", background: "var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,0.08))", borderRadius: "8px" } }, L.dshNewNotice.replace("{cur}", dshVer));
2365
- return null;
2366
- })(),
2367
- !remindClosed && !getBool("dsh-easyrewrite:autoCheckUpdate", false) ? React.createElement("div", { key: "remind", style: { display: "flex", alignItems: "center", gap: "8px", padding: "6px 8px", background: "rgba(77,107,254,0.08)", borderRadius: "8px" } },
2368
- React.createElement("span", { style: { fontSize: "12px", lineHeight: "18px", color: "var(--dsw-alias-label-primary)", flex: "1", minWidth: "0" } }, L.suggestAutoCheck),
2369
- React.createElement("button", { type: "button", style: { appearance: "none", border: "none", background: "var(--dsw-static-deepseek-500, #4d6bfe)", color: "#ffffff", borderRadius: "6px", padding: "3px 10px", fontSize: "12px", cursor: "pointer", fontFamily: "inherit", flex: "none" }, onClick: function () { setBool("dsh-easyrewrite:autoCheckUpdate", true); setBool("dsh-easyrewrite:autoCheckReminded", true); setAutoCheckOn(true); setRemindClosed(true); } }, L.enableNow),
2370
- React.createElement("button", { type: "button", title: L.remindIgnore, style: { appearance: "none", border: "none", background: "var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,0.16))", color: "var(--dsw-alias-label-secondary)", borderRadius: "6px", padding: "3px 10px", fontSize: "12px", cursor: "pointer", fontFamily: "inherit", flex: "none" }, onClick: function () { setBool("dsh-easyrewrite:autoCheckReminded", true); setRemindClosed(true); } }, L.remindIgnore)
2371
- ) : null,
2372
- React.createElement("div", { style: rowStyle },
2373
- React.createElement("span", { style: hintStyle, flex: "1" }, updateMsg || ""),
2374
- React.createElement("button", {
2375
- type: "button",
2376
- style: {
2377
- appearance: "none",
2378
- border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.3))",
2379
- background: "transparent",
2380
- color: "var(--dsw-alias-label-secondary)",
2381
- borderRadius: "6px",
2382
- padding: "3px 10px",
2383
- fontSize: "12px",
2384
- cursor: "pointer",
2385
- fontFamily: "inherit",
2386
- flex: "none"
2387
- },
2388
- onClick: function () { doCheckUpdate(); }
2389
- }, updateChecking ? L.checking : L.checkUpdate)
2390
- ),
2391
- updateAvailableVer ? React.createElement("div", { style: rowStyle },
2392
- React.createElement("span", { style: { fontSize: "12px", color: "var(--dsw-alias-brand-primary, #4d6bfe)", flex: "1" } }, L.newVersion.replace("{ver}", updateAvailableVer)),
2393
- React.createElement("button", {
2394
- type: "button",
2395
- style: {
2396
- appearance: "none",
2397
- border: "none",
2398
- background: "var(--dsw-static-deepseek-500, #4d6bfe)",
2399
- color: "#ffffff",
2400
- borderRadius: "6px",
2401
- padding: "3px 12px",
2402
- fontSize: "12px",
2403
- cursor: "pointer",
2404
- fontFamily: "inherit",
2405
- flex: "none"
2406
- },
2407
- onClick: function () { doUpdatePlugin(); }
2408
- }, updateUpdating ? L.updating : L.updateNow)
2409
- ) : null,
2410
- switchRow(L.autoCheckUpdate, autoCheckOn, function (v) {
2411
- setAutoCheckOn(v);
2412
- setBool("dsh-easyrewrite:autoCheckUpdate", v);
2413
- })
2267
+ (function () {
2268
+ if (!dshVer) return null;
2269
+ if (cmpDsh(dshVer, MIN_DSH_VERSION) < 0) return React.createElement(
2270
+ UI.Banner,
2271
+ { key: "dsh-low", kind: "warn" },
2272
+ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "4px", width: "100%" } },
2273
+ React.createElement("span", null, L.dshLowWarning.replace("{cur}", dshVer).replace("{min}", MIN_DSH_VERSION)),
2274
+ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: "6px" } },
2275
+ React.createElement("code", { style: { fontSize: "11px", flex: "1", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, "npm install -g @deepseek-ai/dsh@" + MIN_DSH_VERSION),
2276
+ React.createElement(UI.Button, {
2277
+ variant: "secondary",
2278
+ size: "sm",
2279
+ onClick: function () { try { legacyCopy("npm install -g @deepseek-ai/dsh@" + MIN_DSH_VERSION); } catch (e) {} },
2280
+ }, L.copyUpgradeCmd)
2281
+ )
2282
+ )
2283
+ );
2284
+ if (cmpDsh(MAX_TESTED_DSH_VERSION, dshVer) < 0) return React.createElement(UI.Banner, { key: "dsh-new", kind: "info" }, L.dshNewNotice.replace("{cur}", dshVer));
2285
+ return null;
2286
+ })(),
2287
+ !remindClosed && !getBool("dsh-easyrewrite:autoCheckUpdate", false) ? React.createElement(
2288
+ UI.Banner,
2289
+ { key: "remind", kind: "info" },
2290
+ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: "8px", width: "100%" } },
2291
+ React.createElement("span", { style: { flex: "1", minWidth: 0 } }, L.suggestAutoCheck),
2292
+ React.createElement(UI.Button, {
2293
+ variant: "primary",
2294
+ size: "sm",
2295
+ onClick: function () {
2296
+ setBool("dsh-easyrewrite:autoCheckUpdate", true);
2297
+ setBool("dsh-easyrewrite:autoCheckReminded", true);
2298
+ setAutoCheckOn(true);
2299
+ setRemindClosed(true);
2300
+ },
2301
+ }, L.enableNow),
2302
+ React.createElement(UI.Button, {
2303
+ variant: "ghost",
2304
+ size: "sm",
2305
+ title: L.remindIgnore,
2306
+ onClick: function () {
2307
+ setBool("dsh-easyrewrite:autoCheckReminded", true);
2308
+ setRemindClosed(true);
2309
+ },
2310
+ }, L.remindIgnore)
2414
2311
  )
2415
- ),
2416
- React.createElement("span", { style: hintStyle }, L.instant)
2417
- );
2418
- // plugins.item(0.1.7 起):页面把同一个组件按 view 渲染两次 —— summary 当列表行与
2419
- // 详情页的一行描述,page 当配置主体。图标、标题(取自插槽 label)、启用开关与详情页
2420
- // 外壳都由页面提供,所以这里两者都不画头部。
2421
- if (embedded) return props.view === "summary" ? L.subtitle : cardBody;
2422
- // settings.plugin.item(0.1.5 及以下):插槽不提供外壳,卡片自画可点头部。
2423
- return React.createElement("li", { style: Object.assign({}, cardStyle, open ? cardOpenStyle : null), "data-dsh-easyrewrite": "settings-card" },
2424
- React.createElement("button", {
2425
- type: "button",
2426
- style: headStyle,
2427
- "aria-expanded": open,
2428
- "aria-label": (open ? L.collapse : L.expand) + ": " + L.title,
2429
- onClick: function () { setOpen(!open); }
2430
- },
2431
- React.createElement("span", { style: headTextStyle },
2432
- React.createElement("span", { style: titleStyle }, L.title),
2433
- React.createElement("span", { style: subStyle }, L.subtitle)
2434
- ),
2435
- React.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", style: Object.assign({}, chevronStyle, open ? chevronOpenStyle : null) },
2436
- React.createElement("path", { d: "M9 6l6 6-6 6", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }))
2312
+ ) : null,
2313
+ updateAvailableVer ? React.createElement(
2314
+ UI.Banner,
2315
+ { key: "update-avail", kind: "ok" },
2316
+ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: "8px", width: "100%" } },
2317
+ React.createElement("span", { style: { flex: "1" } }, L.newVersion.replace("{ver}", updateAvailableVer)),
2318
+ React.createElement(UI.Button, {
2319
+ variant: "primary",
2320
+ size: "sm",
2321
+ loading: updateUpdating,
2322
+ onClick: function () { doUpdatePlugin(); },
2323
+ }, updateUpdating ? L.updating : L.updateNow)
2324
+ )
2325
+ ) : null,
2326
+ renderSwitch(L.autoCheckUpdate, autoCheckOn, function (v) {
2327
+ setAutoCheckOn(v);
2328
+ setBool("dsh-easyrewrite:autoCheckUpdate", v);
2329
+ }),
2437
2330
  ),
2438
- open ? cardBody : null
2331
+
2332
+ React.createElement("span", { key: "instant-hint", className: "dsp-field-hint" }, L.instant),
2333
+ ];
2334
+
2335
+ return React.createElement(
2336
+ UI.SettingsCard,
2337
+ {
2338
+ title: L.title,
2339
+ description: L.subtitle,
2340
+ },
2341
+ children,
2439
2342
  );
2440
2343
  }
2441
2344
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logictan/dsh-easyrewrite",
3
- "version": "2.7.1",
3
+ "version": "2.7.2",
4
4
  "description": "DSH Web内目前最无感的消息撤回、重编辑插件,原版体验,兼容性强,功能简单可开关,设置丰富,现代化轻量ui框架。The most seamless message recall & re-edit plugin for DSH Web — native experience, strong compatibility, simple toggles, rich settings, modern lightweight UI. DSH Web で最もシームレスなメッセージ撤回・再編集プラグイン——ネイティブ体験、高い互換性、シンプルなトグル、充実した設定、モダンで軽量な UI。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -14,6 +14,9 @@
14
14
  "patch": "./cordis.patch.yml"
15
15
  },
16
16
  "client": {
17
+ "external": [
18
+ "@logictan/dsh-plugins-shared-client"
19
+ ],
17
20
  "inject": [
18
21
  "slots",
19
22
  "sessions",
@@ -63,9 +66,17 @@
63
66
  "prepare": "node build.mjs",
64
67
  "prepack": "node build.mjs"
65
68
  },
69
+ "peerDependencies": {
70
+ "@logictan/dsh-plugins-shared-client": "^0.1.0"
71
+ },
66
72
  "repository": {
67
73
  "type": "git",
68
74
  "url": "git+https://github.com/dale0525/dsh-plugins.git",
69
75
  "directory": "packages/dsh-easyrewrite"
76
+ },
77
+ "peerDependenciesMeta": {
78
+ "@logictan/dsh-plugins-shared-client": {
79
+ "optional": true
80
+ }
70
81
  }
71
82
  }