diff options
| author | shu <nvcsadjkojf@outlook.com> | 2026-07-28 19:16:16 +0800 |
|---|---|---|
| committer | shu <nvcsadjkojf@outlook.com> | 2026-07-28 19:16:16 +0800 |
| commit | 8efba22ed34b6eeb28b5f410177e5a1545b5d798 (patch) | |
| tree | b4e751ec9a0940659bc50651974e96c57f6a66d8 /tiddlers/plugin-org-gtd-codemirror.tid | |
| parent | 1e2031e8850fa91ed9e3b5cd11f7a5b01bbd999e (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.tid | 341 |
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); +}); + +})(); |
