Markdown記法を除外する
#UserScript
2025/5/1辺りにCosense公式で入ったのでお役御免になったmrsekut.icon
と思ったが、たまに壊れるのでたまに使っている
GPT-4.iconの回答などを貼り付ける時に整形するのがめんどいので
code:script.js
scrapbox.PopupMenu.addButton({
title: '-md',
onClick: text => {
const transformations = [
removeListMarkers,
removeStrongFormatting,
removeHeadingFormatting,
convertInlineTex,
convertBlockTex,
convertCodeBlocks,
convertTables,
convertLinks,
];
return transformations.reduce((acc, t) => t(acc), text);
},
});
// 行頭のリストマーカー(*, -, +)を消す
function removeListMarkers(text) {
return text
.split(/\n/)
.map(l => l.replace(/^(\s*)\*\-\+ /, '$1'))
.join('\n');
}
// strong(**, __)を外す
function removeStrongFormatting(text) {
return text
.split(/\n/)
.map(l => l.replace(/\*{2}(^*+)\*{2}/g, '$1'))
.map(l => l.replace(/\_{2}(^_+)\_{2}/g, '$1'))
.join('\n');
}
// 見出し(heading)を削除する
function removeHeadingFormatting(text) {
return text
.split(/\n/)
.map(l => l.replace(/(#{1,6}) (.*)/g, '$2'))
.join('\n');
}
// tex記法の書き換え
function convertInlineTex(text) {
return text
.split(/\n/)
// tex記法の\( ... \)を$ ...に書き換える
.map(l => l.replace(/\\\((.*?)\\\)/g, '$ $1 '))
// tex記法の$...$を$ ...に書き換える
.map(l => l.replace(/\$(.*?)\$/g, '$ $1 '))
.join('\n');
}
// 複数行のtex記法の書き換え
function convertBlockTex(text) {
return text
.split(/\n/)
.map(l => l.replace(/^\s*\\\[$/, match => match.replace('\\[', '[$')))
.map(l => l.replace(/^\s*\\\]$/, match => match.replace('\\]', ' ] ')))
.join('\n');
}
// リンク記法の変換
function convertLinks(text) {
return text
.split(/\n/)
// []の中がURLの場合は単に()を削除
.map(l => l.replace(/\[(https?:\/\/^\]+)\]\(\s*https?:\/\/^\s)+\s*\)/g, '$1'))
// 通常のMarkdownリンクをCosense形式に変換
.map(l => l.replace(/\[(^\]+)\]\((https?:\/\/^\s)+)\)/g, '$1 $2'))
.join('\n');
}
code:script.js
// `hoge を code:hoge に変換
function convertCodeBlocks(text) {
return go(text.split("\n")).join("\n");
function go(lines, inCodeBlock = false, rows = [], nestLevel = 0) {
if (lines.length === 0) return [];
const line, ...rest = lines;
const match = line.match(/^(\s*)`(\S*)?/);
const isStart = match && !inCodeBlock;
const isEnd = match && inCodeBlock;
const isInside = !match && inCodeBlock;
const level = isStart ? match1?.length || 0 : nestLevel;
if (isStart) {
const start = {
type: 'start',
text: match2 ?? '_',
nest: level,
};
return [...go(rest, true, start, level)];
}
if (isInside) {
return [...go(rest, true, ...rows, { type: 'inside', text: line }, nestLevel)];
}
if (isEnd) {
const rows_ = ...rows, { type: 'end' };
return ...rows_.map(convert), ...go(rest);
}
return line, ...go(rest);
}
function convert(code) {
switch (code.type) {
case 'start':
const sp = " ".repeat(code.nest);
return ${sp}code:${code.text};
case 'inside':
return ${code.text};
case 'end':
return '';
}
}
}
code:script.js
// table記法をtable:tableに変換
function convertTables(text) {
return go(text.split("\n")).join("\n");
function go(lines, inTable = false, rows = [], nestLevel = 0) {
if (lines.length === 0) return [];
const line, ...rest = lines;
const match = line.match(/^(\s*)\|.*\|$/);
const isTableRow = !!match;
const level = isTableRow ? match1?.length || 0 : nestLevel;
const isEnd = !isTableRow && inTable;
if (isTableRow) {
return go(rest, true, ...rows, line, level);
}
if (isEnd) {
const table = [
table:table,
...rows.map(convert),
];
return [
...table.map(nest(nestLevel)),
...go(lines),
];
}
// otherwise
return line, ...go(rest);
}
function convert(row) {
return row.replace(/\s*\|\s*/g, "\t").replace(/\t$/, "");
}
function nest(level) {
return text => ${" ".repeat(level)}${text};
}
}
/ugo/MarkdownをCosense記法に置換するUserScript