diff options
46 files changed, 327 insertions, 1876 deletions
diff --git a/.env.example b/.env.example deleted file mode 100644 index 32ae7e5..0000000 --- a/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -BASE_DOMAIN=tdwiki.home.arpa -PUID=1000 -PGID=1000 -TZ=Asia/Shanghai -GOPROXY=https://goproxy.cn,direct @@ -1,5 +1,2 @@ -.env -data/ -secrets/ -docker/authelia/configuration.yml -docker/authelia/users_database.yml +dist/ +files/tdwiki.html diff --git a/DOCKER.md b/DOCKER.md deleted file mode 100644 index f39eb75..0000000 --- a/DOCKER.md +++ /dev/null @@ -1,162 +0,0 @@ -# Docker Compose 部署 - -该部署包含四个服务: - -- `tiddlywiki`:运行 TiddlyWiki 5.4.1,只在 Compose 内网监听 `8080`。 -- `caddy`:唯一对外服务,提供内部 HTTPS、反向代理和 WebDAV。 -- `authelia`:统一处理 Wiki 浏览器登录和 WebDAV Basic 认证。 -- `redis`:保存 Authelia 登录会话。 - -默认局域网域名: - -- Wiki:`https://wiki.tdwiki.home.arpa` -- WebDAV:`https://dav.tdwiki.home.arpa` -- 登录:`https://auth.tdwiki.home.arpa` - -## 初始化 - -复制并按需修改环境配置: - -```bash -cp .env.example .env -``` - -如果当前用户不是 UID/GID `1000`,修改 `.env` 中的 `PUID` 和 `PGID`: - -```bash -id -u -id -g -``` - -`GOPROXY` 只在构建带 WebDAV 插件的 Caddy 时使用。无法访问默认镜像源时可以在 `.env` 中替换为其他可信 Go 模块代理。 - -运行初始化脚本,按提示设置个人用户名和密码: - -```bash -./scripts/init-docker.sh -``` - -脚本会生成: - -- `secrets/` 中的 Authelia 随机密钥。 -- `docker/authelia/configuration.yml`。 -- `docker/authelia/users_database.yml`,密码只保存为 Argon2id 哈希。 - -这些文件均被 `.gitignore` 排除。 - -## 局域网 DNS - -在路由器、AdGuard Home、Pi-hole 或本机 hosts 中,将三个域名指向 Docker 主机 IP。 - -例如 Docker 主机地址为 `192.168.1.20`: - -```text -192.168.1.20 wiki.tdwiki.home.arpa -192.168.1.20 dav.tdwiki.home.arpa -192.168.1.20 auth.tdwiki.home.arpa -``` - -每台客户端都必须能够解析这些名称。 - -## 启动 - -```bash -docker compose up -d --build -docker compose ps -``` - -仅 Caddy 暴露宿主机的 `8081`(HTTP)和 `443`(HTTPS)。TiddlyWiki、Authelia、Redis 均不能从宿主机端口直接访问。日常直接使用上述 HTTPS 地址;HTTP 跳转入口为 `http://域名:8081`。 - -## 信任内部证书 - -Caddy 首次启动后会生成内部 CA: - -```text -data/caddy/data/caddy/pki/authorities/local/root.crt -``` - -该目录由容器中的 root 用户管理。启动后将根证书导出为当前用户可读取的文件: - -```bash -docker compose cp \ - caddy:/data/caddy/pki/authorities/local/root.crt \ - data/caddy/root.crt -chmod 0644 data/caddy/root.crt -``` - -Linux 可以安装到系统信任库: - -```bash -sudo cp data/caddy/root.crt \ - /usr/local/share/ca-certificates/tdwiki-caddy.crt -sudo update-ca-certificates -``` - -手机、Windows、macOS 和使用独立证书库的 Firefox 也需要导入该根证书。不要把 `root.key` 分发给任何客户端。 - -## 登录 - -访问 `https://wiki.tdwiki.home.arpa` 时会跳转到 Authelia。用户名和密码在同一个页面输入。 - -Wiki 与 WebDAV 都使用 `one_factor`,因此 WebDAV 客户端可以使用同一个个人用户名和密码: - -```text -https://dav.tdwiki.home.arpa/ -``` - -WebDAV 的 Basic 凭据只通过 HTTPS 传输。客户端必须先信任 Caddy 内部 CA。 - -可以使用 curl 验证: - -```bash -curl --cacert data/caddy/root.crt \ - -u '用户名:密码' \ - -X PROPFIND \ - -H 'Depth: 0' \ - https://dav.tdwiki.home.arpa/ -``` - -## 文件目录 - -`files/` 同时挂载到: - -- Caddy:读写,作为 WebDAV 根目录。 -- TiddlyWiki:只读,用于 `/files/...` 和 `/api/files`。 - -WebDAV 创建的文件可能由容器中的 root 用户持有。如果宿主机需要直接编辑,可在停止服务后修正权限: - -```bash -sudo chown -R "$(id -u):$(id -g)" files -``` - -## 修改账户密码 - -重新生成用户数据库并重启 Authelia: - -```bash -./scripts/init-docker.sh --force-user -docker compose restart authelia -``` - -## 更换域名 - -修改 `.env` 的 `BASE_DOMAIN`,然后重新运行初始化脚本: - -```bash -./scripts/init-docker.sh -docker compose up -d --build -``` - -脚本会按新域名重新生成 Authelia 配置。对应 DNS 和客户端证书名称也必须同步更新。 - -## 备份 - -至少备份以下内容: - -- `tiddlers/` -- `files/` -- `data/authelia/` -- `docker/authelia/users_database.yml` -- `secrets/` - -`data/caddy/` 可以重新生成,但重新生成后客户端需要再次安装新的内部 CA。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..8555974 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# tdwiki + +这个仓库支持两种本地使用方式:生成单文件 Wiki,或通过 Node.js 运行目录版 Wiki。 + +## 环境 + +需要 Node.js,以及与项目一致的 TiddlyWiki 5.4.1: + +```sh +npm install --global tiddlywiki@5.4.1 +``` + +确认安装: + +```sh +tiddlywiki --version +``` + +## 生成单文件 + +```sh +./scripts/build.sh +``` + +输出文件为 `dist/tdwiki.html`。构建在临时 Wiki 中完成,不会改写源码目录下的条目。 + +`tiddlywiki.info` 中也保留了对应的 `singlefile` 构建目标;日常构建建议使用上述脚本,以自动排除仅服务端使用的官方插件并检查输出内容。 + +单文件包含浏览器端的本地 utilities 插件。 + +如需指定其他 TiddlyWiki 可执行文件: + +```sh +TIDDLYWIKI_BIN=/path/to/tiddlywiki ./scripts/build.sh +``` + +## 使用 Node.js 运行 + +```sh +./scripts/run.sh +``` + +默认地址为 `http://127.0.0.1:10000`。可以通过环境变量修改监听地址和端口: + +```sh +TDWIKI_LISTEN_HOST=0.0.0.0 TDWIKI_LISTEN_PORT=8080 ./scripts/run.sh +``` + +目录版会加载 filesystem、tiddlyweb 和本地 utilities 插件,并直接使用仓库中的 `tiddlers/`、`plugins/` 与 `files/`。 diff --git a/compose.yaml b/compose.yaml deleted file mode 100644 index 5a909c3..0000000 --- a/compose.yaml +++ /dev/null @@ -1,100 +0,0 @@ -name: tdwiki - -services: - tiddlywiki: - build: - context: . - dockerfile: docker/tiddlywiki/Dockerfile - restart: unless-stopped - init: true - user: "${PUID:-1000}:${PGID:-1000}" - command: - - . - - --listen - - host=0.0.0.0 - - port=8080 - - gzip=yes - - use-browser-cache=yes - volumes: - - ./tiddlers:/wiki/tiddlers - - ./files:/wiki/files:ro - - ./tiddlywiki.info:/wiki/tiddlywiki.info:ro - expose: - - "8080" - healthcheck: - test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8080/"] - interval: 10s - timeout: 3s - retries: 12 - start_period: 10s - security_opt: - - no-new-privileges:true - - redis: - image: redis:7.4-alpine - restart: unless-stopped - command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"] - volumes: - - redis-data:/data - healthcheck: - test: ["CMD", "redis-cli", "ping"] - interval: 10s - timeout: 3s - retries: 5 - security_opt: - - no-new-privileges:true - - authelia: - image: authelia/authelia:4.39.20 - restart: unless-stopped - depends_on: - redis: - condition: service_healthy - volumes: - - ./docker/authelia:/config:ro - - ./data/authelia:/var/lib/authelia - - ./secrets:/secrets:ro - environment: - TZ: "${TZ:-Asia/Shanghai}" - AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE: /secrets/authelia_jwt_secret - AUTHELIA_SESSION_SECRET_FILE: /secrets/authelia_session_secret - AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: /secrets/authelia_storage_encryption_key - expose: - - "9091" - healthcheck: - test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:9091/api/health"] - interval: 10s - timeout: 3s - retries: 12 - start_period: 10s - security_opt: - - no-new-privileges:true - - caddy: - build: - context: . - dockerfile: docker/caddy/Dockerfile - args: - GOPROXY: "${GOPROXY:-https://goproxy.cn,direct}" - restart: unless-stopped - depends_on: - tiddlywiki: - condition: service_healthy - authelia: - condition: service_healthy - environment: - BASE_DOMAIN: "${BASE_DOMAIN:-tdwiki.home.arpa}" - ports: - - "8081:80" - - "443:443" - - "443:443/udp" - volumes: - - ./docker/caddy/Caddyfile:/etc/caddy/Caddyfile:ro - - ./files:/srv/files - - ./data/caddy/data:/data - - ./data/caddy/config:/config - security_opt: - - no-new-privileges:true - -volumes: - redis-data: diff --git a/docker/authelia/configuration.template.yml b/docker/authelia/configuration.template.yml deleted file mode 100644 index bd29e00..0000000 --- a/docker/authelia/configuration.template.yml +++ /dev/null @@ -1,66 +0,0 @@ -server: - address: tcp://0.0.0.0:9091/ - -log: - level: info - format: text - -theme: auto - -identity_validation: - reset_password: {} - -authentication_backend: - password_reset: - disable: true - file: - path: /config/users_database.yml - watch: true - password: - algorithm: argon2 - argon2: - variant: argon2id - iterations: 3 - memory: 65536 - parallelism: 4 - key_length: 32 - salt_length: 16 - -access_control: - default_policy: deny - rules: - - domain: auth.__BASE_DOMAIN__ - policy: bypass - - domain: - - wiki.__BASE_DOMAIN__ - - dav.__BASE_DOMAIN__ - policy: one_factor - subject: - - group:tdwiki - -session: - name: authelia_session - same_site: lax - inactivity: 1h - expiration: 12h - remember_me: 30d - cookies: - - domain: __BASE_DOMAIN__ - authelia_url: https://auth.__BASE_DOMAIN__ - default_redirection_url: https://wiki.__BASE_DOMAIN__ - redis: - host: redis - port: 6379 - -regulation: - max_retries: 5 - find_time: 2m - ban_time: 10m - -storage: - local: - path: /var/lib/authelia/db.sqlite3 - -notifier: - filesystem: - filename: /var/lib/authelia/notification.txt diff --git a/docker/authelia/users_database.template.yml b/docker/authelia/users_database.template.yml deleted file mode 100644 index 2ea7159..0000000 --- a/docker/authelia/users_database.template.yml +++ /dev/null @@ -1,8 +0,0 @@ -users: - __USERNAME__: - disabled: false - displayname: "__DISPLAY_NAME__" - password: "__PASSWORD_HASH__" - email: "__EMAIL__" - groups: - - tdwiki diff --git a/docker/caddy/Caddyfile b/docker/caddy/Caddyfile deleted file mode 100644 index aee104b..0000000 --- a/docker/caddy/Caddyfile +++ /dev/null @@ -1,44 +0,0 @@ -{ - admin off -} - -(authelia) { - forward_auth authelia:9091 { - uri /api/authz/forward-auth - copy_headers Remote-User Remote-Groups Remote-Email Remote-Name - } -} - -auth.{$BASE_DOMAIN:tdwiki.home.arpa} { - tls internal - encode zstd gzip - reverse_proxy authelia:9091 -} - -wiki.{$BASE_DOMAIN:tdwiki.home.arpa} { - tls internal - encode zstd gzip - import authelia - - header { - X-Content-Type-Options nosniff - Referrer-Policy same-origin - } - - reverse_proxy tiddlywiki:8080 -} - -dav.{$BASE_DOMAIN:tdwiki.home.arpa} { - tls internal - import authelia - - header { - X-Content-Type-Options nosniff - } - - route { - webdav { - root /srv/files - } - } -} diff --git a/docker/caddy/Dockerfile b/docker/caddy/Dockerfile deleted file mode 100644 index 9f06d02..0000000 --- a/docker/caddy/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM caddy:2.11.4-builder AS builder - -ARG GOPROXY=https://proxy.golang.org,direct -ENV GOPROXY=${GOPROXY} - -RUN xcaddy build \ - --with github.com/mholt/caddy-webdav@fa2f366b0d75e54c2e381c0aefc3a8df8bf5794b - -FROM caddy:2.11.4-alpine - -COPY --from=builder /usr/bin/caddy /usr/bin/caddy diff --git a/docker/tiddlywiki/Dockerfile b/docker/tiddlywiki/Dockerfile deleted file mode 100644 index cfee4aa..0000000 --- a/docker/tiddlywiki/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM node:22-alpine - -ARG TIDDLYWIKI_VERSION=5.4.1 - -RUN npm install --global "tiddlywiki@${TIDDLYWIKI_VERSION}" \ - && npm cache clean --force - -WORKDIR /wiki -USER node - -EXPOSE 8080 - -ENTRYPOINT ["tiddlywiki"] diff --git a/files/photo_2026-07-26_11-10-12.jpg b/files/photo_2026-07-26_11-10-12.jpg Binary files differdeleted file mode 100644 index 7ddab14..0000000 --- a/files/photo_2026-07-26_11-10-12.jpg +++ /dev/null diff --git a/tiddlers/plugin-copy-codeblock-widget.tid b/plugins/utilities/copy-codeblock-widget.tid index 2251c62..2251c62 100644 --- a/tiddlers/plugin-copy-codeblock-widget.tid +++ b/plugins/utilities/copy-codeblock-widget.tid diff --git a/tiddlers/plugin-current-tiddler-toc-sidebar.tid b/plugins/utilities/current-tiddler-toc-sidebar.tid index 9d73d6d..9d73d6d 100644 --- a/tiddlers/plugin-current-tiddler-toc-sidebar.tid +++ b/plugins/utilities/current-tiddler-toc-sidebar.tid diff --git a/tiddlers/plugin-current-tiddler-toc-widget.tid b/plugins/utilities/current-tiddler-toc-widget.tid index a68e449..a68e449 100644 --- a/tiddlers/plugin-current-tiddler-toc-widget.tid +++ b/plugins/utilities/current-tiddler-toc-widget.tid diff --git a/tiddlers/plugin-highlight-caddyfile.tid b/plugins/utilities/highlight-caddyfile.tid index 8427c3f..8427c3f 100644 --- a/tiddlers/plugin-highlight-caddyfile.tid +++ b/plugins/utilities/highlight-caddyfile.tid diff --git a/tiddlers/plugin-highlight-json5.tid b/plugins/utilities/highlight-json5.tid index fae9bd4..fae9bd4 100644 --- a/tiddlers/plugin-highlight-json5.tid +++ b/plugins/utilities/highlight-json5.tid diff --git a/tiddlers/plugin-highlight-tiddlywiki.tid b/plugins/utilities/highlight-tiddlywiki.tid index dd1a891..dd1a891 100644 --- a/tiddlers/plugin-highlight-tiddlywiki.tid +++ b/plugins/utilities/highlight-tiddlywiki.tid diff --git a/tiddlers/plugin-journal-calendar-widget.tid b/plugins/utilities/journal-calendar-widget.tid index cd54724..c16276d 100644 --- a/tiddlers/plugin-journal-calendar-widget.tid +++ b/plugins/utilities/journal-calendar-widget.tid @@ -48,6 +48,11 @@ function parseMonth(value) { }; } +function formatMonth(value) { + var month = parseMonth(value); + return month.year + "年" + month.month + "月"; +} + JournalCalendarWidget.prototype.render = function(parent,nextSibling) { this.parentDomNode = parent; this.computeAttributes(); @@ -143,6 +148,18 @@ JournalCalendarWidget.prototype.setState = function(title,value) { }); }; +JournalCalendarWidget.prototype.getMonthJournals = function(value,byDate) { + return Object.keys(byDate).filter(function(date) { + return date.substring(0,7) === value; + }).sort().map(function(date) { + return { + date: date, + title: byDate[date].title, + tags: byDate[date].tags + }; + }); +}; + JournalCalendarWidget.prototype.createButton = function(label,className,handler) { var button = this.document.createElement("button"); button.setAttribute("type","button"); @@ -152,9 +169,23 @@ JournalCalendarWidget.prototype.createButton = function(label,className,handler) return button; }; +JournalCalendarWidget.prototype.renderOpenMonthButton = function(value,journals) { + if(!journals.length) { + return null; + } + + var self = this, + button = this.createButton("展开本月日志(" + journals.length + ")","tc-btn-invisible tc-journal-calendar-expand",function(event) { + self.openMonthJournals(journals,event.currentTarget,event); + }); + button.title = "打开" + formatMonth(value) + "的全部日志"; + button.setAttribute("aria-label","打开" + formatMonth(value) + "的全部日志,共" + journals.length + "篇"); + return button; +}; + JournalCalendarWidget.prototype.renderViewSwitcher = function(currentView) { if(this.view !== "switchable") { - return; + return null; } var self = this, @@ -167,12 +198,12 @@ JournalCalendarWidget.prototype.renderViewSwitcher = function(currentView) { controls.appendChild(this.createButton("全部月份","tc-btn-invisible tc-journal-calendar-switch" + (currentView === "all" ? " tc-selected" : ""),function() { self.setState(VIEW_STATE_TITLE,"all"); })); - this.containerNode.appendChild(controls); + return controls; }; JournalCalendarWidget.prototype.renderMonthControls = function(selectedMonth) { if(isValidMonth(this.month)) { - return; + return null; } var self = this, @@ -195,7 +226,40 @@ JournalCalendarWidget.prototype.renderMonthControls = function(selectedMonth) { controls.appendChild(this.createButton("下个月","tc-btn-invisible tc-journal-calendar-nav",function() { setOffset(1); })); - this.containerNode.appendChild(controls); + return controls; +}; + +JournalCalendarWidget.prototype.renderToolbar = function(currentView,selectedMonth,byDate) { + var toolbar = this.document.createElement("div"), + monthSummary = this.document.createElement("div"), + monthTitle = this.document.createElement("h2"), + monthControls, + expandButton, + viewSwitcher; + + toolbar.className = "tc-journal-calendar-toolbar"; + monthSummary.className = "tc-journal-calendar-month-summary"; + monthTitle.className = "tc-journal-calendar-current-month"; + monthTitle.textContent = currentView === "all" ? "全部月份" : formatMonth(selectedMonth); + monthSummary.appendChild(monthTitle); + + if(currentView === "month") { + monthControls = this.renderMonthControls(selectedMonth); + if(monthControls) { + monthSummary.appendChild(monthControls); + } + expandButton = this.renderOpenMonthButton(selectedMonth,this.getMonthJournals(selectedMonth,byDate)); + if(expandButton) { + monthSummary.appendChild(expandButton); + } + } + toolbar.appendChild(monthSummary); + + viewSwitcher = this.renderViewSwitcher(currentView); + if(viewSwitcher) { + toolbar.appendChild(viewSwitcher); + } + this.containerNode.appendChild(toolbar); }; JournalCalendarWidget.prototype.navigateTo = function(title,node,event) { @@ -217,13 +281,23 @@ JournalCalendarWidget.prototype.navigateTo = function(title,node,event) { }); }; -JournalCalendarWidget.prototype.renderMonth = function(value,byDate) { +JournalCalendarWidget.prototype.openMonthJournals = function(journals,node,event) { + var self = this; + journals.forEach(function(journal) { + self.navigateTo(journal.title,node,event); + }); +}; + +JournalCalendarWidget.prototype.renderMonth = function(value,byDate,showHeading) { var self = this, selected = parseMonth(value), section = this.document.createElement("section"), - heading = this.document.createElement("h2"), + monthHeader = this.document.createElement("div"), + heading, + expandButton, viewport = this.document.createElement("div"), grid = this.document.createElement("div"), + journals = this.getMonthJournals(value,byDate), weekdays = ["一","二","三","四","五","六","日"], firstWeekday = (new Date(selected.year,selected.month - 1,1).getDay() + 6) % 7, daysInMonth = new Date(selected.year,selected.month,0).getDate(), @@ -231,9 +305,18 @@ JournalCalendarWidget.prototype.renderMonth = function(value,byDate) { index; section.className = "tc-journal-calendar-month"; - heading.className = "tc-journal-calendar-month-title"; - heading.textContent = selected.year + "年" + selected.month + "月"; - section.appendChild(heading); + monthHeader.className = "tc-journal-calendar-month-header"; + if(showHeading) { + heading = this.document.createElement("h2"); + heading.className = "tc-journal-calendar-month-title"; + heading.textContent = formatMonth(value); + monthHeader.appendChild(heading); + expandButton = this.renderOpenMonthButton(value,journals); + if(expandButton) { + monthHeader.appendChild(expandButton); + } + section.appendChild(monthHeader); + } viewport.className = "tc-journal-calendar-viewport"; grid.className = "tc-journal-calendar-grid"; @@ -296,9 +379,10 @@ JournalCalendarWidget.prototype.renderMonth = function(value,byDate) { JournalCalendarWidget.prototype.renderCalendar = function() { var data = this.getJournalData(), - currentView = this.getCurrentView(); + currentView = this.getCurrentView(), + selectedMonth = this.getCurrentMonth(); - this.renderViewSwitcher(currentView); + this.renderToolbar(currentView,selectedMonth,data.byDate); if(currentView === "all") { if(!data.months.length) { @@ -309,14 +393,12 @@ JournalCalendarWidget.prototype.renderCalendar = function() { return; } data.months.forEach(function(month) { - this.renderMonth(month,data.byDate); + this.renderMonth(month,data.byDate,true); },this); return; } - var selectedMonth = this.getCurrentMonth(); - this.renderMonthControls(selectedMonth); - this.renderMonth(selectedMonth,data.byDate); + this.renderMonth(selectedMonth,data.byDate,false); }; JournalCalendarWidget.prototype.refresh = function(changedTiddlers) { diff --git a/plugins/utilities/plugin.info b/plugins/utilities/plugin.info new file mode 100644 index 0000000..0a2c117 --- /dev/null +++ b/plugins/utilities/plugin.info @@ -0,0 +1,11 @@ +{ + "title": "$:/plugins/local/utilities", + "name": "Local Utilities", + "description": "Small browser-side utilities used by this wiki", + "author": "local", + "version": "1.1.1", + "core-version": ">=5.4.1", + "plugin-type": "plugin", + "dependents": ["$:/plugins/tiddlywiki/highlight"], + "list": "readme" +} diff --git a/plugins/utilities/readme.tid b/plugins/utilities/readme.tid new file mode 100644 index 0000000..e2b35a9 --- /dev/null +++ b/plugins/utilities/readme.tid @@ -0,0 +1,8 @@ +title: $:/plugins/local/utilities/readme +type: text/vnd.tiddlywiki + +! Local Utilities + +收纳当前条目目录、代码块复制、日志日历,以及 Caddyfile、JSON5 和 TiddlyWiki 的 Highlight 语言扩展。 + +Highlight 语言扩展依赖官方插件:`$:/plugins/tiddlywiki/highlight`。 diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..ac7651f --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +PROJECT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +OUTPUT_DIR="$PROJECT_DIR/dist" +OUTPUT_FILE="$OUTPUT_DIR/tdwiki.html" +TIDDLYWIKI_BIN=${TIDDLYWIKI_BIN:-tiddlywiki} +BUILD_TMP_DIR= +BUILD_WIKI_DIR= + +cleanup() { + if [ -n "$BUILD_TMP_DIR" ] && [ -d "$BUILD_TMP_DIR" ]; then + rm -rf -- "$BUILD_TMP_DIR" + fi +} + +prepare_build_wiki() { + BUILD_TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/tdwiki-build.XXXXXX") + BUILD_WIKI_DIR="$BUILD_TMP_DIR/wiki" + mkdir -p "$BUILD_WIKI_DIR" + cp -R "$PROJECT_DIR/tiddlers" "$BUILD_WIKI_DIR/tiddlers" + cp -R "$PROJECT_DIR/plugins" "$BUILD_WIKI_DIR/plugins" + + node -e ' +const fs = require("fs"); +const source = process.argv[1]; +const destination = process.argv[2]; +const info = JSON.parse(fs.readFileSync(source,"utf8")); +const serverPlugins = new Set([ + "tiddlywiki/filesystem", + "tiddlywiki/tiddlyweb" +]); +info.plugins = (info.plugins || []).filter(name => !serverPlugins.has(name)); +fs.writeFileSync(destination,JSON.stringify(info,null,4) + "\n"); +' "$PROJECT_DIR/tiddlywiki.info" "$BUILD_WIKI_DIR/tiddlywiki.info" +} + +verify_output() { + node -e ' +const fs = require("fs"); +const filename = process.argv[1]; +const html = fs.readFileSync(filename,"utf8"); + +for(const title of ["$:/plugins/local/utilities"]) { + if(!html.includes(`"title":"${title}"`)) { + throw new Error(`single-file wiki is missing ${title}`); + } +} + +' "$OUTPUT_FILE" +} + +trap cleanup EXIT HUP INT TERM + +prepare_build_wiki +mkdir -p "$OUTPUT_DIR" + +"$TIDDLYWIKI_BIN" "$BUILD_WIKI_DIR" \ + --output "$OUTPUT_DIR" \ + --rendertiddler '$:/core/save/all' \ + tdwiki.html text/plain + +verify_output +printf 'Built %s\n' "$OUTPUT_FILE" diff --git a/scripts/dev.sh b/scripts/dev.sh deleted file mode 100755 index 7d43567..0000000 --- a/scripts/dev.sh +++ /dev/null @@ -1,3 +0,0 @@ -#/usr/bin/bash - -tiddlywiki . --listen port=10000 gzip=yes use-browser-cache=yes
\ No newline at end of file diff --git a/scripts/init-docker.sh b/scripts/init-docker.sh deleted file mode 100755 index 4bae29b..0000000 --- a/scripts/init-docker.sh +++ /dev/null @@ -1,157 +0,0 @@ -#!/bin/sh - -set -eu - -SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) -PROJECT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) -FORCE_USER=no - -if [ "${1:-}" = "--force-user" ]; then - FORCE_USER=yes -elif [ "$#" -gt 0 ]; then - printf 'Usage: %s [--force-user]\n' "$0" >&2 - exit 1 -fi - -if [ ! -f "$PROJECT_DIR/.env" ]; then - cp "$PROJECT_DIR/.env.example" "$PROJECT_DIR/.env" - printf 'Created .env from .env.example\n' -fi - -set -a -. "$PROJECT_DIR/.env" -set +a - -BASE_DOMAIN=${BASE_DOMAIN:-tdwiki.home.arpa} - -case "$BASE_DOMAIN" in - *[!a-zA-Z0-9.-]*|.*|*.) - printf 'Invalid BASE_DOMAIN: %s\n' "$BASE_DOMAIN" >&2 - exit 1 - ;; -esac - -mkdir -p \ - "$PROJECT_DIR/data/authelia" \ - "$PROJECT_DIR/data/caddy/data" \ - "$PROJECT_DIR/data/caddy/config" \ - "$PROJECT_DIR/secrets" - -chmod 700 "$PROJECT_DIR/secrets" - -generate_secret() { - file=$1 - bytes=$2 - if [ ! -s "$file" ]; then - umask 077 - openssl rand -hex "$bytes" > "$file" - printf 'Generated %s\n' "${file#"$PROJECT_DIR/"}" - fi -} - -generate_secret "$PROJECT_DIR/secrets/authelia_jwt_secret" 32 -generate_secret "$PROJECT_DIR/secrets/authelia_session_secret" 32 -generate_secret "$PROJECT_DIR/secrets/authelia_storage_encryption_key" 32 - -sed "s/__BASE_DOMAIN__/$BASE_DOMAIN/g" \ - "$PROJECT_DIR/docker/authelia/configuration.template.yml" \ - > "$PROJECT_DIR/docker/authelia/configuration.yml" - -USERS_FILE="$PROJECT_DIR/docker/authelia/users_database.yml" - -if [ ! -f "$USERS_FILE" ] || [ "$FORCE_USER" = yes ]; then - USERNAME=${AUTHELIA_USERNAME:-} - DISPLAY_NAME=${AUTHELIA_DISPLAY_NAME:-} - EMAIL=${AUTHELIA_EMAIL:-} - PASSWORD=${AUTHELIA_PASSWORD:-} - - if [ -z "$USERNAME" ]; then - printf 'Authelia username [admin]: ' - read -r USERNAME - USERNAME=${USERNAME:-admin} - fi - - case "$USERNAME" in - *[!a-zA-Z0-9_.-]*) - printf 'Username may only contain letters, digits, dot, underscore, and hyphen.\n' >&2 - exit 1 - ;; - esac - - if [ -z "$DISPLAY_NAME" ]; then - printf 'Display name [%s]: ' "$USERNAME" - read -r DISPLAY_NAME - DISPLAY_NAME=${DISPLAY_NAME:-$USERNAME} - fi - - if [ -z "$EMAIL" ]; then - printf 'Email [%s@%s]: ' "$USERNAME" "$BASE_DOMAIN" - read -r EMAIL - EMAIL=${EMAIL:-$USERNAME@$BASE_DOMAIN} - fi - - case "$DISPLAY_NAME$EMAIL" in - *'"'*|*'\'*) - printf 'Display name and email cannot contain quote or backslash characters.\n' >&2 - exit 1 - ;; - esac - - if [ -z "$PASSWORD" ]; then - if [ ! -t 0 ]; then - printf 'Set AUTHELIA_PASSWORD for non-interactive initialization.\n' >&2 - exit 1 - fi - printf 'Password: ' - stty -echo - read -r PASSWORD - stty echo - printf '\nConfirm password: ' - stty -echo - read -r PASSWORD_CONFIRM - stty echo - printf '\n' - if [ "$PASSWORD" != "$PASSWORD_CONFIRM" ]; then - printf 'Passwords do not match.\n' >&2 - exit 1 - fi - fi - - if [ "${#PASSWORD}" -lt 12 ]; then - printf 'Password must contain at least 12 characters.\n' >&2 - exit 1 - fi - - HASH_OUTPUT=$(docker run --rm authelia/authelia:4.39.20 \ - authelia crypto hash generate argon2 \ - --password "$PASSWORD" \ - --no-confirm) - PASSWORD_HASH=${HASH_OUTPUT#Digest: } - unset PASSWORD PASSWORD_CONFIRM HASH_OUTPUT - - escape_sed() { - printf '%s' "$1" | sed 's/[|&]/\\&/g' - } - - USERNAME_ESCAPED=$(escape_sed "$USERNAME") - DISPLAY_NAME_ESCAPED=$(escape_sed "$DISPLAY_NAME") - EMAIL_ESCAPED=$(escape_sed "$EMAIL") - PASSWORD_HASH_ESCAPED=$(escape_sed "$PASSWORD_HASH") - - sed \ - -e "s|__USERNAME__|$USERNAME_ESCAPED|g" \ - -e "s|__DISPLAY_NAME__|$DISPLAY_NAME_ESCAPED|g" \ - -e "s|__EMAIL__|$EMAIL_ESCAPED|g" \ - -e "s|__PASSWORD_HASH__|$PASSWORD_HASH_ESCAPED|g" \ - "$PROJECT_DIR/docker/authelia/users_database.template.yml" \ - > "$USERS_FILE" - chmod 600 "$USERS_FILE" - printf 'Created Authelia user %s\n' "$USERNAME" -fi - -docker compose --project-directory "$PROJECT_DIR" config --quiet - -printf '\nInitialization complete.\n' -printf 'Add wiki.%s, dav.%s, and auth.%s to local DNS, then run:\n' \ - "$BASE_DOMAIN" "$BASE_DOMAIN" "$BASE_DOMAIN" -printf ' docker compose up -d --build\n' diff --git a/scripts/run.sh b/scripts/run.sh index 9164aa6..8843f9f 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -1,3 +1,15 @@ -#/usr/bin/bash +#!/bin/sh -tiddlywiki . --listen port=10000 use-browser-cache=yes
\ No newline at end of file +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +PROJECT_DIR=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd) +TIDDLYWIKI_BIN=${TIDDLYWIKI_BIN:-tiddlywiki} +TDWIKI_LISTEN_HOST=${TDWIKI_LISTEN_HOST:-127.0.0.1} +TDWIKI_LISTEN_PORT=${TDWIKI_LISTEN_PORT:-10000} + +exec "$TIDDLYWIKI_BIN" "$PROJECT_DIR" --listen \ + host="$TDWIKI_LISTEN_HOST" \ + port="$TDWIKI_LISTEN_PORT" \ + gzip=yes \ + use-browser-cache=yes diff --git a/tiddlers/$__StoryList.tid b/tiddlers/$__StoryList.tid index 1231352..e69de29 100644 --- a/tiddlers/$__StoryList.tid +++ b/tiddlers/$__StoryList.tid @@ -1,3 +0,0 @@ -list: tag 日历 tiddlywiki -title: $:/StoryList -type: text/vnd.tiddlywiki
\ No newline at end of file diff --git a/tiddlers/$__StoryList_2.tid b/tiddlers/$__StoryList_2.tid new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tiddlers/$__StoryList_2.tid diff --git a/tiddlers/$__config_org-gtd_extraKeys.json b/tiddlers/$__config_org-gtd_extraKeys.json deleted file mode 100644 index d79f694..0000000 --- a/tiddlers/$__config_org-gtd_extraKeys.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Alt-T": "orgGtdCycleState", - "Ctrl-Left": "orgGtdPromote", - "Ctrl-Right": "orgGtdDemote", - "Ctrl-Up": "orgGtdMoveUp", - "Ctrl-Down": "orgGtdMoveDown", - "Alt-Y": "orgGtdToggleFold" -} diff --git a/tiddlers/$__config_org-gtd_extraKeys.json.meta b/tiddlers/$__config_org-gtd_extraKeys.json.meta deleted file mode 100644 index 2fb750a..0000000 --- a/tiddlers/$__config_org-gtd_extraKeys.json.meta +++ /dev/null @@ -1,4 +0,0 @@ -created: 20260728104833107 -modified: 20260728105401160 -title: $:/config/org-gtd/extraKeys -type: application/json
\ No newline at end of file diff --git a/tiddlers/GTD Agenda.tid b/tiddlers/GTD Agenda.tid deleted file mode 100644 index 69614ea..0000000 --- a/tiddlers/GTD Agenda.tid +++ /dev/null @@ -1,5 +0,0 @@ -created: 20260728000000000 -title: GTD Agenda -type: text/vnd.tiddlywiki - -<$org-gtd-agenda days="7"/> diff --git a/tiddlers/GTD Inbox.org b/tiddlers/GTD Inbox.org deleted file mode 100644 index 742123e..0000000 --- a/tiddlers/GTD Inbox.org +++ /dev/null @@ -1,5 +0,0 @@ -* TODO 熟悉 Org GTD 插件 :gtd: - SCHEDULED: <2026-07-28 Tue> -** WAIT 根据实际工作流调整任务内容 -** NEXT 打开 GTD Agenda 查看任务 -* DONE 已安装 Org GTD 插件
\ No newline at end of file diff --git a/tiddlers/GTD Inbox.org.meta b/tiddlers/GTD Inbox.org.meta deleted file mode 100644 index 7855b61..0000000 --- a/tiddlers/GTD Inbox.org.meta +++ /dev/null @@ -1,4 +0,0 @@ -created: 20260728000000000 -modified: 20260728105340434 -title: GTD Inbox -type: text/x-org
\ No newline at end of file diff --git a/tiddlers/Org GTD 使用说明.tid b/tiddlers/Org GTD 使用说明.tid deleted file mode 100644 index 2a670ee..0000000 --- a/tiddlers/Org GTD 使用说明.tid +++ /dev/null @@ -1,46 +0,0 @@ -created: 20260728000000000 -modified: 20260728090815424 -tags: 🍀 -title: Org GTD 使用说明 -type: text/vnd.tiddlywiki - -!语法 - -在 [[GTD Agenda]] 中点击“新建 Org 文档”,使用以下子集: - -```org -* TODO 收集任务 :work: - SCHEDULED: <2026-07-29 Wed> - DEADLINE: <2026-07-31 Fri> -** NEXT 下一步行动 -** WAIT 等待他人 -* DONE 已完成事项 -``` - -!CodeMirror 快捷键 - -插件默认不绑定快捷键,避免与浏览器、桌面环境和 TiddlyWiki 冲突。普通按键(包括 Tab)保持 CodeMirror 原有行为。 - -需要快捷键时,编辑 [[$:/config/org-gtd/extraKeys]],按 CodeMirror 键名到命令名的格式填写 JSON。组合键内部使用连字符,例如 `Ctrl-Left`;空格表示连续按键,例如 `F8 T`。配置只在 Org 编辑器中生效;保存后重新打开编辑器。 - -例如,将 `Ctrl-Left` 绑定为提升层级,并将 `F8` 后按 `T` 绑定为循环任务状态: - -```json -{ - "Ctrl-Left": "orgGtdPromote", - "F8 T": "orgGtdCycleState" -} -``` - -可用命令: - -|命令 |操作 |h -|orgGtdCycleState |循环 TODO → NEXT → WAIT → DONE | -|orgGtdPromote / orgGtdDemote |提升 / 降低当前子树层级 | -|orgGtdMoveUp / orgGtdMoveDown |向上 / 向下移动当前子树 | -|orgGtdInsertSibling |新建同级 TODO 标题 | -|orgGtdToggleFold |折叠 / 展开当前子树 | - -保持配置内容为 `{}` 即可禁用全部 Org GTD 快捷键。 - -Agenda 显示逾期、今天、未来 7 天和无日期的 NEXT。点击 Agenda 事项会打开源 Org 文档并定位到对应标题。 diff --git a/tiddlers/files API demo.tid b/tiddlers/files API demo.tid deleted file mode 100644 index dce6e46..0000000 --- a/tiddlers/files API demo.tid +++ /dev/null @@ -1,51 +0,0 @@ -created: 20260726073547121 -modified: 20260726085801292 -tags: tiddlywiki -title: files API demo -type: text/vnd.tiddlywiki - -\procedure store-file-list() -<$action-setfield - $tiddler="$:/temp/files-list" - text=<<data>> - type="application/json" -/> -\end - -<$button> - <$action-sendmessage - $message="tm-http-request" - url="./api/files" - method="GET" - oncompletion=<<store-file-list>> - /> - 刷新文件列表 -</$button> - -<pre><$text text={{$:/temp/files-list}}/></pre> - -[img[./files/photo_2026-07-26_11-10-12.jpg]] - - -```caddyfile -example.com { - encode zstd gzip - - @api path /api/* - reverse_proxy @api localhost:3000 - - header { - X-Content-Type-Options nosniff - } -} -``` - -```json5 -{ - // 支持注释 - unquoted: 'value', - hexadecimal: 0xFF, - infinity: Infinity, - trailingComma: true, -} -```
\ No newline at end of file diff --git a/tiddlers/files-list-api.tid.js.meta b/tiddlers/files-list-api.tid.js.meta deleted file mode 100644 index 066c85b..0000000 --- a/tiddlers/files-list-api.tid.js.meta +++ /dev/null @@ -1,5 +0,0 @@ -created: 20260726082748103 -modified: 20260726082841751 -tags: tiddlywiki -title: files-list-api.tid -type: application/javascript
\ No newline at end of file diff --git a/tiddlers/plugin-files-api-route.tid b/tiddlers/plugin-files-api-route.tid deleted file mode 100644 index 6236926..0000000 --- a/tiddlers/plugin-files-api-route.tid +++ /dev/null @@ -1,132 +0,0 @@ -title: $:/plugins/local/files-api/get-files.js -type: application/javascript -module-type: route - -/*\ -title: $:/plugins/local/files-api/get-files.js -type: application/javascript -module-type: route - -GET /api/files -\*/ -"use strict"; - -var fs = require("fs"), - path = require("path"); - -exports.methods = ["GET"]; -exports.path = /^\/api\/files\/?$/; - -exports.info = { - priority: 100 -}; - -function encodeFilePath(filename) { - return filename - .split(path.sep) - .map(encodeURIComponent) - .join("/"); -} - -async function scanDirectory(rootDirectory,directory,prefix) { - var entries, - files = []; - - try { - entries = await fs.promises.readdir(directory,{ - withFileTypes: true - }); - } catch(error) { - if(error.code === "ENOENT") { - return []; - } - throw error; - } - - for(var index = 0; index < entries.length; index++) { - var entry = entries[index]; - - // 不公开隐藏文件。 - if(entry.name.charAt(0) === ".") { - continue; - } - - var absolutePath = path.join(directory,entry.name); - - // 忽略符号链接,避免扫描到 files/ 目录之外。 - if(entry.isDirectory()) { - var childFiles = await scanDirectory( - rootDirectory, - absolutePath, - path.join(prefix,entry.name) - ); - files.push.apply(files,childFiles); - continue; - } - - if(!entry.isFile()) { - continue; - } - - var relativePath = path.join(prefix,entry.name), - extension = path.extname(entry.name).toLowerCase(), - extensionInfo = $tw.config.fileExtensionInfo[extension], - stats; - - try { - stats = await fs.promises.stat(absolutePath); - } catch(error) { - // 文件可能在扫描过程中被删除。 - continue; - } - - files.push({ - name: entry.name, - path: relativePath.split(path.sep).join("/"), - url: (prefix === "" ? "" : "") + - encodeFilePath(relativePath), - type: extensionInfo ? - extensionInfo.type : - "application/octet-stream", - size: stats.size, - modified: stats.mtime.toISOString() - }); - } - - return files; -} - -exports.handler = function(request,response,state) { - var rootDirectory = path.resolve(state.boot.wikiPath,"files"), - pathPrefix = state.pathPrefix || ""; - - scanDirectory(rootDirectory,rootDirectory,"") - .then(function(files) { - files.sort(function(left,right) { - return left.path.localeCompare(right.path); - }); - - files.forEach(function(file) { - file.url = pathPrefix + "/files/" + file.url; - }); - - state.sendResponse( - 200, - {"Content-Type": "application/json; charset=utf-8"}, - JSON.stringify(files), - "utf8" - ); - }) - .catch(function(error) { - console.error("Unable to list files directory:",error); - - state.sendResponse( - 500, - {"Content-Type": "application/json; charset=utf-8"}, - JSON.stringify({ - error: "Unable to list files directory" - }), - "utf8" - ); - }); -}; diff --git a/tiddlers/plugin-org-gtd-agenda-widget.tid b/tiddlers/plugin-org-gtd-agenda-widget.tid deleted file mode 100644 index 4749564..0000000 --- a/tiddlers/plugin-org-gtd-agenda-widget.tid +++ /dev/null @@ -1,229 +0,0 @@ -title: $:/plugins/local/org-gtd/agenda-widget.js -type: application/javascript -module-type: widget - -/*\ -title: $:/plugins/local/org-gtd/agenda-widget.js -type: application/javascript -module-type: widget -\*/ -(function() { -"use strict"; - -var Widget = require("$:/core/modules/widgets/widget.js").widget, - org = require("$:/plugins/local/org-gtd/library.js"), - JUMP_STATE_TITLE = "$:/state/org-gtd/jump"; - -var OrgGtdAgendaWidget = function(parseTreeNode,options) { - this.initialise(parseTreeNode,options); - this.parseCache = Object.create(null); -}; - -OrgGtdAgendaWidget.prototype = new Widget(); -OrgGtdAgendaWidget.prototype.constructor = OrgGtdAgendaWidget; - -function startOfDay(date) { - return new Date(date.getFullYear(),date.getMonth(),date.getDate()).getTime(); -} - -function earliestDate(task) { - var values = [task.scheduled,task.deadline].filter(function(value) { - return !isNaN(org.dateToTime(value)); - }); - values.sort(); - return values[0] || ""; -} - -OrgGtdAgendaWidget.prototype.render = function(parent,nextSibling) { - this.parentDomNode = parent; - this.computeAttributes(); - this.execute(); - - var container = this.document.createElement("div"); - container.className = "tc-org-agenda"; - parent.insertBefore(container,nextSibling); - this.domNodes.push(container); - this.containerNode = container; - this.renderAgenda(); -}; - -OrgGtdAgendaWidget.prototype.execute = function() { - this.days = Math.max(1,Number(this.getAttribute("days","7")) || 7); -}; - -OrgGtdAgendaWidget.prototype.createButton = function(label,className,handler) { - var button = this.document.createElement("button"); - button.type = "button"; - button.className = "tc-btn-invisible " + className; - button.textContent = label; - button.addEventListener("click",handler); - return button; -}; - -OrgGtdAgendaWidget.prototype.getTasks = function() { - var self = this, - tasks = [], - activeTitles = Object.create(null); - this.wiki.allTitles().forEach(function(title) { - var tiddler = self.wiki.getTiddler(title), - text, - cached, - parsed; - if(!tiddler || tiddler.fields.type !== "text/x-org" || tiddler.fields["draft.of"]) { - return; - } - activeTitles[title] = true; - text = tiddler.fields.text || ""; - cached = self.parseCache[title]; - parsed = cached && cached.text === text ? cached.parsed : org.parse(text); - self.parseCache[title] = {text: text,parsed: parsed}; - parsed.headings.forEach(function(heading) { - if(heading.state && heading.state !== "DONE" && heading.title) { - tasks.push({ - tiddler: title, - title: heading.title, - state: heading.state, - level: heading.level, - line: heading.sourceLine, - scheduled: heading.scheduled, - deadline: heading.deadline, - date: earliestDate(heading) - }); - } - }); - }); - Object.keys(this.parseCache).forEach(function(title) { - if(!activeTitles[title]) { - delete self.parseCache[title]; - } - }); - return tasks; -}; - -OrgGtdAgendaWidget.prototype.openTask = function(task,event) { - this.wiki.setText(JUMP_STATE_TITLE,"text",null,JSON.stringify({title: task.tiddler,line: task.line})); - this.dispatchEvent({ - type: "tm-edit-tiddler", - param: task.tiddler, - tiddlerTitle: task.tiddler, - navigateFromTitle: this.getVariable("storyTiddler"), - event: event - }); -}; - -OrgGtdAgendaWidget.prototype.renderTask = function(parent,task) { - var self = this, - button = this.createButton(task.title,"tc-org-agenda-task",function(event) { - self.openTask(task,event); - }), - state = this.document.createElement("span"), - source = this.document.createElement("span"), - meta = this.document.createElement("span"), - row = this.document.createElement("div"), - parts = []; - row.className = "tc-org-agenda-row"; - state.className = "tc-org-state tc-org-state-" + task.state.toLowerCase(); - state.textContent = task.state; - source.className = "tc-org-agenda-source"; - source.textContent = task.tiddler; - if(task.scheduled) { - parts.push("计划 " + task.scheduled); - } - if(task.deadline) { - parts.push("截止 " + task.deadline); - } - meta.className = "tc-org-agenda-meta"; - meta.textContent = parts.join(" · "); - row.appendChild(state); - row.appendChild(button); - row.appendChild(source); - if(parts.length) { - row.appendChild(meta); - } - parent.appendChild(row); -}; - -OrgGtdAgendaWidget.prototype.renderSection = function(title,tasks,className) { - var self = this, - section = this.document.createElement("section"), - heading = this.document.createElement("h2"), - count = this.document.createElement("span"); - section.className = "tc-org-agenda-section " + className; - heading.className = "tc-org-agenda-section-title"; - heading.textContent = title; - count.className = "tc-org-agenda-count"; - count.textContent = String(tasks.length); - heading.appendChild(count); - section.appendChild(heading); - if(tasks.length) { - tasks.forEach(function(task) { - self.renderTask(section,task); - }); - } else { - var empty = this.document.createElement("p"); - empty.className = "tc-org-agenda-empty"; - empty.textContent = "没有事项"; - section.appendChild(empty); - } - this.containerNode.appendChild(section); -}; - -OrgGtdAgendaWidget.prototype.renderAgenda = function() { - var self = this, - today = startOfDay(new Date()), - horizonDate = new Date(today), - horizon, - groups = {overdue: [],today: [],upcoming: [],next: []}, - toolbar = this.document.createElement("div"), - newButton; - horizonDate.setDate(horizonDate.getDate() + this.days); - horizon = horizonDate.getTime(); - newButton = this.createButton("+ 新建 Org 文档","tc-org-agenda-new",function(event) { - var title = self.wiki.generateNewTitle("GTD Inbox"); - self.dispatchEvent({ - type: "tm-new-tiddler", - paramObject: {title: title,type: "text/x-org",text: "* TODO "}, - navigateFromTitle: self.getVariable("storyTiddler"), - event: event - }); - }); - toolbar.className = "tc-org-agenda-toolbar"; - toolbar.appendChild(newButton); - this.containerNode.appendChild(toolbar); - this.getTasks().forEach(function(task) { - var time = org.dateToTime(task.date); - if(!isNaN(time)) { - if(time < today) { - groups.overdue.push(task); - } else if(time === today) { - groups.today.push(task); - } else if(time <= horizon) { - groups.upcoming.push(task); - } - } else if(task.state === "NEXT") { - groups.next.push(task); - } - }); - Object.keys(groups).forEach(function(key) { - groups[key].sort(function(left,right) { - return (left.date || "9999").localeCompare(right.date || "9999") || left.title.localeCompare(right.title,"zh-Hans"); - }); - }); - this.renderSection("逾期",groups.overdue,"tc-org-agenda-overdue"); - this.renderSection("今天",groups.today,"tc-org-agenda-today"); - this.renderSection("未来 " + this.days + " 天",groups.upcoming,"tc-org-agenda-upcoming"); - this.renderSection("无日期的下一步",groups.next,"tc-org-agenda-next"); -}; - -OrgGtdAgendaWidget.prototype.refresh = function(changedTiddlers) { - var changedAttributes = this.computeAttributes(); - if(Object.keys(changedAttributes).length || Object.keys(changedTiddlers).length) { - this.refreshSelf(); - return true; - } - return false; -}; - -exports["org-gtd-agenda"] = OrgGtdAgendaWidget; - -})(); diff --git a/tiddlers/plugin-org-gtd-codemirror.tid b/tiddlers/plugin-org-gtd-codemirror.tid deleted file mode 100644 index 09b3e6c..0000000 --- a/tiddlers/plugin-org-gtd-codemirror.tid +++ /dev/null @@ -1,341 +0,0 @@ -title: $:/plugins/local/org-gtd/codemirror.js -type: application/javascript -module-type: codemirror - -/*\ -title: $:/plugins/local/org-gtd/codemirror.js -type: application/javascript -module-type: codemirror -\*/ -(function() { -"use strict"; - -var CodeMirror = require("$:/plugins/tiddlywiki/codemirror/lib/codemirror.js"), - org = require("$:/plugins/local/org-gtd/library.js"), - JUMP_STATE_TITLE = "$:/state/org-gtd/jump", - KEY_MAP_TITLE = "$:/config/org-gtd/extraKeys"; - -function isOrgEditor(cm) { - return cm.getMode().name === "org-gtd"; -} - -function readOrgGtdKeyMap(cm) { - var widget = cm.getOption("editWidget"), - text, - keyMap; - if(!widget || !widget.wiki) { - return null; - } - text = widget.wiki.getTiddlerText(KEY_MAP_TITLE,"{}"); - try { - keyMap = JSON.parse(text); - } catch(error) { - console.error("Org GTD shortcut configuration is invalid JSON",error); - return null; - } - if(!keyMap || typeof keyMap !== "object" || Array.isArray(keyMap) || Object.keys(keyMap).length === 0) { - return null; - } - try { - return CodeMirror.normalizeKeyMap(keyMap); - } catch(error) { - console.error("Org GTD shortcut configuration contains an invalid key name",error); - return null; - } -} - -function syncOrgGtdKeyMap(cm) { - var keyMap; - if(cm.state.orgGtdKeyMap) { - cm.removeKeyMap(cm.state.orgGtdKeyMap); - cm.state.orgGtdKeyMap = null; - } - if(!isOrgEditor(cm)) { - return; - } - keyMap = readOrgGtdKeyMap(cm); - if(keyMap) { - cm.addKeyMap(keyMap); - cm.state.orgGtdKeyMap = keyMap; - } -} - -function clearFolds(cm) { - var marks = cm.state.orgGtdFoldMarks || []; - marks.forEach(function(mark) { - if(mark.find()) { - mark.clear(); - } - }); - cm.state.orgGtdFoldMarks = []; -} - -function replaceDocument(cm,lines,cursorLine,cursorCh) { - var lastLine = cm.lastLine(), - lastCh = cm.getLine(lastLine).length; - clearFolds(cm); - cm.operation(function() { - cm.replaceRange(lines.join("\n"),CodeMirror.Pos(0,0),CodeMirror.Pos(lastLine,lastCh),"+org-gtd"); - cm.setCursor(CodeMirror.Pos(cursorLine,cursorCh || 0)); - }); -} - -function currentHeading(cm) { - var parsed = org.parse(cm.getValue()), - heading = org.headingAtLine(parsed,cm.getCursor().line); - return {parsed: parsed,heading: heading}; -} - -CodeMirror.defineMode("org-gtd",function() { - return { - startState: function() { - return {heading: false,planning: false}; - }, - token: function(stream,state) { - if(stream.sol()) { - state.heading = /^\*+\s+/.test(stream.string); - state.planning = /^\s*(?:SCHEDULED|DEADLINE):/.test(stream.string); - } - if(state.heading) { - if(stream.match(/^\*+\s+/)) { - return "header"; - } - if(stream.match(/^(?:TODO|NEXT|WAIT|DONE)\b/)) { - return "keyword"; - } - if(stream.match(/^\s+:[A-Za-z0-9_@#%+.-]+(?::[A-Za-z0-9_@#%+.-]+)*:\s*$/)) { - return "tag"; - } - } - if(state.planning) { - if(stream.match(/^\s*(?:SCHEDULED|DEADLINE):/)) { - return "def"; - } - if(stream.match(/^\s*<[0-9]{4}-[0-9]{2}-[0-9]{2}(?:\s+[^>]*)?>/)) { - return "atom"; - } - } - if(stream.sol() && stream.match(/^\s*#.*$/)) { - return "comment"; - } - stream.next(); - return null; - }, - lineComment: "# " - }; -}); - -CodeMirror.defineMIME("text/x-org","org-gtd"); - -CodeMirror.commands.orgGtdCycleState = function(cm) { - var cursor, - line, - result; - if(!isOrgEditor(cm)) { - return CodeMirror.Pass; - } - cursor = cm.getCursor(); - line = cm.getLine(cursor.line); - result = org.cycleState(line); - if(!result) { - return CodeMirror.Pass; - } - cm.replaceRange(result.line,CodeMirror.Pos(cursor.line,0),CodeMirror.Pos(cursor.line,line.length),"+org-gtd"); - cm.setCursor(CodeMirror.Pos(cursor.line,Math.min(cursor.ch + (result.line.length - line.length),result.line.length))); -}; - -function changeLevel(cm,direction) { - var context, - lines; - if(!isOrgEditor(cm)) { - return CodeMirror.Pass; - } - context = currentHeading(cm); - if(!context.heading || (direction < 0 && context.heading.level === 1)) { - return CodeMirror.Pass; - } - lines = context.parsed.lines.slice(0); - for(var index = context.heading.line; index <= context.heading.subtreeEndLine; index++) { - if(/^\*+\s+/.test(lines[index])) { - lines[index] = direction > 0 ? "*" + lines[index] : lines[index].substring(1); - } - } - replaceDocument(cm,lines,context.heading.line,Math.max(0,cm.getCursor().ch + direction)); -} - -CodeMirror.commands.orgGtdPromote = function(cm) { - return changeLevel(cm,-1); -}; - -CodeMirror.commands.orgGtdDemote = function(cm) { - return changeLevel(cm,1); -}; - -function moveSubtree(cm,direction) { - var context, - heading, - headings, - index, - other, - lines, - before, - current, - after, - newLine; - if(!isOrgEditor(cm)) { - return CodeMirror.Pass; - } - context = currentHeading(cm); - heading = context.heading; - if(!heading) { - return CodeMirror.Pass; - } - headings = context.parsed.headings; - index = headings.indexOf(heading); - if(direction < 0) { - for(index = index - 1; index >= 0; index--) { - if(headings[index].level < heading.level) { - return CodeMirror.Pass; - } - if(headings[index].level === heading.level) { - other = headings[index]; - break; - } - } - if(!other) { - return CodeMirror.Pass; - } - lines = context.parsed.lines; - before = lines.slice(0,other.line); - current = lines.slice(heading.line,heading.subtreeEndLine + 1); - after = lines.slice(other.line,heading.line).concat(lines.slice(heading.subtreeEndLine + 1)); - newLine = other.line; - replaceDocument(cm,before.concat(current,after),newLine,cm.getCursor().ch); - } else { - for(index = index + 1; index < headings.length; index++) { - if(headings[index].line <= heading.subtreeEndLine) { - continue; - } - if(headings[index].level < heading.level) { - return CodeMirror.Pass; - } - if(headings[index].level === heading.level) { - other = headings[index]; - break; - } - } - if(!other) { - return CodeMirror.Pass; - } - lines = context.parsed.lines; - before = lines.slice(0,heading.line); - current = lines.slice(heading.line,heading.subtreeEndLine + 1); - after = lines.slice(other.line,other.subtreeEndLine + 1); - newLine = heading.line + after.length; - replaceDocument(cm,before.concat(after,current,lines.slice(other.subtreeEndLine + 1)),newLine,cm.getCursor().ch); - } -} - -CodeMirror.commands.orgGtdMoveUp = function(cm) { - return moveSubtree(cm,-1); -}; - -CodeMirror.commands.orgGtdMoveDown = function(cm) { - return moveSubtree(cm,1); -}; - -CodeMirror.commands.orgGtdToggleFold = function(cm) { - var context, - heading, - marks, - existing, - marker, - mark; - if(!isOrgEditor(cm)) { - return CodeMirror.Pass; - } - context = currentHeading(cm); - heading = context.heading; - if(!heading || cm.getCursor().line !== heading.line || heading.subtreeEndLine === heading.line) { - return CodeMirror.Pass; - } - marks = cm.state.orgGtdFoldMarks = cm.state.orgGtdFoldMarks || []; - existing = marks.filter(function(item) { - var range = item.find(); - return range && range.from.line === heading.line; - })[0]; - if(existing) { - existing.clear(); - cm.state.orgGtdFoldMarks = marks.filter(function(item) {return item !== existing;}); - return; - } - marker = cm.getWrapperElement().ownerDocument.createElement("span"); - marker.className = "cm-org-gtd-fold-marker"; - marker.textContent = " ..."; - mark = cm.markText( - CodeMirror.Pos(heading.line,cm.getLine(heading.line).length), - CodeMirror.Pos(heading.subtreeEndLine,cm.getLine(heading.subtreeEndLine).length), - {collapsed: true,replacedWith: marker,atomic: true} - ); - marks.push(mark); - marker.addEventListener("click",function() { - mark.clear(); - cm.state.orgGtdFoldMarks = marks.filter(function(item) {return item !== mark;}); - }); -}; - -CodeMirror.commands.orgGtdInsertSibling = function(cm) { - var context, - heading, - lines, - newLine; - if(!isOrgEditor(cm)) { - return CodeMirror.Pass; - } - context = currentHeading(cm); - heading = context.heading; - if(!heading) { - return CodeMirror.Pass; - } - lines = context.parsed.lines.slice(0); - newLine = heading.subtreeEndLine + 1; - lines.splice(newLine,0,new Array(heading.level + 1).join("*") + " TODO "); - replaceDocument(cm,lines,newLine,heading.level + 6); -}; - -CodeMirror.defineInitHook(function(cm) { - var widget = cm.getOption("editWidget"), - request, - currentTitle, - tiddler, - originalTitle; - syncOrgGtdKeyMap(cm); - cm.on("optionChange",function(instance,option) { - if(option === "mode") { - syncOrgGtdKeyMap(instance); - } - }); - if(!widget || !widget.wiki) { - return; - } - request = $tw.utils.parseJSONSafe(widget.wiki.getTiddlerText(JUMP_STATE_TITLE,""),null); - if(!request) { - return; - } - currentTitle = widget.editTitle || widget.getVariable("currentTiddler",""); - tiddler = widget.wiki.getTiddler(currentTitle); - originalTitle = tiddler && tiddler.fields["draft.of"] ? tiddler.fields["draft.of"] : currentTitle; - if(originalTitle !== request.title) { - return; - } - setTimeout(function() { - var line = Math.max(0,Math.min(Number(request.line || 1) - 1,cm.lastLine())), - position = CodeMirror.Pos(line,0); - cm.setCursor(position); - cm.scrollIntoView(position,100); - cm.focus(); - widget.wiki.deleteTiddler(JUMP_STATE_TITLE); - },0); -}); - -})(); diff --git a/tiddlers/plugin-org-gtd-content-type.tid b/tiddlers/plugin-org-gtd-content-type.tid deleted file mode 100644 index 64d2c76..0000000 --- a/tiddlers/plugin-org-gtd-content-type.tid +++ /dev/null @@ -1,12 +0,0 @@ -title: $:/plugins/local/org-gtd/content-type.js -type: application/javascript -module-type: config - -/*\ -title: $:/plugins/local/org-gtd/content-type.js -type: application/javascript -module-type: config -\*/ -"use strict"; - -$tw.utils.registerFileType("text/x-org","utf8",".org"); diff --git a/tiddlers/plugin-org-gtd-editor-mapping.tid b/tiddlers/plugin-org-gtd-editor-mapping.tid deleted file mode 100644 index 251f966..0000000 --- a/tiddlers/plugin-org-gtd-editor-mapping.tid +++ /dev/null @@ -1,2 +0,0 @@ -title: $:/config/EditorTypeMappings/text/x-org -text: codemirror diff --git a/tiddlers/plugin-org-gtd-library.tid b/tiddlers/plugin-org-gtd-library.tid deleted file mode 100644 index af41028..0000000 --- a/tiddlers/plugin-org-gtd-library.tid +++ /dev/null @@ -1,141 +0,0 @@ -title: $:/plugins/local/org-gtd/library.js -type: application/javascript -module-type: library - -/*\ -title: $:/plugins/local/org-gtd/library.js -type: application/javascript -module-type: library -\*/ -(function() { -"use strict"; - -var TODO_STATES = ["TODO","NEXT","WAIT","DONE"], - TODO_LOOKUP = Object.create(null), - HEADING_PATTERN = /^(\*+)\s+(?:(TODO|NEXT|WAIT|DONE)\s+)?(.*)$/, - TAG_PATTERN = /\s+:([A-Za-z0-9_@#%+.-]+(?::[A-Za-z0-9_@#%+.-]+)*):\s*$/, - DATE_PATTERN = /\b(SCHEDULED|DEADLINE):\s*<([0-9]{4}-[0-9]{2}-[0-9]{2})(?:\s+[^>]*)?>/g; - -TODO_STATES.forEach(function(state) { - TODO_LOOKUP[state] = true; -}); - -function parseHeading(line,lineIndex) { - var match = HEADING_PATTERN.exec(line), - tagMatch, - title, - tags = []; - if(!match) { - return null; - } - title = match[3]; - tagMatch = TAG_PATTERN.exec(title); - if(tagMatch) { - tags = tagMatch[1].split(":"); - title = title.substring(0,tagMatch.index).trim(); - } - return { - level: match[1].length, - state: match[2] || "", - title: title, - tags: tags, - line: lineIndex, - sourceLine: lineIndex + 1, - endLine: lineIndex, - scheduled: "", - deadline: "" - }; -} - -function parse(text) { - var lines = (text || "").split("\n"), - headings = [], - current = null; - lines.forEach(function(line,index) { - var heading = parseHeading(line,index), - match; - if(heading) { - if(current) { - current.endLine = index - 1; - } - headings.push(heading); - current = heading; - return; - } - if(current) { - DATE_PATTERN.lastIndex = 0; - while((match = DATE_PATTERN.exec(line)) !== null) { - if(match[1] === "SCHEDULED") { - current.scheduled = match[2]; - } else { - current.deadline = match[2]; - } - } - } - }); - if(current) { - current.endLine = lines.length - 1; - } - for(var index = 0; index < headings.length; index++) { - var heading = headings[index], - nextIndex = index + 1; - while(nextIndex < headings.length && headings[nextIndex].level > heading.level) { - nextIndex++; - } - heading.subtreeEndLine = nextIndex < headings.length ? headings[nextIndex].line - 1 : lines.length - 1; - } - return {lines: lines,headings: headings}; -} - -function headingAtLine(parsed,line) { - var result = null; - parsed.headings.forEach(function(heading) { - if(heading.line <= line && heading.subtreeEndLine >= line) { - if(!result || heading.level >= result.level) { - result = heading; - } - } - }); - return result; -} - -function cycleState(line) { - var heading = parseHeading(line,0), - stars, - nextState, - rest; - if(!heading) { - return null; - } - stars = new Array(heading.level + 1).join("*"); - if(heading.state) { - nextState = TODO_STATES[(TODO_STATES.indexOf(heading.state) + 1) % TODO_STATES.length]; - rest = line.replace(/^(\*+)\s+(?:TODO|NEXT|WAIT|DONE)\s+/,"$1 " + nextState + " "); - } else { - rest = line.replace(/^(\*+)\s+/,"$1 TODO "); - } - return {line: rest,state: nextState || "TODO",level: stars.length}; -} - -function dateToTime(value) { - var match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value || ""), - date; - if(!match) { - return NaN; - } - date = new Date(Number(match[1]),Number(match[2]) - 1,Number(match[3])); - if(date.getFullYear() !== Number(match[1]) || date.getMonth() !== Number(match[2]) - 1 || date.getDate() !== Number(match[3])) { - return NaN; - } - return date.getTime(); -} - -exports.TODO_STATES = TODO_STATES; -exports.TODO_LOOKUP = TODO_LOOKUP; -exports.parseHeading = parseHeading; -exports.parse = parse; -exports.headingAtLine = headingAtLine; -exports.cycleState = cycleState; -exports.dateToTime = dateToTime; - -})(); diff --git a/tiddlers/plugin-org-gtd-parser.tid b/tiddlers/plugin-org-gtd-parser.tid deleted file mode 100644 index 4aa4bd4..0000000 --- a/tiddlers/plugin-org-gtd-parser.tid +++ /dev/null @@ -1,76 +0,0 @@ -title: $:/plugins/local/org-gtd/parser.js -type: application/javascript -module-type: parser - -/*\ -title: $:/plugins/local/org-gtd/parser.js -type: application/javascript -module-type: parser -\*/ -(function() { -"use strict"; - -var org = require("$:/plugins/local/org-gtd/library.js"); - -function attr(value) { - return {type: "string",value: String(value)}; -} - -function text(value) { - return {type: "text",text: String(value)}; -} - -function element(tag,className,children,attributes) { - var attrs = attributes || {}; - if(className) { - attrs["class"] = attr(className); - } - return { - type: "element", - tag: tag, - attributes: attrs, - children: children || [], - isBlock: tag !== "span" - }; -} - -function headingNode(heading) { - var children = [], - level = Math.min(heading.level,6), - classes = "tc-org-heading tc-org-level-" + heading.level; - if(heading.state) { - classes += " tc-org-heading-" + heading.state.toLowerCase(); - children.push(element("span","tc-org-state tc-org-state-" + heading.state.toLowerCase(),[text(heading.state)])); - children.push(text(" ")); - } - children.push(text(heading.title)); - if(heading.tags.length) { - children.push(text(" ")); - children.push(element("span","tc-org-tags",[text(":" + heading.tags.join(":") + ":")])); - } - return element("h" + level,classes,children,{"data-org-line": attr(heading.sourceLine)}); -} - -function OrgParser(type,source,options) { - var parsed = org.parse(source || ""), - headingsByLine = Object.create(null), - tree = []; - parsed.headings.forEach(function(heading) { - headingsByLine[heading.line] = heading; - }); - parsed.lines.forEach(function(line,index) { - var heading = headingsByLine[index]; - if(heading) { - tree.push(headingNode(heading)); - } else if(/^\s*(?:SCHEDULED|DEADLINE):/.test(line)) { - tree.push(element("div","tc-org-planning",[text(line.trim())])); - } else if(line.trim()) { - tree.push(element("div","tc-org-body-line",[text(line)])); - } - }); - this.tree = options.parseAsInline ? [text(source || "")] : [element("div","tc-org-document",tree)]; -} - -exports["text/x-org"] = OrgParser; - -})(); diff --git a/tiddlers/plugin-org-gtd-sidebar.tid b/tiddlers/plugin-org-gtd-sidebar.tid deleted file mode 100644 index 467f946..0000000 --- a/tiddlers/plugin-org-gtd-sidebar.tid +++ /dev/null @@ -1,13 +0,0 @@ -caption: GTD -list-after: 🍀 -tags: $:/tags/SideBar -title: $:/plugins/local/org-gtd/sidebar -type: text/vnd.tiddlywiki - -<div class="tc-sidebar-tab-open"> -<$link to="GTD Agenda">打开 Agenda</$link> - -<$list filter="[type[text/x-org]!has[draft.of]sort[title]]" emptyMessage="还没有 Org 文档。"> -<div class="tc-sidebar-tab-open-item"><$link/></div> -</$list> -</div> diff --git a/tiddlers/plugin-org-gtd-styles.tid b/tiddlers/plugin-org-gtd-styles.tid deleted file mode 100644 index 345a4ee..0000000 --- a/tiddlers/plugin-org-gtd-styles.tid +++ /dev/null @@ -1,159 +0,0 @@ -title: $:/plugins/local/org-gtd/styles -tags: $:/tags/Stylesheet - -.tc-org-document { - --org-todo: #c74b50; - --org-next: <<colour primary>>; - --org-wait: #b27b2f; - --org-done: <<colour muted-foreground>>; -} - -.tc-org-heading { - margin: 1.15rem 0 0.45rem; - line-height: 1.35; -} - -.tc-org-heading-done { - color: <<colour muted-foreground>>; - text-decoration: line-through; -} - -.tc-org-state { - display: inline-block; - padding: 0.1rem 0.4rem; - font-size: 0.72rem; - font-weight: 700; - line-height: 1.25; - letter-spacing: 0.03em; - border: 1px solid currentColor; - border-radius: 0.25rem; -} - -.tc-org-state-todo { color: #c74b50; } -.tc-org-state-next { color: <<colour primary>>; } -.tc-org-state-wait { color: #b27b2f; } -.tc-org-state-done { color: <<colour muted-foreground>>; } - -.tc-org-tags { - color: <<colour muted-foreground>>; - font-size: 0.78em; - font-weight: 500; -} - -.tc-org-planning { - margin: 0.25rem 0 0.25rem 1rem; - color: <<colour muted-foreground>>; - font-family: monospace; - font-size: 0.88em; -} - -.tc-org-body-line { - margin: 0.2rem 0; - white-space: pre-wrap; -} - -.cm-org-gtd-fold-marker { - color: <<colour primary>>; - cursor: pointer; - font-weight: 700; -} - -.tc-org-agenda-toolbar { - display: flex; - justify-content: flex-end; - margin-bottom: 1rem; -} - -.tc-org-agenda-new { - padding: 0.45rem 0.8rem; - color: <<colour background>>; - background: <<colour primary>>; - border-radius: 0.35rem; -} - -.tc-org-agenda-section { - margin: 0 0 1.5rem; - border-top: 2px solid <<colour tiddler-border>>; -} - -.tc-org-agenda-overdue { - border-top-color: #c74b50; -} - -.tc-org-agenda-today { - border-top-color: <<colour primary>>; -} - -.tc-org-agenda-section-title { - display: flex; - align-items: center; - gap: 0.5rem; - margin: 0; - padding: 0.6rem 0 0.45rem; - font-size: 1rem; -} - -.tc-org-agenda-count { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 1.45rem; - height: 1.45rem; - padding: 0 0.35rem; - color: <<colour muted-foreground>>; - background: <<colour sidebar-tab-background>>; - border-radius: 999px; - font-size: 0.75rem; -} - -.tc-org-agenda-row { - display: grid; - grid-template-columns: auto minmax(8rem,1fr) auto; - align-items: center; - gap: 0.45rem 0.7rem; - padding: 0.5rem 0; - border-bottom: 1px solid <<colour tiddler-border>>; -} - -.tc-org-agenda-task { - min-width: 0; - color: <<colour foreground>>; - font-weight: 600; - text-align: left; -} - -.tc-org-agenda-task:hover, -.tc-org-agenda-task:focus { - color: <<colour primary>>; - text-decoration: underline; -} - -.tc-org-agenda-source, -.tc-org-agenda-meta, -.tc-org-agenda-empty { - color: <<colour muted-foreground>>; - font-size: 0.82rem; -} - -.tc-org-agenda-meta { - grid-column: 2 / -1; -} - -.tc-org-agenda-empty { - margin: 0; - padding: 0.6rem 0; -} - -@media (max-width: 600px) { - .tc-org-agenda-row { - grid-template-columns: auto minmax(0,1fr); - } - - .tc-org-agenda-source { - grid-column: 2; - } - - .tc-org-agenda-meta { - grid-column: 2; - } -} diff --git a/tiddlers/tiddlywiki.tid b/tiddlers/tiddlywiki.tid index 9c209ee..8a108a6 100644 --- a/tiddlers/tiddlywiki.tid +++ b/tiddlers/tiddlywiki.tid @@ -6,7 +6,7 @@ type: text/vnd.tiddlywiki ! 新增一个语法高亮 -增加一个 [[tiddlers/highlight-tiddlywiki.tid|$:/plugins/local/highlight-languages/tiddlywiki.js]] 文件,重启服务就可以了 +源码位于 [[plugins/utilities/highlight-tiddlywiki.tid|$:/plugins/local/highlight-languages/tiddlywiki.js]],由 `$:/plugins/local/utilities` 插件统一打包。 <details> <summary>点击展开文件内容</summary> @@ -60,7 +60,7 @@ list-after: $:/core/ui/SideBar/More ! 插件 - 代码复制 -在代码块右上角展示一个复制按钮,插件内容保存在 [[tiddlers/copy-codeblock.tid|$:/plugins/local/copy-codeblock/copy-codeblock.js]] +在代码块右上角展示一个复制按钮,源码位于 [[plugins/utilities/copy-codeblock-widget.tid|$:/plugins/local/copy-codeblock/copy-codeblock.js]]。 <details> <summary>点击展开文件内容</summary> @@ -71,42 +71,6 @@ list-after: $:/core/ui/SideBar/More ! 资源文件管理 -图片文件可以和条目文件分开保存,可以和 html 分开加载 - -资源文件可以放置在 files 文件夹中,通过 ./files/... 的方式引用 - -资源文件的上传下载,用 webdav 好了 - -增加索取所有文件的接口,写入到 tiddlers/files-list-api.tid 文件中 - -<details> -<summary>点击展开文件内容</summary> - -{{files-list-api.tid}} - -</details> - -在 wiki 中获取文件列表 - -```tid -\procedure store-file-list() -<$action-setfield - $tiddler="$:/temp/files-list" - text=<<data>> - type="application/json" -/> -\end - -<$button> - <$action-sendmessage - $message="tm-http-request" - url="./api/files" - method="GET" - oncompletion=<<store-file-list>> - /> - 刷新文件列表 -</$button> - -<pre><$text text={{$:/temp/files-list}}/></pre> -``` +图片文件可以和条目文件分开保存,也可以和 HTML 分开加载。 +资源文件可以放在 `files/` 文件夹中,通过 `./files/...` 引用。 diff --git a/tiddlers/日历.tid b/tiddlers/日历.tid index 0b11acb..ce84a8b 100644 --- a/tiddlers/日历.tid +++ b/tiddlers/日历.tid @@ -9,13 +9,44 @@ type: text/vnd.tiddlywiki width: 100%; } +.tc-journal-calendar-toolbar { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 0.75rem 1rem; + margin-bottom: 1rem; +} + +.tc-journal-calendar-month-summary { + display: flex; + flex-wrap: wrap; + flex: 1 1 auto; + align-items: center; + gap: 0.5rem 1rem; + min-width: 0; +} + +.tc-journal-calendar-current-month { + margin: 0; + white-space: nowrap; +} + .tc-journal-calendar-view-switcher, .tc-journal-calendar-month-controls { display: flex; - flex-wrap: wrap; - justify-content: center; + flex-wrap: nowrap; gap: 0.5rem; - margin-bottom: 1rem; +} + +.tc-journal-calendar-view-switcher { + flex: 0 0 auto; + justify-content: flex-end; + margin-left: auto; +} + +.tc-journal-calendar-month-controls { + justify-content: flex-start; } .tc-journal-calendar-switch, @@ -34,9 +65,30 @@ type: text/vnd.tiddlywiki margin-bottom: 2rem; } +.tc-journal-calendar-month-header { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 0.5rem 1rem; + margin-bottom: 0.75rem; +} + .tc-journal-calendar-month-title { - margin: 0 0 0.75rem; - text-align: center; + margin: 0; +} + +.tc-journal-calendar-expand { + margin-left: auto; + padding: 0.35rem 0.8rem; + border: 1px solid <<colour tiddler-border>>; + border-radius: 999px; +} + +.tc-journal-calendar-expand:hover, +.tc-journal-calendar-expand:focus { + color: <<colour background>>; + background: <<colour primary>>; } .tc-journal-calendar-viewport { @@ -101,6 +153,10 @@ type: text/vnd.tiddlywiki color: <<colour primary>>; } +.tc-journal-calendar-day-today .tc-journal-calendar-day-link { + color: <<colour background>>; +} + .tc-journal-calendar-day-number { margin-bottom: 0.35rem; font-weight: 600; @@ -133,7 +189,14 @@ type: text/vnd.tiddlywiki } @media (max-width: 600px) { + .tc-journal-calendar-month-summary { + flex: 1 1 100%; + flex-wrap: wrap; + } + .tc-journal-calendar-view-switcher { + flex: 1 1 100%; + margin-left: 0; display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 0.35rem; @@ -159,6 +222,10 @@ type: text/vnd.tiddlywiki font-size: 1.25rem; } + .tc-journal-calendar-expand { + min-height: 2.75rem; + } + .tc-journal-calendar-viewport { overflow-x: visible; } diff --git a/tiddlywiki.info b/tiddlywiki.info index e11cfa9..3cbb6c7 100644 --- a/tiddlywiki.info +++ b/tiddlywiki.info @@ -1,5 +1,5 @@ { - "description": "Full edition, containing all languages, themes and plugins", + "description": "Personal TiddlyWiki for single-file builds and Node.js directory mode", "plugins": [ "tiddlywiki/filesystem", "tiddlywiki/tiddlyweb", @@ -77,11 +77,11 @@ "tiddlywiki/starlight" ], "build": { - "index": [ + "singlefile": [ "--rendertiddler", "$:/core/save/all", - "index.html", + "tdwiki.html", "text/plain" ] } -}
\ No newline at end of file +} |
