aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorshu <nvcsadjkojf@outlook.com>2026-08-19 15:56:06 +0800
committershu <nvcsadjkojf@outlook.com>2026-08-19 15:56:06 +0800
commitca1a4a31706d0c03673fe4c414c645ed826b21c6 (patch)
treec412265a19dd4694c39aac65b6e1a8903a513978
parent490d41f154615a52734f117d7d7bda5a9331e303 (diff)
精简 Wiki 构建与运行方式
将浏览器端工具整理为 utilities 插件,移除 Docker、Org GTD 和 files API,并补充单文件构建及 Node.js 运行入口。
-rw-r--r--.env.example5
-rw-r--r--.gitignore7
-rw-r--r--DOCKER.md162
-rw-r--r--README.md49
-rw-r--r--compose.yaml100
-rw-r--r--docker/authelia/configuration.template.yml66
-rw-r--r--docker/authelia/users_database.template.yml8
-rw-r--r--docker/caddy/Caddyfile44
-rw-r--r--docker/caddy/Dockerfile11
-rw-r--r--docker/tiddlywiki/Dockerfile13
-rw-r--r--files/photo_2026-07-26_11-10-12.jpgbin89573 -> 0 bytes
-rw-r--r--plugins/utilities/copy-codeblock-widget.tid (renamed from tiddlers/plugin-copy-codeblock-widget.tid)0
-rw-r--r--plugins/utilities/current-tiddler-toc-sidebar.tid (renamed from tiddlers/plugin-current-tiddler-toc-sidebar.tid)0
-rw-r--r--plugins/utilities/current-tiddler-toc-widget.tid (renamed from tiddlers/plugin-current-tiddler-toc-widget.tid)0
-rw-r--r--plugins/utilities/highlight-caddyfile.tid (renamed from tiddlers/plugin-highlight-caddyfile.tid)0
-rw-r--r--plugins/utilities/highlight-json5.tid (renamed from tiddlers/plugin-highlight-json5.tid)0
-rw-r--r--plugins/utilities/highlight-tiddlywiki.tid (renamed from tiddlers/plugin-highlight-tiddlywiki.tid)0
-rw-r--r--plugins/utilities/journal-calendar-widget.tid (renamed from tiddlers/plugin-journal-calendar-widget.tid)112
-rw-r--r--plugins/utilities/plugin.info11
-rw-r--r--plugins/utilities/readme.tid8
-rwxr-xr-xscripts/build.sh66
-rwxr-xr-xscripts/dev.sh3
-rwxr-xr-xscripts/init-docker.sh157
-rwxr-xr-xscripts/run.sh16
-rw-r--r--tiddlers/$__StoryList.tid3
-rw-r--r--tiddlers/$__StoryList_2.tid0
-rw-r--r--tiddlers/$__config_org-gtd_extraKeys.json8
-rw-r--r--tiddlers/$__config_org-gtd_extraKeys.json.meta4
-rw-r--r--tiddlers/GTD Agenda.tid5
-rw-r--r--tiddlers/GTD Inbox.org5
-rw-r--r--tiddlers/GTD Inbox.org.meta4
-rw-r--r--tiddlers/Org GTD 使用说明.tid46
-rw-r--r--tiddlers/files API demo.tid51
-rw-r--r--tiddlers/files-list-api.tid.js.meta5
-rw-r--r--tiddlers/plugin-files-api-route.tid132
-rw-r--r--tiddlers/plugin-org-gtd-agenda-widget.tid229
-rw-r--r--tiddlers/plugin-org-gtd-codemirror.tid341
-rw-r--r--tiddlers/plugin-org-gtd-content-type.tid12
-rw-r--r--tiddlers/plugin-org-gtd-editor-mapping.tid2
-rw-r--r--tiddlers/plugin-org-gtd-library.tid141
-rw-r--r--tiddlers/plugin-org-gtd-parser.tid76
-rw-r--r--tiddlers/plugin-org-gtd-sidebar.tid13
-rw-r--r--tiddlers/plugin-org-gtd-styles.tid159
-rw-r--r--tiddlers/tiddlywiki.tid44
-rw-r--r--tiddlers/日历.tid77
-rw-r--r--tiddlywiki.info8
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
diff --git a/.gitignore b/.gitignore
index fc56b0d..869d886 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
deleted file mode 100644
index 7ddab14..0000000
--- a/files/photo_2026-07-26_11-10-12.jpg
+++ /dev/null
Binary files differ
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
+}