aboutsummaryrefslogtreecommitdiffstats
path: root/tiddlers/plugin-org-gtd-codemirror.tid
diff options
context:
space:
mode:
authorshu <nvcsadjkojf@outlook.com>2026-07-28 19:16:16 +0800
committershu <nvcsadjkojf@outlook.com>2026-07-28 19:16:16 +0800
commit8efba22ed34b6eeb28b5f410177e5a1545b5d798 (patch)
treeb4e751ec9a0940659bc50651974e96c57f6a66d8 /tiddlers/plugin-org-gtd-codemirror.tid
parent1e2031e8850fa91ed9e3b5cd11f7a5b01bbd999e (diff)
用 Org GTD 工作流替换事情看板
注册 text/x-org 内容类型和 CodeMirror 编辑器,支持 TODO 状态流转、层级调整、子树移动与折叠。 新增 Org 文档解析、Agenda 日期分组、源任务定位、侧栏入口和响应式样式,并补充中文使用说明。 快捷键改为仅在 Org 编辑器中按配置加载,默认不绑定;无效 JSON 或键名只禁用自定义键位,不影响编辑器启动。 移除旧事情看板的字段模板、组件、样式和默认入口,统一使用 Org GTD 工作流。
Diffstat (limited to 'tiddlers/plugin-org-gtd-codemirror.tid')
-rw-r--r--tiddlers/plugin-org-gtd-codemirror.tid341
1 files changed, 341 insertions, 0 deletions
diff --git a/tiddlers/plugin-org-gtd-codemirror.tid b/tiddlers/plugin-org-gtd-codemirror.tid
new file mode 100644
index 0000000..09b3e6c
--- /dev/null
+++ b/tiddlers/plugin-org-gtd-codemirror.tid
@@ -0,0 +1,341 @@
+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);
+});
+
+})();