hamzus-ui 0.0.244 → 0.0.246
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/package.json
CHANGED
|
@@ -3,45 +3,28 @@
|
|
|
3
3
|
import IconButton from '..//IconButton/IconButton.svelte';
|
|
4
4
|
|
|
5
5
|
// ---------- État ----------
|
|
6
|
-
export let
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## Installation
|
|
11
|
-
|
|
12
|
-
\`\`\`bash
|
|
13
|
-
npm install mon-projet
|
|
14
|
-
\`\`\`
|
|
15
|
-
|
|
16
|
-
## Utilisation
|
|
17
|
-
|
|
18
|
-
\`\`\`js
|
|
19
|
-
import { run } from 'mon-projet'
|
|
20
|
-
run()
|
|
21
|
-
\`\`\`
|
|
22
|
-
|
|
23
|
-
- [x] Setup du repo
|
|
24
|
-
- [ ] Écrire les tests
|
|
25
|
-
- [ ] Publier sur npm
|
|
26
|
-
|
|
27
|
-
Voir la [documentation](https://example.com) pour plus de détails.
|
|
28
|
-
`;
|
|
29
|
-
|
|
30
|
-
let source = initialValue;
|
|
6
|
+
export let value = ``;
|
|
7
|
+
export let onChange = ()=>{};
|
|
8
|
+
let previousValue = value;
|
|
31
9
|
let mode = 'split'; // 'edit' | 'split' | 'preview'
|
|
32
10
|
let filename = 'README.md';
|
|
33
11
|
let dirty = false;
|
|
34
12
|
let textareaEl;
|
|
35
13
|
let copied = false;
|
|
36
14
|
|
|
37
|
-
$: wordCount = (
|
|
38
|
-
$: charCount =
|
|
15
|
+
$: wordCount = (value.match(/\S+/g) || []).length;
|
|
16
|
+
$: charCount = value.length;
|
|
39
17
|
$: readMinutes = Math.max(1, Math.round(wordCount / 200));
|
|
40
|
-
$: html = renderMarkdown(
|
|
18
|
+
$: html = renderMarkdown(value);
|
|
41
19
|
|
|
42
20
|
function onInput(e) {
|
|
43
|
-
|
|
21
|
+
value = e.target.value;
|
|
44
22
|
dirty = true;
|
|
23
|
+
if (previousValue !== value) {
|
|
24
|
+
onChange(value)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
previousValue = value
|
|
45
28
|
}
|
|
46
29
|
|
|
47
30
|
// ---------- Petit moteur markdown (sans dépendance) ----------
|
|
@@ -186,9 +169,9 @@ Voir la [documentation](https://example.com) pour plus de détails.
|
|
|
186
169
|
if (!textareaEl) return;
|
|
187
170
|
const start = textareaEl.selectionStart;
|
|
188
171
|
const end = textareaEl.selectionEnd;
|
|
189
|
-
const selected =
|
|
190
|
-
const next =
|
|
191
|
-
|
|
172
|
+
const selected = value.slice(start, end) || placeholder;
|
|
173
|
+
const next = value.slice(0, start) + before + selected + after + value.slice(end);
|
|
174
|
+
value = next;
|
|
192
175
|
dirty = true;
|
|
193
176
|
requestAnimationFrame(() => {
|
|
194
177
|
textareaEl.focus();
|
|
@@ -200,8 +183,8 @@ Voir la [documentation](https://example.com) pour plus de détails.
|
|
|
200
183
|
function insertLinePrefix(prefix) {
|
|
201
184
|
if (!textareaEl) return;
|
|
202
185
|
const start = textareaEl.selectionStart;
|
|
203
|
-
const lineStart =
|
|
204
|
-
|
|
186
|
+
const lineStart = value.lastIndexOf('\n', start - 1) + 1;
|
|
187
|
+
value = value.slice(0, lineStart) + prefix + value.slice(lineStart);
|
|
205
188
|
dirty = true;
|
|
206
189
|
requestAnimationFrame(() => textareaEl.focus());
|
|
207
190
|
}
|
|
@@ -220,7 +203,7 @@ Voir la [documentation](https://example.com) pour plus de détails.
|
|
|
220
203
|
|
|
221
204
|
async function copyMarkdown() {
|
|
222
205
|
try {
|
|
223
|
-
await navigator.clipboard.writeText(
|
|
206
|
+
await navigator.clipboard.writeText(value);
|
|
224
207
|
copied = true;
|
|
225
208
|
setTimeout(() => (copied = false), 1600);
|
|
226
209
|
} catch (e) {
|
|
@@ -229,7 +212,7 @@ Voir la [documentation](https://example.com) pour plus de détails.
|
|
|
229
212
|
}
|
|
230
213
|
|
|
231
214
|
function downloadFile() {
|
|
232
|
-
const blob = new Blob([
|
|
215
|
+
const blob = new Blob([value], { type: 'text/markdown;charset=utf-8' });
|
|
233
216
|
const url = URL.createObjectURL(blob);
|
|
234
217
|
const a = document.createElement('a');
|
|
235
218
|
a.href = url;
|
|
@@ -262,7 +245,7 @@ Voir la [documentation](https://example.com) pour plus de détails.
|
|
|
262
245
|
bind:this={textareaEl}
|
|
263
246
|
class="editor-pane"
|
|
264
247
|
spellcheck="false"
|
|
265
|
-
value={
|
|
248
|
+
value={value}
|
|
266
249
|
on:input={onInput}
|
|
267
250
|
placeholder="Écris ton README en Markdown..."
|
|
268
251
|
></textarea>
|
|
@@ -305,8 +288,6 @@ Voir la [documentation](https://example.com) pour plus de détails.
|
|
|
305
288
|
.readme-editor {
|
|
306
289
|
display: flex;
|
|
307
290
|
flex-direction: column;
|
|
308
|
-
height: 640px;
|
|
309
|
-
max-height: 90vh;
|
|
310
291
|
background: var(--bg);
|
|
311
292
|
color: var(--text);
|
|
312
293
|
border: 1px solid var(--border);
|
|
@@ -191,8 +191,6 @@ Voir la [documentation](https://example.com) pour plus de détails.
|
|
|
191
191
|
.readme-preview {
|
|
192
192
|
display: flex;
|
|
193
193
|
flex-direction: column;
|
|
194
|
-
height: 640px;
|
|
195
|
-
max-height: 90vh;
|
|
196
194
|
background: var(--bg);
|
|
197
195
|
color: var(--text);
|
|
198
196
|
border: 1px solid var(--border);
|
|
@@ -1,383 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
// ---------- État ----------
|
|
3
|
-
export let initialValue = `# Mon Projet
|
|
4
|
-
|
|
5
|
-
> Une courte description qui donne envie de scroller.
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
8
|
-
|
|
9
|
-
\`\`\`bash
|
|
10
|
-
npm install mon-projet
|
|
11
|
-
\`\`\`
|
|
12
|
-
|
|
13
|
-
## Utilisation
|
|
14
|
-
|
|
15
|
-
\`\`\`js
|
|
16
|
-
import { run } from 'mon-projet'
|
|
17
|
-
run()
|
|
18
|
-
\`\`\`
|
|
19
|
-
|
|
20
|
-
- [x] Setup du repo
|
|
21
|
-
- [ ] Écrire les tests
|
|
22
|
-
- [ ] Publier sur npm
|
|
23
|
-
|
|
24
|
-
Voir la [documentation](https://example.com) pour plus de détails.
|
|
25
|
-
`;
|
|
26
|
-
export let filename = 'README.md';
|
|
27
|
-
|
|
28
|
-
let source = initialValue;
|
|
29
|
-
|
|
30
|
-
$: html = renderMarkdown(source);
|
|
31
|
-
|
|
32
|
-
// ---------- Petit moteur markdown (sans dépendance) ----------
|
|
33
|
-
function escapeHtml(str) {
|
|
34
|
-
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function renderInline(text) {
|
|
38
|
-
let t = escapeHtml(text);
|
|
39
|
-
t = t.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
40
|
-
t = t.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img alt="$1" src="$2" />');
|
|
41
|
-
t = t.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
|
|
42
|
-
t = t.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
43
|
-
t = t.replace(/(^|[^*])\*([^*]+)\*(?!\*)/g, '$1<em>$2</em>');
|
|
44
|
-
t = t.replace(/~~([^~]+)~~/g, '<del>$1</del>');
|
|
45
|
-
return t;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function renderMarkdown(md) {
|
|
49
|
-
const lines = md.replace(/\r\n/g, '\n').split('\n');
|
|
50
|
-
let out = [];
|
|
51
|
-
let i = 0;
|
|
52
|
-
let inCodeBlock = false;
|
|
53
|
-
let codeLang = '';
|
|
54
|
-
let codeBuf = [];
|
|
55
|
-
let listBuf = [];
|
|
56
|
-
let listType = null;
|
|
57
|
-
|
|
58
|
-
function flushList() {
|
|
59
|
-
if (listBuf.length) {
|
|
60
|
-
const tag = listType === 'ol' ? 'ol' : 'ul';
|
|
61
|
-
out.push(`<${tag}>${listBuf.join('')}</${tag}>`);
|
|
62
|
-
listBuf = [];
|
|
63
|
-
listType = null;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
while (i < lines.length) {
|
|
68
|
-
const line = lines[i];
|
|
69
|
-
const fence = line.match(/^```(.*)$/);
|
|
70
|
-
|
|
71
|
-
if (fence) {
|
|
72
|
-
if (!inCodeBlock) {
|
|
73
|
-
inCodeBlock = true;
|
|
74
|
-
codeLang = fence[1].trim();
|
|
75
|
-
codeBuf = [];
|
|
76
|
-
} else {
|
|
77
|
-
inCodeBlock = false;
|
|
78
|
-
out.push(
|
|
79
|
-
`<pre class="code-block" data-lang="${escapeHtml(codeLang || 'text')}"><code>${escapeHtml(codeBuf.join('\n'))}</code></pre>`
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
i++;
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
if (inCodeBlock) {
|
|
86
|
-
codeBuf.push(line);
|
|
87
|
-
i++;
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const heading = line.match(/^(#{1,6})\s+(.*)$/);
|
|
92
|
-
if (heading) {
|
|
93
|
-
flushList();
|
|
94
|
-
const level = heading[1].length;
|
|
95
|
-
out.push(`<h${level}>${renderInline(heading[2])}</h${level}>`);
|
|
96
|
-
i++;
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const quote = line.match(/^>\s?(.*)$/);
|
|
101
|
-
if (quote) {
|
|
102
|
-
flushList();
|
|
103
|
-
out.push(`<blockquote>${renderInline(quote[1])}</blockquote>`);
|
|
104
|
-
i++;
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const hr = line.match(/^(-{3,}|\*{3,})\s*$/);
|
|
109
|
-
if (hr) {
|
|
110
|
-
flushList();
|
|
111
|
-
out.push('<hr />');
|
|
112
|
-
i++;
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const taskItem = line.match(/^\s*[-*]\s+\[( |x|X)\]\s+(.*)$/);
|
|
117
|
-
if (taskItem) {
|
|
118
|
-
if (listType !== 'ul') {
|
|
119
|
-
flushList();
|
|
120
|
-
listType = 'ul';
|
|
121
|
-
}
|
|
122
|
-
const checked = taskItem[1].toLowerCase() === 'x';
|
|
123
|
-
listBuf.push(
|
|
124
|
-
`<li class="task"><input type="checkbox" disabled ${checked ? 'checked' : ''} />${renderInline(taskItem[2])}</li>`
|
|
125
|
-
);
|
|
126
|
-
i++;
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const ulItem = line.match(/^\s*[-*]\s+(.*)$/);
|
|
131
|
-
if (ulItem) {
|
|
132
|
-
if (listType !== 'ul') {
|
|
133
|
-
flushList();
|
|
134
|
-
listType = 'ul';
|
|
135
|
-
}
|
|
136
|
-
listBuf.push(`<li>${renderInline(ulItem[1])}</li>`);
|
|
137
|
-
i++;
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const olItem = line.match(/^\s*\d+\.\s+(.*)$/);
|
|
142
|
-
if (olItem) {
|
|
143
|
-
if (listType !== 'ol') {
|
|
144
|
-
flushList();
|
|
145
|
-
listType = 'ol';
|
|
146
|
-
}
|
|
147
|
-
listBuf.push(`<li>${renderInline(olItem[1])}</li>`);
|
|
148
|
-
i++;
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (line.trim() === '') {
|
|
153
|
-
flushList();
|
|
154
|
-
i++;
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
flushList();
|
|
159
|
-
out.push(`<p>${renderInline(line)}</p>`);
|
|
160
|
-
i++;
|
|
161
|
-
}
|
|
162
|
-
flushList();
|
|
163
|
-
if (inCodeBlock && codeBuf.length) {
|
|
164
|
-
out.push(`<pre class="code-block"><code>${escapeHtml(codeBuf.join('\n'))}</code></pre>`);
|
|
165
|
-
}
|
|
166
|
-
return out.join('\n');
|
|
167
|
-
}
|
|
168
|
-
</script>
|
|
169
|
-
|
|
170
|
-
<div class="readme-preview">
|
|
171
|
-
|
|
172
|
-
<div class="preview-pane">
|
|
173
|
-
<div class="markdown-body">
|
|
174
|
-
{@html html}
|
|
175
|
-
</div>
|
|
176
|
-
</div>
|
|
177
|
-
</div>
|
|
178
|
-
|
|
179
|
-
<style>
|
|
180
|
-
:root {
|
|
181
|
-
--bg: var(--bg-1);
|
|
182
|
-
--panel: var(--bg-blur);
|
|
183
|
-
--panel-alt: var(--bg-blur);
|
|
184
|
-
--border: var(--stroke);
|
|
185
|
-
--text: var(--font-1);
|
|
186
|
-
--muted: var(--font-3);
|
|
187
|
-
--amber: var(--orange);
|
|
188
|
-
--teal: var(--blur);
|
|
189
|
-
--code-bg: var(--bg-2);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
.readme-preview {
|
|
193
|
-
display: flex;
|
|
194
|
-
flex-direction: column;
|
|
195
|
-
height: 640px;
|
|
196
|
-
max-height: 90vh;
|
|
197
|
-
background: var(--bg);
|
|
198
|
-
color: var(--text);
|
|
199
|
-
border: 1px solid var(--border);
|
|
200
|
-
border-radius: 10px;
|
|
201
|
-
overflow: hidden;
|
|
202
|
-
font-family:
|
|
203
|
-
'Inter',
|
|
204
|
-
-apple-system,
|
|
205
|
-
BlinkMacSystemFont,
|
|
206
|
-
'Segoe UI',
|
|
207
|
-
sans-serif;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
.chrome {
|
|
211
|
-
display: flex;
|
|
212
|
-
align-items: center;
|
|
213
|
-
background: var(--panel);
|
|
214
|
-
border-bottom: 1px solid var(--border);
|
|
215
|
-
padding: 0 10px;
|
|
216
|
-
flex-shrink: 0;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.preview-pane {
|
|
220
|
-
flex: 1;
|
|
221
|
-
min-height: 0;
|
|
222
|
-
overflow-y: auto;
|
|
223
|
-
padding: 20px 28px;
|
|
224
|
-
background: var(--panel);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.markdown-body {
|
|
228
|
-
max-width: 640px;
|
|
229
|
-
font-size: 14.5px;
|
|
230
|
-
line-height: 1.7;
|
|
231
|
-
color: var(--text);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
.markdown-body :global(h1),
|
|
235
|
-
.markdown-body :global(h2),
|
|
236
|
-
.markdown-body :global(h3) {
|
|
237
|
-
font-family: 'Inter', sans-serif;
|
|
238
|
-
font-weight: 700;
|
|
239
|
-
color: var(--text);
|
|
240
|
-
margin: 1.1em 0 0.5em;
|
|
241
|
-
line-height: 1.3;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.markdown-body :global(h1) {
|
|
245
|
-
font-size: 1.7em;
|
|
246
|
-
border-bottom: 1px solid var(--border);
|
|
247
|
-
padding-bottom: 0.3em;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
.markdown-body :global(h2) {
|
|
251
|
-
font-size: 1.32em;
|
|
252
|
-
border-bottom: 1px solid var(--border);
|
|
253
|
-
padding-bottom: 0.25em;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
.markdown-body :global(h3) {
|
|
257
|
-
font-size: 1.1em;
|
|
258
|
-
color: var(--teal);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
.markdown-body :global(p) {
|
|
262
|
-
margin: 0.6em 0;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
.markdown-body :global(a) {
|
|
266
|
-
color: var(--teal);
|
|
267
|
-
text-decoration: none;
|
|
268
|
-
border-bottom: 1px solid rgba(79, 209, 197, 0.35);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
.markdown-body :global(a:hover) {
|
|
272
|
-
border-bottom-color: var(--teal);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
.markdown-body :global(code) {
|
|
276
|
-
background: var(--code-bg);
|
|
277
|
-
color: var(--amber);
|
|
278
|
-
padding: 0.15em 0.4em;
|
|
279
|
-
border-radius: 4px;
|
|
280
|
-
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
281
|
-
font-size: 0.88em;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
.markdown-body :global(.code-block) {
|
|
285
|
-
background: var(--code-bg);
|
|
286
|
-
border: 1px solid var(--border);
|
|
287
|
-
border-radius: 8px;
|
|
288
|
-
padding: 14px 16px;
|
|
289
|
-
overflow-x: auto;
|
|
290
|
-
margin: 0.8em 0;
|
|
291
|
-
position: relative;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
.markdown-body :global(.code-block::before) {
|
|
295
|
-
content: attr(data-lang);
|
|
296
|
-
position: absolute;
|
|
297
|
-
top: 6px;
|
|
298
|
-
right: 10px;
|
|
299
|
-
font-size: 10px;
|
|
300
|
-
letter-spacing: 0.05em;
|
|
301
|
-
text-transform: uppercase;
|
|
302
|
-
color: var(--muted);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
.markdown-body :global(.code-block code) {
|
|
306
|
-
background: none;
|
|
307
|
-
color: var(--text);
|
|
308
|
-
padding: 0;
|
|
309
|
-
font-size: 0.85em;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
.markdown-body :global(blockquote) {
|
|
313
|
-
margin: 0.8em 0;
|
|
314
|
-
padding: 0.4em 1em;
|
|
315
|
-
border-left: 3px solid var(--amber);
|
|
316
|
-
color: var(--muted);
|
|
317
|
-
background: rgba(232, 179, 57, 0.05);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
.markdown-body :global(ul),
|
|
321
|
-
.markdown-body :global(ol) {
|
|
322
|
-
margin: 0.6em 0;
|
|
323
|
-
padding-left: 1.4em;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
.markdown-body :global(li) {
|
|
327
|
-
margin: 0.25em 0;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
.markdown-body :global(li.task) {
|
|
331
|
-
list-style: none;
|
|
332
|
-
margin-left: -1.4em;
|
|
333
|
-
display: flex;
|
|
334
|
-
align-items: baseline;
|
|
335
|
-
gap: 6px;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
.markdown-body :global(hr) {
|
|
339
|
-
border: none;
|
|
340
|
-
border-top: 1px solid var(--border);
|
|
341
|
-
margin: 1.4em 0;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
.markdown-body :global(img) {
|
|
345
|
-
max-width: 100%;
|
|
346
|
-
border-radius: 6px;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
.markdown-body :global(strong) {
|
|
350
|
-
color: var(--text);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
.readme-preview :global(input[type='checkbox']) {
|
|
354
|
-
appearance: none;
|
|
355
|
-
-webkit-appearance: none;
|
|
356
|
-
width: 16px;
|
|
357
|
-
height: 16px;
|
|
358
|
-
flex-shrink: 0;
|
|
359
|
-
margin: 0;
|
|
360
|
-
border: 1.5px solid var(--border, #262c3a);
|
|
361
|
-
border-radius: 4px;
|
|
362
|
-
background: var(--code-bg, #0d0f16);
|
|
363
|
-
cursor: default;
|
|
364
|
-
position: relative;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
.readme-preview :global(input[type='checkbox']:checked) {
|
|
368
|
-
background: var(--amber, #e8b339);
|
|
369
|
-
border-color: var(--amber, #e8b339);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
.readme-preview :global(input[type='checkbox']:checked::after) {
|
|
373
|
-
content: '';
|
|
374
|
-
position: absolute;
|
|
375
|
-
left: 4px;
|
|
376
|
-
top: 1px;
|
|
377
|
-
width: 4px;
|
|
378
|
-
height: 8px;
|
|
379
|
-
border: solid var(--font-1);
|
|
380
|
-
border-width: 0 2px 2px 0;
|
|
381
|
-
transform: rotate(45deg);
|
|
382
|
-
}
|
|
383
|
-
</style>
|