mellos-mapping 0.23.0 → 0.25.0
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/README.md +48 -10
- package/README.zh-CN.md +38 -9
- package/dist/hook-session-start.mjs +1 -1
- package/dist/native/LICENSES.txt +443 -0
- package/dist/native/darwin-arm64/fs-native-extensions.node +0 -0
- package/dist/native/darwin-x64/fs-native-extensions.node +0 -0
- package/dist/native/linux-arm64/fs-native-extensions.node +0 -0
- package/dist/native/linux-x64/fs-native-extensions.node +0 -0
- package/dist/native/win32-arm64/fs-native-extensions.node +0 -0
- package/dist/native/win32-x64/fs-native-extensions.node +0 -0
- package/dist/native-lock.cjs +346 -0
- package/dist/server.mjs +198 -138
- package/dist/terminal-worker.mjs +117 -74
- package/dist/watch.mjs +223 -100
- package/dist/web/app.css +106 -2
- package/dist/web/app.js +78 -0
- package/dist/web/index.html +1 -1
- package/dist/web/terminal.css +103 -0
- package/dist/web/terminal.html +1 -1
- package/dist/web/terminal.js +78 -0
- package/dist/web.mjs +305 -112
- package/docs/codex.md +4 -1
- package/docs/locking.md +115 -0
- package/docs/map-api.md +29 -13
- package/lib/store/migration.js +19 -12
- package/lib/store/native-lock.d.ts +5 -0
- package/lib/store/native-lock.js +27 -0
- package/lib/store/transaction.d.ts +9 -2
- package/lib/store/transaction.js +79 -50
- package/package.json +6 -3
package/dist/web/app.css
CHANGED
|
@@ -724,7 +724,8 @@ h2 {
|
|
|
724
724
|
margin-left: 20px;
|
|
725
725
|
}
|
|
726
726
|
footer {
|
|
727
|
-
height: 29px;
|
|
727
|
+
min-height: 29px;
|
|
728
|
+
flex-wrap: wrap;
|
|
728
729
|
flex-shrink: 0;
|
|
729
730
|
border-top: 1px solid var(--line);
|
|
730
731
|
display: flex;
|
|
@@ -919,7 +920,7 @@ dialog p {
|
|
|
919
920
|
#app {
|
|
920
921
|
display: grid;
|
|
921
922
|
grid-template-columns: minmax(0, 1fr) 300px;
|
|
922
|
-
grid-template-rows: 58px auto auto minmax(0, 1fr)
|
|
923
|
+
grid-template-rows: 58px auto auto minmax(0, 1fr) auto;
|
|
923
924
|
}
|
|
924
925
|
header {
|
|
925
926
|
grid-column: 1 / -1;
|
|
@@ -965,3 +966,106 @@ dialog p {
|
|
|
965
966
|
grid-column: 1 / -1;
|
|
966
967
|
}
|
|
967
968
|
}
|
|
969
|
+
|
|
970
|
+
#star-reminder {
|
|
971
|
+
--star-surface: linear-gradient(115deg, #15302e, #202341 48%, #36243b);
|
|
972
|
+
--star-text: #f2f5ff;
|
|
973
|
+
--star-muted: #c9d2e5;
|
|
974
|
+
width: 100%;
|
|
975
|
+
display: flex;
|
|
976
|
+
flex-wrap: wrap;
|
|
977
|
+
align-items: center;
|
|
978
|
+
gap: 10px 14px;
|
|
979
|
+
margin: 4px 0 8px;
|
|
980
|
+
padding: 12px 16px;
|
|
981
|
+
border: 1px solid transparent;
|
|
982
|
+
border-radius: 12px;
|
|
983
|
+
background: var(--star-surface) padding-box,
|
|
984
|
+
linear-gradient(110deg, #69ebc9, #8299ff, #d897ff, #ffc17d, #69ebc9) border-box;
|
|
985
|
+
background-size: 200% 100%;
|
|
986
|
+
color: var(--star-text);
|
|
987
|
+
font-size: 14px;
|
|
988
|
+
line-height: 1.5;
|
|
989
|
+
animation: star-color-flow 6s ease-in-out 2, star-glow 3s ease-in-out 4;
|
|
990
|
+
}
|
|
991
|
+
#star-reminder::before {
|
|
992
|
+
content: "★";
|
|
993
|
+
display: grid;
|
|
994
|
+
place-items: center;
|
|
995
|
+
flex: 0 0 36px;
|
|
996
|
+
height: 36px;
|
|
997
|
+
border: 1px solid #ffe49950;
|
|
998
|
+
border-radius: 10px;
|
|
999
|
+
background: #f9c76916;
|
|
1000
|
+
color: #ffe49b;
|
|
1001
|
+
font-size: 26px;
|
|
1002
|
+
line-height: 1;
|
|
1003
|
+
text-shadow: 0 0 14px #ffd47870;
|
|
1004
|
+
animation: star-beat 3s ease-in-out 4;
|
|
1005
|
+
}
|
|
1006
|
+
#star-reminder > span { flex: 1 1 230px; font-weight: 550; }
|
|
1007
|
+
#star-reminder a {
|
|
1008
|
+
display: inline-flex;
|
|
1009
|
+
align-items: center;
|
|
1010
|
+
justify-content: center;
|
|
1011
|
+
min-height: 36px;
|
|
1012
|
+
padding: 7px 16px;
|
|
1013
|
+
border: 1px solid #ffffff50;
|
|
1014
|
+
border-radius: 8px;
|
|
1015
|
+
background: linear-gradient(110deg, #8bf0d0, #babaff 55%, #ffd0aa);
|
|
1016
|
+
color: #17243a;
|
|
1017
|
+
font-size: 13px;
|
|
1018
|
+
font-weight: 750;
|
|
1019
|
+
line-height: 1.4;
|
|
1020
|
+
text-decoration: none;
|
|
1021
|
+
white-space: nowrap;
|
|
1022
|
+
box-shadow: 0 2px 12px #8b96ff25;
|
|
1023
|
+
}
|
|
1024
|
+
#star-reminder a:hover { filter: brightness(1.1); }
|
|
1025
|
+
#star-reminder a[aria-disabled=true] { cursor: progress; opacity: .8; }
|
|
1026
|
+
#star-reminder .star-open-error { flex: 1 1 100%; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 12px; color: var(--star-muted); }
|
|
1027
|
+
#star-reminder .star-open-error input { min-width: 0; max-width: 100%; width: 340px; padding: 6px; color: var(--star-text); background: transparent; border: 1px solid #b8c5e470; border-radius: 6px; }
|
|
1028
|
+
#star-reminder button {
|
|
1029
|
+
min-height: 34px;
|
|
1030
|
+
padding: 6px 10px;
|
|
1031
|
+
border: 1px solid #b8c5e42e;
|
|
1032
|
+
border-radius: 8px;
|
|
1033
|
+
background: transparent;
|
|
1034
|
+
color: var(--star-muted);
|
|
1035
|
+
font-size: 12px;
|
|
1036
|
+
line-height: 1.4;
|
|
1037
|
+
white-space: nowrap;
|
|
1038
|
+
}
|
|
1039
|
+
#star-reminder button:hover { background: #a0b5f014; border-color: #b8c5e470; }
|
|
1040
|
+
#star-reminder a:focus-visible, #star-reminder button:focus-visible {
|
|
1041
|
+
outline: 2px solid #b0b9ff;
|
|
1042
|
+
outline-offset: 3px;
|
|
1043
|
+
}
|
|
1044
|
+
#star-reminder:hover, #star-reminder:focus-within,
|
|
1045
|
+
#star-reminder:hover::before, #star-reminder:focus-within::before { animation-play-state: paused; }
|
|
1046
|
+
[data-theme=light] #star-reminder {
|
|
1047
|
+
--star-surface: linear-gradient(115deg, #e4fbf5, #eeedff 48%, #fff0e5);
|
|
1048
|
+
--star-text: #263451;
|
|
1049
|
+
--star-muted: #53607b;
|
|
1050
|
+
}
|
|
1051
|
+
[data-theme=light] #star-reminder::before { color: #a5660c; background: #ffe6aa80; border-color: #dbae6340; text-shadow: none; }
|
|
1052
|
+
@keyframes star-color-flow {
|
|
1053
|
+
0%, 100% { background-position: 0% 50%; }
|
|
1054
|
+
50% { background-position: 100% 50%; }
|
|
1055
|
+
}
|
|
1056
|
+
@keyframes star-glow {
|
|
1057
|
+
0%, 100% { box-shadow: 0 0 0 #8299ff00; }
|
|
1058
|
+
50% { box-shadow: 0 0 16px #8299ff30, inset 0 0 18px #b897ff0c; }
|
|
1059
|
+
}
|
|
1060
|
+
@keyframes star-beat {
|
|
1061
|
+
0%, 100% { transform: scale(1) rotate(0deg); }
|
|
1062
|
+
50% { transform: scale(1.1) rotate(-7deg); }
|
|
1063
|
+
}
|
|
1064
|
+
@media (max-width: 520px) {
|
|
1065
|
+
#star-reminder { padding: 12px; gap: 10px; }
|
|
1066
|
+
#star-reminder > span { flex-basis: calc(100% - 50px); font-size: 13px; }
|
|
1067
|
+
#star-reminder a { margin-left: 46px; }
|
|
1068
|
+
}
|
|
1069
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1070
|
+
#star-reminder, #star-reminder::before { animation: none; }
|
|
1071
|
+
}
|
package/dist/web/app.js
CHANGED
|
@@ -904,6 +904,82 @@ function zoomScene(state, factor, x, y) {
|
|
|
904
904
|
};
|
|
905
905
|
}
|
|
906
906
|
|
|
907
|
+
// src/web/star-reminder.ts
|
|
908
|
+
function createStarNotice(base2) {
|
|
909
|
+
let attempted = false;
|
|
910
|
+
const notice = document.getElementById("star-reminder");
|
|
911
|
+
const link = notice.querySelector("a");
|
|
912
|
+
let opening = false;
|
|
913
|
+
link.title = "\u5728\u7CFB\u7EDF\u9ED8\u8BA4\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 GitHub";
|
|
914
|
+
const dismiss = () => {
|
|
915
|
+
notice.hidden = true;
|
|
916
|
+
};
|
|
917
|
+
notice.querySelector("button").addEventListener("click", dismiss);
|
|
918
|
+
const launch = async (event) => {
|
|
919
|
+
event.preventDefault();
|
|
920
|
+
if (opening) return;
|
|
921
|
+
opening = true;
|
|
922
|
+
link.setAttribute("aria-disabled", "true");
|
|
923
|
+
link.textContent = "\u6B63\u5728\u6253\u5F00\u2026";
|
|
924
|
+
notice.querySelector(".star-open-error")?.remove();
|
|
925
|
+
try {
|
|
926
|
+
const response = await fetch(new URL("api/star-reminder/open", base2), { method: "POST", signal: AbortSignal.timeout(8e3) });
|
|
927
|
+
const result = await response.json();
|
|
928
|
+
if (!response.ok || result.opened !== true) throw new Error("Not opened");
|
|
929
|
+
dismiss();
|
|
930
|
+
} catch {
|
|
931
|
+
const error = document.createElement("div");
|
|
932
|
+
error.className = "star-open-error";
|
|
933
|
+
error.setAttribute("role", "status");
|
|
934
|
+
const message = document.createElement("span");
|
|
935
|
+
message.textContent = "\u9ED8\u8BA4\u6D4F\u89C8\u5668\u672A\u80FD\u6253\u5F00\uFF0C\u53EF\u91CD\u8BD5\u6216\u590D\u5236\u5730\u5740\u3002";
|
|
936
|
+
const copy = document.createElement("button");
|
|
937
|
+
copy.type = "button";
|
|
938
|
+
copy.textContent = "\u590D\u5236 GitHub \u5730\u5740";
|
|
939
|
+
copy.addEventListener("click", () => {
|
|
940
|
+
void (async () => {
|
|
941
|
+
try {
|
|
942
|
+
await navigator.clipboard.writeText(link.href);
|
|
943
|
+
copy.textContent = "\u5DF2\u590D\u5236";
|
|
944
|
+
} catch {
|
|
945
|
+
const address = document.createElement("input");
|
|
946
|
+
address.readOnly = true;
|
|
947
|
+
address.value = link.href;
|
|
948
|
+
address.setAttribute("aria-label", "GitHub \u5730\u5740");
|
|
949
|
+
copy.replaceWith(address);
|
|
950
|
+
address.focus();
|
|
951
|
+
address.select();
|
|
952
|
+
}
|
|
953
|
+
})();
|
|
954
|
+
});
|
|
955
|
+
error.append(message, copy);
|
|
956
|
+
notice.append(error);
|
|
957
|
+
} finally {
|
|
958
|
+
opening = false;
|
|
959
|
+
link.removeAttribute("aria-disabled");
|
|
960
|
+
link.textContent = "\u53BB\u70B9 Star \u2197";
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
link.addEventListener("click", (event) => {
|
|
964
|
+
void launch(event);
|
|
965
|
+
});
|
|
966
|
+
link.addEventListener("auxclick", (event) => {
|
|
967
|
+
if (event.button === 1) void launch(event);
|
|
968
|
+
});
|
|
969
|
+
return (page) => {
|
|
970
|
+
if (attempted || document.visibilityState !== "visible") return;
|
|
971
|
+
attempted = true;
|
|
972
|
+
const url = new URL("api/star-reminder/visit", base2);
|
|
973
|
+
if (page) url.searchParams.set("page", page);
|
|
974
|
+
void fetch(url, { method: "POST", signal: AbortSignal.timeout(3e3) }).then(async (response) => {
|
|
975
|
+
if (!response.ok) return;
|
|
976
|
+
const result = await response.json();
|
|
977
|
+
if (result.show === true && document.visibilityState === "visible") notice.hidden = false;
|
|
978
|
+
}).catch(() => {
|
|
979
|
+
});
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
|
|
907
983
|
// src/web/app.ts
|
|
908
984
|
var element = (id) => document.getElementById(id);
|
|
909
985
|
var viewport = element("viewport");
|
|
@@ -913,6 +989,7 @@ var pages = element("pages");
|
|
|
913
989
|
var search = element("search");
|
|
914
990
|
var dialog = element("dialog");
|
|
915
991
|
var base = new URL(".", window.location.href);
|
|
992
|
+
var starNotice = createStarNotice(base);
|
|
916
993
|
var kinds = { dev: "\u5F00\u53D1\u5730\u56FE", architecture: "\u67B6\u6784\u5730\u56FE", dataflow: "\u6570\u636E\u6D41", "behavior-tree": "\u884C\u4E3A\u6811", sequence: "\u65F6\u5E8F\u5730\u56FE" };
|
|
917
994
|
var snapshot = { project: "", pages: [] };
|
|
918
995
|
var revision = "";
|
|
@@ -1340,6 +1417,7 @@ async function poll() {
|
|
|
1340
1417
|
}
|
|
1341
1418
|
if (initial || JSON.stringify(priorPage) !== JSON.stringify(current())) render();
|
|
1342
1419
|
else renderHeading();
|
|
1420
|
+
if ((current()?.map?.nodes.length ?? 0) > 0 && !current()?.error) starNotice(currentId);
|
|
1343
1421
|
element("updated").textContent = `\u5DF2\u540C\u6B65 ${(/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false })}`;
|
|
1344
1422
|
}
|
|
1345
1423
|
}
|
package/dist/web/index.html
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
<section class="map-heading"><div class="project-line"><span id="project">本地项目</span><span>/</span><span id="kind">开发地图</span></div><div class="title-row"><h1 id="title">正在读取地图…</h1><select id="pages" aria-label="切换地图页面"></select></div><div id="breadcrumbs"></div><div class="progress-row"><span id="summary"></span><div id="progress"><i></i></div><button id="follow" class="follow active" aria-pressed="true">● 自动跟随</button></div></section>
|
|
6
6
|
<section class="toolbar"><label class="search"><svg viewBox="0 0 20 20" aria-hidden="true"><circle cx="8" cy="8" r="5.5"/><path d="m12 12 5 5"/></svg><input id="search" placeholder="搜索模块、说明…" aria-label="搜索模块"><kbd>/</kbd></label><button id="overview" class="tool-button" aria-pressed="false">分组概览</button><button id="export" class="icon-button" title="导出 SVG" aria-label="导出 SVG">↓</button><button id="delete" class="icon-button" title="删除当前地图" aria-label="删除当前地图">×</button></section>
|
|
7
7
|
<section class="canvas-shell"><div id="viewport" tabindex="0" aria-label="交互地图画布"><div id="stage"></div><div id="empty" hidden></div></div><div id="filters" class="filters" aria-label="按状态筛选"><button data-status="all" class="active">全部</button><button data-status="planned"><i class="planned"></i>待开发</button><button data-status="in-progress"><i class="in-progress"></i>进行中</button><button data-status="done"><i class="done"></i>已验证</button><button data-status="regressed"><i class="regressed"></i>回归</button><button data-status="unverified"><i class="unverified"></i>待验证</button></div><div class="zoom-controls"><button id="zoom-out" aria-label="缩小">−</button><output id="zoom-label">100%</output><button id="zoom-in" aria-label="放大">+</button><span></span><button id="fit" title="适应窗口 · 0" aria-label="适应窗口">⛶</button></div><div id="minimap" title="点击移动视口"></div><div id="match-count" role="status"></div></section>
|
|
8
|
-
<div id="divider" role="separator" aria-label="调整详情高度" aria-orientation="horizontal" aria-valuemin="100" aria-valuemax="500" aria-valuenow="210" tabindex="0"><i></i></div><aside id="inspector"></aside><footer><span>滚轮缩放 · 拖动画布 · 双击进入子地图</span><span id="updated">等待同步</span></footer>
|
|
8
|
+
<div id="divider" role="separator" aria-label="调整详情高度" aria-orientation="horizontal" aria-valuemin="100" aria-valuemax="500" aria-valuenow="210" tabindex="0"><i></i></div><aside id="inspector"></aside><footer><span>滚轮缩放 · 拖动画布 · 双击进入子地图</span><span id="updated">等待同步</span><aside id="star-reminder" aria-label="支持 Mellos Mapping" hidden><span>地图有帮到你?欢迎在 GitHub 点个 Star。</span><a href="https://github.com/GuangminJu/mellos-mapping" target="_blank" rel="noopener noreferrer">去点 Star ↗</a><button type="button">不再提醒</button></aside></footer>
|
|
9
9
|
</main><div id="toast" role="status" hidden></div><dialog id="dialog"><h2 id="dialog-title"></h2><p id="dialog-body"></p><div class="dialog-actions"><button id="dialog-cancel">关闭</button><button id="dialog-confirm" class="danger" hidden>删除此地图</button></div></dialog></body></html>
|
package/dist/web/terminal.css
CHANGED
|
@@ -7,3 +7,106 @@ header{display:flex;align-items:center;justify-content:space-between;gap:12px;pa
|
|
|
7
7
|
footer{min-height:32px;display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;padding:5px 14px;border-top:1px solid #283343;color:#8c9eb5;font-size:11px}#connection{overflow-wrap:anywhere}#connection[data-state="connected"]{color:#81dbae}#connection[data-state="error"]{color:#ffaf87}.hint{margin-left:auto}[hidden]{display:none!important}
|
|
8
8
|
@media(max-width:440px){.brand span{margin-left:6px}.hint{margin-left:0;width:100%}header{padding:0 10px}#terminal .xterm{padding-left:4px;padding-right:4px}}
|
|
9
9
|
dialog{max-width:min(420px,90vw);border:1px solid #3b4b60;border-radius:10px;background:#151c25;color:#d9e2ed;padding:24px}dialog::backdrop{background:#0009}dialog h2{font-size:18px;margin-top:0}dialog p{line-height:1.9;font-size:13px}dialog form{text-align:right}
|
|
10
|
+
|
|
11
|
+
#star-reminder {
|
|
12
|
+
--star-surface: linear-gradient(115deg, #15302e, #202341 48%, #36243b);
|
|
13
|
+
--star-text: #f2f5ff;
|
|
14
|
+
--star-muted: #c9d2e5;
|
|
15
|
+
width: 100%;
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-wrap: wrap;
|
|
18
|
+
align-items: center;
|
|
19
|
+
gap: 10px 14px;
|
|
20
|
+
margin: 4px 0 8px;
|
|
21
|
+
padding: 12px 16px;
|
|
22
|
+
border: 1px solid transparent;
|
|
23
|
+
border-radius: 12px;
|
|
24
|
+
background: var(--star-surface) padding-box,
|
|
25
|
+
linear-gradient(110deg, #69ebc9, #8299ff, #d897ff, #ffc17d, #69ebc9) border-box;
|
|
26
|
+
background-size: 200% 100%;
|
|
27
|
+
color: var(--star-text);
|
|
28
|
+
font-size: 14px;
|
|
29
|
+
line-height: 1.5;
|
|
30
|
+
animation: star-color-flow 6s ease-in-out 2, star-glow 3s ease-in-out 4;
|
|
31
|
+
}
|
|
32
|
+
#star-reminder::before {
|
|
33
|
+
content: "★";
|
|
34
|
+
display: grid;
|
|
35
|
+
place-items: center;
|
|
36
|
+
flex: 0 0 36px;
|
|
37
|
+
height: 36px;
|
|
38
|
+
border: 1px solid #ffe49950;
|
|
39
|
+
border-radius: 10px;
|
|
40
|
+
background: #f9c76916;
|
|
41
|
+
color: #ffe49b;
|
|
42
|
+
font-size: 26px;
|
|
43
|
+
line-height: 1;
|
|
44
|
+
text-shadow: 0 0 14px #ffd47870;
|
|
45
|
+
animation: star-beat 3s ease-in-out 4;
|
|
46
|
+
}
|
|
47
|
+
#star-reminder > span { flex: 1 1 230px; font-weight: 550; }
|
|
48
|
+
#star-reminder a {
|
|
49
|
+
display: inline-flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
justify-content: center;
|
|
52
|
+
min-height: 36px;
|
|
53
|
+
padding: 7px 16px;
|
|
54
|
+
border: 1px solid #ffffff50;
|
|
55
|
+
border-radius: 8px;
|
|
56
|
+
background: linear-gradient(110deg, #8bf0d0, #babaff 55%, #ffd0aa);
|
|
57
|
+
color: #17243a;
|
|
58
|
+
font-size: 13px;
|
|
59
|
+
font-weight: 750;
|
|
60
|
+
line-height: 1.4;
|
|
61
|
+
text-decoration: none;
|
|
62
|
+
white-space: nowrap;
|
|
63
|
+
box-shadow: 0 2px 12px #8b96ff25;
|
|
64
|
+
}
|
|
65
|
+
#star-reminder a:hover { filter: brightness(1.1); }
|
|
66
|
+
#star-reminder a[aria-disabled=true] { cursor: progress; opacity: .8; }
|
|
67
|
+
#star-reminder .star-open-error { flex: 1 1 100%; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 12px; color: var(--star-muted); }
|
|
68
|
+
#star-reminder .star-open-error input { min-width: 0; max-width: 100%; width: 340px; padding: 6px; color: var(--star-text); background: transparent; border: 1px solid #b8c5e470; border-radius: 6px; }
|
|
69
|
+
#star-reminder button {
|
|
70
|
+
min-height: 34px;
|
|
71
|
+
padding: 6px 10px;
|
|
72
|
+
border: 1px solid #b8c5e42e;
|
|
73
|
+
border-radius: 8px;
|
|
74
|
+
background: transparent;
|
|
75
|
+
color: var(--star-muted);
|
|
76
|
+
font-size: 12px;
|
|
77
|
+
line-height: 1.4;
|
|
78
|
+
white-space: nowrap;
|
|
79
|
+
}
|
|
80
|
+
#star-reminder button:hover { background: #a0b5f014; border-color: #b8c5e470; }
|
|
81
|
+
#star-reminder a:focus-visible, #star-reminder button:focus-visible {
|
|
82
|
+
outline: 2px solid #b0b9ff;
|
|
83
|
+
outline-offset: 3px;
|
|
84
|
+
}
|
|
85
|
+
#star-reminder:hover, #star-reminder:focus-within,
|
|
86
|
+
#star-reminder:hover::before, #star-reminder:focus-within::before { animation-play-state: paused; }
|
|
87
|
+
[data-theme=light] #star-reminder {
|
|
88
|
+
--star-surface: linear-gradient(115deg, #e4fbf5, #eeedff 48%, #fff0e5);
|
|
89
|
+
--star-text: #263451;
|
|
90
|
+
--star-muted: #53607b;
|
|
91
|
+
}
|
|
92
|
+
[data-theme=light] #star-reminder::before { color: #a5660c; background: #ffe6aa80; border-color: #dbae6340; text-shadow: none; }
|
|
93
|
+
@keyframes star-color-flow {
|
|
94
|
+
0%, 100% { background-position: 0% 50%; }
|
|
95
|
+
50% { background-position: 100% 50%; }
|
|
96
|
+
}
|
|
97
|
+
@keyframes star-glow {
|
|
98
|
+
0%, 100% { box-shadow: 0 0 0 #8299ff00; }
|
|
99
|
+
50% { box-shadow: 0 0 16px #8299ff30, inset 0 0 18px #b897ff0c; }
|
|
100
|
+
}
|
|
101
|
+
@keyframes star-beat {
|
|
102
|
+
0%, 100% { transform: scale(1) rotate(0deg); }
|
|
103
|
+
50% { transform: scale(1.1) rotate(-7deg); }
|
|
104
|
+
}
|
|
105
|
+
@media (max-width: 520px) {
|
|
106
|
+
#star-reminder { padding: 12px; gap: 10px; }
|
|
107
|
+
#star-reminder > span { flex-basis: calc(100% - 50px); font-size: 13px; }
|
|
108
|
+
#star-reminder a { margin-left: 46px; }
|
|
109
|
+
}
|
|
110
|
+
@media (prefers-reduced-motion: reduce) {
|
|
111
|
+
#star-reminder, #star-reminder::before { animation: none; }
|
|
112
|
+
}
|
package/dist/web/terminal.html
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
<html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Mellos · 终端地图</title><link rel="icon" href="data:,"><link rel="stylesheet" href="./xterm.css"><link rel="stylesheet" href="./terminal.css"><script type="module" src="./terminal.js"></script></head>
|
|
3
3
|
<body><header><a class="brand" id="graphic" href="./" title="切换到图形地图">Mellos <span>图形地图 ↗</span></a><div class="tools"><label for="font-size">字号</label><select id="font-size" aria-label="终端字号"><option>10</option><option>11</option><option>12</option><option selected>13</option><option>14</option><option>15</option><option>16</option><option>18</option><option>20</option><option>24</option><option>28</option></select><button id="help" aria-label="操作帮助">?</button></div></header>
|
|
4
4
|
<main id="terminal" aria-label="Mellos 终端地图"></main>
|
|
5
|
-
<footer><span id="connection" role="status" aria-live="polite">正在连接…</span><button id="restart" hidden>重新连接</button><span class="hint">滚轮缩放 · 拖动平移 · 点击选择 · ? 帮助</span></footer>
|
|
5
|
+
<footer><span id="connection" role="status" aria-live="polite">正在连接…</span><button id="restart" hidden>重新连接</button><span class="hint">滚轮缩放 · 拖动平移 · 点击选择 · ? 帮助</span><aside id="star-reminder" aria-label="支持 Mellos Mapping" hidden><span>地图有帮到你?欢迎在 GitHub 点个 Star。</span><a href="https://github.com/GuangminJu/mellos-mapping" target="_blank" rel="noopener noreferrer">去点 Star ↗</a><button type="button">不再提醒</button></aside></footer>
|
|
6
6
|
<dialog id="help-dialog"><h2>终端地图操作</h2><p>滚轮 / + / −:总览 → 模块 → 细节<br>拖动:平移 · 单击:固定详情<br>双击带 ⊞ 的节点:进入子地图<br>Backspace:返回父地图<br>Tab / 数字 1–9:切换页面<br>f:自动跟随 · 0:重置视图<br>x 按两次:确认删除当前页<br>q:关闭地图,可用「重新连接」再次打开</p><p>右上角字号只影响此网页终端。</p><form method="dialog"><button>关闭帮助</button></form></dialog>
|
|
7
7
|
</body></html>
|
package/dist/web/terminal.js
CHANGED
|
@@ -9137,6 +9137,82 @@ var o = class {
|
|
|
9137
9137
|
}
|
|
9138
9138
|
};
|
|
9139
9139
|
|
|
9140
|
+
// src/web/star-reminder.ts
|
|
9141
|
+
function createStarNotice(base2) {
|
|
9142
|
+
let attempted = false;
|
|
9143
|
+
const notice = document.getElementById("star-reminder");
|
|
9144
|
+
const link = notice.querySelector("a");
|
|
9145
|
+
let opening = false;
|
|
9146
|
+
link.title = "\u5728\u7CFB\u7EDF\u9ED8\u8BA4\u6D4F\u89C8\u5668\u4E2D\u6253\u5F00 GitHub";
|
|
9147
|
+
const dismiss = () => {
|
|
9148
|
+
notice.hidden = true;
|
|
9149
|
+
};
|
|
9150
|
+
notice.querySelector("button").addEventListener("click", dismiss);
|
|
9151
|
+
const launch = async (event) => {
|
|
9152
|
+
event.preventDefault();
|
|
9153
|
+
if (opening) return;
|
|
9154
|
+
opening = true;
|
|
9155
|
+
link.setAttribute("aria-disabled", "true");
|
|
9156
|
+
link.textContent = "\u6B63\u5728\u6253\u5F00\u2026";
|
|
9157
|
+
notice.querySelector(".star-open-error")?.remove();
|
|
9158
|
+
try {
|
|
9159
|
+
const response = await fetch(new URL("api/star-reminder/open", base2), { method: "POST", signal: AbortSignal.timeout(8e3) });
|
|
9160
|
+
const result = await response.json();
|
|
9161
|
+
if (!response.ok || result.opened !== true) throw new Error("Not opened");
|
|
9162
|
+
dismiss();
|
|
9163
|
+
} catch {
|
|
9164
|
+
const error = document.createElement("div");
|
|
9165
|
+
error.className = "star-open-error";
|
|
9166
|
+
error.setAttribute("role", "status");
|
|
9167
|
+
const message = document.createElement("span");
|
|
9168
|
+
message.textContent = "\u9ED8\u8BA4\u6D4F\u89C8\u5668\u672A\u80FD\u6253\u5F00\uFF0C\u53EF\u91CD\u8BD5\u6216\u590D\u5236\u5730\u5740\u3002";
|
|
9169
|
+
const copy = document.createElement("button");
|
|
9170
|
+
copy.type = "button";
|
|
9171
|
+
copy.textContent = "\u590D\u5236 GitHub \u5730\u5740";
|
|
9172
|
+
copy.addEventListener("click", () => {
|
|
9173
|
+
void (async () => {
|
|
9174
|
+
try {
|
|
9175
|
+
await navigator.clipboard.writeText(link.href);
|
|
9176
|
+
copy.textContent = "\u5DF2\u590D\u5236";
|
|
9177
|
+
} catch {
|
|
9178
|
+
const address = document.createElement("input");
|
|
9179
|
+
address.readOnly = true;
|
|
9180
|
+
address.value = link.href;
|
|
9181
|
+
address.setAttribute("aria-label", "GitHub \u5730\u5740");
|
|
9182
|
+
copy.replaceWith(address);
|
|
9183
|
+
address.focus();
|
|
9184
|
+
address.select();
|
|
9185
|
+
}
|
|
9186
|
+
})();
|
|
9187
|
+
});
|
|
9188
|
+
error.append(message, copy);
|
|
9189
|
+
notice.append(error);
|
|
9190
|
+
} finally {
|
|
9191
|
+
opening = false;
|
|
9192
|
+
link.removeAttribute("aria-disabled");
|
|
9193
|
+
link.textContent = "\u53BB\u70B9 Star \u2197";
|
|
9194
|
+
}
|
|
9195
|
+
};
|
|
9196
|
+
link.addEventListener("click", (event) => {
|
|
9197
|
+
void launch(event);
|
|
9198
|
+
});
|
|
9199
|
+
link.addEventListener("auxclick", (event) => {
|
|
9200
|
+
if (event.button === 1) void launch(event);
|
|
9201
|
+
});
|
|
9202
|
+
return (page2) => {
|
|
9203
|
+
if (attempted || document.visibilityState !== "visible") return;
|
|
9204
|
+
attempted = true;
|
|
9205
|
+
const url = new URL("api/star-reminder/visit", base2);
|
|
9206
|
+
if (page2) url.searchParams.set("page", page2);
|
|
9207
|
+
void fetch(url, { method: "POST", signal: AbortSignal.timeout(3e3) }).then(async (response) => {
|
|
9208
|
+
if (!response.ok) return;
|
|
9209
|
+
const result = await response.json();
|
|
9210
|
+
if (result.show === true && document.visibilityState === "visible") notice.hidden = false;
|
|
9211
|
+
}).catch(() => {
|
|
9212
|
+
});
|
|
9213
|
+
};
|
|
9214
|
+
}
|
|
9215
|
+
|
|
9140
9216
|
// src/web/terminal-app.ts
|
|
9141
9217
|
var element = (id) => document.getElementById(id);
|
|
9142
9218
|
var container = element("terminal");
|
|
@@ -9145,6 +9221,7 @@ var restart = element("restart");
|
|
|
9145
9221
|
var font = element("font-size");
|
|
9146
9222
|
var graphic = element("graphic");
|
|
9147
9223
|
var base = new URL(".", location.href);
|
|
9224
|
+
var starNotice = createStarNotice(base);
|
|
9148
9225
|
var page = new URL(location.href).searchParams.get("page") ?? void 0;
|
|
9149
9226
|
var socket;
|
|
9150
9227
|
var retryTimer;
|
|
@@ -9264,6 +9341,7 @@ function connect() {
|
|
|
9264
9341
|
} else if (message.type === "view") {
|
|
9265
9342
|
page = message.page;
|
|
9266
9343
|
syncPage();
|
|
9344
|
+
starNotice(page);
|
|
9267
9345
|
} else if (message.type === "error") setStatus(message.message, "error");
|
|
9268
9346
|
});
|
|
9269
9347
|
connection.addEventListener("close", (event) => {
|