md-lv 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.2.6] - 2026-02-05
9
+
10
+ ### Fixed
11
+
12
+ - **YAML Front Matter**: Fixed support for YAML multiline string notation (`>-`, `>`, `|`, `|-`)
13
+ - Folded scalars (newlines replaced with spaces)
14
+ - Literal scalars (newlines preserved)
15
+
16
+ ## [1.2.5] - 2026-02-05
17
+
18
+ ### Added
19
+
20
+ - **Sidebar Scroll Position**: Sidebar now preserves scroll position when navigating between files
21
+ - **Sticky Breadcrumbs**: Path display (breadcrumbs) now stays fixed at the top when scrolling
22
+
8
23
  ## [1.2.3] - 2026-01-31
9
24
 
10
25
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md-lv",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "description": "Serve Markdown files as HTML with live features - syntax highlighting, Mermaid diagrams, and MathJax formulas",
5
5
  "type": "module",
6
6
  "engines": {
package/public/js/app.js CHANGED
@@ -38,15 +38,56 @@ function parseFrontmatter(markdown) {
38
38
  return { frontmatter: null, body };
39
39
  }
40
40
 
41
- // YAML パース(key: value 形式と配列形式に対応)
41
+ // YAML パース(key: value 形式、配列形式、複数行文字列に対応)
42
42
  const frontmatter = {};
43
43
  const lines = yamlContent.split('\n');
44
44
  let currentKey = null;
45
+ let multilineMode = null; // 'folded' (>) or 'literal' (|)
46
+ let multilineBuffer = [];
47
+ let multilineStripTrailing = false;
48
+
49
+ for (let i = 0; i < lines.length; i++) {
50
+ const line = lines[i];
51
+
52
+ // 複数行モード中の処理
53
+ if (multilineMode && currentKey) {
54
+ // インデントされた行は複数行コンテンツの一部
55
+ if (line.match(/^[ \t]+\S/) || line.trim() === '') {
56
+ // インデントを除去して保存
57
+ const indentMatch = line.match(/^([ \t]+)(.*)$/);
58
+ if (indentMatch) {
59
+ multilineBuffer.push(indentMatch[2]);
60
+ } else if (line.trim() === '') {
61
+ multilineBuffer.push('');
62
+ }
63
+ continue;
64
+ } else {
65
+ // インデントされていない行は複数行モードの終了
66
+ let result = multilineBuffer.join('\n');
67
+
68
+ if (multilineMode === 'folded') {
69
+ // 改行を空白に置き換え(空行は段落区切りとして保持)
70
+ result = result.replace(/\n(?!\n)/g, ' ').replace(/\n\n+/g, '\n\n');
71
+ }
72
+
73
+ // 末尾の改行を処理
74
+ if (multilineStripTrailing) {
75
+ result = result.replace(/\n+$/, '');
76
+ }
77
+
78
+ frontmatter[currentKey] = result.trim();
79
+ multilineMode = null;
80
+ multilineBuffer = [];
81
+ multilineStripTrailing = false;
82
+ // 現在の行を再処理するためにインデックスを戻す
83
+ i--;
84
+ continue;
85
+ }
86
+ }
45
87
 
46
- for (const line of lines) {
47
88
  // 配列アイテム(- value)のチェック
48
89
  const arrayItemMatch = line.match(/^[ \t]+-[ \t]+(.*)$/);
49
- if (arrayItemMatch && currentKey) {
90
+ if (arrayItemMatch && currentKey && !multilineMode) {
50
91
  const itemValue = parseYamlValue(arrayItemMatch[1]);
51
92
  if (!Array.isArray(frontmatter[currentKey])) {
52
93
  frontmatter[currentKey] = [];
@@ -63,15 +104,37 @@ function parseFrontmatter(markdown) {
63
104
 
64
105
  if (key) {
65
106
  currentKey = key;
66
- if (value) {
107
+
108
+ // 複数行文字列の開始記号をチェック
109
+ if (value === '>' || value === '>-' || value === '|' || value === '|-') {
110
+ multilineMode = (value.startsWith('>') ? 'folded' : 'literal');
111
+ multilineStripTrailing = value.endsWith('-');
112
+ multilineBuffer = [];
113
+ } else if (value) {
67
114
  // 同じ行に値がある場合
68
115
  frontmatter[key] = parseYamlValue(value);
116
+ currentKey = key; // 配列の可能性のためにキーを保持
69
117
  }
70
- // 値が空の場合は次の行で配列として処理される可能性がある
118
+ // 値が空の場合は次の行で配列または複数行として処理される
71
119
  }
72
120
  }
73
121
  }
74
122
 
123
+ // ファイル終端で複数行モードが残っている場合
124
+ if (multilineMode && currentKey && multilineBuffer.length > 0) {
125
+ let result = multilineBuffer.join('\n');
126
+
127
+ if (multilineMode === 'folded') {
128
+ result = result.replace(/\n(?!\n)/g, ' ').replace(/\n\n+/g, '\n\n');
129
+ }
130
+
131
+ if (multilineStripTrailing) {
132
+ result = result.replace(/\n+$/, '');
133
+ }
134
+
135
+ frontmatter[currentKey] = result.trim();
136
+ }
137
+
75
138
  return { frontmatter, body };
76
139
  }
77
140
 
@@ -55,6 +55,31 @@ function removeExpandedDirectory(path) {
55
55
  saveExpandedDirectories(filtered);
56
56
  }
57
57
 
58
+ /**
59
+ * サイドバーのスクロール位置をlocalStorageに保存
60
+ */
61
+ function saveSidebarScrollPosition(scrollTop) {
62
+ try {
63
+ localStorage.setItem('sidebar-scroll-position', scrollTop.toString());
64
+ } catch {
65
+ // Storage full or unavailable
66
+ }
67
+ }
68
+
69
+ /**
70
+ * サイドバーのスクロール位置をlocalStorageから取得して復元
71
+ */
72
+ function restoreSidebarScrollPosition(element) {
73
+ try {
74
+ const savedScrollTop = localStorage.getItem('sidebar-scroll-position');
75
+ if (savedScrollTop !== null) {
76
+ element.scrollTop = parseInt(savedScrollTop, 10);
77
+ }
78
+ } catch {
79
+ // Storage unavailable
80
+ }
81
+ }
82
+
58
83
  /**
59
84
  * サイドバーナビゲーションを初期化
60
85
  */
@@ -138,6 +163,23 @@ function initSidebar() {
138
163
  // 現在のパスを取得
139
164
  const currentPath = window.location.pathname;
140
165
 
166
+ // サイドバーコンテンツのスクロール位置を復元・保存
167
+ const sidebarContent = document.querySelector('.sidebar-content');
168
+ if (sidebarContent) {
169
+ // ページロード時にスクロール位置を復元
170
+ restoreSidebarScrollPosition(sidebarContent);
171
+
172
+ // スクロール時にスクロール位置を保存
173
+ sidebarContent.addEventListener('scroll', () => {
174
+ saveSidebarScrollPosition(sidebarContent.scrollTop);
175
+ });
176
+
177
+ // ページ遷移前にスクロール位置を保存
178
+ window.addEventListener('beforeunload', () => {
179
+ saveSidebarScrollPosition(sidebarContent.scrollTop);
180
+ });
181
+ }
182
+
141
183
  // ルートディレクトリを読み込み
142
184
  loadDirectory('/', fileTree, 0).then(async () => {
143
185
  // 保存された展開状態を復元
@@ -145,6 +187,11 @@ function initSidebar() {
145
187
  for (const dirPath of expandedDirs) {
146
188
  await expandToPath(dirPath, fileTree);
147
189
  }
190
+
191
+ // ツリーの読み込み後、再度スクロール位置を復元(コンテンツが変わった場合に備えて)
192
+ if (sidebarContent) {
193
+ restoreSidebarScrollPosition(sidebarContent);
194
+ }
148
195
  });
149
196
  }
150
197
 
@@ -78,6 +78,10 @@ a:hover {
78
78
  border-radius: 6px;
79
79
  margin-bottom: 24px;
80
80
  border: 1px solid var(--color-border);
81
+ position: sticky;
82
+ top: 0;
83
+ z-index: 100;
84
+ box-shadow: var(--shadow-sm);
81
85
  }
82
86
 
83
87
  #breadcrumbs a {