aboutsummaryrefslogtreecommitdiffstats
path: root/tiddlers/plugin-org-gtd-agenda-widget.tid
diff options
context:
space:
mode:
Diffstat (limited to 'tiddlers/plugin-org-gtd-agenda-widget.tid')
-rw-r--r--tiddlers/plugin-org-gtd-agenda-widget.tid229
1 files changed, 229 insertions, 0 deletions
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;
+
+})();