選択範囲をMarkdown記法に変換してclip boardにcopyするPopupMenu
script.jsimport { ScrapboxParser } from "/api/code/takker/scrapbox-parser.min.js/parser.js";
scrapbox.PopupMenu.addButton({
title: "+md",
onClick: (text) => {
try {
const blocks = ScrapboxParser.parse(text, { hasTitle: false });
const topIndentLevel = Math.min(...blocks.map((block) => block.indent));
navigator.clipboard.writeText(
blocks.map((block) => convertSb2Md(block, topIndentLevel)).join("\n")
);
} catch (e) {
alert(`Failed to copy:\n${JSON.stringify(e)}`);
}
},
});
script.js// @ts-check
/**
* @typedef {import("../scrapbox-parser/mod.ts").Block} Block
* @typedef {import("../scrapbox-parser/mod.ts").Table} Table
* @typedef {import("../scrapbox-parser/mod.ts").Line} Line
* @typedef {import("../scrapbox-parser/mod.ts").Node} NodeType
* @typedef {import("../scrapbox-jp%2Ftypes/userscript.ts").Scrapbox} Scrapbox
*/
/** Scrapbox記法をMarkdown記法に変える
*
* @param {Block} block
* @param {number} topIndentLevel
* @return {string}
*/
function convertSb2Md(block, topIndentLevel) {
switch (block.type) {
case 'title':
return ''; // タイトルは選択範囲に入らないので無視
case 'codeBlock':
return [
block.fileName,
`\n\`\`\`${getFileType(block.fileName)}`,
block.content,
'```\n',
].join('\n');
case 'table':
return convertTable(block);
case 'line':
return convertLine(block, topIndentLevel);
}
}
/** Table記法の変換
*
* @param {Table} table
* @return {string}
*/
function convertTable(table) {
const maxCol = Math.max(...table.cells.map(row => row.length));
return [
table.fileName,
...table.cells.flatMap((row, i) => [
`| ${row
.map(column => column.map(node => convertNode(node)).join(''))
.join(' | ')} |`,
...(i === 0 ? [`|${' -- |'.repeat(maxCol)}`] : []),
]),
].join('\n');
}
const INDENT = ' '; // インデントに使う文字
script.js/** 行の変換
*
* @param {Line} line
* @param {number} topIndentLevel
* @return {string}
*/
function convertLine(line, topIndentLevel) {
const content = line.nodes
.map(node => convertNode(node, { section: line.indent === topIndentLevel }))
.join('')
.trim();
if (content === '') return ''; // 空行はそのまま返す
// リストを作る
if (line.indent === topIndentLevel) return content; // トップレベルの行はインデントにしない
let result = INDENT.repeat(line.indent - topIndentLevel - 1);
if (!/^\d+\. /.test(content)) result += '- '; // 番号なしの行は`-`を入れる
return result + content;
}
script.js/** Nodeを変換する
*
* @param {NodeType} node
* @param {{section?:boolean}} [init]
* @return {string}
*/
function convertNode(node, init) {
const { section = false } = init ?? {};
switch (node.type) {
case 'quote':
return `> ${node.nodes.map(node => convertNode(node)).join('')}`;
case 'helpfeel':
return `\`? ${node.text}\``;
case 'image':
case 'strongImage':
return ``;
case 'icon':
case 'strongIcon':
// 仕切り線だけ変換する
return ['/icons/hr', '/scrapboxlab/hr'].includes(node.path) ? '---' : '';
case 'strong':
return `**${node.nodes.map(node => convertNode(node)).join('')}**`;
case 'formula':
return `$${node.formula}$`;
case 'decoration': {
let result = node.nodes.map(node => convertNode(node)).join('');
if (node.decos.includes('/')) result = `*${result}*`;
// 見出しの変換
// お好みで変えて下さい
if (section) {
if (node.decos.includes('*-3')) result = `# ${result}\n`;
if (node.decos.includes('*-2')) result = `## ${result}\n`;
if (node.decos.includes('*-1')) result = `### ${result}\n`;
} else {
if (node.decos.some(deco => /\*-/.test(deco[0]))) {
result = `**${result}**`;
}
}
if (node.decos.includes('~')) result = `~~${result}~~`;
return result;
}
case 'code':
return `\`${node.text}\``;
case 'commandLine':
return `\`${node.symbol} ${node.text}\``;
case 'link':
switch (node.pathType) {
case 'root':
return `[${node.href}](https://scrapbox.io${encodeURI(node.href)})`;
case 'relative':
//@ts-ignore declare宣言が使えないため、`scrapbox`に型定義をつけられない
return `[${node.href}](https://scrapbox.io/${
scrapbox.Project.name
}/${encodeURI(node.href)})`;
default:
return node.content === ''
? ` ${node.href} `
: `[${node.content}](${encodeURI(node.href)})`;
}
case 'googleMap':
return `[${node.place}](${node.url})`;
case 'hashTag':
//@ts-ignore declare宣言が使えないため、`scrapbox`に型定義をつけられない
return `[#${node.href}](https://scrapbox.io/${scrapbox.Project.name}/${node.href})`;
case 'numberList':
return `${node.number}. ${node.nodes
.map(node => convertNode(node))
.join('')}`;
case 'blank':
case 'plain':
return node.text;
}
}