.mdを読み込んでXDのアートボード上に書き出してみる

需要があるか分かりませんが、最近XDでプレゼンをやる人が多い気がしているので、
.mdで作った原稿をもとに、テンプレートにそってページを複製するプラグインがあったら便利かなと思い作ってみました。

テンプレートを作る余裕がなかったので、「[AdobeXDでプレゼン資料のテンプレートを作ったからダウンロードしてね](https://arutega.jp/xd-presen/)」という記事で公開されていたテンプレートを利用させていただきました。

# 準備

ダウンロードしたテンプレートから複製の元となるパーツをシンボル化しておきます。
目印として、シンボルの名前を「h1」や「p」としておいて、
パース後のマークダウンの内容と無理やり関連づけます。（もうちょいいいやり方ありそうな気はしている）
<img width="1440" alt="シンボルを作ってアートボードに配置しておく" src="https://qiita-image-store.s3.amazonaws.com/0/100937/842099fe-0f6d-2792-d7ec-4588e8f9af97.png">
## ソースの構成

今回、マークダウンのパースには[markdown-it](https://github.com/markdown-it/markdown-it)というパッケージを使うので、Webpack前提で開発します。構成は

	replaceText/
	 ├ src
	 │   └ utils.js
	 │   └ main.js
	 ├ manifest.json
	 ├ package.json
	 └ webpack.config.js

です。webpack.config.jsはこんな感じ。

```webpack.config.js
module.exports = {
    entry: "./src/main.js",
    output: {
        path: __dirname,
        filename: 'main.js',
        libraryTarget: "commonjs2"
    },
    devtool: "none",
    externals: {
        uxp: "uxp",
        scenegraph: "scenegraph",
        commands: "commands"
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ["style-loader", "css-loader"]
            }
        ]
    }
};
```

## コード

実際の処理はmain.jsに書いていきます。とりあえず全部のせます。

```main.js
global.setTimeout = function (fn) { fn() };
global.clearTimeout = function () { };

const {Artboard, Text, SymbolInstance} = require("scenegraph");
const commands = require("commands");
const fs = require("uxp").storage.localFileSystem;
const MarkdownIt = require('markdown-it');
const md = new MarkdownIt();
const {
    setText
} = require('./utils');
let symbols = {};

// シンボルを保存しておく
function makeTemplate(children) {
    children.forEach(child => {
        if (child instanceof SymbolInstance) {
            symbols[child.name] = child;
        }
    });
}

async function openFileDialog(selection, root) {
    // ルート上のtemplateアートボードとpageアートボードを探す
    root.children.forEach(child => {
        if (child instanceof Artboard) {
            if (child.name === 'template') {
                makeTemplate(child.children);
            } else if (child.name === 'page') {
                symbols['page'] = child;
            }
        }
    });

    // ファイルの読み込み
    const aFile = await fs.getFileForOpening({ types: ["md"] });

    if (aFile) {
        const contents = await aFile.read();
        const json = md.parse(contents);
        let artboard;

        for (let i = 0, len = json.length; i < len; i++){
            const elm = json[i];

            // hrの時は「page」アートボードを複製
            if (elm.type.includes('hr')) {
                selection.items = symbols['page'];
                commands.duplicate();
                artboard = selection.items[0];
            // 見出しとパラグラフの出力
            } else if (elm.type.includes('heading_open') || elm.type.includes('paragraph_open')) {
                selection.items = symbols[elm.tag];
                commands.duplicate();

                const heading = selection.items[0];
                if (artboard) {
                    selection.items = heading;
                    heading.removeFromParent();
                    setText(artboard, heading, json[i + 1].content);
                }
            }
        }
    }
}

module.exports = {
    commands: {
        importMarkdown: openFileDialog
    }
};
```

# ざっくりとした説明

流れとしては

1. template用のアートボードを作り、そこにシンボルを配置しておく
2. プラグインを実行した時に、「template」アートボードの子要素を探索
3. SymbolInstanceが見つかったら保存しておく
4. ファイルダイアログを開いて、.mdファイルを読み込み
5. 読み込んだファイルをパース
6. 保存しておいたSymbolの名前と、タグの名前がマッチしたら複製して出力する

という感じです。プラグインからシンボルのリストを取得できないので、あらかじめテンプレート用のアートボードにシンボルを配置しています。
今回はマークダウン内にhrがあったら新規のアートボード、つまりページ送りというルールにして作りました。

# 実行結果

実際やってみると、

<img width="1439" alt="配置したテンプレートをもとにマークダウンの内容が書き出されている" src="https://qiita-image-store.s3.amazonaws.com/0/100937/d3cfc02b-0177-4764-4b85-955996790a08.png">

こんな感じです。位置は、、、このあと手動で調整します:joy:
投稿前にいろいろ消したりしていますが、この記事を.mdファイルで書いていたので、読み込んでみました。olリストとかまではまだ処理を書けていないですが、ちゃんと作ればマークダウンからスライド作れちゃうかも…？？

手動でテキストをコピペしていくよりは楽だと思うけど、まだまだ改善の余地はありそうです。ただやはりEdit Contextの制約と、サブツリーを一気に`addChild`できないのはツラさがありますね…。今後のアップデートに期待です。

コードかなり汚いですが、[GitHub](https://github.com/tkskto/XD-MDImport)にあげてます。少しづつ形にしていきたいです。