Skip to content

Instantly share code, notes, and snippets.

@podhmo
Last active February 27, 2025 09:59
Show Gist options
  • Select an option

  • Save podhmo/73b53034f154f1af4c023af9386e7418 to your computer and use it in GitHub Desktop.

Select an option

Save podhmo/73b53034f154f1af4c023af9386e7418 to your computer and use it in GitHub Desktop.
スマホのアウトラインエディタで雑な文章が書きたい
// 行を表す型定義
interface Line {
content: string;
indent: number; // インデントレベル(スペースの数)
isBullet: boolean; // 箇条書きかどうか
isText: boolean; // テキストとして扱うかどうか(@付きなど)
starLevel: number; // * の数(箇条書きのレベル)
}
// Markdown を変換するメイン関数
export function convertMarkdown(input: string): string {
// 入力を改行で分割し、各行を解析
const lines = parseLines(input);
let output = "";
let currentTopLevel = "";
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// インデントなしで箇条書きでない場合、トップレベルのセクションとして扱う
if (line.indent === 0 && !line.isBullet) {
currentTopLevel = line.content;
output += `## ${currentTopLevel}\n\n`;
continue;
}
// 空行の場合、単純に改行を追加
if (line.content.trim() === "" && line.isBullet) {
output += "\n";
continue;
}
// @付きまたはテキストとして扱う場合
if (line.isText) {
output += `${line.content}\n\n`;
continue;
}
// 箇条書きの処理
if (line.isBullet) {
const group = collectGroup(lines, i); // 同じトップレベルの箇条書きを収集
const maxDepth = Math.max(...group.map((l) => l.indent)); // グループ内の最大深度
group.forEach((item, idx) => {
if (item.starLevel > 0) {
// * を使った箇条書きの場合
const bulletIndent = " ".repeat(item.starLevel - 1);
output += `${bulletIndent}- ${item.content}\n`;
} else if (item.indent === maxDepth || item.isText) {
// 最大深度またはテキスト扱いの場合、テキストとして出力
output += `${item.content}\n`;
if (idx < group.length - 1) output += "\n";
} else {
// それ以外はセクションとして出力
const level = Math.min(3 + item.indent, 6); // h3 以降を使用
output += `${"#".repeat(level)} ${item.content}\n\n`;
}
});
i += group.length - 1; // グループ分スキップ
}
}
return output.trim();
}
// 入力を解析して Line オブジェクトの配列に変換
function parseLines(input: string): Line[] {
return input.split("\n").map((line) => {
const trimmed = line.trimStart();
const indent = (line.length - trimmed.length) / 2; // 2スペースを1インデントとみなす
const isBullet = trimmed.startsWith("-");
let content = trimmed.replace(/^-/, "").trim();
let isText = false;
let starLevel = 0;
if (content.startsWith("@")) {
isText = true;
content = content.slice(1).trim();
} else if (content.startsWith("*")) {
const match = content.match(/^\*+/);
starLevel = match ? match[0].length : 0;
content = content.slice(starLevel).trim();
}
return { content, indent, isBullet, isText, starLevel };
});
}
// 同じトップレベルの箇条書きをグループ化
function collectGroup(lines: Line[], startIdx: number): Line[] {
const group: Line[] = [];
const startIndent = lines[startIdx].indent;
for (let i = startIdx; i < lines.length; i++) {
const line = lines[i];
if (!line.isBullet || line.indent < startIndent) break;
if (
line.indent === startIndent && group.length > 0 && !line.isText &&
line.starLevel === 0
) break;
group.push(line);
}
return group;
}
// テスト用の入力
const input1 = `
aaa
- xxx
- yyy
- zzz
`;
const input2 = `
aaa
- @ bbb
- xxx
- iii
- jjj
- kkk
- yyy
- zzz
`;
const input3 = `
aaa
- @ bbb
- xxx
- iii
-
- jjj
-
- kkk
- yyy
- * 111
- ** 222
- ** 333
- * 111
- @ zzz
`;
// テスト実行
console.log("Test 1:");
console.log(convertMarkdown(input1.trim()));
console.log("\nTest 2:");
console.log(convertMarkdown(input2.trim()));
console.log("\nTest 3:");
console.log(convertMarkdown(input3.trim()));
// テストコード
import { assertEquals } from "jsr:@std/assert";
import { convertMarkdown } from "./main.ts";
Deno.test("基本的な箇条書きの変換", () => {
const input = `
aaa
- xxx
- yyy
- zzz
`;
const expected = `
## aaa
xxx
yyy
zzz
`.trim();
const result = convertMarkdown(input);
assertEquals(result, expected);
});
Deno.test("ネストした箇条書きの変換", () => {
const input = `
aaa
- @ bbb
- xxx
- iii
- jjj
- kkk
- yyy
- zzz
`;
const expected = `
## aaa
bbb
### xxx
iii
jjj
kkk
## yyy
## zzz
`.trim();
const result = convertMarkdown(input);
assertEquals(result, expected);
});
Deno.test("空白行と * を使った箇条書きの変換", () => {
const input = `
aaa
- @ bbb
- xxx
- iii
-
- jjj
-
- kkk
- yyy
- * 111
- ** 222
- ** 333
- * 111
- @ zzz
`;
const expected = `
## aaa
bbb
### xxx
iii
jjj
kkk
## yyy
- 111
- 222
- 333
- 111
zzz
`.trim();
const result = convertMarkdown(input);
assertEquals(result, expected);
});
@podhmo
Copy link
Author

podhmo commented Feb 27, 2025

書き換え前のアプローチの方が好きだな。

@podhmo
Copy link
Author

podhmo commented Feb 27, 2025

真面目に扱うことにするか

@podhmo
Copy link
Author

podhmo commented Feb 27, 2025

こういう入力で渡したい

タイトル
- @ ここに文章を書いてみる
- ここは章タイトル
  - @ ここは章の文章
  - ここは節タイトル
    - ここは節の文章
    - ここも同様
  - 節2のタイトル
    - これは節2の文章
  - @ これは実質節2の文章
- @ ここも実質節2の文章
- ここは章2のタイトル
  - ここは章2の文章
  - * 箇条書き
  - ** ネストしたもの
  - ** ネストしたもの2
  - * 箇条書き2
  - ここも章2の文章

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment