日付が変わるごとに見出しを入れるJavaScriptマクロ(サクラエディタ)

サクラエディタのJavaScriptマクロで、ツイートの絶対年月日がブレイクする位置に区切りの見出しをつける

サクラエディタのJavaScriptマクロで、ツイートの絶対年月日がブレイクする位置に区切りの見出しをつける

// datekaiseki.js
// 全選択してテキストを取得
Editor.SelectAll();
var text = Editor.GetSelectedString();

// 改行コードで分割して1行ずつ処理
var lines = text.split(/\r?\n/);
var result = [];

// 英語の月名を数値に変換するマッピング
var monthMap = {
    "Jan": 0, "Feb": 1, "Mar": 2, "Apr": 3, "May": 4, "Jun": 5,
    "Jul": 6, "Aug": 7, "Sep": 8, "Oct": 9, "Nov": 10, "Dec": 11
};

// 月の数値から英語の短縮名へ変換する配列
var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

var lastDate = null; // 直前の行のDateオブジェクトを記録
var firstDateForLine = null; // 各行の中で最初に見つかった日時(日付見出し用)

for (var i = 0; i < lines.length; i++) {
    var line = lines[i];
    firstDateForLine = null;
    
    // 「·」または「&#183;」のどちらにもマッチするように正規表現を修正
    var timeRegex = /([A-Z][a-z]{2})\s+(\d{1,2}),\s+(\d{4})\s*(?:\·|&#183;)\s*(\d{1,2}):(\d{2})\s*(AM|PM)\s*UTC/gi;
    
    line = line.replace(timeRegex, function(match, monthEn, dayStr, yearStr, hourStr, minStr, ampmStr) {
        var dayNum  = parseInt(dayStr, 10);
        var yearNum = parseInt(yearStr, 10);
        var hourNum = parseInt(hourStr, 10);
        var minNum  = parseInt(minStr, 10);
        var ampm    = ampmStr.toUpperCase();
        var monthNum = monthMap[monthEn];
        
        if (monthNum === undefined) {
            return match; // マッチしない場合はそのまま返す
        }
        
        // 12時間表記を24時間表記に変換
        if (ampm === "PM" && hourNum < 12) {
            hourNum += 12;
        } else if (ampm === "AM" && hourNum === 12) {
            hourNum = 0;
        }
        
        // UTCとしてDateオブジェクトを作成し、+9時間(JST)を加算する
        var utcMillis = Date.UTC(yearNum, monthNum, dayNum, hourNum, minNum, 0);
        var jstDate = new Date(utcMillis + (9 * 60 * 60 * 1000));
        
        var jstYear = jstDate.getUTCFullYear();
        var jstMonth = jstDate.getUTCMonth();
        var jstDay = jstDate.getUTCDate();
        var jstHour24 = jstDate.getUTCHours();
        var jstMin = jstDate.getUTCMinutes();
        
        // 24時間表記から再び12時間表記(AM/PM)とJST文字列を復元
        var jstAmpm = jstHour24 >= 12 ? "PM" : "AM";
        var jstHour12 = jstHour24 % 12;
        if (jstHour12 === 0) jstHour12 = 12;
        
        var minStrFormatted = ("0" + jstMin).slice(-2);
        
        // 置換用の新しい日時文字列を生成
        var newDateTimeStr = monthNames[jstMonth] + " " + jstDay + ", " + jstYear + " · " + jstHour12 + ":" + minStrFormatted + " " + jstAmpm + " JST";
        
        // 行内で最初に見つかった日時を日付見出しの基準として保持
        if (firstDateForLine === null) {
            firstDateForLine = new Date(jstYear, jstMonth, jstDay);
        }
        
        return newDateTimeStr;
    });
    
    // 日付見出しの判定・挿入処理
    var targetDateForHeading = firstDateForLine;
    
    if (targetDateForHeading === null) {
        // 時刻パターンに一致しない行は日付のみのパターンがないかフォールバック確認
        var fallbackMatch = line.match(/([A-Z][a-z]{2})\s+(\d{1,2}),\s+(\d{4})/);
        if (fallbackMatch) {
            var monthEn = fallbackMatch[1];
            var dayNum  = parseInt(fallbackMatch[2], 10);
            var yearNum = parseInt(fallbackMatch[3], 10);
            var monthNum = monthMap[monthEn];
            if (monthNum !== undefined) {
                targetDateForHeading = new Date(yearNum, monthNum, dayNum);
            }
        }
    }
    
    if (targetDateForHeading !== null) {
        var currentDate = targetDateForHeading;
        if (lastDate === null) {
            var yearStr = currentDate.getFullYear();
            var monthStr = ("0" + (currentDate.getMonth() + 1)).slice(-2);
            result.push("## " + yearStr + "-" + monthStr + "-");
        } else {
            var diffTime = currentDate.getTime() - lastDate.getTime();
            var diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
            if (diffDays > 0) {
                for (var d = 0; d < diffDays; d++) {
                    var targetDate = new Date(lastDate.getTime());
                    targetDate.setDate(targetDate.getDate() + d + 1);
                    var yearStr = targetDate.getFullYear();
                    var monthStr = ("0" + (targetDate.getMonth() + 1)).slice(-2);
                    if (d < diffDays - 1) {
                        result.push("## " + yearStr + "-" + monthStr + "-|なし");
                    } else {
                        result.push("## " + yearStr + "-" + monthStr + "-");
                    }
                }
            }
        }
        lastDate = currentDate;
    }
    
    // 処理済みの行を追加
    result.push(line);
}

// 編集中のテキストを更新
Editor.InsText(result.join("\r\n"));

// 選択状態を解除してクリップボードにコピー
Editor.SelectAll(0);
Editor.Copy(0);

【欠点1】:月初あたりの初回ツイート日以前において[ツイートなし=見出しなし]は人間が判断して補う必要がある

  • 例えば、## 2019-09-06が初回ツイートなしだとしたら、[## 2019-09-(01)|なし][## 2019-09-(02)|なし][## 2019-09-(03)|なし][## 2019-09-(04)|なし][## 2019-09-(05)|なし]を人間が手動で補う。
  • (01)~(05)は、連番マクロhiduke.jsを使うなら、打ち込む必要はない。[## 2019-09-]を5行分挿入しておくだけでよい。

【欠点2】:月末あたりの最終ツイート日以降において[ツイートなし=見出しなし]は人間が判断して補う必要がある

  • 例えば、## 2019-09-30がツイートなしだとしたら、## 2019-09-29があれば、そこで見出しが止まっているので、## 2019-09-30を人間が手で補う必要がある。
  • 連番マクロhiduke.jsを使うなら、[## 2019-09-]までしか打ち込む必要がない。