From 8efba22ed34b6eeb28b5f410177e5a1545b5d798 Mon Sep 17 00:00:00 2001 From: shu Date: Tue, 28 Jul 2026 19:16:16 +0800 Subject: 用 Org GTD 工作流替换事情看板 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 注册 text/x-org 内容类型和 CodeMirror 编辑器,支持 TODO 状态流转、层级调整、子树移动与折叠。 新增 Org 文档解析、Agenda 日期分组、源任务定位、侧栏入口和响应式样式,并补充中文使用说明。 快捷键改为仅在 Org 编辑器中按配置加载,默认不绑定;无效 JSON 或键名只禁用自定义键位,不影响编辑器启动。 移除旧事情看板的字段模板、组件、样式和默认入口,统一使用 Org GTD 工作流。 --- tiddlers/plugin-org-gtd-agenda-widget.tid | 229 ++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 tiddlers/plugin-org-gtd-agenda-widget.tid (limited to 'tiddlers/plugin-org-gtd-agenda-widget.tid') diff --git a/tiddlers/plugin-org-gtd-agenda-widget.tid b/tiddlers/plugin-org-gtd-agenda-widget.tid new file mode 100644 index 0000000..4749564 --- /dev/null +++ b/tiddlers/plugin-org-gtd-agenda-widget.tid @@ -0,0 +1,229 @@ +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; + +})(); -- cgit v1.2.3