実績時間計算+ソート+現在タスク選択
実績時間計算+ソート+現在タスク選択2
2025/04/22
1Writer
code:js
/* 実績時間 */
function formatTasks(text) {
const taskRegex = /^(-\s+)?(\d{2}:\d{2})-(\d{2}:\d{2})(\s\((\d+(:\d{2})?)\))?\s(.*)$/gm;
function calculateDuration(startTime, endTime) {
const startHour, startMinute = startTime.split(':').map(Number);
const endHour, endMinute = endTime.split(':').map(Number);
let durationMinutes = (endHour * 60 + endMinute) - (startHour * 60 + startMinute);
if (durationMinutes < 0) {
durationMinutes += 24 * 60; // 翌日にまたがる場合
}
// const hours = Math.floor(durationMinutes / 60);
// const minutes = durationMinutes % 60;
return durationMinutes;
}
return text.replace(taskRegex, (match, prefix, startTime, endTime, _existing, _1, _2, taskDescription) => {
const duration = (${calculateDuration(startTime, endTime)});
const formattedPrefix = prefix || "";
return ${formattedPrefix}${startTime}-${endTime} ${duration} ${taskDescription};
});
}
// 現在のファイルの内容を取得
let text = editor.getText();
// 実績時間を追加
let result = formatTasks(text);
// 変更を適用
editor.setText(result);
// ユーザーに通知
// ui.hudSuccess("タスクのフォーマットを更新しました!");
/* ソート */
// ユーザーのテキストを取得
text = editor.getText();
// 行ごとに分割
const lines = text.split("\n");
// セクション区切りの検出パターン("■"で始まる行)
const sectionDelimiter = /^■/;
// 時刻を含む行を検出するパターン(@HH:MM形式の時刻は除外)
const timePattern = /(?<!@)\b\d{1,2}:\d{2}\b/;
let sections = [];
let currentSection = { header: null, linesWithTime: [], linesWithoutTime: [] };
let beforeFirstSection = [];
let sectionMap = {}; // 時刻なし行とセクションのマッピング
// フラグ:最初のセクションが見つかったか
let foundFirstSection = false;
// 行をループしてセクションごとにまとめる
lines.forEach(line => {
if (sectionDelimiter.test(line)) {
foundFirstSection = true;
// 新しいセクションが始まる場合、前のセクションを保存
if (currentSection.header !== null) {
sections.push(currentSection);
}
// 新しいセクション開始
currentSection = { header: line, linesWithTime: [], linesWithoutTime: [] };
} else if (foundFirstSection) {
// 時刻があるかないかで分類
if (timePattern.test(line)) {
currentSection.linesWithTime.push(line);
} else {
currentSection.linesWithoutTime.push(line);
// マッピングに追加(時刻なし行をセクションに紐付け)
if (!sectionMapcurrentSection.header) {
sectionMapcurrentSection.header = [];
}
sectionMapcurrentSection.header.push(line);
}
} else {
// 最初のセクションより前の行をそのまま保持
beforeFirstSection.push(line);
}
});
// 最後のセクションを保存
if (currentSection.header !== null) {
sections.push(currentSection);
}
// セクションと時刻ありの行をすべて集めて一つのリストに
let allItemsWithTime = [];
sections.forEach(section => {
if (timePattern.test(section.header)) {
allItemsWithTime.push({ line: section.header, isHeader: true });
}
section.linesWithTime.forEach(line => {
allItemsWithTime.push({ line: line, isHeader: false });
});
});
// 全ての時刻あり項目を時刻順にソート
allItemsWithTime.sort((a, b) => {
const timeA = a.line.match(timePattern)0;
const timeB = b.line.match(timePattern)0;
return timeA.localeCompare(timeB);
});
// ソート結果をセクションごとに整理して保持
let sortedSections = [];
let currentSortedSection = null;
allItemsWithTime.forEach(item => {
if (item.isHeader) {
// 新しいセクションが開始
if (currentSortedSection !== null) {
sortedSections.push(currentSortedSection);
}
currentSortedSection = { header: item.line, lines: [] };
} else {
// セクションに時刻あり行を追加
if (currentSortedSection !== null) {
currentSortedSection.lines.push(item.line);
}
}
});
// 最後のセクションも追加
if (currentSortedSection !== null) {
sortedSections.push(currentSortedSection);
}
// 時刻なしの行を、元のセクションに基づいて追加
sortedSections.forEach(section => {
const header = section.header;
if (sectionMapheader) {
// マッピングに基づいて、時刻なしの行をセクションの末尾に追加
section.lines.push(...sectionMapheader);
}
});
// セクションごとのリストを結合して元のノートに戻す
result = ...beforeFirstSection; // 最初のセクションより前の行を結果に追加
sortedSections.forEach(section => {
result.push(section.header);
result.push(...section.lines);
});
// 結果をテキストに戻してエディターに設定
editor.setText(result.join("\n"));
// 時刻を含む最終行を選択
const newContent = editor.getText();
const newLines = newContent.split('\n');
let position = 0;
let lastTimeLineIndex = -1;
let lastTimeLinePos = 0;
for (let i = 0; i < newLines.length; i++) {
const line = newLinesi;
const lineLengthWithNewline = line.length + 1; // +1 for newline
// ■で始まる行は無視
if (line.startsWith('■')) {
position += lineLengthWithNewline;
continue;
}
if (timePattern.test(line)) {
lastTimeLineIndex = i;
lastTimeLinePos = position;
}
position += lineLengthWithNewline;
}
if (lastTimeLineIndex !== -1) {
const line = newLineslastTimeLineIndex;
const lineEndPosition = lastTimeLinePos + line.length;
editor.setSelectedRange(lineEndPosition, 0); // 行末にカーソルをセット
}
Obsidian
ActiveTask.md
code:js
<%*
function formatTasks(text) {
const taskRegex = /^(-\s+)?(\d{2}:\d{2})-(\d{2}:\d{2})(\s\((\d+(:\d{2})?)\))?\s(.*)$/gm;
function calculateDuration(startTime, endTime) {
const startHour, startMinute = startTime.split(':').map(Number);
const endHour, endMinute = endTime.split(':').map(Number);
let durationMinutes = (endHour * 60 + endMinute) - (startHour * 60 + startMinute);
if (durationMinutes < 0) {
durationMinutes += 24 * 60; // 翌日にまたがる場合
}
return durationMinutes;
}
return text.replace(taskRegex, (match, prefix, startTime, endTime, _existing, _1, _2, taskDescription) => {
const duration = (${calculateDuration(startTime, endTime)});
const formatted_prefix = prefix ? prefix || "" : "";
return ${formatted_prefix}${startTime}-${endTime} ${duration} ${taskDescription};
});
}
const editor = app.workspace.activeLeaf.view.editor;
// 現在のカーソル行を取得
const cursorLine = editor.getCursor().line;
// 現在のファイル内容を取得
const text = tp.file.content;
// 実績時間を追加
const result = formatTasks(text);
// 現在のファイル内容を上書き
editor.setValue(result);
// カーソル位置を戻す
const currentLineText = editor.getLine(cursorLine);
const cursorPosition = currentLineText.length;
editor.setCursor({ line: cursorLine, ch: cursorPosition });
function sortTasksByTime() {
const activeFile = app.workspace.getActiveFile();
if (!activeFile) {
new Notice('ファイルが開かれていません');
return;
}
// ユーザーのテキストを取得
app.vault.read(activeFile).then(text => {
// 行ごとに分割
const lines = text.split("\n");
// セクション区切りの検出パターン("■"で始まる行)
const sectionDelimiter = /^■.*\d{1,2}:\d{2}/;
// 時刻を含む行を検出するパターン(@HH:MM形式の時刻は除外)
const timePattern = /(?<!@)\b\d{1,2}:\d{2}\b/;
let sections = [];
let currentSection = { header: null, linesWithTime: [], linesWithoutTime: [] };
let beforeFirstSection = [];
let sectionMap = {}; // 時刻なし行とセクションのマッピング
// フラグ:最初のセクションが見つかったか
let foundFirstSection = false;
// 行をループしてセクションごとにまとめる
lines.forEach(line => {
if (sectionDelimiter.test(line)) {
foundFirstSection = true;
// 新しいセクションが始まる場合、前のセクションを保存
if (currentSection.header !== null) {
sections.push(currentSection);
}
// 新しいセクション開始
currentSection = { header: line, linesWithTime: [], linesWithoutTime: [] };
} else if (foundFirstSection) {
// 時刻があるかないかで分類
if (timePattern.test(line)) {
currentSection.linesWithTime.push(line);
} else {
currentSection.linesWithoutTime.push(line);
// マッピングに追加(時刻なし行をセクションに紐付け)
if (!sectionMapcurrentSection.header) {
sectionMapcurrentSection.header = [];
}
sectionMapcurrentSection.header.push(line);
}
} else {
// 最初のセクションより前の行をそのまま保持
beforeFirstSection.push(line);
}
});
// 最後のセクションを保存
if (currentSection.header !== null) {
sections.push(currentSection);
}
// セクションと時刻ありの行をすべて集めて一つのリストに
let allItemsWithTime = [];
sections.forEach(section => {
if (timePattern.test(section.header)) {
allItemsWithTime.push({ line: section.header, isHeader: true });
}
section.linesWithTime.forEach(line => {
allItemsWithTime.push({ line: line, isHeader: false });
});
});
// 全ての時刻あり項目を時刻順にソート
allItemsWithTime.sort((a, b) => {
const timeA = a.line.match(timePattern)0;
const timeB = b.line.match(timePattern)0;
return timeA.localeCompare(timeB);
});
// ソート結果をセクションごとに整理して保持
let sortedSections = [];
let currentSortedSection = null;
allItemsWithTime.forEach(item => {
if (item.isHeader) {
// 新しいセクションが開始
if (currentSortedSection !== null) {
sortedSections.push(currentSortedSection);
}
currentSortedSection = { header: item.line, lines: [] };
} else {
// セクションに時刻あり行を追加
if (currentSortedSection !== null) {
currentSortedSection.lines.push(item.line);
}
}
});
// 最後のセクションも追加
if (currentSortedSection !== null) {
sortedSections.push(currentSortedSection);
}
// 時刻なしの行を、元のセクションに基づいて追加
sortedSections.forEach(section => {
const header = section.header;
if (sectionMapheader) {
// マッピングに基づいて、時刻なしの行をセクションの末尾に追加
section.lines.push(...sectionMapheader);
}
});
// セクションごとのリストを結合して元のノートに戻す
let result = ...beforeFirstSection; // 最初のセクションより前の行を結果に追加
sortedSections.forEach(section => {
result.push(section.header);
result.push(...section.lines);
});
// 結果をテキストに戻してノートに保存
app.vault.modify(activeFile, result.join("\n")).then(() => {
// ===== カーソルを未完了のチェックボックスに合わせる =====
// エディタ内で hh:mm から始まる最後の行を探し、カーソルをその行末に移動する関数
function moveToLastTimeLine() {
const editor = this.app.workspace.activeLeaf.view.sourceMode.cmEditor;
const doc = editor.getDoc();
const lineCount = doc.lineCount();
// 下から上にループ
for (let i = lineCount - 1; i >= 0; i--) {
const lineText = doc.getLine(i);
// ■で始まる行は無視
if (lineText.startsWith('■')) {
continue;
}
if (timePattern.test(lineText)) {
const lineEnd = lineText.length;
doc.setCursor({ line: i, ch: lineEnd });
return;
}
}
// 見つからなかった場合
console.log("hh:mm から始まる行が見つかりませんでした");
}
// コードを実行
moveToLastTimeLine();
});
});
}
// 関数を実行
sortTasksByTime();
%>