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-task-board-widget.tid | 729 ---------------------------------- 1 file changed, 729 deletions(-) delete mode 100644 tiddlers/plugin-task-board-widget.tid (limited to 'tiddlers/plugin-task-board-widget.tid') diff --git a/tiddlers/plugin-task-board-widget.tid b/tiddlers/plugin-task-board-widget.tid deleted file mode 100644 index 397811b..0000000 --- a/tiddlers/plugin-task-board-widget.tid +++ /dev/null @@ -1,729 +0,0 @@ -title: $:/plugins/local/task-board/widget.js -type: application/javascript -module-type: widget - -/*\ -title: $:/plugins/local/task-board/widget.js -type: application/javascript -module-type: widget - -Task board with kanban, list, calendar, and Gantt views. -\*/ -(function() { -"use strict"; - -var Widget = require("$:/core/modules/widgets/widget.js").widget, - VIEW_STATE_TITLE = "$:/state/task-board/view", - MONTH_STATE_TITLE = "$:/state/task-board/month", - DAY_MS = 24 * 60 * 60 * 1000, - DAY_WIDTH = 30, - STATUSES = ["待处理","进行中","等待","完成"], - PRIORITIES = ["P1","P2","P3"], - VIEW_LABELS = { - board: "看板", - list: "列表", - calendar: "日历", - gantt: "甘特图" - }; - -var TaskBoardWidget = function(parseTreeNode,options) { - this.initialise(parseTreeNode,options); -}; - -TaskBoardWidget.prototype = new Widget(); -TaskBoardWidget.prototype.constructor = TaskBoardWidget; - -function pad(value) { - return value < 10 ? "0" + value : "" + value; -} - -function todayKey() { - var now = new Date(); - return now.getFullYear() + "-" + pad(now.getMonth() + 1) + "-" + pad(now.getDate()); -} - -function monthKey(date) { - return date.getUTCFullYear() + "-" + pad(date.getUTCMonth() + 1); -} - -function parseDate(value) { - var match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value || ""), - date; - if(!match) { - return null; - } - date = new Date(0); - date.setUTCHours(0,0,0,0); - date.setUTCFullYear(Number(match[1]),Number(match[2]) - 1,Number(match[3])); - if(date.getUTCFullYear() !== Number(match[1]) || date.getUTCMonth() !== Number(match[2]) - 1 || date.getUTCDate() !== Number(match[3])) { - return null; - } - return date; -} - -function dateKey(date) { - return date.getUTCFullYear() + "-" + pad(date.getUTCMonth() + 1) + "-" + pad(date.getUTCDate()); -} - -function dayNumber(date) { - return Math.floor(date.getTime() / DAY_MS); -} - -function addDays(date,amount) { - return new Date(date.getTime() + amount * DAY_MS); -} - -function isValidMonth(value) { - var match = /^(\d{4})-(\d{2})$/.exec(value || ""); - return !!match && Number(match[2]) >= 1 && Number(match[2]) <= 12; -} - -function priorityRank(priority) { - var rank = PRIORITIES.indexOf(priority); - return rank === -1 ? PRIORITIES.length : rank; -} - -function compareTasks(left,right) { - var priorityDifference = priorityRank(left.effectivePriority) - priorityRank(right.effectivePriority); - if(priorityDifference) { - return priorityDifference; - } - var leftDue = left.dueDate ? dayNumber(left.dueDate) : Infinity, - rightDue = right.dueDate ? dayNumber(right.dueDate) : Infinity; - if(leftDue !== rightDue) { - return leftDue - rightDue; - } - return left.title.localeCompare(right.title); -} - -TaskBoardWidget.prototype.render = function(parent,nextSibling) { - this.parentDomNode = parent; - this.computeAttributes(); - this.execute(); - - this.containerNode = this.document.createElement("div"); - this.containerNode.className = "tc-task-board"; - parent.insertBefore(this.containerNode,nextSibling); - this.domNodes.push(this.containerNode); - this.renderBoard(); -}; - -TaskBoardWidget.prototype.execute = function() { - this.tag = this.getAttribute("tag","事情"); -}; - -TaskBoardWidget.prototype.createElement = function(tagName,className,text) { - var node = this.document.createElement(tagName); - if(className) { - node.className = className; - } - if(text !== undefined && text !== null) { - node.textContent = text; - } - return node; -}; - -TaskBoardWidget.prototype.createButton = function(label,className,handler) { - var button = this.createElement("button",className,label); - button.setAttribute("type","button"); - button.addEventListener("click",handler); - return button; -}; - -TaskBoardWidget.prototype.setState = function(title,value) { - this.wiki.addTiddler({title: title,text: value}); -}; - -TaskBoardWidget.prototype.getView = function() { - var view = this.wiki.getTiddlerText(VIEW_STATE_TITLE,"board"); - return VIEW_LABELS[view] ? view : "board"; -}; - -TaskBoardWidget.prototype.getMonth = function() { - var value = this.wiki.getTiddlerText(MONTH_STATE_TITLE,""); - if(isValidMonth(value)) { - return value; - } - var today = parseDate(todayKey()); - return monthKey(today); -}; - -TaskBoardWidget.prototype.getTasks = function() { - var self = this, - tasks = []; - - this.wiki.getTiddlersWithTag(this.tag).forEach(function(title) { - var tiddler = self.wiki.getTiddler(title); - if(!tiddler || tiddler.fields["draft.of"]) { - return; - } - var fields = tiddler.fields, - start = fields.start || "", - end = fields.end || "", - due = fields.due || "", - startDate = parseDate(start), - endDate = parseDate(end), - dueDate = parseDate(due), - issues = [], - rangeError, - dateError, - calendarDates = []; - - if(start && !startDate) { - issues.push("开始日期无效"); - } - if(end && !endDate) { - issues.push("结束日期无效"); - } - if(due && !dueDate) { - issues.push("截止日期无效"); - } - if(startDate && endDate && endDate < startDate) { - issues.push("结束日期早于开始日期"); - } - dateError = !!((start && !startDate) || (end && !endDate) || (due && !dueDate)); - rangeError = !!((start && !startDate) || (end && !endDate) || (startDate && endDate && endDate < startDate)); - [ - {type: "start",label: "开始",date: startDate}, - {type: "end",label: "结束",date: endDate}, - {type: "due",label: "截止",date: dueDate} - ].forEach(function(item) { - if(item.date) { - calendarDates.push(item); - } - }); - calendarDates.sort(function(left,right) { - return dayNumber(left.date) - dayNumber(right.date); - }); - if(STATUSES.indexOf(fields.status) === -1) { - issues.push("状态无效"); - } - if(PRIORITIES.indexOf(fields.priority) === -1) { - issues.push("优先级无效"); - } - - tasks.push({ - title: title, - status: fields.status || "", - effectiveStatus: STATUSES.indexOf(fields.status) === -1 ? STATUSES[0] : fields.status, - priority: fields.priority || "", - effectivePriority: PRIORITIES.indexOf(fields.priority) === -1 ? "P2" : fields.priority, - start: start, - end: end, - due: due, - startDate: startDate, - endDate: endDate, - dueDate: dueDate, - rangeValid: !!startDate && !rangeError, - rangeError: rangeError, - dateError: dateError, - calendarDates: calendarDates, - tags: (fields.tags || []).filter(function(tag) {return tag !== self.tag;}), - issues: issues - }); - }); - - return tasks.sort(compareTasks); -}; - -TaskBoardWidget.prototype.navigateTo = function(title,node,event) { - var bounds = node.getBoundingClientRect(); - this.dispatchEvent({ - type: "tm-navigate", - navigateTo: title, - navigateFromTitle: this.getVariable("storyTiddler"), - navigateFromNode: this, - navigateFromClientRect: { - top: bounds.top, - left: bounds.left, - width: bounds.width, - right: bounds.right, - bottom: bounds.bottom, - height: bounds.height - }, - navigateSuppressNavigation: event && (event.metaKey || event.ctrlKey || event.button === 1) - }); -}; - -TaskBoardWidget.prototype.createTaskLink = function(task,className,label) { - var self = this, - button = this.createButton(label !== undefined ? label : task.title,"tc-btn-invisible " + className,function(event) { - self.navigateTo(task.title,event.currentTarget,event); - }); - button.title = "打开事情:" + task.title; - button.setAttribute("aria-label","打开事情:" + task.title); - return button; -}; - -TaskBoardWidget.prototype.renderBoard = function() { - var tasks = this.getTasks(), - view = this.getView(); - this.renderToolbar(view); - if(view === "list") { - this.renderList(tasks); - } else if(view === "calendar") { - this.renderCalendar(tasks); - } else if(view === "gantt") { - this.renderGantt(tasks); - } else { - this.renderKanban(tasks); - } -}; - -TaskBoardWidget.prototype.renderToolbar = function(currentView) { - var self = this, - toolbar = this.createElement("div","tc-task-board-toolbar"), - switcher = this.createElement("div","tc-task-board-switcher"); - - toolbar.appendChild(this.createButton("+ 新建事情","tc-btn-invisible tc-task-board-new",function() { - self.dispatchEvent({ - type: "tm-new-tiddler", - paramObject: { - tags: $tw.utils.stringifyList([self.tag]), - priority: "P2", - status: "待处理", - start: todayKey() - }, - navigateFromTitle: self.getVariable("storyTiddler") - }); - })); - - Object.keys(VIEW_LABELS).forEach(function(view) { - var button = self.createButton(VIEW_LABELS[view],"tc-btn-invisible tc-task-board-view-button" + (view === currentView ? " tc-selected" : ""),function() { - self.setState(VIEW_STATE_TITLE,view); - }); - button.setAttribute("aria-pressed",view === currentView ? "true" : "false"); - switcher.appendChild(button); - }); - toolbar.appendChild(switcher); - this.containerNode.appendChild(toolbar); -}; - -TaskBoardWidget.prototype.renderPriority = function(task) { - var badge = this.createElement("span","tc-task-priority tc-task-priority-" + task.effectivePriority.toLowerCase(),task.priority || "未设置"); - badge.title = "优先级"; - return badge; -}; - -TaskBoardWidget.prototype.renderIssues = function(task,parent) { - if(task.issues.length) { - var issue = this.createElement("span","tc-task-issues","⚠ " + task.issues.join(";")); - issue.title = task.issues.join(";"); - parent.appendChild(issue); - } -}; - -TaskBoardWidget.prototype.renderTaskDates = function(task,parent) { - var values = []; - if(task.start) {values.push("开始 " + task.start);} - if(task.end) {values.push("结束 " + task.end);} - if(task.due) {values.push("截止 " + task.due);} - parent.appendChild(this.createElement("div","tc-task-card-dates",values.length ? values.join(" · ") : "未排期")); -}; - -TaskBoardWidget.prototype.updateTaskStatus = function(title,status) { - var tiddler = this.wiki.getTiddler(title); - if(tiddler && !tiddler.fields["draft.of"] && (tiddler.fields.tags || []).indexOf(this.tag) !== -1 && STATUSES.indexOf(status) !== -1) { - this.wiki.addTiddler(new $tw.Tiddler(tiddler,{status: status},this.wiki.getModificationFields())); - } -}; - -TaskBoardWidget.prototype.renderKanban = function(tasks) { - var self = this, - board = this.createElement("div","tc-task-kanban"); - - STATUSES.forEach(function(status) { - var column = self.createElement("section","tc-task-kanban-column tc-task-status-" + STATUSES.indexOf(status)), - columnTasks = tasks.filter(function(task) {return task.effectiveStatus === status;}), - heading = self.createElement("h2","tc-task-kanban-heading",status + " · " + columnTasks.length), - cards = self.createElement("div","tc-task-kanban-cards"); - - column.setAttribute("data-task-status",status); - column.addEventListener("dragover",function(event) { - event.preventDefault(); - column.classList.add("tc-drag-over"); - }); - column.addEventListener("dragleave",function() { - column.classList.remove("tc-drag-over"); - }); - column.addEventListener("drop",function(event) { - event.preventDefault(); - column.classList.remove("tc-drag-over"); - var title = self.draggedTaskTitle; - if(title) { - self.updateTaskStatus(title,status); - } - self.draggedTaskTitle = null; - }); - - column.appendChild(heading); - columnTasks.forEach(function(task) { - var card = self.createElement("article","tc-task-card"); - card.setAttribute("draggable","true"); - card.setAttribute("data-task-title",task.title); - card.addEventListener("dragstart",function(event) { - self.draggedTaskTitle = task.title; - if(event.dataTransfer) { - event.dataTransfer.setData("application/x-tiddlywiki-task",task.title); - event.dataTransfer.effectAllowed = "move"; - } - card.classList.add("tc-dragging"); - }); - card.addEventListener("dragend",function() { - self.draggedTaskTitle = null; - card.classList.remove("tc-dragging"); - }); - var cardTop = self.createElement("div","tc-task-card-top"); - cardTop.appendChild(self.createTaskLink(task,"tc-task-card-title")); - cardTop.appendChild(self.renderPriority(task)); - card.appendChild(cardTop); - self.renderTaskDates(task,card); - if(task.tags.length) { - card.appendChild(self.createElement("div","tc-task-card-tags",task.tags.join(" · "))); - } - self.renderIssues(task,card); - cards.appendChild(card); - }); - if(!columnTasks.length) { - cards.appendChild(self.createElement("div","tc-task-empty-column","暂无事情")); - } - column.appendChild(cards); - board.appendChild(column); - }); - this.containerNode.appendChild(board); -}; - -TaskBoardWidget.prototype.renderList = function(tasks) { - var self = this, - wrapper = this.createElement("div","tc-task-list-wrapper"), - table = this.createElement("table","tc-task-list"), - thead = this.createElement("thead"), - headerRow = this.createElement("tr"), - tbody = this.createElement("tbody"); - - ["事情","优先级","状态","开始","结束","截止"].forEach(function(label) { - headerRow.appendChild(self.createElement("th","",label)); - }); - thead.appendChild(headerRow); - table.appendChild(thead); - - tasks.forEach(function(task) { - var row = self.createElement("tr","tc-task-list-row" + (task.issues.length ? " tc-task-invalid" : "")), - titleCell = self.createElement("td","tc-task-list-title"); - titleCell.setAttribute("data-label","事情"); - titleCell.appendChild(self.createTaskLink(task,"tc-task-list-link")); - self.renderIssues(task,titleCell); - row.appendChild(titleCell); - [ - ["优先级",task.priority || "-"], - ["状态",task.status || "-"], - ["开始",task.start || "-"], - ["结束",task.end || "-"], - ["截止",task.due || "-"] - ].forEach(function(item) { - var cell = self.createElement("td","",item[1]); - cell.setAttribute("data-label",item[0]); - row.appendChild(cell); - }); - tbody.appendChild(row); - }); - - if(!tasks.length) { - var emptyRow = this.createElement("tr"), - emptyCell = this.createElement("td","tc-task-empty","还没有事情"); - emptyCell.setAttribute("colspan","6"); - emptyRow.appendChild(emptyCell); - tbody.appendChild(emptyRow); - } - - table.appendChild(tbody); - wrapper.appendChild(table); - this.containerNode.appendChild(wrapper); -}; - -TaskBoardWidget.prototype.renderMonthControls = function(value) { - var self = this, - controls = this.createElement("div","tc-task-calendar-controls"), - parts = value.split("-"), - year = Number(parts[0]), - month = Number(parts[1]); - - function move(offset) { - self.setState(MONTH_STATE_TITLE,monthKey(new Date(Date.UTC(year,month - 1 + offset,1)))); - } - - controls.appendChild(this.createButton("上个月","tc-btn-invisible tc-task-month-button",function() {move(-1);})); - controls.appendChild(this.createElement("h2","tc-task-calendar-title",year + "年" + month + "月")); - controls.appendChild(this.createButton("本月","tc-btn-invisible tc-task-month-button",function() { - self.setState(MONTH_STATE_TITLE,monthKey(parseDate(todayKey()))); - })); - controls.appendChild(this.createButton("下个月","tc-btn-invisible tc-task-month-button",function() {move(1);})); - this.containerNode.appendChild(controls); -}; - -TaskBoardWidget.prototype.renderCalendar = function(tasks) { - var self = this, - value = this.getMonth(), - parts = value.split("-"), - year = Number(parts[0]), - month = Number(parts[1]), - firstDate = new Date(Date.UTC(year,month - 1,1)), - firstWeekday = (firstDate.getUTCDay() + 6) % 7, - daysInMonth = new Date(Date.UTC(year,month,0)).getUTCDate(), - calendar = this.createElement("div","tc-task-calendar-grid"), - header = this.createElement("div","tc-task-calendar-header"), - weeks = this.createElement("div","tc-task-calendar-weeks"), - weekdays = ["一","二","三","四","五","六","日"], - unscheduled = tasks.filter(function(task) { - return !task.calendarDates.length || task.dateError || task.rangeError; - }), - today = todayKey(), - monthStart = firstDate, - monthEnd = new Date(Date.UTC(year,month - 1,daysInMonth)), - calendarStart = addDays(firstDate,-firstWeekday), - weekCount = Math.ceil((firstWeekday + daysInMonth) / 7); - - this.renderMonthControls(value); - calendar.setAttribute("role","grid"); - header.setAttribute("role","row"); - weeks.setAttribute("role","rowgroup"); - weekdays.forEach(function(weekday) { - var heading = self.createElement("div","tc-task-calendar-weekday",weekday); - heading.setAttribute("role","columnheader"); - header.appendChild(heading); - }); - calendar.appendChild(header); - - for(var weekIndex = 0; weekIndex < weekCount; weekIndex++) { - var weekStart = addDays(calendarStart,weekIndex * 7), - weekEnd = addDays(weekStart,6), - week = this.createElement("div","tc-task-calendar-week"), - items = [], - lanes = []; - week.setAttribute("role","row"); - - tasks.forEach(function(task) { - if(task.calendarDates.length) { - var firstCalendarDate = task.calendarDates[0].date, - lastCalendarDate = task.calendarDates[task.calendarDates.length - 1].date, - segmentStart = new Date(Math.max(firstCalendarDate.getTime(),monthStart.getTime(),weekStart.getTime())), - segmentEnd = new Date(Math.min(lastCalendarDate.getTime(),monthEnd.getTime(),weekEnd.getTime())); - if(segmentStart <= segmentEnd) { - items.push({ - task: task, - type: task.calendarDates.length === 1 ? "point" : "range", - start: dayNumber(segmentStart) - dayNumber(weekStart) + 1, - end: dayNumber(segmentEnd) - dayNumber(weekStart) + 1, - starts: dateKey(segmentStart) === dateKey(firstCalendarDate), - ends: dateKey(segmentEnd) === dateKey(lastCalendarDate), - dateLabel: task.calendarDates[0].label - }); - } - if(task.calendarDates.length === 3) { - var middle = task.calendarDates[1]; - if(middle.date >= monthStart && middle.date <= monthEnd && middle.date >= weekStart && middle.date <= weekEnd) { - var middleColumn = dayNumber(middle.date) - dayNumber(weekStart) + 1; - items.push({task: task,type: "milestone",start: middleColumn,end: middleColumn,dateLabel: middle.label}); - } - } - } - }); - - items.forEach(function(item) { - var lane = 0; - while(lanes[lane] && lanes[lane].some(function(occupied) { - return item.start <= occupied.end && item.end >= occupied.start; - })) { - lane++; - } - lanes[lane] = lanes[lane] || []; - lanes[lane].push(item); - item.lane = lane; - }); - - week.style.gridTemplateRows = "minmax(2.25rem,1fr) repeat(" + lanes.length + ",1.5rem)"; - for(var column = 0; column < 7; column++) { - var cellDate = addDays(weekStart,column), - inMonth = cellDate >= monthStart && cellDate <= monthEnd, - key = dateKey(cellDate), - cell = self.createElement("div","tc-task-calendar-day" + (!inMonth ? " tc-task-calendar-blank" : "") + (key === today ? " tc-task-calendar-today" : "")); - cell.style.gridColumn = (column + 1) + ""; - cell.style.gridRow = "1 / span " + (lanes.length + 1); - cell.setAttribute("role","gridcell"); - cell.setAttribute("aria-label",key); - if(inMonth) { - cell.appendChild(self.createElement("div","tc-task-calendar-day-number",cellDate.getUTCDate())); - } - week.appendChild(cell); - } - - items.forEach(function(item) { - var className = item.type === "range" ? "tc-task-calendar-range" + (item.starts ? " tc-range-start" : "") + (item.ends ? " tc-range-end" : "") : item.type === "point" ? "tc-task-calendar-point" : "tc-task-calendar-milestone", - label = item.type === "point" || item.type === "milestone" ? item.dateLabel + " · " + item.task.title : item.task.title, - entry = self.createTaskLink(item.task,className,label); - entry.style.gridColumn = item.start + " / " + (item.end + 1); - entry.style.gridRow = (item.lane + 2) + ""; - entry.setAttribute("data-priority",item.task.effectivePriority); - week.appendChild(entry); - }); - weeks.appendChild(week); - } - - calendar.appendChild(weeks); - this.containerNode.appendChild(calendar); - this.renderUnscheduled(unscheduled,"未排期或日期异常"); -}; - -TaskBoardWidget.prototype.renderUnscheduled = function(tasks,label) { - var self = this; - if(!tasks.length) { - return; - } - var section = this.createElement("section","tc-task-unscheduled"), - list = this.createElement("div","tc-task-unscheduled-list"); - section.appendChild(this.createElement("h3","",label + " · " + tasks.length)); - tasks.forEach(function(task) { - list.appendChild(self.createTaskLink(task,"tc-task-unscheduled-link")); - }); - section.appendChild(list); - this.containerNode.appendChild(section); -}; - -TaskBoardWidget.prototype.renderGantt = function(tasks) { - var self = this, - datedTasks = tasks.filter(function(task) {return task.rangeValid || (!task.start && task.endDate) || task.dueDate;}), - unscheduled = tasks.filter(function(task) {return task.rangeError || (!task.rangeValid && !(!task.start && task.endDate) && !task.dueDate);}), - dates = []; - - datedTasks.forEach(function(task) { - var renderedDates = []; - if(task.rangeValid) { - renderedDates.push(task.startDate,task.endDate || task.startDate); - } else if(!task.start && task.endDate) { - renderedDates.push(task.endDate); - } - if(task.dueDate) { - renderedDates.push(task.dueDate); - } - renderedDates.forEach(function(date) { - if(date) {dates.push(dayNumber(date));} - }); - }); - if(!dates.length) { - this.containerNode.appendChild(this.createElement("div","tc-task-empty","没有可绘制的日期")); - this.renderUnscheduled(unscheduled,"未排期事情"); - return; - } - - var minimum = Math.min.apply(Math,dates) - 2, - maximum = Math.max.apply(Math,dates) + 2, - span = maximum - minimum + 1, - step = Math.max(1,Math.ceil(span / 366)), - units = Math.ceil(span / step), - labelReserve = Math.max.apply(Math,datedTasks.map(function(task) { - return task.title.length * 14 + 32; - }).concat([180])), - startDate = new Date(minimum * DAY_MS), - wrapper = this.createElement("div","tc-task-gantt-scroll"), - gantt = this.createElement("div","tc-task-gantt"), - header = this.createElement("div","tc-task-gantt-row tc-task-gantt-header"), - headerLabel = this.createElement("div","tc-task-gantt-label","事情"), - headerTimeline = this.createElement("div","tc-task-gantt-timeline tc-task-gantt-header-timeline"), - monthTrack = this.createElement("div","tc-task-gantt-months"), - dayTrack = this.createElement("div","tc-task-gantt-days"), - monthGroups = [], - today = parseDate(todayKey()), - todayOffset = (dayNumber(today) - minimum) / step; - - gantt.style.setProperty("--task-gantt-width",units * DAY_WIDTH + labelReserve + "px"); - gantt.style.setProperty("--task-gantt-date-width",units * DAY_WIDTH + "px"); - header.appendChild(headerLabel); - for(var index = 0; index < units; index++) { - var date = addDays(startDate,index * step), - key = monthKey(date), - group = monthGroups[monthGroups.length - 1], - day = this.createElement("div","tc-task-gantt-day",pad(date.getUTCDate())); - if(!group || group.key !== key) { - group = {key: key,date: date,count: 0}; - monthGroups.push(group); - } - group.count++; - day.style.width = DAY_WIDTH + "px"; - day.setAttribute("title",dateKey(date) + (step > 1 ? " 起 " + step + " 天" : "")); - day.setAttribute("aria-label",dateKey(date) + (step > 1 ? " 起 " + step + " 天" : "")); - dayTrack.appendChild(day); - } - monthGroups.forEach(function(group) { - var segment = self.createElement("div","tc-task-gantt-month-segment"), - label = self.createElement("span","tc-task-gantt-month-label",group.date.getUTCFullYear() + "年" + (group.date.getUTCMonth() + 1) + "月"); - segment.style.width = group.count * DAY_WIDTH + "px"; - segment.setAttribute("title",label.textContent); - segment.appendChild(label); - monthTrack.appendChild(segment); - }); - headerTimeline.appendChild(monthTrack); - headerTimeline.appendChild(dayTrack); - header.appendChild(headerTimeline); - gantt.appendChild(header); - - datedTasks.forEach(function(task) { - var row = self.createElement("div","tc-task-gantt-row"), - label = self.createElement("div","tc-task-gantt-label"), - timeline = self.createElement("div","tc-task-gantt-timeline"), - barStart = task.startDate, - barEnd = task.rangeValid ? (task.endDate || task.startDate) : null; - label.appendChild(self.createTaskLink(task,"tc-task-gantt-task-link")); - label.appendChild(self.renderPriority(task)); - self.renderIssues(task,label); - - if(barStart && barEnd) { - var left = (dayNumber(barStart) - minimum) / step * DAY_WIDTH, - width = (dayNumber(barEnd) - dayNumber(barStart) + 1) / step * DAY_WIDTH, - barWidth = Math.max(width,DAY_WIDTH), - bar = self.createTaskLink(task,"tc-task-gantt-bar",""), - barLabel = self.createTaskLink(task,"tc-task-gantt-bar-label"); - bar.style.left = left + "px"; - bar.style.width = barWidth + "px"; - bar.setAttribute("data-priority",task.effectivePriority); - barLabel.style.left = (left + barWidth + 8) + "px"; - timeline.appendChild(bar); - timeline.appendChild(barLabel); - } else if(!task.start && task.endDate) { - var endMarker = self.createTaskLink(task,"tc-task-gantt-end","◆"); - endMarker.style.left = ((dayNumber(task.endDate) - minimum) / step * DAY_WIDTH + DAY_WIDTH / 2) + "px"; - endMarker.title = "计划结束:" + task.end; - timeline.appendChild(endMarker); - } - if(task.dueDate) { - var dueMarker = self.createTaskLink(task,"tc-task-gantt-due","◆"); - dueMarker.style.left = ((dayNumber(task.dueDate) - minimum) / step * DAY_WIDTH + DAY_WIDTH / 2) + "px"; - dueMarker.title = "截止:" + task.due; - timeline.appendChild(dueMarker); - } - if(todayOffset >= 0 && todayOffset < units) { - var todayLine = self.createElement("span","tc-task-gantt-today-line"); - todayLine.style.left = (todayOffset * DAY_WIDTH + DAY_WIDTH / 2) + "px"; - timeline.appendChild(todayLine); - } - row.appendChild(label); - row.appendChild(timeline); - gantt.appendChild(row); - }); - - wrapper.appendChild(gantt); - this.containerNode.appendChild(wrapper); - this.renderUnscheduled(unscheduled,"未排期事情"); -}; - -TaskBoardWidget.prototype.refresh = function(changedTiddlers) { - var changedAttributes = this.computeAttributes(); - if(Object.keys(changedAttributes).length || Object.keys(changedTiddlers).length) { - this.refreshSelf(); - return true; - } - return false; -}; - -exports["task-board"] = TaskBoardWidget; - -})(); -- cgit v1.2.3