Skip to content

Commit db61a43

Browse files
committed
feat: 发布 v3.01 原生 HTML 块版本
- 引擎:新增 html:(…)end 原生 HTML 块,括号内内容不参与 HIC 编译、 按声明顺序原样注入最终导出的 HTML(it → html → in) - 编辑器:html/end 关键字高亮 + 「+ 原生 HTML」代码片段按钮 - 学习中心:新增第 16 章「原生 HTML 块 v3.01」及配套 .hc 练习文件 - 官网/下载中心/README:版本号与 v3.01 特性更新;下载中心改为优先提供 离线单文件版内核,原生安装包转入 GitHub Releases。 - 离线单文件版随 v3.01 重新打包
1 parent 72726b8 commit db61a43

25 files changed

Lines changed: 565 additions & 77 deletions

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ HiCode(简称 **HC**)的诞生源于一个朴素的愿望:**让编程真
3030

3131
## 当前版本
3232

33-
- **v2.00(图形化前端开发)**:HiCode 当前版本,为 HIC 增加图形化前端开发能力。在 v1.0x 已有能力(`it` 变量、`in t/s/b/link/p` 展示、`if/orif/noif` 条件、表达式、`to/fr` 跳转、`for` 循环、`{变量}` 插值、实时转译与导出)的基础上新增:
33+
- **v3.01(原生 HTML 块)**:HiCode 当前版本。在 v2.00 能力基础上新增:
34+
- 🧩 **原生 HTML 块**`html:( … )end` 包裹任意 HTML,括号内内容 **HIC 不做编译**,而是 **原样写入最终导出的 HTML**。它与其他语句一样 **从上到下按声明顺序** 执行(先 `it` → 再 `html:(…)` → 后 `in`)。编辑器顶部带「`+ 原生 HTML`」一键插入按钮,`html` / `end` 关键字自动高亮;学习中心扩充至 16 章。
35+
- **v2.00(图形化前端开发)**:为 HIC 增加图形化前端开发能力。在 v1.0x 已有能力(`it` 变量、`in t/s/b/link/p` 展示、`if/orif/noif` 条件、表达式、`to/fr` 跳转、`for` 循环、`{变量}` 插值、实时转译与导出)的基础上新增:
3436
- 🧩 **触发区域色块**`cf 区域名 … cf 区域名 stop` 标记一段代码,网页转译成彩色色块,**点击或长按约 0.6 秒** 即触发 / 展开该段内容,让"简单点击也能带动一段逻辑"。
3537
- 🖼️ **图片变量支持 URL**`it 名 p 图片地址` 直接引用网络图片,无需上传。
3638
- ⬇️ **可下载文件变量**`it 名 app` 上传任意文件 + `名 in d` 渲染下载按钮,用户点击浏览器弹出下载;使用下载功能时导出仅支持 **.zip 打包**
@@ -48,6 +50,7 @@ HiCode(简称 **HC**)的诞生源于一个朴素的愿望:**让编程真
4850

4951
- 下载页:**[https://HiCodeHC.github.io/download/](https://HiCodeHC.github.io/download/)**
5052
- 可选 **安卓端(.apk)****Windows 电脑端(.exe)****网页端(在线)** 三种形态;本地端基于网页端 IDE 封装的离线运行时,编译更快、体验一致。
53+
- **GitHub Releases**:自 v3.01 起,每个新版本均基于 Git Tag 在 **[GitHub Releases](https://github.com/HiCodeHC/HiCodeHC.github.io/releases)** 默认发布(含离线单文件版 / 源码 / 原生安装包),可前往查看各版本的发布说明与附件。
5154

5255
## 官网
5356

android/css/style.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ button { font-family: inherit; cursor: pointer; border: none; background: none;
162162
.editor-highlight .tk.str { color: #2e7d4f; }
163163
.editor-highlight .tk.num { color: #b0572d; }
164164
.editor-highlight .tk.op { color: #8a8a7a; }
165+
.editor-highlight .tk.bl { color: #c6786a; font-weight: 800; letter-spacing: .3px; }
165166
.editor-highlight .tk.cm { color: rgba(120,90,50,.5); font-style: italic; }
166167
/* ---- 状态栏(专业 IDE 风格:左右分段) ---- */
167168
.statusbar {

android/index.html

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<title>HiCode · 手机开发环境 v2.00(安卓端)</title>
6+
<title>HiCode · 手机开发环境 v3.01(安卓端)</title>
77
<link rel="icon" href="../assets/hicode-logo.jpg" type="image/jpeg" />
88
<meta name="theme-color" content="#f3eee6" />
99
<link rel="stylesheet" href="css/style.css" />
@@ -15,7 +15,7 @@
1515
<div class="brand" id="btnHome" title="返回首页">
1616
<span class="logo-dot"></span>
1717
<span class="brand-name">HiCode</span>
18-
<span class="ver">v2.00 · 手机端</span>
18+
<span class="ver">v3.01 · 手机端</span>
1919
</div>
2020
<div class="crumb" id="crumb" title="当前文件"></div>
2121
<div class="actions">
@@ -75,6 +75,7 @@
7575
<button type="button" class="snippet" data-snip="region" title="插入一个可点击/长按触发的区域色块">+ 触发区域</button>
7676
<button type="button" class="snippet" data-snip="use" title="导入 Python 标准库模块(网页输出为标识占位,打包/原生运行时可用)">+ 标准库 use</button>
7777
<button type="button" class="snippet" data-snip="app" title="声明一个可上传文件、供用户下载的变量">+ 下载文件</button>
78+
<button type="button" class="snippet" data-snip="html" title="插入原生 HTML 块 html:( … )end,括号内内容 HIC 不编译、原样注入导出">+ 原生 HTML</button>
7879
</div>
7980
<textarea id="code" class="codearea" spellcheck="false"
8081
placeholder="在这里编写 HIC 代码…&#10;&#10;it 标题 t Hello HiCode&#10;标题 in t&#10;it 分数 int 85&#10;if 分数 &gt;= 60:&#10; 标题 in s"
@@ -142,7 +143,7 @@
142143
<div class="console-welcome">
143144
<span class="cw-logo">H</span>
144145
<div class="cw-text">
145-
<span class="cw-title">HiCode · HIC 手机开发环境 v2.00(安卓端)</span>
146+
<span class="cw-title">HiCode · HIC 手机开发环境 v3.01(安卓端)</span>
146147
<span class="cw-sub">HIC 引擎已就绪 · 正在加载编辑器…</span>
147148
</div>
148149
</div>
@@ -192,7 +193,7 @@
192193
<div class="home-logo"><span class="logo-dot lg"></span></div>
193194
<h1>HiCode <span class="dim">· HIC 语言在线开发</span></h1>
194195
<p class="home-sub">自研 HIC 语言一键转译为 HTML,零基础也能搭建网页项目。</p>
195-
<div class="home-sub small">v2.00:图形化前端开发 —— 触发区域色块<code>cf 名 … cf 名 stop</code>,点击/长按即触发执行该段代码)、图片变量支持从 URL 直接获取、可下载文件变量<code>it 名 app</code> + <code>名 in d</code>)、Python 标准库导入(<code>use os, sys, math, json, sqlite3…</code>);兼容 for 循环、{变量} 插值、中文变量名、实时转译与任意页面导出。</p>
196+
<div class="home-sub small">v3.01:新增<b>原生 HTML 块</b><code>html:( … )end</code>,括号内直接写 HTML,HIC 不会编译、而是按声明顺序原样放入最终导出)——在色块之外,让你能自由嵌入任意网页元素;并保留 v2.00 的触发区域色块<code>cf 名 … cf 名 stop</code>,点击/长按触发)、URL 图片、可下载文件<code>it 名 app</code> + <code>名 in d</code>)、Python 标准库导入(<code>use os, sys, math, json, sqlite3…</code>);兼容 for 循环、{变量} 插值、中文变量名、实时转译与任意页面导出。</p>
196197
<div class="home-actions">
197198
<button id="homeNew" class="btn solid big">新建项目</button>
198199
<button id="homeImport" class="btn ghost big">导入项目 .hc</button>

android/js/app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,8 @@
822822
insertAtCursor("use math, time, json\n# use 标准库:网页输出为标识占位,打包/后端运行时可用");
823823
} else if (snip === "app") {
824824
insertAtCursor("it 下载包 app\n下载包 in d");
825+
} else if (snip === "html") {
826+
insertAtCursor("html:(\n <div class=\"myself\" style=\"padding:16px;border:1px solid rgba(255,230,190,.2);border-radius:12px;\">\n 在此处写原生 HTML(HIC 不编译)\n </div>\n)end");
825827
}
826828
doLive(); debounceSave();
827829
};

android/js/editor.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
const ID_START = "A-Za-z_\\u4e00-\\u9fff";
1616
const ID_CHAR = "A-Za-z0-9_\\u4e00-\\u9fff";
17-
const KEYWORDS = new Set(["it", "in", "if", "orif", "noif", "point", "to", "fr", "and", "or", "not", "for", "fn", "global", "raw", "g", "cf", "stop", "use", "app", "d"]);
17+
const KEYWORDS = new Set(["it", "in", "if", "orif", "noif", "point", "to", "fr", "and", "or", "not", "for", "fn", "global", "raw", "g", "cf", "stop", "use", "app", "d", "html", "end"]);
1818
const TYPES = new Set(["t", "p", "s", "b", "d", "text", "img", "sub", "body", "link", "app", "int", "float", "str", "bool", "list", "dict", "tuple"]);
1919

2020
function esc(s) {
@@ -33,6 +33,9 @@
3333
function p(seg, cls) { if (seg) out += cls ? '<span class="tk ' + cls + '">' + esc(seg) + "</span>" : esc(seg); }
3434
while (i < bodyEnd) {
3535
const c = text[i];
36+
// 原生 HTML 块定界符 html:( / )end —— 整体高亮为“块”语义
37+
if (text.substr(i, 7).toLowerCase() === "html:(") { p("html:(", "bl"); i += 7; continue; }
38+
if (c === ")" && text.substr(i, 4).toLowerCase() === ")end") { p(")end", "bl"); i += 4; continue; }
3639
// 字符串(内部 {变量} 插值高亮)
3740
if (c === '"' || c === "'") {
3841
let j = i + 1;

android/js/hic.js

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* ============================================================
2-
* HC v2.00 —— HIC 语言引擎
2+
* HC v3.01 —— HIC 语言引擎
33
* 负责:HIC 词法/语法解析、HIC→HTML 转译、轻量 ZIP 写入、下载
44
* 说明:本文件为纯逻辑,不依赖 DOM(localStorage/document),
55
* 唯一例外是 download()(仅浏览器调用);可用 Node 单元测试。
@@ -11,7 +11,7 @@
1111
})(typeof self !== 'undefined' ? self : null, function () {
1212
"use strict";
1313

14-
const APP = { version: "v2.00", name: "HiCode", lang: "HIC" };
14+
const APP = { version: "v3.01", name: "HiCode", lang: "HIC" };
1515

1616
/* ---- 标准库(use 指令):原生/后端运行时可用模块白名单 ---- */
1717
// 说明:这些 Python 标准库模块在「网页/HTML 输出」中为可识别不报错的占位;
@@ -193,6 +193,9 @@
193193
function classify(line) {
194194
// 返回 {kind, ...}
195195
if (!line || line.trim() === "" ) return { kind: "blank" };
196+
// html 原生块:html:( 起始 / )end 结束(括号内为原样 HTML,HIC 不编译、直接注入最终导出)
197+
if (/^html\s*:\s*\(\s*$/i.test(line)) return { kind: "htmlBlockStart" };
198+
if (/^\)\s*end\s*$/i.test(line)) return { kind: "htmlBlockEnd" };
196199
// 标准库导入:use 模块[, 模块...]
197200
const usem = line.match(/^use\s+(.+)$/i);
198201
if (usem) return { kind: "use", modules: usem[1].split(/[,\uFF0C\s]+/).filter(Boolean).map(function (m) { return m.trim(); }) };
@@ -273,14 +276,45 @@
273276
}
274277

275278
/* ---------------- 缩进块解析 ---------------- */
279+
// html:( ... )end 块内容去缩进:去掉与块起始行相同的最多 base 个前导空格/制表符
280+
function dedentHtml(lines, base) {
281+
return lines.map(function (l) {
282+
if (l.trim() === "") return "";
283+
let n = 0;
284+
for (let k = 0; k < l.length; k++) {
285+
if (l[k] === " ") n += 1;
286+
else if (l[k] === "\t") n += 2;
287+
else break;
288+
}
289+
const cut = n > base ? base : n;
290+
return l.slice(cut);
291+
});
292+
}
293+
276294
function parse(code) {
277-
const ls = String(code || "").split("\n").map(function (raw) {
295+
// 先把多行 html:( ... )end 块折叠为单个原子节点;括号内为原样 HTML,HIC 不编译、按声明顺序直接注入
296+
const rawLines = String(code || "").split("\n");
297+
const ls = [];
298+
for (let li = 0; li < rawLines.length; li++) {
299+
const raw = rawLines[li];
278300
const mt = raw.match(/^[ \t]*/)[0];
301+
const lt = raw.replace(/(^|[ \t])#.*$/, "").trim();
302+
if (/^html\s*:\s*\(\s*$/i.test(lt)) {
303+
const baseIndent = mt.replace(/\t/g, " ").length;
304+
const inner = [];
305+
let j = li + 1, closed = false;
306+
for (; j < rawLines.length; j++) {
307+
if (/^\)\s*end\s*$/i.test(rawLines[j].replace(/(^|[ \t])#.*$/, "").trim())) { closed = true; break; }
308+
inner.push(rawLines[j]);
309+
}
310+
ls.push({ indent: baseIndent, kind: "htmlBlock", html: dedentHtml(inner, baseIndent).join("\n") });
311+
li = closed ? j : (j - 1 > li ? j - 1 : li);
312+
continue;
313+
}
314+
if (!lt) continue; // 纯空白/注释行跳过
279315
const indent = mt.replace(/\t/g, " ").length;
280-
// 代码备注:# 整行注释,或行内空白后的 # 到行尾为注释(Python 风格)
281-
const line = raw.replace(/(^|[ \t])#.*$/, "").trim();
282-
return { indent, line };
283-
}).filter(function (x) { return x.line; });
316+
ls.push({ indent, line: lt });
317+
}
284318

285319
// 取 [start,end) 范围内行缩进的最小值(子块的实际根缩进)
286320
function minIndent(start, end) {
@@ -298,6 +332,12 @@
298332
const cur = ls[i];
299333
if (cur.indent < indentGoal) break; // 退回上层
300334
if (cur.indent > indentGoal) { i++; continue; } // 更深缩进由父层子块统一处理
335+
if (cur.kind === "htmlBlock") { // 原样 HTML 块:原子节点,按声明顺序放置
336+
if (pendingIf) { out.push(pendingIf); pendingIf = null; }
337+
out.push(cur);
338+
i++;
339+
continue;
340+
}
301341
const st = classify(cur.line);
302342
if (st.kind === "cond" && st.type === "if") {
303343
if (pendingIf) out.push(pendingIf);
@@ -495,6 +535,9 @@
495535
} else if (n.kind === "region") {
496536
// 触发区域:渲染为可点击/长按的色块,其 body 子项在执行时展开显示
497537
out.push({ kind: "region", name: n.name, bodyItems: renderNodes(n.body, vars, ctx, []) });
538+
} else if (n.kind === "htmlBlock") {
539+
// 原样 HTML:不编译,直接注入最终导出
540+
out.push({ kind: "htmlBlock", html: n.html });
498541
} else if (n.kind === "textline") {
499542
out.push({ kind: "textline", text: n.text });
500543
}
@@ -610,6 +653,10 @@
610653
'<button type="button" class="hic-region-btn" data-name="' + esc(it.name) + '">' + esc(it.name) + ' 触发区域</button>' +
611654
'<div class="hic-region-body" hidden>' + inner + "</div></div>";
612655
}
656+
if (it.kind === "htmlBlock") {
657+
// 原样 HTML:按声明顺序原样注入最终导出(不做转义与编译)
658+
return it.html;
659+
}
613660
if (it.kind === "display" && it.mode === "point") {
614661
const x = Number(it.x) || 0, y = Number(it.y) || 0;
615662
const src = it.value ? String(it.value) : "";

android/js/store.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"use strict";
2424

2525
const KEY = "HICODE_DATA";
26-
const STORE_VERSION = "v2.00";
26+
const STORE_VERSION = "v3.01";
2727

2828
function uuid() {
2929
return "h" + Date.now().toString(36) + Math.random().toString(36).slice(2, 8);

android/test/hic.test.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,5 +338,73 @@ ok(diag.some(function (d) { return d.msg.indexOf("notamodule") >= 0; }), "diagno
338338
const diagRegion = HC.diagnose("cf 未闭合\n it x t 1");
339339
ok(diagRegion.some(function (d) { return d.msg.indexOf("未闭合") >= 0; }), "diagnose 对未闭合触发区域告警");
340340

341+
// ---- 20. v2.00 导出可选去除顶部题目标识(noBar) ----
342+
const barPage = { name: "x", code: "it 标题 t 内容\n标题 in t", images: {}, files: {} };
343+
const withBar = HC.buildSinglePageHtml({ name: "项目" }, barPage, {});
344+
const noBarHtml = HC.buildSinglePageHtml({ name: "项目" }, barPage, { noBar: true });
345+
ok(withBar.indexOf('class="hic-bar"') >= 0, "默认导出含顶部题目标识");
346+
ok(noBarHtml.indexOf('class="hic-bar"') < 0, "勾选后导出不含顶部题目标识(纯内容)");
347+
ok(noBarHtml.indexOf("内容") >= 0 && noBarHtml.indexOf("<!DOCTYPE html>") >= 0, "去题目标识后仍保留页面内容与骨架");
348+
const noBarZip = HC.buildZipEntries([{ name: "项目", pages: { x: barPage } }], { noBar: true });
349+
const noBarAllHtml = HC.buildAllMergedHtml([{ name: "项目", pages: { x: barPage } }], {}, { noBar: true });
350+
ok(noBarAllHtml.indexOf('class="hic-bar"') < 0, "合并导出也可去除顶部题目标识");
351+
ok(noBarZip.every(function (e) { return !e.html || e.html.indexOf('class="hic-bar"') < 0; }), "zip 展开后各页也可去除顶部题目标识");
352+
353+
// ---- 21. v3.01 原生 HTML 块 html:( … )end ----
354+
r = HC.classify("html:(");
355+
ok(r.kind === "htmlBlockStart", "html:( 解析为块起始");
356+
r = HC.classify(")end");
357+
ok(r.kind === "htmlBlockEnd", ")end 解析为块结束");
358+
359+
// 原样注入 + 不被编译(内容中的 it/# 应原样保留)
360+
const pHtml = HC.processPage({
361+
name: "raw",
362+
code: [
363+
"it 标题 t 卡片页",
364+
"html:(",
365+
" <div class=\"card-demo\" style=\"background:#1abc9c;color:#fff;padding:24px;border-radius:12px;\">",
366+
" <b>这是原生 HTML</b> # 不是备注",
367+
" it 不是变量",
368+
" <script>document.write('hi')</script>",
369+
" </div>",
370+
")end",
371+
"标题 in t"
372+
].join("\n"),
373+
images: {}, files: {}
374+
}, {});
375+
const htmlItems = pHtml.items.filter(function (x) { return x.kind === "htmlBlock"; });
376+
ok(htmlItems.length === 1, "解析出 1 个 htmlBlock 节点");
377+
ok(htmlItems[0].html.indexOf("card-demo") >= 0, "htmlBlock 保留原生 HTML 类名");
378+
ok(htmlItems[0].html.indexOf("这是原生 HTML") >= 0, "htmlBlock 内 # 不被当作备注、原样保留");
379+
ok(htmlItems[0].html.indexOf("it 不是变量") >= 0, "htmlBlock 内 it 语句不被编译");
380+
ok(htmlItems[0].html.indexOf("<script>") >= 0, "htmlBlock 内脚本原样保留");
381+
// 去缩进:起始行在顶层(基准0)时内容按原样保留(仅剥掉基准缩进)
382+
ok(htmlItems[0].html.trim().indexOf("<div") === 0, "html 块内容以 <div 开头(忽略首行空白)");
383+
384+
// 声明顺序:it → html → in
385+
ok(pHtml.items.map(function (x) { return x.kind; }).indexOf("htmlBlock") >
386+
pHtml.items.map(function (x) { return x.kind; }).indexOf("it"), "html 块在 it 声明之后");
387+
ok(pHtml.items.map(function (x) { return x.kind; }).indexOf("htmlBlock") <
388+
pHtml.items.map(function (x) { return x.kind; }).indexOf("display"), "html 块在 in 展示之前");
389+
390+
// 最终导出完整骨架 + 注入内容
391+
const rawHtml = HC.buildSinglePageHtml({ name: "raw" }, {
392+
name: "raw",
393+
code: "it 标题 t 卡片页\nhtml:(\n<div class=\"card-demo\">hi</div>\n)end\n标题 in t",
394+
images: {}, files: {}
395+
});
396+
ok(rawHtml.indexOf('<div class="card-demo">hi</div>') >= 0, "最终导出原样包含 html 块内容");
397+
const itPos = rawHtml.indexOf("卡片页");
398+
const htmlPos = rawHtml.indexOf("card-demo");
399+
ok(htmlPos > 0 && (htmlPos < itPos), "导出中 html 块位于声明顺序位置(先 html 后文本)");
400+
401+
// 条件块内也可使用 html 块
402+
const pHtmlCond = HC.processPage({
403+
name: "rawc",
404+
code: "it 开关 int 1\nif 开关 == 1:\n html:(\n <p class=\"ok\">命中</p>\n )end",
405+
images: {}, files: {}
406+
}, {});
407+
ok(pHtmlCond.items.some(function (x) { return x.kind === "htmlBlock" && x.html.indexOf("命中") >= 0; }), "html 块可置于条件分支内");
408+
341409
console.log("\n通过 " + pass + " 项,失败 " + fail + " 项");
342410
process.exit(fail ? 1 : 0);

download/HiCode-ide-offline.html

Lines changed: 62 additions & 12 deletions
Large diffs are not rendered by default.

download/build/build-offline.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ html = html
4848

4949
/* 4) 文案标记为「本地版」 */
5050
html = html.replace(
51-
"<title>HiCode · 桌面开发环境 v2.00(电脑端)</title>",
52-
"<title>HiCode · 本地开发环境 v2.00</title>"
51+
"<title>HiCode · 桌面开发环境 v3.01(电脑端)</title>",
52+
"<title>HiCode · 本地开发环境 v3.01</title>"
5353
);
54-
html = html.replace("v2.00 · 电脑端", "v2.00 · 本地版");
55-
html = html.replace("HiCode · HIC 桌面开发环境 v2.00(电脑端)", "HiCode · HIC 本地开发环境 v2.00");
54+
html = html.replace("v3.01 · 电脑端", "v3.01 · 本地版");
55+
html = html.replace("HiCode · HIC 桌面开发环境 v3.01(电脑端)", "HiCode · HIC 本地开发环境 v3.01");
5656
html = html.replace("网页端在线开发", "本地离线开发");
5757

5858
if (!fs.existsSync(OUT_DIR)) fs.mkdirSync(OUT_DIR, { recursive: true });

0 commit comments

Comments
 (0)