md-lv 1.0.0

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.
@@ -0,0 +1,286 @@
1
+ /**
2
+ * markdown-viewer Search Functionality
3
+ */
4
+
5
+ document.addEventListener('DOMContentLoaded', () => {
6
+ initSearch();
7
+ });
8
+
9
+ /**
10
+ * 検索機能を初期化
11
+ */
12
+ function initSearch() {
13
+ // 検索 UI を作成
14
+ createSearchUI();
15
+
16
+ const searchInput = document.getElementById('search-input');
17
+ const searchResults = document.getElementById('search-results');
18
+
19
+ if (!searchInput || !searchResults) return;
20
+
21
+ let debounceTimer;
22
+
23
+ // 入力時に検索
24
+ searchInput.addEventListener('input', (e) => {
25
+ const query = e.target.value.trim();
26
+
27
+ // デバウンス処理(300ms)
28
+ clearTimeout(debounceTimer);
29
+
30
+ if (query.length < 1) {
31
+ hideResults();
32
+ return;
33
+ }
34
+
35
+ debounceTimer = setTimeout(() => {
36
+ performSearch(query);
37
+ }, 300);
38
+ });
39
+
40
+ // フォーカス時に結果を表示
41
+ searchInput.addEventListener('focus', () => {
42
+ if (searchInput.value.trim().length >= 1) {
43
+ showResults();
44
+ }
45
+ });
46
+
47
+ // クリック外で結果を非表示
48
+ document.addEventListener('click', (e) => {
49
+ const searchContainer = document.getElementById('search-container');
50
+ if (searchContainer && !searchContainer.contains(e.target)) {
51
+ hideResults();
52
+ }
53
+ });
54
+
55
+ // Escape で結果を非表示
56
+ searchInput.addEventListener('keydown', (e) => {
57
+ if (e.key === 'Escape') {
58
+ hideResults();
59
+ searchInput.blur();
60
+ }
61
+
62
+ // Arrow Down で結果にフォーカス
63
+ if (e.key === 'ArrowDown') {
64
+ e.preventDefault();
65
+ const firstResult = searchResults.querySelector('a');
66
+ if (firstResult) firstResult.focus();
67
+ }
68
+ });
69
+ }
70
+
71
+ /**
72
+ * 検索 UI を作成
73
+ */
74
+ function createSearchUI() {
75
+ const breadcrumbs = document.getElementById('breadcrumbs');
76
+ if (!breadcrumbs) return;
77
+
78
+ // 検索コンテナを作成
79
+ const container = document.createElement('div');
80
+ container.id = 'search-container';
81
+ container.innerHTML = `
82
+ <div class="search-wrapper">
83
+ <input type="text"
84
+ id="search-input"
85
+ placeholder="Search files... (press /)"
86
+ autocomplete="off"
87
+ aria-label="Search files">
88
+ <div id="search-results" class="search-results" hidden></div>
89
+ </div>
90
+ `;
91
+
92
+ // スタイルを追加
93
+ const style = document.createElement('style');
94
+ style.textContent = `
95
+ #search-container {
96
+ margin-bottom: 16px;
97
+ }
98
+
99
+ .search-wrapper {
100
+ position: relative;
101
+ }
102
+
103
+ #search-input {
104
+ width: 100%;
105
+ max-width: 400px;
106
+ padding: 8px 12px;
107
+ font-size: 14px;
108
+ border: 1px solid var(--color-border, #e1e4e8);
109
+ border-radius: 6px;
110
+ background-color: var(--color-bg, #ffffff);
111
+ color: var(--color-text, #24292e);
112
+ transition: border-color 0.2s, box-shadow 0.2s;
113
+ }
114
+
115
+ #search-input:focus {
116
+ outline: none;
117
+ border-color: var(--color-link, #0366d6);
118
+ box-shadow: 0 0 0 3px rgba(3, 102, 214, 0.1);
119
+ }
120
+
121
+ #search-input::placeholder {
122
+ color: var(--color-text-muted, #6a737d);
123
+ }
124
+
125
+ .search-results {
126
+ position: absolute;
127
+ top: 100%;
128
+ left: 0;
129
+ right: 0;
130
+ max-width: 400px;
131
+ max-height: 300px;
132
+ overflow-y: auto;
133
+ background-color: var(--color-bg, #ffffff);
134
+ border: 1px solid var(--color-border, #e1e4e8);
135
+ border-radius: 6px;
136
+ box-shadow: var(--shadow-md, 0 4px 6px rgba(0, 0, 0, 0.1));
137
+ z-index: 100;
138
+ margin-top: 4px;
139
+ }
140
+
141
+ .search-results[hidden] {
142
+ display: none;
143
+ }
144
+
145
+ .search-result-item {
146
+ display: block;
147
+ padding: 8px 12px;
148
+ color: var(--color-text, #24292e);
149
+ text-decoration: none;
150
+ border-bottom: 1px solid var(--color-border, #e1e4e8);
151
+ font-size: 14px;
152
+ }
153
+
154
+ .search-result-item:last-child {
155
+ border-bottom: none;
156
+ }
157
+
158
+ .search-result-item:hover,
159
+ .search-result-item:focus {
160
+ background-color: var(--color-bg-secondary, #f6f8fa);
161
+ outline: none;
162
+ }
163
+
164
+ .search-result-item .file-name {
165
+ font-weight: 500;
166
+ }
167
+
168
+ .search-result-item .file-path {
169
+ color: var(--color-text-muted, #6a737d);
170
+ font-size: 12px;
171
+ margin-top: 2px;
172
+ }
173
+
174
+ .search-no-results {
175
+ padding: 12px;
176
+ color: var(--color-text-muted, #6a737d);
177
+ text-align: center;
178
+ }
179
+
180
+ .search-loading {
181
+ padding: 12px;
182
+ text-align: center;
183
+ color: var(--color-text-muted, #6a737d);
184
+ }
185
+ `;
186
+
187
+ document.head.appendChild(style);
188
+
189
+ // ブレッドクラムの後に挿入
190
+ breadcrumbs.parentNode.insertBefore(container, breadcrumbs.nextSibling);
191
+ }
192
+
193
+ /**
194
+ * 検索を実行
195
+ */
196
+ async function performSearch(query) {
197
+ const searchResults = document.getElementById('search-results');
198
+ if (!searchResults) return;
199
+
200
+ // ローディング表示
201
+ searchResults.innerHTML = '<div class="search-loading">Searching...</div>';
202
+ showResults();
203
+
204
+ try {
205
+ const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
206
+ const data = await response.json();
207
+
208
+ if (data.results.length === 0) {
209
+ searchResults.innerHTML = '<div class="search-no-results">No results found</div>';
210
+ return;
211
+ }
212
+
213
+ // 結果を表示
214
+ const html = data.results.map(filePath => {
215
+ const fileName = filePath.split('/').pop();
216
+ const dirPath = filePath.substring(0, filePath.lastIndexOf('/')) || '/';
217
+
218
+ return `
219
+ <a href="${escapeHtml(filePath)}" class="search-result-item">
220
+ <div class="file-name">${escapeHtml(fileName)}</div>
221
+ <div class="file-path">${escapeHtml(dirPath)}</div>
222
+ </a>
223
+ `;
224
+ }).join('');
225
+
226
+ searchResults.innerHTML = html;
227
+
228
+ // キーボードナビゲーション
229
+ const items = searchResults.querySelectorAll('a');
230
+ items.forEach((item, index) => {
231
+ item.addEventListener('keydown', (e) => {
232
+ if (e.key === 'ArrowDown' && index < items.length - 1) {
233
+ e.preventDefault();
234
+ items[index + 1].focus();
235
+ }
236
+ if (e.key === 'ArrowUp') {
237
+ e.preventDefault();
238
+ if (index > 0) {
239
+ items[index - 1].focus();
240
+ } else {
241
+ document.getElementById('search-input').focus();
242
+ }
243
+ }
244
+ });
245
+ });
246
+
247
+ } catch (error) {
248
+ console.error('Search error:', error);
249
+ searchResults.innerHTML = '<div class="search-no-results">Search failed</div>';
250
+ }
251
+ }
252
+
253
+ /**
254
+ * 結果を表示
255
+ */
256
+ function showResults() {
257
+ const searchResults = document.getElementById('search-results');
258
+ if (searchResults) {
259
+ searchResults.hidden = false;
260
+ }
261
+ }
262
+
263
+ /**
264
+ * 結果を非表示
265
+ */
266
+ function hideResults() {
267
+ const searchResults = document.getElementById('search-results');
268
+ if (searchResults) {
269
+ searchResults.hidden = true;
270
+ }
271
+ }
272
+
273
+ /**
274
+ * HTML エスケープ
275
+ */
276
+ function escapeHtml(str) {
277
+ const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' };
278
+ return str.replace(/[&<>"']/g, c => map[c]);
279
+ }
280
+
281
+ // グローバルに公開
282
+ window.mdvSearch = {
283
+ performSearch,
284
+ showResults,
285
+ hideResults
286
+ };
File without changes
@@ -0,0 +1,314 @@
1
+ /* ==========================================================================
2
+ markdown-viewer Base Styles
3
+ GitHub-inspired Markdown styling
4
+ ========================================================================== */
5
+
6
+ /* Reset & Base */
7
+ *, *::before, *::after {
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ html {
12
+ font-size: 16px;
13
+ -webkit-font-smoothing: antialiased;
14
+ -moz-osx-font-smoothing: grayscale;
15
+ }
16
+
17
+ body {
18
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
19
+ font-size: 16px;
20
+ line-height: 1.6;
21
+ color: #24292e;
22
+ background-color: #ffffff;
23
+ max-width: 980px;
24
+ margin: 0 auto;
25
+ padding: 45px;
26
+ }
27
+
28
+ /* Breadcrumbs */
29
+ #breadcrumbs {
30
+ font-size: 14px;
31
+ color: #586069;
32
+ margin-bottom: 20px;
33
+ padding-bottom: 10px;
34
+ border-bottom: 1px solid #e1e4e8;
35
+ }
36
+
37
+ #breadcrumbs a {
38
+ color: #0366d6;
39
+ text-decoration: none;
40
+ }
41
+
42
+ #breadcrumbs a:hover {
43
+ text-decoration: underline;
44
+ }
45
+
46
+ /* Main Content */
47
+ #content {
48
+ min-height: 400px;
49
+ }
50
+
51
+ /* Typography */
52
+ h1, h2, h3, h4, h5, h6 {
53
+ margin-top: 24px;
54
+ margin-bottom: 16px;
55
+ font-weight: 600;
56
+ line-height: 1.25;
57
+ }
58
+
59
+ h1 {
60
+ font-size: 2em;
61
+ border-bottom: 1px solid #e1e4e8;
62
+ padding-bottom: 0.3em;
63
+ }
64
+
65
+ h2 {
66
+ font-size: 1.5em;
67
+ border-bottom: 1px solid #e1e4e8;
68
+ padding-bottom: 0.3em;
69
+ }
70
+
71
+ h3 { font-size: 1.25em; }
72
+ h4 { font-size: 1em; }
73
+ h5 { font-size: 0.875em; }
74
+ h6 { font-size: 0.85em; color: #6a737d; }
75
+
76
+ p {
77
+ margin-top: 0;
78
+ margin-bottom: 16px;
79
+ }
80
+
81
+ /* Links */
82
+ a {
83
+ color: #0366d6;
84
+ text-decoration: none;
85
+ }
86
+
87
+ a:hover {
88
+ text-decoration: underline;
89
+ }
90
+
91
+ /* Lists */
92
+ ul, ol {
93
+ padding-left: 2em;
94
+ margin-top: 0;
95
+ margin-bottom: 16px;
96
+ }
97
+
98
+ ul ul, ul ol, ol ul, ol ol {
99
+ margin-top: 0;
100
+ margin-bottom: 0;
101
+ }
102
+
103
+ li {
104
+ margin-top: 0.25em;
105
+ }
106
+
107
+ li + li {
108
+ margin-top: 0.25em;
109
+ }
110
+
111
+ /* Code */
112
+ code {
113
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
114
+ font-size: 85%;
115
+ background-color: rgba(27, 31, 35, 0.05);
116
+ border-radius: 3px;
117
+ padding: 0.2em 0.4em;
118
+ }
119
+
120
+ pre {
121
+ background-color: #f6f8fa;
122
+ border-radius: 6px;
123
+ padding: 16px;
124
+ overflow: auto;
125
+ font-size: 85%;
126
+ line-height: 1.45;
127
+ margin-top: 0;
128
+ margin-bottom: 16px;
129
+ }
130
+
131
+ pre code {
132
+ background-color: transparent;
133
+ padding: 0;
134
+ border-radius: 0;
135
+ font-size: 100%;
136
+ }
137
+
138
+ /* Blockquote */
139
+ blockquote {
140
+ margin: 0 0 16px 0;
141
+ padding: 0 1em;
142
+ color: #6a737d;
143
+ border-left: 0.25em solid #dfe2e5;
144
+ }
145
+
146
+ blockquote > :first-child {
147
+ margin-top: 0;
148
+ }
149
+
150
+ blockquote > :last-child {
151
+ margin-bottom: 0;
152
+ }
153
+
154
+ /* Tables */
155
+ table {
156
+ border-collapse: collapse;
157
+ border-spacing: 0;
158
+ width: 100%;
159
+ margin-bottom: 16px;
160
+ overflow: auto;
161
+ }
162
+
163
+ table th,
164
+ table td {
165
+ padding: 6px 13px;
166
+ border: 1px solid #dfe2e5;
167
+ }
168
+
169
+ table th {
170
+ font-weight: 600;
171
+ background-color: #f6f8fa;
172
+ }
173
+
174
+ table tr {
175
+ background-color: #ffffff;
176
+ border-top: 1px solid #c6cbd1;
177
+ }
178
+
179
+ table tr:nth-child(2n) {
180
+ background-color: #f6f8fa;
181
+ }
182
+
183
+ /* Horizontal Rule */
184
+ hr {
185
+ height: 0.25em;
186
+ padding: 0;
187
+ margin: 24px 0;
188
+ background-color: #e1e4e8;
189
+ border: 0;
190
+ }
191
+
192
+ /* Images */
193
+ img {
194
+ max-width: 100%;
195
+ box-sizing: content-box;
196
+ }
197
+
198
+ /* Directory Listing */
199
+ .directory-listing {
200
+ list-style: none;
201
+ padding: 0;
202
+ margin: 0;
203
+ }
204
+
205
+ .directory-listing li {
206
+ padding: 10px 15px;
207
+ border-bottom: 1px solid #e1e4e8;
208
+ display: flex;
209
+ align-items: center;
210
+ }
211
+
212
+ .directory-listing li:hover {
213
+ background-color: #f6f8fa;
214
+ }
215
+
216
+ .directory-listing li::before {
217
+ content: '';
218
+ display: inline-block;
219
+ width: 20px;
220
+ height: 20px;
221
+ margin-right: 10px;
222
+ background-size: contain;
223
+ background-repeat: no-repeat;
224
+ background-position: center;
225
+ }
226
+
227
+ .directory-listing li.folder::before {
228
+ content: '📁';
229
+ }
230
+
231
+ .directory-listing li.file::before {
232
+ content: '📄';
233
+ }
234
+
235
+ .directory-listing li.file-md::before {
236
+ content: '📝';
237
+ }
238
+
239
+ .directory-listing a {
240
+ flex: 1;
241
+ }
242
+
243
+ /* Keyboard Navigation Focus */
244
+ .directory-listing li.keyboard-focus {
245
+ background-color: #f6f8fa;
246
+ outline: 2px solid #0366d6;
247
+ outline-offset: -2px;
248
+ }
249
+
250
+ .directory-listing li.keyboard-focus a {
251
+ color: #0366d6;
252
+ }
253
+
254
+ /* Task Lists (GitHub style) */
255
+ .task-list-item {
256
+ list-style-type: none;
257
+ }
258
+
259
+ .task-list-item input[type="checkbox"] {
260
+ margin: 0 0.2em 0.25em -1.6em;
261
+ vertical-align: middle;
262
+ }
263
+
264
+ /* Mermaid Diagrams */
265
+ .mermaid {
266
+ margin: 16px 0;
267
+ text-align: center;
268
+ }
269
+
270
+ /* MathJax */
271
+ mjx-container {
272
+ overflow-x: auto;
273
+ overflow-y: hidden;
274
+ }
275
+
276
+ /* Footer */
277
+ footer {
278
+ margin-top: 40px;
279
+ padding-top: 20px;
280
+ color: #6a737d;
281
+ font-size: 12px;
282
+ }
283
+
284
+ footer hr {
285
+ margin-bottom: 20px;
286
+ }
287
+
288
+ /* Responsive */
289
+ @media (max-width: 767px) {
290
+ body {
291
+ padding: 15px;
292
+ }
293
+
294
+ h1 { font-size: 1.5em; }
295
+ h2 { font-size: 1.25em; }
296
+
297
+ table {
298
+ display: block;
299
+ overflow-x: auto;
300
+ }
301
+ }
302
+
303
+ /* Print Styles */
304
+ @media print {
305
+ body {
306
+ max-width: none;
307
+ padding: 0;
308
+ }
309
+
310
+ #breadcrumbs,
311
+ footer {
312
+ display: none;
313
+ }
314
+ }