mirror of
https://github.com/wth461694678/text-block-timer.git
synced 2026-07-22 05:46:04 +00:00
- V1.0.3
1. ==Major Optimization==: Closing a file no longer causes the timer to fail. Users can freely choose whether to continue timing when the or Obsidian is closed (see custom settings). 2. Added custom settings: Users can now choose the position to insert the timer (before or after texts). 3. Improve compatibility with markdown, adding support for ordered checkboxes and headings.
This commit is contained in:
parent
bbb399de15
commit
e040e48462
4 changed files with 381 additions and 76 deletions
54
README.md
54
README.md
|
|
@ -1,51 +1,49 @@
|
|||
[English](README.md) | [中文](README.zh.md)
|
||||
|
||||
# Text Block Timer Plugin
|
||||
|
||||
A text block timing tool designed for Obsidian, allowing you to add **count-up** timing functionality to any text line in your notes.
|
||||
A text block timing tool designed for Obsidian, allowing you to add a **count-up stopwatch** function to any text line in your notes.
|
||||
|
||||
## Features
|
||||
|
||||
<img src="https://github.com/wth461694678/text-block-timer/blob/main/demo.gif" width="75%" alt="">
|
||||

|
||||
|
||||
- Add timers to any text line as many as you wsh (count-up timing)
|
||||
- ▶️/⏸️ Supports start, pause, and continue timing, with multi-segment timing accumulation for a single task
|
||||
- ⏳ Real-time display of accumulated time
|
||||
- [💾](https://www.emojiall.com/zh-hans/emoji/%F0%9F%92%BE) Timing data is persistently saved
|
||||
- Add timers to any text line, supporting multiple timers running simultaneously (stopwatch function).
|
||||
- ▶️/⏸️ Start, pause, and resume timing for tasks.
|
||||
- ⏳ Real-time display of cumulative time cost.
|
||||
- 💾 Persistent storage of timing data.
|
||||
- 🔄 Users can optionally choose whether to continue timing when files or Obsidian is closed (by default, closing a file will not cause failure of the real-time stopwatch).
|
||||
|
||||
## Usage Method
|
||||
## Usage
|
||||
|
||||
[Strongly Recommended] Write your task first before adding a timer!
|
||||
### (I) Command Palette + Shortcut (Recommended⭐️)
|
||||
|
||||
### (1) Command Palette + Shortcuts (Recommended ⭐️)
|
||||
1. Configure a shortcut for the command "Text Block Timer: Toggle timer" for quick access.
|
||||
2. When the cursor is on a text block, press the shortcut key to start, pause, or resume.
|
||||
3. The timer will be displayed in the format `【⏳00:00:00 】` at the beginning of the text line.
|
||||
|
||||
1. Assign a shortcut for the command "Text Block Timer: Toggle timer" for quick access
|
||||
2. Place your cursor on a text block, press the shortcut to start, pause, or continue
|
||||
<img src="https://github.com/wth461694678/text-block-timer/blob/main/command_shortcut.gif" width="75%" alt="">
|
||||

|
||||
|
||||
### (2) Mouse Operations
|
||||
### (II) Mouse Operations
|
||||
|
||||
1. Right-click on a text line
|
||||
2. Select "Start Timer", "Pause Timer", or "Continue Timer"
|
||||
3. The timer will display in the format `【⏳00:00:00 】` at the start of the text block
|
||||
<img src="https://github.com/wth461694678/text-block-timer/blob/main/right_click.gif" width="75%" alt="">
|
||||
1. Right-click on a text line.
|
||||
2. Select "Start Timer," "Pause Timer," or "Resume Timer."
|
||||
|
||||
## Notes
|
||||

|
||||
|
||||
- It is recommended to keep the file (containing a running tiemr) open; otherwise, the real-time second updates of the timer may fail.
|
||||
- However, the file will record the timer state, which can be manually resumed.
|
||||
|
||||
## Changelog:
|
||||
## Version Log:
|
||||
|
||||
- V1.0.3
|
||||
1. ==Major Optimization==: Closing a file no longer causes the timer to fail. Users can freely choose whether to continue timing when the or Obsidian is closed (see custom settings).
|
||||
2. Added custom settings: Users can now choose the position to insert the timer (before or after texts).
|
||||
3. Improve compatibility with markdown, adding support for ordered checkboxes and headings.
|
||||
- V1.0.2
|
||||
1. Add language support for English, Chinese, Japanese and Korean.
|
||||
1. Added language support for Chinese, English, Japanese, and Korean.
|
||||
- V1.0.1
|
||||
1. A space is added before the time tag to better distinguish it from your content.
|
||||
2. Fixed the issue where adding a timer disrupted the structure of checkboxes in Markdown.
|
||||
1. Improved display effects by adding a space before the time label.
|
||||
2. Fixed the issue where adding a timer to a task list caused the task checkbox to fail.
|
||||
- V1.0.0
|
||||
1. Plugin release
|
||||
|
||||
## Development Information
|
||||
|
||||
- Developer: frankthwang
|
||||
- Version: 1.0.2
|
||||
- Version: 1.0.3
|
||||
23
README.zh.md
23
README.zh.md
|
|
@ -1,4 +1,4 @@
|
|||
[English](README.md) | [中文](README.zh.md)
|
||||
[English](https://github.com/wth461694678/text-block-timer/blob/main/README.md) | [中文](https://github.com/wth461694678/text-block-timer/blob/main/README.zh.md)
|
||||
|
||||
# 文本块计时器插件
|
||||
|
||||
|
|
@ -6,36 +6,35 @@
|
|||
|
||||
## 功能特性
|
||||
|
||||
<img src="https://github.com/wth461694678/text-block-timer/blob/main/demo.gif" width="50%" alt="">
|
||||
<img src="https://github.com/wth461694678/text-block-timer/blob/main/demo.gif" width="100%" alt="">
|
||||
|
||||
- 为任意文本行添加计时器,支持同时执行多个计时器(正计时)
|
||||
- ▶️/⏸️ 支持开始、暂停、继续计时,支持一个任务的多段计时累计
|
||||
- ▶️/⏸️ 支持开始、暂停、继续计时,支持任务分段耗时累计
|
||||
- ⏳ 实时显示累计时间
|
||||
- 💾 计时数据持久化保存
|
||||
- 🔄 可自由选择关闭文件、关闭Obsidian后是否持续计时 (默认设置下,关闭文件不会导致实时计时失效)
|
||||
|
||||
## 使用方法
|
||||
|
||||
【强烈推荐】先撰写文本,再添加Timer为你的任务即计时!!
|
||||
|
||||
### (一)命令行+快捷键(推荐⭐️)
|
||||
|
||||
1. 为命令 "Text Block Timer: Toggle timer" 配置快捷键,方便快速使用
|
||||
2. 当光标在某个文本块时,按下快捷键,即可实现timer的开始、暂停、继续操作
|
||||
3. 计时器会以 `【⏳00:00:00 】`格式显示在文本块的行首
|
||||
<img src="https://github.com/wth461694678/text-block-timer/blob/main/command_shortcut.gif" width="50%" alt="">
|
||||
<img src="https://github.com/wth461694678/text-block-timer/blob/main/command_shortcut.gif" width="80%" alt="">
|
||||
|
||||
### (二)鼠标操作
|
||||
|
||||
1. 在文本行上右键点击
|
||||
2. 选择"开始计时"、"暂停计时"或"继续计时"
|
||||
<img src="https://github.com/wth461694678/text-block-timer/blob/main/right_click.gif" width="50%" alt="">
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 建议保持文本块所在文件打开,否则会导致计时器的实时跳秒失效。但文件会保留计时器状态,下次打开文件时可以手动恢复计时。
|
||||
<img src="https://github.com/wth461694678/text-block-timer/blob/main/right_click.gif" width="80%" alt="">
|
||||
|
||||
## 版本日志:
|
||||
|
||||
- V1.0.3
|
||||
1. ==重大优化==:现在关闭文件不会导致计时器失效,用户可自由选择关闭文件或关闭Obsidian后是否持续计时(见自定义设置)
|
||||
2. 增加自定义设置:现在用户可自由选择插入文本块位置(文本前或文本后)
|
||||
3. 优化对Markdown的处理逻辑,增加对有序复选框、标题的支持
|
||||
- V1.0.2
|
||||
1. 增加了对中文、英文、日语、韩语的语言支持
|
||||
- V1.0.1
|
||||
|
|
@ -47,4 +46,4 @@
|
|||
## 开发信息
|
||||
|
||||
- 开发者: frankthwang
|
||||
- 版本: 1.0.2
|
||||
- 版本: 1.0.3
|
||||
378
main.js
378
main.js
|
|
@ -3,7 +3,6 @@
|
|||
const obsidian = require('obsidian');
|
||||
|
||||
// Regex constants
|
||||
const TIMER_REGEX = /<span class="timer-btn" timerId="([^"]+)" Status="([^"]+)" AccumulatedTime="([^"]+)" currentStartTimeStamp="([^"]+)" lineId="([^"]+)"([^>]*)>([^<]+)<\/span>/;
|
||||
const UPDATE_INTERVAL = 1000;
|
||||
|
||||
// —— Utility: Format time and render span —— //
|
||||
|
|
@ -14,24 +13,41 @@ class TimerRenderer {
|
|||
.substr(11, 8);
|
||||
const colorStyle =
|
||||
timerData.Status === 'Running' ? 'style="color: #10b981;"' : '';
|
||||
return `<span class="timer-btn" timerId="${timerData.timerId}" Status="${timerData.Status}" AccumulatedTime="${timerData.AccumulatedTime}" currentStartTimeStamp="${timerData.currentStartTimeStamp}" lineId="${timerData.lineId}" ${colorStyle}>【⏳${formatted} 】 </span>`;
|
||||
return `<span class="timer-btn" timerId="${timerData.timerId}" Status="${timerData.Status}" AccumulatedTime="${timerData.AccumulatedTime}" currentStartTimeStamp="${timerData.currentStartTimeStamp}" lineId="${timerData.lineId}" PLT="${timerData.PLT}" ${colorStyle}>【⏳${formatted} 】 </span>`;
|
||||
}
|
||||
}
|
||||
|
||||
// —— Utility: Parse existing span data —— //
|
||||
class TimerParser {
|
||||
static parse(lineText) {
|
||||
const m = lineText.match(TIMER_REGEX);
|
||||
if (!m) return null;
|
||||
// const spanMatch = lineText.match(/<span class="timer-btn"[^>]*>/);
|
||||
const spanMatch = lineText.match(/<span class="timer-btn"[^>]*>.*?<\/span>/);
|
||||
if (!spanMatch) return null;
|
||||
|
||||
const spanTag = spanMatch[0];
|
||||
const attributesText = spanTag.substring(6, spanTag.length - 1); // 去掉 "<span " 和 ">"
|
||||
|
||||
const attributes = {};
|
||||
const attributeList = attributesText.split(/\s+/); // 按空格分割成属性列表
|
||||
for (const attribute of attributeList) {
|
||||
if (attribute.includes('=')) {
|
||||
const [key, value] = attribute.split('=', 2);
|
||||
attributes[key] = value.replace(/"/g, '').replace(/'/g, '');
|
||||
} else {
|
||||
attributes[attribute] = true; // 处理没有值的属性(如 disabled)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
raw: m[0],
|
||||
beforeIndex: m.index,
|
||||
afterIndex: m.index + m[0].length,
|
||||
timerId: m[1],
|
||||
Status: m[2],
|
||||
AccumulatedTime: parseInt(m[3], 10),
|
||||
currentStartTimeStamp: parseInt(m[4], 10),
|
||||
lineId: parseInt(m[5], 10),
|
||||
raw: spanTag,
|
||||
beforeIndex: spanMatch.index,
|
||||
afterIndex: spanMatch.index + spanTag.length,
|
||||
timerId: attributes.timerId || null,
|
||||
Status: attributes.Status || null,
|
||||
AccumulatedTime: parseInt(attributes.AccumulatedTime, 10) || 0,
|
||||
currentStartTimeStamp: parseInt(attributes.currentStartTimeStamp, 10) || null,
|
||||
lineId: parseInt(attributes.lineId, 10) || null,
|
||||
PLT: parseInt(attributes.PLT, 10) || 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -46,6 +62,7 @@ class TimerManager {
|
|||
if (this.timers.has(timerId)) return;
|
||||
const id = setInterval(() => tickCallback(timerId), UPDATE_INTERVAL);
|
||||
this.timers.set(timerId, id);
|
||||
console.log('start timerId:', timerId);
|
||||
}
|
||||
|
||||
stop(timerId) {
|
||||
|
|
@ -53,6 +70,7 @@ class TimerManager {
|
|||
if (id) {
|
||||
clearInterval(id);
|
||||
this.timers.delete(timerId);
|
||||
console.log('stop timerId:', timerId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -67,43 +85,102 @@ class TimerManager {
|
|||
// —— Main plugin class —— //
|
||||
class TimerPlugin extends obsidian.Plugin {
|
||||
async onload() {
|
||||
|
||||
this.manager = new TimerManager();
|
||||
// Stores context for each timerId: { editor, lineNum }
|
||||
this.timerContexts = new Map();
|
||||
this.fileFirstOpen = true;
|
||||
this.pluginLoadTime = Date.now();
|
||||
|
||||
this.default_settings = {
|
||||
autoStopTimers: 'quit', // default value
|
||||
timerInsertLocation: 'head' // default value
|
||||
};
|
||||
|
||||
await this.loadSettings();
|
||||
|
||||
// 获取当前界面语言
|
||||
const currentLanguage = window.localStorage.getItem('language');
|
||||
console.log('Current Obsidian Interface Language:', currentLanguage);
|
||||
// console.log('Current Obsidian Interface Language:', currentLanguage);
|
||||
|
||||
if (currentLanguage === 'zh') {
|
||||
this.plugin_name = '文本块计时器';
|
||||
this.plugin_desc = '强烈建议配合快捷键使用';
|
||||
this.command_name = '启动计时器/切换计时器状态';
|
||||
this.action_paused = '暂停计时';
|
||||
this.action_continue = '继续计时';
|
||||
this.action_start = '开始计时';
|
||||
this.setting_autostop_label = '自动停止计时器';
|
||||
this.setting_autostop_desc = '哪些行为视作用户手动停止计时器';
|
||||
this.setting_never = '从不停止,除非用户手动停止';
|
||||
this.setting_quit = '仅退出Obsidian时停止,关闭文件依然后台计时';
|
||||
this.setting_close = '关闭文件时立即停止';
|
||||
this.setting_insertlocation_label = '计时器插入位置';
|
||||
this.setting_insertlocation_head = '在文本前插入';
|
||||
this.setting_insertlocation_tail = '在文本后插入';
|
||||
} else if (currentLanguage === 'zh-TW') {
|
||||
this.plugin_name = '文本塊計時器';
|
||||
this.plugin_desc = '強烈建議配合快捷鍵使用';
|
||||
this.command_name = '啟動計時器/切換計時器狀態';
|
||||
this.action_paused = '暫停計時';
|
||||
this.action_continue = '繼續計時';
|
||||
this.action_start = '開始計時';
|
||||
this.setting_autostop_label = '自動停止計時器';
|
||||
this.setting_autostop_desc = '哪些行為視為用戶手動停止計時器';
|
||||
this.setting_never = '從不停止,除非用戶手動停止';
|
||||
this.setting_quit = '僅退出Obsidian時停止,關閉文件依後台計時';
|
||||
this.setting_close = '關閉文件時立即停止';
|
||||
this.setting_insertlocation_label = '計時器插入位置';
|
||||
this.setting_insertlocation_head = '在文本前插入';
|
||||
this.setting_insertlocation_tail = '在文本後插入';
|
||||
} else if (currentLanguage === 'ja') {
|
||||
this.plugin_name = 'テキストブロックタイマー';
|
||||
this.plugin_desc = 'ショートカットキーと合わせて使うのがおすすめです';
|
||||
this.command_name = 'タイマーを始める/切替タイマ';
|
||||
this.action_paused = 'タイマーを止める';
|
||||
this.action_continue = 'タイマーを続ける';
|
||||
this.action_start = 'タイマーを始める';
|
||||
this.setting_autostop_label = 'タイマー自動停止';
|
||||
this.setting_autostop_desc = 'ユーザーが手働でタイマーを停止したものとみなします';
|
||||
this.setting_never = 'ユーザーが手動で止めない限り停止しません';
|
||||
this.setting_quit = 'Obsidianをログアウトしたときだけ停止し、ファイルを閉じたままバックグラウンドカウントします';
|
||||
this.setting_close = 'ファイルを閉じたらすぐに停止します';
|
||||
this.setting_insertlocation_label = 'タイマー挿入位置です';
|
||||
this.setting_insertlocation_head = 'テキストの前に挿入します';
|
||||
this.setting_insertlocation_tail = 'テキストの後に挿入します';
|
||||
} else if (currentLanguage === 'ko') {
|
||||
this.plugin_name = '텍스트 블록 타이머';
|
||||
this.plugin_desc = '단축키에 맞춰 사용하는 것을 권장합니다';
|
||||
this.command_name = '타이머 전환';
|
||||
this.action_paused = '타이머 일시 정지';
|
||||
this.action_continue = '타이머 계속';
|
||||
this.action_start = '타이머 시작';
|
||||
this.setting_autostop_label = '타이머 자동 정지';
|
||||
this.setting_autostop_desc = '사용자가 타이머를 수동으로 종료한 것으로 간주할 행동입니다';
|
||||
this.setting_never = '사용자가 수동으로 중지하지 않는 한 정지하지 않는다';
|
||||
this.setting_quit = 'obsidian을 종료할 때만 종료되며, 닫기 파일은 여전히 백그라운드 타임을 유지한다';
|
||||
this.setting_close = '파일을 닫을 때 즉시 중지합니다';
|
||||
this.setting_insertlocation_label = '타이머 삽입 위치';
|
||||
this.setting_insertlocation_head = '텍스트 앞에 삽입';
|
||||
this.setting_insertlocation_tail = '텍스트 뒤에 삽입';
|
||||
|
||||
} else {
|
||||
this.plugin_name = 'Text Block Timer';
|
||||
this.plugin_desc = 'It is strongly recommened to control timer by shortcut key';
|
||||
this.command_name = 'toggle-timer';
|
||||
this.action_paused = 'Pause Timer';
|
||||
this.action_continue = 'Continue Timer';
|
||||
this.action_start = 'Start Timer';
|
||||
this.setting_autostop_label = 'Auto Stop Timers';
|
||||
this.setting_autostop_desc = 'Which actions should stop the timer automatically?';
|
||||
this.setting_never = 'Never Stop unless user stop timer manually';
|
||||
this.setting_quit = 'Auto stop when quit Obsidian, continue timing when file closed';
|
||||
this.setting_close = 'Auto stop when file closed';
|
||||
this.setting_insertlocation_label = 'Timer insert position';
|
||||
this.setting_insertlocation_head = 'Insert before text';
|
||||
this.setting_insertlocation_tail = 'Insert after text';
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Add to editor context menu
|
||||
this.registerEvent(
|
||||
this.app.workspace.on('editor-menu', this.onEditorMenu.bind(this))
|
||||
|
|
@ -132,6 +209,16 @@ class TimerPlugin extends obsidian.Plugin {
|
|||
}
|
||||
},
|
||||
});
|
||||
|
||||
// 监听文件打开事件
|
||||
this.registerEvent(
|
||||
this.app.workspace.on('file-open', this.onFileOpen.bind(this))
|
||||
);
|
||||
|
||||
|
||||
// 注册设置
|
||||
this.addSettingTab(new TimerSettingTab(this.app, this));
|
||||
|
||||
}
|
||||
|
||||
onunload() {
|
||||
|
|
@ -175,7 +262,7 @@ class TimerPlugin extends obsidian.Plugin {
|
|||
this.timerContexts.set(timerId, { editor, lineNum });
|
||||
this.manager.start(timerId, this.onTick.bind(this));
|
||||
this.updateTimer('continue', editor, lineNum, timerId);
|
||||
this.timerContexts.set(timerId, { editor, lineNum });
|
||||
// this.timerContexts.set(timerId, { editor, lineNum });
|
||||
}
|
||||
|
||||
handleContinue(parsed, editor, lineNum) {
|
||||
|
|
@ -183,7 +270,7 @@ class TimerPlugin extends obsidian.Plugin {
|
|||
this.timerContexts.set(timerId, { editor, lineNum });
|
||||
this.manager.start(timerId, this.onTick.bind(this));
|
||||
this.updateTimer('continue', editor, lineNum, timerId);
|
||||
this.timerContexts.set(timerId, { editor, lineNum });
|
||||
// this.timerContexts.set(timerId, { editor, lineNum });
|
||||
}
|
||||
|
||||
handlePause(parsed, editor, lineNum) {
|
||||
|
|
@ -191,9 +278,27 @@ class TimerPlugin extends obsidian.Plugin {
|
|||
this.timerContexts.set(timerId, { editor, lineNum });
|
||||
this.manager.stop(timerId);
|
||||
this.updateTimer('pause', editor, lineNum, timerId);
|
||||
this.timerContexts.set(timerId, { editor, lineNum });
|
||||
// this.timerContexts.set(timerId, { editor, lineNum });
|
||||
}
|
||||
|
||||
handleRestore(parsed, editor, lineNum) {
|
||||
const timerId = parsed.timerId;
|
||||
// 在ontick之前,先把数据恢复
|
||||
this.updateTimer('restore', editor, lineNum, timerId);
|
||||
this.timerContexts.set(timerId, { editor, lineNum });
|
||||
this.manager.start(timerId, this.onTick.bind(this));
|
||||
// this.timerContexts.set(timerId, { editor, lineNum });
|
||||
}
|
||||
|
||||
handleForcePause(parsed, editor, lineNum) {
|
||||
const timerId = parsed.timerId;
|
||||
// this.timerContexts.set(timerId, { editor, lineNum });
|
||||
// this.manager.stop(timerId);
|
||||
this.updateTimer('forcepause', editor, lineNum, timerId);
|
||||
// this.timerContexts.set(timerId, { editor, lineNum });
|
||||
}
|
||||
|
||||
|
||||
onTick(timerId) {
|
||||
const ctx = this.timerContexts.get(timerId);
|
||||
if (ctx) {
|
||||
|
|
@ -201,7 +306,6 @@ class TimerPlugin extends obsidian.Plugin {
|
|||
const lineText = editor.getLine(lineNum) || '';
|
||||
if (lineText.includes(`timerId="${timerId}"`)) {
|
||||
this.updateTimer('update', editor, lineNum, timerId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -234,28 +338,34 @@ class TimerPlugin extends obsidian.Plugin {
|
|||
|
||||
let data;
|
||||
switch (action) {
|
||||
case 'init':
|
||||
case 'init': // init:Timer初始化,TimerId为当前时间戳,初始化时为暂停状态,累计耗时=0,记录创建时的时间戳
|
||||
data = {
|
||||
timerId: Date.now().toString(),
|
||||
lineId: lineNum,
|
||||
Status: 'Paused',
|
||||
AccumulatedTime: 0,
|
||||
currentStartTimeStamp: null,
|
||||
currentStartTimeStamp: now,
|
||||
PLT: this.pluginLoadTime,
|
||||
};
|
||||
break;
|
||||
case 'continue':
|
||||
case 'continue': // continue:暂停状态下启动: 启动瞬间的timer状态应当和暂停时状态相同,只是把status改为Running
|
||||
{
|
||||
const old = parsed || {};
|
||||
// const elapsed = old ?
|
||||
// now - old.currentStartTimeStamp : 0;
|
||||
const elapsed = 0;
|
||||
data = {
|
||||
timerId: timerId || old.timerId || Date.now().toString(),
|
||||
lineId: lineNum,
|
||||
Status: 'Running',
|
||||
AccumulatedTime: old.AccumulatedTime || 0,
|
||||
AccumulatedTime:
|
||||
(old.AccumulatedTime || 0) + elapsed,
|
||||
currentStartTimeStamp: now,
|
||||
PLT: this.pluginLoadTime,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'pause':
|
||||
case 'pause': // 逻辑应当与update一致,只是把状态改为Paused
|
||||
{
|
||||
const old = parsed;
|
||||
const elapsed = old ?
|
||||
|
|
@ -266,11 +376,12 @@ class TimerPlugin extends obsidian.Plugin {
|
|||
Status: 'Paused',
|
||||
AccumulatedTime:
|
||||
(old.AccumulatedTime || 0) + elapsed,
|
||||
currentStartTimeStamp: null,
|
||||
currentStartTimeStamp: now,
|
||||
PLT: this.pluginLoadTime,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'update':
|
||||
case 'update': // AccumulatedTime和currentStartTimeStamp应当更新为当前值
|
||||
{
|
||||
const old = parsed;
|
||||
const elapsed = old ?
|
||||
|
|
@ -282,6 +393,37 @@ class TimerPlugin extends obsidian.Plugin {
|
|||
AccumulatedTime:
|
||||
(old.AccumulatedTime || 0) + elapsed,
|
||||
currentStartTimeStamp: now,
|
||||
PLT: this.pluginLoadTime,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'restore': // 把一个有遗留数据,且状态为Running的Timer, 把ontick失效期间的时间累加到AccumulatedTime, 把currentStartTimeStamp更新为当前时间戳, 把状态改为Running
|
||||
{
|
||||
const old = parsed;
|
||||
const elapsed = old ?
|
||||
now - old.currentStartTimeStamp : 0;
|
||||
data = {
|
||||
timerId: old.timerId,
|
||||
lineId: lineNum,
|
||||
Status: 'Running',
|
||||
AccumulatedTime:
|
||||
(old.AccumulatedTime || 0) + elapsed,
|
||||
currentStartTimeStamp: now,
|
||||
PLT: this.pluginLoadTime,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'forcepause': // forcepause: 强制暂停,一切保留原样,只是状态强制改写为暂停
|
||||
{
|
||||
const old = parsed;
|
||||
data = {
|
||||
timerId: old.timerId,
|
||||
lineId: lineNum,
|
||||
Status: 'Paused',
|
||||
AccumulatedTime:
|
||||
(old.AccumulatedTime || 0),
|
||||
currentStartTimeStamp: now,
|
||||
PLT: this.pluginLoadTime,
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
|
@ -299,24 +441,190 @@ class TimerPlugin extends obsidian.Plugin {
|
|||
if (parsed) {
|
||||
before = parsed.beforeIndex;
|
||||
after = parsed.afterIndex;
|
||||
} else {
|
||||
// Calculate insertion position after indentation and checkboxes
|
||||
const indentMatch = /^(\s*)/.exec(lineText);
|
||||
const indentLen = indentMatch ? indentMatch[1].length : 0;
|
||||
} else if (this.settings.timerInsertLocation === 'head') {
|
||||
|
||||
// Check for markdown checkbox or list patterns after indentation
|
||||
const afterIndent = lineText.slice(indentLen);
|
||||
const checkboxMatch = /^([+\-*]\s\[[^\]]\]\s+|\s*[-+*]\s+)/.exec(afterIndent);
|
||||
const checkboxLen = checkboxMatch ? checkboxMatch[1].length : 0;
|
||||
// ol+checkbox
|
||||
const olcheckboxMatch = /(^\s*#*\d\.\s\[.?\]\s)/.exec(lineText);
|
||||
const olcheckboxLen = olcheckboxMatch ? olcheckboxMatch[0].length : 0;
|
||||
// ul+checkbox
|
||||
const ulcheckboxMatch = /(^\s*#*[-/+/*]\s\[.?\]\s)/.exec(lineText);
|
||||
const ulcheckboxLen = ulcheckboxMatch ? ulcheckboxMatch[0].length : 0;
|
||||
// ol
|
||||
const orderedListMatch = /(^\s*#*\d+\.\s)/.exec(lineText);
|
||||
const orderedListLen = orderedListMatch ? orderedListMatch[0].length : 0;
|
||||
// ul
|
||||
const ulMatch = /(^\s*#*[-/+/*]\s)/.exec(lineText);
|
||||
const ulLen = ulMatch ? ulMatch[0].length : 0;
|
||||
// header
|
||||
const headerMatch = /(^\s*#+\s)/.exec(lineText);
|
||||
const headerLen = headerMatch ? headerMatch[0].length : 0;
|
||||
|
||||
before = after = indentLen + checkboxLen;
|
||||
|
||||
if (olcheckboxLen > 0) {
|
||||
var textleft
|
||||
textleft = lineText.slice(olcheckboxLen);
|
||||
console.log('textleft after olcheckbox:', textleft);
|
||||
before = after = olcheckboxLen;
|
||||
} else if (ulcheckboxLen > 0) {
|
||||
var textleft
|
||||
textleft = lineText.slice(ulcheckboxLen);
|
||||
console.log('textleft after checkbox:', textleft);
|
||||
before = after = ulcheckboxLen;
|
||||
} else if (orderedListLen > 0) {
|
||||
var textleft
|
||||
textleft = lineText.slice(orderedListLen);
|
||||
console.log('textleft after ol:', textleft);
|
||||
before = after = orderedListLen;
|
||||
} else if (ulLen > 0) {
|
||||
var textleft
|
||||
textleft = lineText.slice(ulLen);
|
||||
console.log('textleft after ul:', textleft);
|
||||
before = after = ulLen;
|
||||
} else if (headerLen > 0) {
|
||||
var textleft
|
||||
textleft = lineText.slice(headerLen);
|
||||
console.log('textleft after header:', textleft);
|
||||
before = after = headerLen;
|
||||
} else {
|
||||
before = after = 0;
|
||||
}
|
||||
before = before ? before : 0;
|
||||
after = after ? after : 0;
|
||||
} else if (this.settings.timerInsertLocation === 'tail') {
|
||||
before = after = lineText.length;
|
||||
}
|
||||
|
||||
editor.replaceRange(
|
||||
newSpan, { line: lineNum, ch: before }, { line: lineNum, ch: after }
|
||||
);
|
||||
console.log('lineNum:', lineNum, 'before:', before, 'after:', after);
|
||||
return data.timerId;
|
||||
}
|
||||
|
||||
// 用于标记是否是第一次监听到onFileOpen事件
|
||||
onFileOpen(event) {
|
||||
if (this.fileFirstOpen) {
|
||||
// 如果是第一次监听到onFileOpen事件,则针对每个leaf都执行一次恢复计时器的操作
|
||||
const leaves = this.app.workspace.getLeavesOfType('markdown');
|
||||
for (const leaf of leaves) {
|
||||
if (leaf.view && leaf.view.editor) {
|
||||
const editor = leaf.view.editor;
|
||||
this.restoreTimers(editor);
|
||||
}
|
||||
}
|
||||
this.fileFirstOpen = false; // 标记为已处理过第一次监听
|
||||
} else {
|
||||
// 如果不是第一次监听,则按照原来的逻辑处理特定的文件
|
||||
const leaves = this.app.workspace.getLeavesOfType('markdown');
|
||||
for (const leaf of leaves) {
|
||||
if (leaf.view && leaf.view.file === event) {
|
||||
const editor = leaf.view.editor;
|
||||
if (editor) {
|
||||
// 精简逻辑,可能有问题
|
||||
// this.restoreTimers(editor);
|
||||
|
||||
// 启动一个定时器,每隔1秒检查文件行数
|
||||
const checkInterval = setInterval(() => {
|
||||
const lineCount = editor.lineCount();
|
||||
if (lineCount > 1) {
|
||||
// 如果行数大于1,则执行Restore逻辑
|
||||
this.restoreTimers(editor);
|
||||
clearInterval(checkInterval); // 删除定时器
|
||||
}
|
||||
}, 100);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 新增Restore逻辑
|
||||
restoreTimers(editor) {
|
||||
const lineCount = editor.lineCount();
|
||||
for (let i = 0; i < lineCount; i++) {
|
||||
const lineText = editor.getLine(i);
|
||||
const parsed = TimerParser.parse(lineText);
|
||||
if (parsed && parsed.Status === 'Running' && !this.manager.timers.has(parsed.timerId)) {
|
||||
|
||||
if (this.settings.autoStopTimers === 'never') {
|
||||
// 对于状态为Running且timerId不在this.manager中的计时器,执行暂停和继续操作(立即恢复)
|
||||
this.handleRestore(parsed, editor, i);
|
||||
} else if (this.settings.autoStopTimers === 'quit') {
|
||||
// 对于状态为Running且timerId不在this.manager中的计时器,如果计时器的本次启动时间 等于 当前插件启动时间,则继续,否则强行暂停
|
||||
console.log('PLT:', parsed.PLT, 'pluginLoadTime:', this.pluginLoadTime);
|
||||
if (parsed.PLT === this.pluginLoadTime) {
|
||||
this.handleRestore(parsed, editor, i);
|
||||
} else {
|
||||
this.handleForcePause(parsed, editor, i);
|
||||
}
|
||||
} else if (this.settings.autoStopTimers === 'close') {
|
||||
// 对于状态为Running且timerId不在this.manager中的计时器,执行暂停操作
|
||||
this.handleForcePause(parsed, editor, i);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置项相关方法
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, this.default_settings, await this.loadData());
|
||||
}
|
||||
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class TimerSettingTab extends obsidian.PluginSettingTab {
|
||||
constructor(app, plugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display() {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
containerEl.createEl('h2', { text: this.plugin.plugin_name });
|
||||
containerEl.createEl('h3', { text: this.plugin.plugin_desc });
|
||||
|
||||
new obsidian.Setting(containerEl)
|
||||
.setName(this.plugin.setting_autostop_label)
|
||||
.setDesc(this.plugin.setting_autostop_desc)
|
||||
.addDropdown(dropdown => {
|
||||
dropdown
|
||||
.addOption('never', this.plugin.setting_never)
|
||||
.addOption('quit', this.plugin.setting_quit)
|
||||
.addOption('close', this.plugin.setting_close)
|
||||
.setValue(this.plugin.settings.autoStopTimers)
|
||||
.onChange(async(value) => {
|
||||
this.plugin.settings.autoStopTimers = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
new obsidian.Setting(containerEl)
|
||||
.setName(this.plugin.setting_insertlocation_label)
|
||||
.addDropdown(dropdown => {
|
||||
dropdown
|
||||
.addOption('head', this.plugin.setting_insertlocation_head)
|
||||
.addOption('tail', this.plugin.setting_insertlocation_tail)
|
||||
.setValue(this.plugin.settings.timerInsertLocation)
|
||||
.onChange(async(value) => {
|
||||
this.plugin.settings.timerInsertLocation = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TimerPlugin;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"id": "text-block-timer",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"minAppVersion": "1.8.9",
|
||||
"name": "Text Block Timer",
|
||||
"author": "frankthwang",
|
||||
|
|
|
|||
Loading…
Reference in a new issue