🔖

dynalist→Markdown→Typoraでプロットを整形して爆速で書き始める

2024/06/01に公開

https://dynalist.io/

私はCoCシナリオのプロットをDynalistで書いている。

これがまた楽で、これだけでCoCシナリオを書くときもある。
だが大体はTyporaと併用している。

そんなときに、Dynalistで書いたプロットをそのままTyporaに貼り付けて、シナリオを書く煩わしさを少し軽減できないかと考えた。

https://typora.io/

https://bloglab.naenote.net/entry/dynalist-markdown-gutenberg
https://sacalyo.hatenablog.com/entry/2019/12/03/214741
https://zenn.dev/laiso/articles/466da1b5c20d98

実装

# シティ
## A パチンコ屋
 常連かも 

> # 《目星》
>
> こうやって下げたところが技能判定になるよ

```
【共有メモ】
こうやって下げると共有メモになるよ
```

こういうのがTyporaのソースコードで表示される。ぶっちゃけTyporaの画面に貼り付けたらそのままいい感じに表示される。
特別な条件で変換されるのは以下の条件で書かれたもの。

  • H1~3のHeadding
  • 《 から始まるネストされた文章
  • 【 から始まるネストされた文章

これは、dynalistの出力方式「Formated」を利用して、出力されるものを弄っている。
violentMonkeyなどのブラウザ拡張機能で実装することができる。

violentMonkeyをブラウザにいれる

https://violentmonkey.github.io/

これを自分のブラウザにいれる。私はChromeなので拡張機能をいれた。
多分FireFoxにもある。

dynalist to My markdown

// ==UserScript==
// @name         Dynalist to mymarkdown
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Format exported content in Dynalist according to specific rules.
// @author       tthr
// @match        https://dynalist.io/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    function modifyContent() {
        var contentSection = document.querySelector('.export-content-section.mod-formatted.u-use-pref-font.is-selected');
        if (contentSection) {
            var liElements = contentSection.querySelectorAll('li');

            liElements.forEach(function (li) {
                // 《で始まる<li>の中でリストが入れ子になっている場合、Markdown形式のBlockquoteに変換
                if (li.innerHTML.trim().startsWith('《') && li.querySelector('ul')) {
                    li.innerHTML = '\n' + formatBlockquoteContent(li.innerHTML);
                }
            });

liElements.forEach(function (li) {
    // 【で始まる<li>の中でリストが入れ子になっている場合、Markdown形式のcodeblockに変換
    if (li.innerHTML.trim().startsWith('【') && li.querySelector('ul')) {
        li.innerHTML = '\n' + formatCodeblockContent(li.innerHTML);
    }
});

// 装飾のないプレーンなテキストの行頭に全角スペースを追加
liElements.forEach(function (li) {
    // 特定の装飾を持たないテキストにのみ適用
    if (!li.querySelector('h1, h2, h3, blockquote, pre, ul')) {
        // テキストの各行に全角スペースを追加
        var lines = li.innerHTML.split('\n');
        var modifiedLines = lines.map(function(line) {
            // 既に全角スペースで始まっている場合は変更しない
            if (!line.startsWith(' ') && line.trim() !== '') {
                return ' ' + line;
            }
            return line;
        });
        li.innerHTML = modifiedLines.join('\n');
    }
});

  // <blockquote>を処理した後のすべてのテキストにMarkdown形式の改行を追加
  liElements.forEach(function (li) {
      if (!li.querySelector('h1, h2, h3, blockquote,codeblock')) {
          li.innerHTML += '\n\n';
      }
  });
        // <ul>と<li>タグを消去
        cleanUp(contentSection);
    }
}

// コードブロック *シナリオ内共有メモ
// 【で始まる+下にネストされた文章がある場合
// ネストが解除されるまでコードブロックとみなす
function formatCodeblockContent(content) {
    var headingMatch = content.match(/([^]+)/);
    var headingLine = headingMatch ? headingMatch[0] : '';
    var bodyText = content.substring(headingLine.length);
    var formattedHeading = headingLine ? `<h1>${headingLine}</h1>` : '';
    var formattedBody = bodyText.split('\n').map(line => `${line}`).join('\n');
    return `<pre><code>${formattedHeading}${formattedBody}</code></pre>`;
}

// Blockquote *技能判定
// 《で始まる+下にネストされた文章がある場合
// ネストが解除されるまで引用ブロックとみなす。
// 出力は「> #《技能名》」の引用ブロック入れ子で表示される
function formatBlockquoteContent(content) {
    var headingMatch = content.match(/([^]+)/);
    var headingLine = headingMatch ? headingMatch[0] : '';
    var bodyText = content.substring(headingLine.length);
    var formattedHeading = headingLine ? `<h1>${headingLine}</h1>` : '';
    var formattedBody = bodyText.split('\n').map(line => `${line}`).join('\n');
    return `<blockquote>${formattedHeading}${formattedBody}</blockquote>`;
}

function formatContent(content) {
    var headingMatch = content.match(/([^]+)/);
    var headingLine = headingMatch ? headingMatch[0] : '';
    var bodyText = content.substring(headingLine.length);
    bodyText = processNestedLists(bodyText);
    return `# ${headingLine}${bodyText}`;
}

// 入れ子リストを処理し、適切に改行を挿入する関数
function processNestedLists(text) {
    text = text.replace(/<\/li><li>/g, '</li><li>');
    text = text.replace(/<ul>/g, '<ul>');
    text = text.replace(/<\/ul>/g, '</ul>');
    return text;
}

// <ul>と<li>タグを消去する関数
function cleanUp(element) {
    element.querySelectorAll('ul, li').forEach(function (el) {
        var breakElement = document.createElement('br');
        el.parentNode.insertBefore(breakElement, el);
        while (el.firstChild) {
            el.parentNode.insertBefore(el.firstChild, el);
        }
        el.parentNode.removeChild(el);
    });
}

// MutationObserverの設定
var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
            var target = mutation.target;
            if (target.classList.contains('export-nav-item') && target.classList.contains('is-selected')) {
                modifyContent();
            }
        }
    });
});

  var targetNode = document.querySelector('body');
  if (targetNode) {
      observer.observe(targetNode, { attributes: true, subtree: true });
  }
})();

死ぬほど楽になった

CoCシナリオを書くのに、いちいちDynalistで書いたプロットメモをTyporaに貼り付けて、コードを適用させながら書いていくのが死ぬほど面倒だったのでやった。

おかげでこれで簡単なシナリオを書いてTyporaに貼り付けて、エクスポートするとささっとシナリオPDFやテキストが仕上がるようになった。

楽だ……………………。

Discussion