Skip to content
Go back

Obsidianのルビ記法プラグインを改造する

  • 先日の続きでJapanese Novel Rubyをいじる
  • 編集モードで装飾が付く場合の挙動が怪しい
    • 編集モードで、ルビ記法の外側に隙間なく装飾を付けても適用されない
      • 本文ルビ**本文《ルビ》**
      • 文の間にルビ記法が挟まれているような場合には、その範囲がまとめて適用される
    • 編集モードで、ルビ記法の中に装飾を付けても適用されなかったり、ルビが付かなかったりする
      • 本文ルビ**本文**《ルビ》
    • 編集モードで、コードの中でもルビが付いてしまう
      • これは好みの範疇かもしれないけど、コードや数式には適用されないほうが便利だと思う
  • そもそもObsidianのMarkdown記法が日本語環境で誤動作しやすいみたい
    • 連続する記号の中に**があると太字記法としてみなされないため、ルビ記法の》**が太字の終端として解釈されない
    • その直後に空白を入れると解消されるけど、日本語として不格好になってしまう
    • Markdown自体がそうなっているみたいなので、こちらで対処できるものではないのかも

装飾方法の変更#

  • オリジナルはwidget decorationを使ったDOM操作でrubyタグを作っているので、Markdown標準の装飾が反映されない仕組みになっている
  • なので、コンテンツにタグを挿入するだけを行うmark decorationで以下のように書き直すことにした
    • 内容を置換えずにrubyタグで囲い、その下で《》NovelRubyInvisibleWidgetで非表示にしている
    • 変数名はプラグインのバージョンによって変わるかもしれない
// var C = class extends y.WidgetType { constructor(e, s, t = !1) { super(); this.body = e; this.ruby = s; this.hide = t } toDOM(e) { let s = document.createElement("ruby"); return this.hide && (s.className = "ruby-hide"), s.appendText(this.body), s.createEl("rt", { text: this.ruby }), s } }; の直下に以下を追記する
var NovelRubyInvisibleWidget = class extends y.WidgetType { toDOM() { let s = document.createElement("span"); return (s.style.width = "0px"), (s.style.display = "inline-block"), (s.style.overflow = "hidden"), s } ignoreEvent() { return false; } };
// ,T&&s.add(v,k,y.Decoration.widget({widget:new C(U,O,n.settings.hideRuby)}))} を以下で置き換える
; if (T) {
    const hasBar = h.groups?.body2 != null;
    const mid = v + U.length + (hasBar ? 1 : 0);
    if (hasBar) {
        s.add(v, v + 1, y.Decoration.replace(new NovelRubyInvisibleWidget()))
    }
    s.add(v, k, y.Decoration.mark({ tagName: "ruby" }))
    s.add(mid, mid + 1, y.Decoration.replace(new NovelRubyInvisibleWidget()))
    s.add(mid, k, y.Decoration.mark({ tagName: "rt" }))
    s.add(k - 1, k, y.Decoration.replace(new NovelRubyInvisibleWidget()))
}
  • ルビ記法の外の装飾はこれで正しく適用されるはず
  • ルビ記法の中の装飾はMarkdown記法とルビ記法をすべて考慮した正規表現を作らなければならないので、省力化のため対応しないことにします
  • このままだと、ルビテキストが太字になったりして読みづらくなるかもれないので、以下のCSSで装飾を解除するのも良いかも
/* ルビテキストの装飾を取り除く */
rt {
    font-weight: normal !important;
    /* color: var(--text-normal) !important; */
}

太字bold**太字《bold》**

斜体italic*|斜体《italic》*

装飾位置での特例対応#

  • 装飾のある位置が何を表しているかは、syntaxTreeをイテレートして範囲内にあるノードの種類を調べると良いらしい
  • ルビ記法を見つけた場所がコードや数式だったら、以下のように装飾を追加しないように、先述のif (T) {...}に被せて使う
    • node.nameの内容は実装次第だけど、codemathで引っ掛ければ概ね大丈夫と思う
    • 変数名はプラグインのバージョンによって変わるかもしれない
//var y=require("@codemirror/view"),V=require("@codemirror/state"),m=require("obsidian") あたりにうまいこと以下を追記する
, cmLanguage = require("@codemirror/language")
// ,T&&s.add(v,k,y.Decoration.widget({widget:new C(U,O,n.settings.hideRuby)}))} を以下で置き換える
if (T) {
    let inside = false;
    cmLanguage.syntaxTree(e.state).iterate({
        v,
        k,
        enter: (node) => {
            if (inside) return false;
            if (["code", "math"].some(s => node.name.includes(s))) {
                if (node.from < k && node.to > v) {
                    inside = true;
                    return false;
                }
            }
            return true;
        }
    });

    if (!inside) {
        const hasBar = h.groups?.body2 != null;
        const mid = v + U.length + (hasBar ? 1 : 0);
        if (hasBar) {
            s.add(v, v + 1, y.Decoration.replace(new NovelRubyInvisibleWidget()))
        }
        s.add(v, k, y.Decoration.mark({ tagName: "ruby" }))
        s.add(mid, mid + 1, y.Decoration.replace(new NovelRubyInvisibleWidget()))
        s.add(mid, k, y.Decoration.mark({ tagName: "rt" }))
        s.add(k - 1, k, y.Decoration.replace(new NovelRubyInvisibleWidget()))
    }
}
  • syntaxTreeの走査を事前に行って、必要な情報だけ取り出しておくほうが結果的に速いかもしれない

インラインコード《inline code》

数式《すうしき》\text{数式《すうしき》}

|数式ブロック《すうしき—》\text{|数式ブロック《すうしき—》}
コードブロック《code block》