✨ feat(prototype): 代码阅读侧栏文件注释原型
This commit is contained in:
1 parent
2ed5caf8d0
commit
beabffdf2c
3 files changed
+172
-20
No files matched your search
@@ -23,6 +23,9 @@
|
||||
| `Prototype / issueLock.ts · 编辑 / 预览` | `vscode/src/panel/issueLock.ts` | 中文注释块、多行返回语句块 |
|
||||
| `Prototype / routes.py · 编辑 / 预览` | luffy-agent-platform 的 `backend/src/luffy_agent/user/routes.py` | Python:带装饰器、默认值、返回类型的 FastAPI 路由函数,属性表;`is not None` → 不是 空 |
|
||||
| `Prototype / envLock.ts · 预览(编辑区变窄)` | — | 窗口只有 1000 宽时块跟着变窄,长句换行 |
|
||||
| `Prototype / 文件注释 · 悬停出按钮` | luffy 的 `campaign` 目录 | 鼠标停在侧栏某一行,行尾出现一排操作按钮,第一个「添加注释」,按钮下面是提示 |
|
||||
| `Prototype / 文件注释 · 填写` | 同上 | 点「添加注释」后 VS Code 顶部弹出单行输入框,标题带上目录 / 文件名;回车保存,Esc 取消,清空后保存即删除 |
|
||||
| `Prototype / 文件注释 · 已保存` | 同上 | 有注释的目录 / 文件在名字后面灰字显示注释,太长截断;鼠标停在有注释的行上,按钮换成「编辑注释」,悬停提示里是路径和注释全文 |
|
||||
|
||||
## 导入与运行
|
||||
|
||||
@@ -46,7 +49,9 @@
|
||||
- `Function / <函数名>`、`Import / <来源>`、`Const / <名称>`、`Variable / <名称>`:每块一套 `State=preview | edit`,预览是属性表,编辑是这一块的源码。行数因块而异,所以每块一个组件集;重名的变量后面加行号。
|
||||
- `Reader / BlockTree`:每个示例文件一整棵代码块树,预览画板直接放它的实例。
|
||||
- `Reader / ModeToggle`:编辑器右上角的「编辑 / 预览」。
|
||||
- `Explorer / TreeRow`、`Explorer / Header`:侧栏文件树。
|
||||
- `Explorer / TreeRow`:侧栏文件树的一行,文件夹展开 / 收起 / 展开且悬停,文件默认 / 选中 / 有改动 / 有改动且选中 / 悬停,共 8 个。名字后面的注释层默认藏起来,有注释的实例里打开;悬停状态行尾带「添加注释」「编辑注释」两个按钮,实例里按有没有注释显示其中一个。
|
||||
- `Explorer / Header`:侧栏顶部。
|
||||
- `Window / QuickInput`:VS Code 顶部弹出的单行输入框,标题和内容在实例里改。
|
||||
- `Window / TitleBar`、`Window / ActivityBar`、`Window / StatusBar`、`Editor / TitleBar`、`Editor / Breadcrumb`:VS Code 窗口外壳。
|
||||
|
||||
## 数值来源
|
||||
@@ -65,6 +70,7 @@
|
||||
- 块右边「编辑」→ 这一块换成源码;「预览」→ 换回译文。
|
||||
- 编辑器右上角「编辑」→ 整个文件回到 VS Code 编辑器;「预览」→ 回到代码块树。
|
||||
- 侧栏点 `envLock.ts` / `issueLock.ts` → 在编辑器里打开那个文件。
|
||||
- 从「文件注释」起点开始:点 `campaign` 行尾的「添加注释」→ 顶部输入框;点输入框 → 保存后的侧栏。
|
||||
- 编辑区和侧栏可以上下滚动。
|
||||
|
||||
## 本地检查
|
||||
@@ -88,4 +94,5 @@ node --test test.js
|
||||
- [ ] 译文里的关键字、变量名、函数名颜色和编辑器里对应的代码一致,关键字前后都有空格
|
||||
- [ ] 有子块的块左边有折叠箭头,没有子块的没有,标签仍然对齐
|
||||
- [ ] 属性表:函数、导入、常量、变量的属性名一列对齐;导入项每项一行,左边竖线;输入参数下每个参数一行,名称 / 类型 / 默认值三列对齐,左边竖线从第一个参数连到最后一个;长类型(`Annotated[...]`)换行不挤掉默认值列
|
||||
- [ ] 文件注释:悬停行的按钮贴在行尾、不挤掉名字;注释灰字紧跟名字、长了截断;顶部输入框居中,看得出是 VS Code 自带的样子
|
||||
- [ ] 演示:块的编辑 / 预览、右上角编辑 / 预览、侧栏切文件都能点通
|
||||
+129
-15
@@ -17,6 +17,9 @@ const ACTIVITY_W = 48;
|
||||
const SIDEBAR_W = 280;
|
||||
const EDITOR_W = WINDOW_W - ACTIVITY_W - SIDEBAR_W - 1;
|
||||
const TREE_W = 880;
|
||||
const QUICK_INPUT_W = 600;
|
||||
const ROW_H = 22;
|
||||
const SIDEBAR_HEADER_H = 34;
|
||||
const VIEW_PAD = 24;
|
||||
|
||||
// VS Code Dark Modern 主题的实际色值,注释是对应的主题变量。
|
||||
@@ -35,6 +38,10 @@ const COLORS = {
|
||||
activityOn: "#d7d7d7", // activityBar.foreground
|
||||
focus: "#0078d4", // focusBorder / activityBar.activeBorder
|
||||
selection: "#04395e", // list.activeSelectionBackground
|
||||
hover: "#2a2d2e", // list.hoverBackground / toolbar.hoverBackground
|
||||
widget: "#202020", // editorHoverWidget.background / quickInput.background
|
||||
widgetBorder: "#454545", // editorHoverWidget.border / widget.border
|
||||
input: "#313131", // input.background
|
||||
toggleOn: "#37373d",
|
||||
gitModified: "#e2c08d", // gitDecoration.modifiedResourceForeground
|
||||
code: "#d4d4d4",
|
||||
@@ -105,6 +112,17 @@ const LUFFY_TREE = [
|
||||
{ depth: 3, type: "folder", name: "luffy_agent", open: true },
|
||||
{ depth: 4, type: "folder", name: "agent" },
|
||||
{ depth: 4, type: "folder", name: "billing" },
|
||||
{ depth: 4, type: "folder", name: "campaign", open: true },
|
||||
{ depth: 5, type: "folder", name: "__pycache__" },
|
||||
{ depth: 5, type: "folder", name: "console" },
|
||||
{ depth: 5, type: "file", name: "__init__.py" },
|
||||
{ depth: 5, type: "file", name: "benefits.py" },
|
||||
{ depth: 5, type: "file", name: "models.py" },
|
||||
{ depth: 5, type: "file", name: "order_bonus.py" },
|
||||
{ depth: 5, type: "file", name: "repository.py" },
|
||||
{ depth: 5, type: "file", name: "service.py" },
|
||||
{ depth: 5, type: "file", name: "tasks.py" },
|
||||
{ depth: 5, type: "file", name: "topup_bonus_routes.py" },
|
||||
{ depth: 4, type: "folder", name: "commerce" },
|
||||
{ depth: 4, type: "folder", name: "core" },
|
||||
{ depth: 4, type: "folder", name: "user", open: true },
|
||||
@@ -123,6 +141,16 @@ const WORKSPACES = {
|
||||
superwork: { root: "superwork", rows: TREE },
|
||||
luffy: { root: "luffy-agent-platform", rows: LUFFY_TREE },
|
||||
};
|
||||
// 文件注释示例:读代码时随手记的笔记,按目录 / 文件对上,侧栏在名字后面灰字显示。
|
||||
const NOTE_DRAFT = { row: "campaign", text: "营销活动:充值赠送、下单返利、权益发放" };
|
||||
const NOTES = {
|
||||
campaign: NOTE_DRAFT.text,
|
||||
"benefits.py": "权益发放,被充值和返利共用",
|
||||
"order_bonus.py": "下单返利:订单完成后按活动规则发积分,失败重试 3 次",
|
||||
"routes.py": "用户资料 / 头像接口",
|
||||
};
|
||||
const NOTE_HOVER = "order_bonus.py";
|
||||
|
||||
const EXT_LABEL = { ts: "TS", tsx: "TSX", json: "{}", md: "M↓", py: "PY", toml: "TM" };
|
||||
|
||||
// 活动栏:VS Code 自带的几个 + Superpowers 的小火箭 + 新注册的「代码阅读」
|
||||
@@ -781,6 +809,8 @@ const LUCIDE = {
|
||||
"triangle-alert": [["path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" }], ["path", { d: "M12 9v4" }], ["path", { d: "M12 17h.01" }]],
|
||||
"circle-x": [["circle", { cx: "12", cy: "12", r: "10" }], ["path", { d: "m15 9-6 6" }], ["path", { d: "m9 9 6 6" }]],
|
||||
"eye": [["path", { d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" }], ["circle", { cx: "12", cy: "12", r: "3" }]],
|
||||
"message-square-plus": [["path", { d: "M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z" }], ["path", { d: "M12 8v6" }], ["path", { d: "M9 11h6" }]],
|
||||
"message-square-text": [["path", { d: "M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z" }], ["path", { d: "M7 11h10" }], ["path", { d: "M7 15h6" }], ["path", { d: "M7 7h8" }]],
|
||||
"pencil": [["path", { d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" }], ["path", { d: "m15 5 4 4" }]],
|
||||
"refresh-cw": [["path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }], ["path", { d: "M21 3v5h-5" }], ["path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }], ["path", { d: "M8 16H3v5" }]],
|
||||
"file-plus": [["path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }], ["path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }], ["path", { d: "M9 15h6" }], ["path", { d: "M12 18v-6" }]],
|
||||
@@ -1103,16 +1133,21 @@ function modeToggle(ctx, mode) {
|
||||
return node;
|
||||
}
|
||||
|
||||
/** Explorer / TreeRow — 文件夹展开 / 收起,文件默认 / 选中 / 有改动 / 有改动且选中。名字和扩展名在实例里改。 */
|
||||
/**
|
||||
* Explorer / TreeRow — 文件夹展开 / 收起,文件默认 / 选中 / 有改动 / 有改动且选中,外加鼠标悬停。名字和扩展名在实例里改。
|
||||
* 名字后面的 Note 是这一项的注释(VS Code 树的 description),默认藏起来,有注释的实例里打开。
|
||||
* 悬停时行尾出现一排操作按钮(VS Code 树的行内操作):没注释时「添加注释」,有注释时实例里换成「编辑注释」。
|
||||
*/
|
||||
const ROW_VARIANTS = [
|
||||
["folder", "closed"], ["folder", "open"],
|
||||
["file", "default"], ["file", "selected"], ["file", "modified"], ["file", "modified-selected"],
|
||||
["folder", "closed"], ["folder", "open"], ["folder", "open-hover"],
|
||||
["file", "default"], ["file", "selected"], ["file", "modified"], ["file", "modified-selected"], ["file", "hover"],
|
||||
];
|
||||
function treeRow(ctx, type, state) {
|
||||
const selected = state.endsWith("selected");
|
||||
const modified = state.startsWith("modified");
|
||||
const hovered = state.endsWith("hover");
|
||||
const node = frame(ctx, `Type=${type}, State=${state}`, { component: true, dir: "HORIZONTAL", width: SIDEBAR_W, py: 3, gap: 6,
|
||||
align: "CENTER", fill: selected ? COLORS.selection : null, stroke: selected ? COLORS.focus : null });
|
||||
align: "CENTER", fill: selected ? COLORS.selection : hovered ? COLORS.hover : null, stroke: selected ? COLORS.focus : null });
|
||||
node.paddingLeft = 8;
|
||||
node.paddingRight = 12;
|
||||
const twisty = append(node, frame(ctx, "Twisty", { dir: "HORIZONTAL", width: 16, height: 16, align: "CENTER", justify: "CENTER" }));
|
||||
@@ -1123,9 +1158,33 @@ function treeRow(ctx, type, state) {
|
||||
const ext = append(node, frame(ctx, "Ext", { dir: "HORIZONTAL", width: 16, height: 16, align: "CENTER", justify: "CENTER" }));
|
||||
text(ctx, ext, "Ext label", "TS", { hug: true, medium: true, size: 8, lineHeight: 10, color: COLORS.ts });
|
||||
}
|
||||
text(ctx, node, "Name", type === "folder" ? "folder" : "file.ts", { size: 13, lineHeight: 16, singleLine: true,
|
||||
text(ctx, node, "Name", type === "folder" ? "folder" : "file.ts", { hug: true, size: 13, lineHeight: 16,
|
||||
color: modified ? COLORS.gitModified : COLORS.fg });
|
||||
const note = text(ctx, node, "Note", "注释", { size: 12, lineHeight: 16, singleLine: true, color: COLORS.desc });
|
||||
note.opacity = 0.8;
|
||||
note.visible = false;
|
||||
if (modified) text(ctx, node, "Git", "M", { hug: true, size: 12, lineHeight: 16, color: COLORS.gitModified });
|
||||
if (hovered) {
|
||||
const actions = append(node, frame(ctx, "Actions", { dir: "HORIZONTAL", gap: 2, align: "CENTER" }));
|
||||
for (const [label, iconName, visible] of [["添加注释", "message-square-plus", true], ["编辑注释", "message-square-text", false]]) {
|
||||
const button = append(actions, frame(ctx, `Action / ${label}`, { dir: "HORIZONTAL", width: 22, height: 22, radius: 5,
|
||||
align: "CENTER", justify: "CENTER" }));
|
||||
append(button, icon(ctx, iconName, COLORS.fg, 16));
|
||||
button.visible = visible;
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/** Window / QuickInput — VS Code 顶部弹出的单行输入框,填注释用。标题、内容在实例里改。 */
|
||||
function quickInput(ctx) {
|
||||
const node = frame(ctx, "State=typing", { component: true, width: QUICK_INPUT_W, pad: 8, gap: 8, radius: 8,
|
||||
fill: COLORS.widget, stroke: COLORS.widgetBorder });
|
||||
text(ctx, node, "Title", "添加注释", { size: 12, lineHeight: 16, align: "CENTER", color: COLORS.fg });
|
||||
const field = filled(ctx, node, "Field", { dir: "HORIZONTAL", px: 6, py: 4, radius: 4, align: "CENTER", fill: COLORS.input, stroke: COLORS.focus });
|
||||
text(ctx, field, "Value", "注释", { hug: true, size: 13, lineHeight: 18, color: COLORS.fgStrong });
|
||||
append(field, frame(ctx, "Caret", { width: 1, height: 16, fill: COLORS.fgStrong }));
|
||||
text(ctx, node, "Message", "按 Enter 保存,按 Esc 取消;清空后保存即删除注释", { size: 12, lineHeight: 16, color: COLORS.desc });
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -1242,6 +1301,7 @@ function createComponents(ctx, page) {
|
||||
(entry) => codeBlock(ctx, ...entry.split("/")), (item) => item.node);
|
||||
lib.row = make("Explorer / TreeRow", ROW_VARIANTS.map(([type, state]) => `${type}/${state}`), 2,
|
||||
(entry) => treeRow(ctx, ...entry.split("/")));
|
||||
lib.quickInput = make("Window / QuickInput", ["typing"], 1, () => quickInput(ctx));
|
||||
lib.explorerHeader = make("Explorer / Header", ["default"], 1, () => explorerHeader(ctx));
|
||||
lib.windowTitle = make("Window / TitleBar", ["active"], 1, () => windowTitle(ctx));
|
||||
lib.activity = make("Window / ActivityBar", ["reader"], 1, () => activityBar(ctx));
|
||||
@@ -1269,15 +1329,16 @@ function createComponents(ctx, page) {
|
||||
|
||||
// ─── Boards ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function rowVariant(item, selected) {
|
||||
function rowVariant(item, selected, hover) {
|
||||
if (item.name === hover) return item.type === "folder" ? "folder/open-hover" : "file/hover";
|
||||
if (item.type === "folder") return `folder/${item.open ? "open" : "closed"}`;
|
||||
const isSelected = item.file === selected;
|
||||
if (item.git) return isSelected ? "file/modified-selected" : "file/modified";
|
||||
return isSelected ? "file/selected" : "file/default";
|
||||
}
|
||||
|
||||
/** 侧栏:「代码阅读」视图,外观照搬 VS Code 资源管理器。 */
|
||||
function sidebar(ctx, parent, lib, selected) {
|
||||
/** 侧栏:「代码阅读」视图,外观照搬 VS Code 资源管理器。o.hover:鼠标停在哪一行;o.notes:各行的注释。 */
|
||||
function sidebar(ctx, parent, lib, selected, o = {}) {
|
||||
const workspace = WORKSPACES[FILES[selected].workspace];
|
||||
const pane = append(parent, frame(ctx, "Sidebar", { width: SIDEBAR_W, fill: COLORS.side }));
|
||||
pane.layoutSizingVertical = "FILL";
|
||||
@@ -1292,11 +1353,22 @@ function sidebar(ctx, parent, lib, selected) {
|
||||
text(ctx, root, "Name", workspace.root, { hug: true, medium: true, size: 13, lineHeight: 16, color: COLORS.fgStrong });
|
||||
const rows = new Map();
|
||||
for (const item of workspace.rows) {
|
||||
const instance = lib.row.get(rowVariant(item, selected)).createInstance();
|
||||
const instance = lib.row.get(rowVariant(item, selected, o.hover)).createInstance();
|
||||
instance.name = `Row / ${item.name}`;
|
||||
append(pane, instance, true);
|
||||
instance.paddingLeft = 8 + item.depth * 14;
|
||||
setText(instance, "Name", item.name);
|
||||
const note = (o.notes || {})[item.name];
|
||||
if (note) {
|
||||
const label = named(instance, "Note");
|
||||
label.characters = note;
|
||||
label.visible = true;
|
||||
if (item.name === o.hover) {
|
||||
named(instance, "Action / 添加注释").visible = false;
|
||||
named(instance, "Action / 编辑注释").visible = true;
|
||||
}
|
||||
}
|
||||
if (item.name === o.hover) rows.set("hover", instance);
|
||||
if (item.type === "file") {
|
||||
const ext = item.name.split(".").pop();
|
||||
const label = named(instance, "Ext label");
|
||||
@@ -1351,7 +1423,7 @@ function windowBoard(ctx, page, lib, name, o) {
|
||||
activity.name = "Activity bar";
|
||||
main.appendChild(activity);
|
||||
activity.layoutSizingVertical = "FILL";
|
||||
const side = sidebar(ctx, main, lib, o.file);
|
||||
const side = sidebar(ctx, main, lib, o.file, o);
|
||||
divider(ctx, main, { vertical: true });
|
||||
const editor = append(main, frame(ctx, "Editor", { width: editorWidth, fill: COLORS.editor }));
|
||||
editor.layoutSizingVertical = "FILL";
|
||||
@@ -1366,7 +1438,34 @@ function windowBoard(ctx, page, lib, name, o) {
|
||||
if (o.mode === "edit") setText(status, "Item / TypeScript", FILES[o.file].lang === "python" ? "Python" : "TypeScript");
|
||||
status.name = "Status bar";
|
||||
append(screen, status, true);
|
||||
return { screen, rows: side.rows, titleBar, view };
|
||||
const board = { screen, rows: side.rows, titleBar, view };
|
||||
if (o.tooltip) board.tooltip = tooltip(ctx, screen, o.tooltip, WORKSPACES[FILES[o.file].workspace].rows);
|
||||
if (o.quickInput) {
|
||||
const input = lib.quickInput.get("typing").createInstance();
|
||||
input.name = "Quick input";
|
||||
screen.appendChild(input);
|
||||
input.layoutPositioning = "ABSOLUTE";
|
||||
input.x = (width - QUICK_INPUT_W) / 2;
|
||||
input.y = TITLE_H + 6;
|
||||
setText(input, "Title", o.quickInput.title);
|
||||
setText(input, "Value", o.quickInput.value);
|
||||
board.quickInput = input;
|
||||
}
|
||||
return board;
|
||||
}
|
||||
|
||||
/** 悬停提示:贴在某一行下面,靠右对齐(按钮提示)或靠左对齐(这一项的注释全文)。位置按行号算,不读图层尺寸。 */
|
||||
function tooltip(ctx, screen, o, rows) {
|
||||
const index = rows.findIndex((item) => item.name === o.row);
|
||||
const node = frame(ctx, "Tooltip", { px: 8, py: 5, gap: 2, radius: 4, fill: COLORS.widget, stroke: COLORS.widgetBorder });
|
||||
for (const [index, line] of o.lines.entries())
|
||||
text(ctx, node, `Line ${index + 1}`, line, { hug: true, medium: index === 0 && o.lines.length > 1, size: 12, lineHeight: 16,
|
||||
color: index === 0 ? COLORS.fgStrong : COLORS.fg });
|
||||
screen.appendChild(node);
|
||||
node.layoutPositioning = "ABSOLUTE";
|
||||
node.x = o.align === "right" ? ACTIVITY_W + SIDEBAR_W - 12 - 22 - 40 : ACTIVITY_W + 40;
|
||||
node.y = TITLE_H + SIDEBAR_HEADER_H + ROW_H * (index + 2) + 2;
|
||||
return node;
|
||||
}
|
||||
|
||||
// ─── Entry ───────────────────────────────────────────────────────────────────
|
||||
@@ -1395,6 +1494,16 @@ async function build(api, stage = () => {}) {
|
||||
};
|
||||
}
|
||||
const narrow = windowBoard(ctx, page, lib, "envLock.ts · 预览(编辑区变窄)", { file: "envLock", mode: "preview", width: WINDOW_NARROW });
|
||||
const notes = {
|
||||
hover: windowBoard(ctx, page, lib, "文件注释 · 悬停出按钮", { file: "routes", mode: "preview",
|
||||
hover: NOTE_DRAFT.row, notes: { "routes.py": NOTES["routes.py"] },
|
||||
tooltip: { row: NOTE_DRAFT.row, align: "right", lines: ["添加注释"] } }),
|
||||
typing: windowBoard(ctx, page, lib, "文件注释 · 填写", { file: "routes", mode: "preview",
|
||||
hover: NOTE_DRAFT.row, notes: { "routes.py": NOTES["routes.py"] },
|
||||
quickInput: { title: `添加注释 · ${NOTE_DRAFT.row}`, value: NOTE_DRAFT.text } }),
|
||||
saved: windowBoard(ctx, page, lib, "文件注释 · 已保存", { file: "routes", mode: "preview", hover: NOTE_HOVER, notes: NOTES,
|
||||
tooltip: { row: NOTE_HOVER, lines: [`backend/src/luffy_agent/campaign/${NOTE_HOVER}`, NOTES[NOTE_HOVER]] } }),
|
||||
};
|
||||
|
||||
stage("连接演示入口");
|
||||
// 代码块:「编辑」换成源码,「预览」换回译文
|
||||
@@ -1419,8 +1528,13 @@ async function build(api, stage = () => {}) {
|
||||
}
|
||||
}
|
||||
|
||||
// 文件注释:悬停出的「添加注释」→ 顶部输入框,输入框里回车 → 保存后侧栏显示注释
|
||||
await connect(named(notes.hover.rows.get("hover"), "Action / 添加注释"), notes.typing.screen, "NAVIGATE");
|
||||
await connect(named(notes.typing.quickInput, "Field"), notes.saved.screen, "NAVIGATE");
|
||||
|
||||
stage("排布画板");
|
||||
const screens = [...Object.values(boards).flatMap((pair) => [pair.edit.screen, pair.preview.screen]), narrow.screen];
|
||||
const screens = [...Object.values(boards).flatMap((pair) => [pair.edit.screen, pair.preview.screen]), narrow.screen,
|
||||
...Object.values(notes).map((board) => board.screen)];
|
||||
let y = 0;
|
||||
for (const screen of screens) {
|
||||
screen.x = 0;
|
||||
@@ -1434,10 +1548,10 @@ async function build(api, stage = () => {}) {
|
||||
setY += set.height + 64;
|
||||
}
|
||||
const start = boards.envLock.preview.screen;
|
||||
page.flowStartingPoints = [{ nodeId: start.id, name: "预览 envLock.ts" }];
|
||||
page.flowStartingPoints = [{ nodeId: start.id, name: "预览 envLock.ts" }, { nodeId: notes.hover.screen.id, name: "文件注释" }];
|
||||
page.selection = [start];
|
||||
api.viewport.scrollAndZoomIntoView(page.selection);
|
||||
return { page, components, boards, narrow, screens };
|
||||
return { page, components, boards, narrow, notes, screens };
|
||||
}
|
||||
|
||||
async function run(api) {
|
||||
@@ -1456,6 +1570,6 @@ async function run(api) {
|
||||
}
|
||||
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = { PLAIN_KINDS, parseImport, parseVariable, translateCode, build, run, nextPageName, PAGE_NAME, NAMESPACE, TREE, WORKSPACES, FILES, CODE_TREE, BLOCK_KINDS, SOURCES, blockCode, lineRange, walkTree, translateBlock, highlightCode, parseSignature, typeSegments };
|
||||
module.exports = { NOTES, NOTE_DRAFT, PLAIN_KINDS, parseImport, parseVariable, translateCode, build, run, nextPageName, PAGE_NAME, NAMESPACE, TREE, WORKSPACES, FILES, CODE_TREE, BLOCK_KINDS, SOURCES, blockCode, lineRange, walkTree, translateBlock, highlightCode, parseSignature, typeSegments };
|
||||
}
|
||||
if (typeof figma !== "undefined") void run(figma);
|
||||
+35
-4
@@ -2,7 +2,7 @@ const assert = require("node:assert/strict");
|
||||
const { test } = require("node:test");
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const { PLAIN_KINDS, build, run, nextPageName, PAGE_NAME, NAMESPACE, WORKSPACES, FILES, CODE_TREE, BLOCK_KINDS, SOURCES, blockCode, lineRange, walkTree, translateBlock, highlightCode, parseSignature, parseImport, parseVariable, typeSegments } = require("./code.js");
|
||||
const { NOTES, NOTE_DRAFT, PLAIN_KINDS, build, run, nextPageName, PAGE_NAME, NAMESPACE, WORKSPACES, FILES, CODE_TREE, BLOCK_KINDS, SOURCES, blockCode, lineRange, walkTree, translateBlock, highlightCode, parseSignature, parseImport, parseVariable, typeSegments } = require("./code.js");
|
||||
const { fakeFigma } = require("../../figma-lab/fake-figma");
|
||||
|
||||
function action(node) { return node.reactions[0].actions[0]; }
|
||||
@@ -19,13 +19,15 @@ function countBlocks(nodes) {
|
||||
// 代码块:属性表以外的 9 种各「编辑 ↔ 预览」2 条 = 18
|
||||
// 属性表:8 个函数、14 条导入、2 个常量、12 个变量,共 36 块各 2 条 = 72
|
||||
// 窗口:3 个文件 × 编辑 / 预览各 1 个切换 = 6;superwork 的 4 个窗口里侧栏点另一个示例文件 = 4
|
||||
// (luffy 只有一个示例文件,侧栏没有可跳的);合计 100
|
||||
const EXPECTED_LINKS = 100;
|
||||
// (luffy 只有一个示例文件,侧栏没有可跳的)
|
||||
// 文件注释:悬停出的「添加注释」→ 填写,输入框回车 → 已保存 = 2;合计 102
|
||||
const EXPECTED_LINKS = 102;
|
||||
|
||||
const EXPECTED_SETS = [
|
||||
["Reader / ModeToggle", 2],
|
||||
["Reader / CodeBlock", 18],
|
||||
["Explorer / TreeRow", 6],
|
||||
["Explorer / TreeRow", 8],
|
||||
["Window / QuickInput", 1],
|
||||
["Explorer / Header", 1],
|
||||
["Window / TitleBar", 1],
|
||||
["Window / ActivityBar", 1],
|
||||
@@ -304,6 +306,35 @@ test("build creates the variant sets, reuses them on boards and wires the flows"
|
||||
assert.equal(named(issueLock.preview.view, "Block tree / issueLock.ts").mainComponentId, lib.tree.get("issueLock").node.id);
|
||||
});
|
||||
|
||||
test("file notes: hover shows the action, the quick input takes the note, the tree shows it", async () => {
|
||||
const api = fakeFigma();
|
||||
const { notes } = await build(api);
|
||||
const hovered = notes.hover.rows.get("hover");
|
||||
assert.equal(named(hovered, "Name").characters, NOTE_DRAFT.row);
|
||||
assert.equal(named(hovered, "Action / 添加注释").visible, true);
|
||||
assert.equal(named(hovered, "Action / 编辑注释").visible, false);
|
||||
assert.equal(action(named(hovered, "Action / 添加注释")).destinationId, notes.typing.screen.id);
|
||||
assert.equal(named(notes.hover.screen, "Line 1").characters, "添加注释");
|
||||
|
||||
assert.equal(named(notes.typing.quickInput, "Title").characters, `添加注释 · ${NOTE_DRAFT.row}`);
|
||||
assert.equal(named(notes.typing.quickInput, "Value").characters, NOTE_DRAFT.text);
|
||||
assert.equal(notes.typing.quickInput.layoutPositioning, "ABSOLUTE");
|
||||
assert.equal(action(named(notes.typing.quickInput, "Field")).destinationId, notes.saved.screen.id);
|
||||
|
||||
// 已保存:有注释的行在名字后面显示注释;鼠标停着的那行按钮换成「编辑注释」,提示里是注释全文
|
||||
const rows = notes.saved.screen.findAll((node) => node.name.startsWith("Row / "));
|
||||
for (const [name, note] of Object.entries(NOTES)) {
|
||||
const row = rows.find((node) => node.name === `Row / ${name}`);
|
||||
assert.equal(named(row, "Note").visible, true, name);
|
||||
assert.equal(named(row, "Note").characters, note);
|
||||
}
|
||||
assert.equal(named(rows.find((node) => node.name === "Row / models.py"), "Note").visible, false);
|
||||
const savedHover = notes.saved.rows.get("hover");
|
||||
assert.equal(named(savedHover, "Action / 添加注释").visible, false);
|
||||
assert.equal(named(savedHover, "Action / 编辑注释").visible, true);
|
||||
assert.equal(named(notes.saved.screen, "Line 2").characters, NOTES["order_bonus.py"]);
|
||||
});
|
||||
|
||||
test("boards and sets sit on the page without overlapping", async () => {
|
||||
const api = fakeFigma();
|
||||
const result = await build(api);
|
||||
|
||||
Reference in new issue
Block a user