feat(gitea): 新增 HTML 匯出與範本風格設定
What:新增 html-export 與 html-style 兩支技能、tools/html-render.sh 與 tools/html-style.sh 兩支工具,以及六種版型乘五種風格的 HTML 範本。 Why:wiki 頁與議題要拿給 Gitea 以外的人看時,只能複製 markdown;不同類型的文件也該有各自的版面,不是每份都長一樣。 How:版型(report、slide、dashboard、spec、timeline、onepager)決定內容怎麼排,風格(minimal、corporate、dark、print、vivid)決定看起來長怎樣,兩者自由搭配。哪一種頁面套哪一組由設定決定:專案的 .jsc/html-styles 優先,其次 $JSC_HOME/html-styles.conf,對不到退 DEFAULT,再對不到才用內建的 report/minimal。產出是單一 HTML 檔,CSS 與腳本全部內嵌。 Who:需要把 wiki 頁或議題寄給客戶、主管或跨團隊同事的人。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Executable
+111
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env sh
|
||||
# html-render.sh — 把 markdown 套上版型與風格,輸出單一 HTML 檔(供 jsc-gitea:html-export 使用)。
|
||||
#
|
||||
# 為什麼要有這支腳本:markdown 轉 HTML 交給 Gitea 自己渲染(gitea.sh markdown),出來的排版才跟
|
||||
# wiki、議題頁看到的一致;版型與風格則是固定的字串替換。兩件事都有標準輸入輸出,不必每次重寫。
|
||||
#
|
||||
# 用法:
|
||||
# html-render.sh --markdown <檔案> --title <標題> --out <輸出檔>
|
||||
# [--subtitle <副標>] [--layout <版型>] [--style <風格>]
|
||||
# [--source-url <來源網址>]
|
||||
#
|
||||
# --layout 預設 report,--style 預設 minimal。可用清單見 html-style.sh layouts / styles。
|
||||
#
|
||||
# 輸出: 寫出 --out 指定的 HTML 檔,並在標準輸出印出該檔路徑。
|
||||
# 結束碼: 0=成功 1=渲染或寫檔失敗 2=用法錯誤 4=找不到版型或風格範本
|
||||
#
|
||||
# 陷阱:
|
||||
# - HTML 是單一檔案,CSS 直接內嵌,不外連任何資源;產出物常常是寄給別人看的,外連在對方那裡會破圖。
|
||||
# - markdown 渲染走 Gitea API。連不上就失敗收場,不自己拼一套半套的轉換——半套轉換出來的表格
|
||||
# 跟 wiki 上看到的不一樣,比失敗更難發現。
|
||||
# - 渲染端點不吃 wiki 情境,`[[頁名]]` 這種 wiki 內部連結會原樣留著。呼叫端要先換成絕對網址,
|
||||
# 產出的 HTML 才連得回去。
|
||||
set -eu
|
||||
|
||||
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
plugin_root="${CLAUDE_PLUGIN_ROOT:-$script_dir/..}"
|
||||
TPL="$plugin_root/templates/html"
|
||||
GITEA="$script_dir/gitea.sh"
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
用法:
|
||||
html-render.sh --markdown <檔案> --title <標題> --out <輸出檔>
|
||||
[--subtitle <副標>] [--layout <版型>] [--style <風格>]
|
||||
[--source-url <來源網址>]
|
||||
結束碼: 0=成功 1=渲染或寫檔失敗 2=用法錯誤 4=找不到版型或風格範本
|
||||
EOF
|
||||
exit 2
|
||||
}
|
||||
|
||||
md=''; title=''; out=''; subtitle=''; layout=report; style=minimal; source_url=''
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
--markdown) [ "$#" -ge 2 ] || usage; md="$2"; shift 2 ;;
|
||||
--title) [ "$#" -ge 2 ] || usage; title="$2"; shift 2 ;;
|
||||
--out) [ "$#" -ge 2 ] || usage; out="$2"; shift 2 ;;
|
||||
--subtitle) [ "$#" -ge 2 ] || usage; subtitle="$2"; shift 2 ;;
|
||||
--layout) [ "$#" -ge 2 ] || usage; layout="$2"; shift 2 ;;
|
||||
--style) [ "$#" -ge 2 ] || usage; style="$2"; shift 2 ;;
|
||||
--source-url) [ "$#" -ge 2 ] || usage; source_url="$2"; shift 2 ;;
|
||||
*) echo "[jsc][HTML 產生][ERR]:不認得的選項「$1」。" >&2; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[ -n "$md" ] && [ -n "$title" ] && [ -n "$out" ] || usage
|
||||
[ -f "$md" ] || { echo "[jsc][HTML 產生][ERR]:找不到 markdown 檔「$md」。" >&2; exit 2; }
|
||||
|
||||
layout_file="$TPL/layout/$layout.html"
|
||||
style_file="$TPL/style/$style.css"
|
||||
[ -f "$layout_file" ] || { echo "[jsc][HTML 產生][ERR]:找不到版型範本「$layout_file」。" >&2; exit 4; }
|
||||
[ -f "$style_file" ] || { echo "[jsc][HTML 產生][ERR]:找不到風格範本「$style_file」。" >&2; exit 4; }
|
||||
|
||||
# markdown -> HTML 片段:交給 Gitea 自己渲染,排版才跟站上一致。
|
||||
fragment=$(mktemp)
|
||||
trap 'rm -f "$fragment"' EXIT
|
||||
|
||||
if ! "$GITEA" markdown "$md" > "$fragment" 2>/dev/null || [ ! -s "$fragment" ]; then
|
||||
echo '[jsc][HTML 產生][ERR]:Gitea 的 markdown 渲染失敗,這次不出檔。請確認 GITEA_HOST 與權杖後重跑。' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python3 - "$layout_file" "$style_file" "$fragment" "$out" "$title" "$subtitle" "$source_url" "$layout" "$style" "$TPL" <<'PY'
|
||||
import html, sys, datetime, os
|
||||
|
||||
layout_file, style_file, frag_file, out_file, title, subtitle, source_url, layout, style, tpl_dir = sys.argv[1:11]
|
||||
|
||||
def read(p):
|
||||
with open(p, encoding='utf-8') as f:
|
||||
return f.read()
|
||||
|
||||
page = read(layout_file)
|
||||
css = read(style_file)
|
||||
base_css = read(os.path.join(tpl_dir, 'base.css'))
|
||||
base_js = read(os.path.join(tpl_dir, 'base.js'))
|
||||
content = read(frag_file)
|
||||
generated = datetime.datetime.now().astimezone().strftime('%Y-%m-%d %H:%M')
|
||||
|
||||
source_html = ''
|
||||
if source_url:
|
||||
safe = html.escape(source_url, quote=True)
|
||||
source_html = '<a class="source" href="%s" rel="noreferrer">來源:%s</a>' % (safe, safe)
|
||||
|
||||
for key, value in (
|
||||
('{{TITLE}}', html.escape(title)),
|
||||
('{{SUBTITLE}}', html.escape(subtitle)),
|
||||
('{{BASE}}', base_css),
|
||||
('{{BASE_JS}}', base_js),
|
||||
('{{STYLE}}', css),
|
||||
('{{CONTENT}}', content),
|
||||
('{{SOURCE}}', source_html),
|
||||
('{{GENERATED}}', generated),
|
||||
('{{LAYOUT}}', html.escape(layout)),
|
||||
('{{STYLE_NAME}}', html.escape(style)),
|
||||
):
|
||||
page = page.replace(key, value)
|
||||
|
||||
with open(out_file, 'w', encoding='utf-8') as f:
|
||||
f.write(page)
|
||||
PY
|
||||
|
||||
printf '%s\n' "$out"
|
||||
Reference in New Issue
Block a user