markpaste 0.0.1
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/AGENTS.md +28 -0
- package/LICENSE +201 -0
- package/README.md +74 -0
- package/index.html +153 -0
- package/package.json +40 -0
- package/playwright.config.ts +24 -0
- package/prettier.config.mjs +14 -0
- package/src/app.js +369 -0
- package/src/cleaner.js +145 -0
- package/src/converter.js +61 -0
- package/src/markpaste.js +26 -0
- package/src/pandoc.js +98 -0
- package/src/renderer.js +31 -0
- package/src/style.css +556 -0
- package/test/node/cleaner.test.js +50 -0
- package/test/node/converter.test.js +18 -0
- package/test/node/hello.test.js +6 -0
- package/test/node/index.test.js +23 -0
- package/test/node/pandoc.test.js +13 -0
- package/test/web/basic-load.spec.ts +75 -0
- package/test/web/cleaner.spec.ts +32 -0
- package/test/web/pasting.spec.ts +117 -0
- package/third_party/pandoc.wasm +0 -0
- package/tsconfig.json +17 -0
- package/types/browser_wasi_shim.d.ts +7 -0
- package/types/globals.d.ts +47 -0
- package/types/pandoc-wasm.d.ts +11 -0
package/src/renderer.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { marked } from 'marked';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Renders markdown into a target element, sanitizing it first.
|
|
5
|
+
* @param {string} markdown - The markdown string to render.
|
|
6
|
+
* @param {HTMLElement} targetElement - The element to render into.
|
|
7
|
+
*/
|
|
8
|
+
export async function renderMarkdown(markdown, targetElement) {
|
|
9
|
+
if (!markdown) {
|
|
10
|
+
targetElement.innerHTML = '';
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const rawHtml = await marked.parse(markdown);
|
|
15
|
+
|
|
16
|
+
// @ts-ignore
|
|
17
|
+
if (targetElement.setHTML) {
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
const sanitizer = new Sanitizer();
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
targetElement.setHTML(rawHtml, { sanitizer });
|
|
22
|
+
} else {
|
|
23
|
+
// Fallback if setHTML/Sanitizer is not supported (though we should encourage it)
|
|
24
|
+
// For now, we will just set innerHTML as a fallback or warn.
|
|
25
|
+
// Given the prompt asks for Sanitizer API, we assume it's available or polyfilled,
|
|
26
|
+
// but in reality it's very experimental.
|
|
27
|
+
// We'll stick to the requested API.
|
|
28
|
+
console.warn('Sanitizer API (setHTML) not supported. Falling back to innerHTML (UNSAFE).');
|
|
29
|
+
targetElement.innerHTML = rawHtml;
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/style.css
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg-color: #0f172a;
|
|
3
|
+
--text-color: #e2e8f0;
|
|
4
|
+
--text-muted: #94a3b8;
|
|
5
|
+
--panel-bg: rgba(30, 41, 59, 0.7);
|
|
6
|
+
--border-color: rgba(148, 163, 184, 0.1);
|
|
7
|
+
--primary-color: #38bdf8;
|
|
8
|
+
--primary-hover: #0ea5e9;
|
|
9
|
+
--secondary-bg: rgba(51, 65, 85, 0.5);
|
|
10
|
+
--secondary-hover: rgba(71, 85, 105, 0.5);
|
|
11
|
+
--glass-border: 1px solid rgba(255, 255, 255, 0.1);
|
|
12
|
+
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
13
|
+
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
|
|
14
|
+
--font-mono: 'JetBrains Mono', monospace;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
[data-theme='light'] {
|
|
18
|
+
--bg-color: #f8fafc;
|
|
19
|
+
--text-color: #1e293b;
|
|
20
|
+
--text-muted: #64748b;
|
|
21
|
+
--panel-bg: rgba(255, 255, 255, 0.8);
|
|
22
|
+
--border-color: rgba(0, 0, 0, 0.1);
|
|
23
|
+
--primary-color: #0284c7;
|
|
24
|
+
--primary-hover: #0369a1;
|
|
25
|
+
--secondary-bg: rgba(226, 232, 240, 0.5);
|
|
26
|
+
--secondary-hover: rgba(203, 213, 225, 0.5);
|
|
27
|
+
--glass-border: 1px solid rgba(255, 255, 255, 0.5);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
* {
|
|
31
|
+
box-sizing: border-box;
|
|
32
|
+
margin: 0;
|
|
33
|
+
padding: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
body {
|
|
37
|
+
font-family: var(--font-sans);
|
|
38
|
+
background-color: var(--bg-color);
|
|
39
|
+
color: var(--text-color);
|
|
40
|
+
accent-color: var(--primary-color);
|
|
41
|
+
min-height: 100vh;
|
|
42
|
+
display: flex;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
align-items: center;
|
|
45
|
+
overflow-x: hidden;
|
|
46
|
+
transition:
|
|
47
|
+
background-color 0.3s ease,
|
|
48
|
+
color 0.3s ease;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Background Blobs */
|
|
52
|
+
.background-blobs {
|
|
53
|
+
position: fixed;
|
|
54
|
+
top: 0;
|
|
55
|
+
left: 0;
|
|
56
|
+
width: 100%;
|
|
57
|
+
height: 100%;
|
|
58
|
+
z-index: -1;
|
|
59
|
+
overflow: hidden;
|
|
60
|
+
pointer-events: none;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.blob {
|
|
64
|
+
position: absolute;
|
|
65
|
+
border-radius: 50%;
|
|
66
|
+
filter: blur(80px);
|
|
67
|
+
opacity: 0.4;
|
|
68
|
+
animation: float 20s infinite alternate;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.blob-1 {
|
|
72
|
+
top: -10%;
|
|
73
|
+
left: -10%;
|
|
74
|
+
width: 50vw;
|
|
75
|
+
height: 50vw;
|
|
76
|
+
background: #4f46e5;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.blob-2 {
|
|
80
|
+
bottom: -10%;
|
|
81
|
+
right: -10%;
|
|
82
|
+
width: 40vw;
|
|
83
|
+
height: 40vw;
|
|
84
|
+
background: #0ea5e9;
|
|
85
|
+
animation-delay: -5s;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.blob-3 {
|
|
89
|
+
top: 40%;
|
|
90
|
+
left: 40%;
|
|
91
|
+
width: 30vw;
|
|
92
|
+
height: 30vw;
|
|
93
|
+
background: #ec4899;
|
|
94
|
+
animation-delay: -10s;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@keyframes float {
|
|
98
|
+
0% {
|
|
99
|
+
transform: translate(0, 0) rotate(0deg);
|
|
100
|
+
}
|
|
101
|
+
100% {
|
|
102
|
+
transform: translate(50px, 50px) rotate(20deg);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* App Container */
|
|
107
|
+
.app-container {
|
|
108
|
+
width: 95%;
|
|
109
|
+
max-width: 1400px;
|
|
110
|
+
height: 95vh;
|
|
111
|
+
display: flex;
|
|
112
|
+
flex-direction: column;
|
|
113
|
+
backdrop-filter: blur(16px);
|
|
114
|
+
-webkit-backdrop-filter: blur(16px);
|
|
115
|
+
background: var(--panel-bg);
|
|
116
|
+
border: var(--glass-border);
|
|
117
|
+
border-radius: 6px;
|
|
118
|
+
box-shadow: var(--shadow-lg);
|
|
119
|
+
overflow: hidden;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Main Content - Vertical Layout */
|
|
123
|
+
main.vertical-layout {
|
|
124
|
+
flex: 1;
|
|
125
|
+
display: flex;
|
|
126
|
+
flex-direction: column;
|
|
127
|
+
padding: 1rem 1rem 0;
|
|
128
|
+
gap: 1rem;
|
|
129
|
+
overflow-y: auto; /* Allow scrolling if content is tall */
|
|
130
|
+
min-height: 0; /* Important for flex child scrolling */
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Sections */
|
|
134
|
+
.section-input {
|
|
135
|
+
flex: 0 0 auto;
|
|
136
|
+
min-height: 50px;
|
|
137
|
+
display: flex;
|
|
138
|
+
flex-direction: column;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.section-html-preview {
|
|
142
|
+
flex: 0 0 auto;
|
|
143
|
+
display: flex;
|
|
144
|
+
flex-direction: column;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.section-controls {
|
|
148
|
+
flex: 0 0 auto;
|
|
149
|
+
display: flex;
|
|
150
|
+
justify-content: flex-end; /* or center? Mock shows right aligned maybe? No, let's right align to match controls */
|
|
151
|
+
padding-bottom: 0.5rem;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.section-outputs {
|
|
155
|
+
flex: 1;
|
|
156
|
+
min-height: 200px; /* Minimum height for outputs */
|
|
157
|
+
display: flex;
|
|
158
|
+
gap: 1rem;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.section-copy-actions {
|
|
162
|
+
flex: 0 0 auto;
|
|
163
|
+
/* padding: 1rem 0; */
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
/* Input Panel Specifics */
|
|
168
|
+
.input-panel {
|
|
169
|
+
height: 100%;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
/* 2-Column Grid */
|
|
174
|
+
.two-column-grid {
|
|
175
|
+
display: flex;
|
|
176
|
+
flex-direction: row;
|
|
177
|
+
align-items: stretch;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.output-column {
|
|
181
|
+
flex: 1;
|
|
182
|
+
display: flex;
|
|
183
|
+
flex-direction: column;
|
|
184
|
+
min-width: 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.output-column .panel {
|
|
188
|
+
flex: 1;
|
|
189
|
+
display: flex;
|
|
190
|
+
flex-direction: column;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.output-column .editor-container {
|
|
194
|
+
flex: 1;
|
|
195
|
+
/* Ensure previews scroll internally */
|
|
196
|
+
overflow: hidden;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.column-footer {
|
|
200
|
+
display: flex;
|
|
201
|
+
justify-content: center;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.radio-label {
|
|
205
|
+
display: block;
|
|
206
|
+
text-align: center;
|
|
207
|
+
gap: 0.5rem;
|
|
208
|
+
cursor: pointer;
|
|
209
|
+
font-size: 0.9rem;
|
|
210
|
+
color: var(--text-muted);
|
|
211
|
+
width: 100%;
|
|
212
|
+
padding: 0.3rem 0;
|
|
213
|
+
margin-top: 0.1rem;
|
|
214
|
+
border-radius: 4px;
|
|
215
|
+
transition: 150ms background-color;
|
|
216
|
+
|
|
217
|
+
&:hover {
|
|
218
|
+
background: var(--secondary-bg);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.radio-label input {
|
|
223
|
+
accent-color: var(--primary-color);
|
|
224
|
+
vertical-align: middle;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
/* View Toggle Buttons */
|
|
229
|
+
.view-toggle {
|
|
230
|
+
display: flex;
|
|
231
|
+
background: var(--secondary-bg);
|
|
232
|
+
padding: 0.25rem;
|
|
233
|
+
border-radius: 8px;
|
|
234
|
+
gap: 0.25rem;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.view-toggle .toggle-btn {
|
|
238
|
+
background: none;
|
|
239
|
+
border: none;
|
|
240
|
+
padding: 0.4rem 1rem;
|
|
241
|
+
border-radius: 6px;
|
|
242
|
+
font-size: 0.85rem;
|
|
243
|
+
color: var(--text-muted);
|
|
244
|
+
cursor: pointer;
|
|
245
|
+
transition: all 0.2s ease;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.view-toggle .toggle-btn.active {
|
|
249
|
+
background: var(--bg-color);
|
|
250
|
+
color: var(--text-color);
|
|
251
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
252
|
+
font-weight: 500;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* Render Preview (vs Editor) */
|
|
256
|
+
.render-preview {
|
|
257
|
+
width: 100%;
|
|
258
|
+
height: 100%;
|
|
259
|
+
padding: 0.5rem;
|
|
260
|
+
overflow-y: auto;
|
|
261
|
+
background: white; /* Rendered HTML usually expects white bg, or adapt to theme? */
|
|
262
|
+
color: black;
|
|
263
|
+
border-radius: 6px;
|
|
264
|
+
font-size: 70%;
|
|
265
|
+
|
|
266
|
+
a:link {
|
|
267
|
+
color: var(--text-muted)
|
|
268
|
+
}
|
|
269
|
+
a:link:hover {
|
|
270
|
+
text-decoration-style: wavy;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* preview stuff should look like normal */
|
|
274
|
+
* {
|
|
275
|
+
margin: revert;
|
|
276
|
+
padding: revert;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
[data-theme='dark'] .render-preview {
|
|
282
|
+
background: #1e293b; /* darker bg for dark mode */
|
|
283
|
+
color: #e2e8f0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* Hidden Utility */
|
|
287
|
+
.hidden {
|
|
288
|
+
display: none !important;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* App Footer */
|
|
292
|
+
.app-footer {
|
|
293
|
+
display: flex;
|
|
294
|
+
justify-content: space-between;
|
|
295
|
+
align-items: center;
|
|
296
|
+
padding: 1rem 2rem;
|
|
297
|
+
border-top: var(--glass-border);
|
|
298
|
+
background: var(--panel-bg);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
#themeToggle {
|
|
303
|
+
background: transparent;
|
|
304
|
+
border: 0;
|
|
305
|
+
color: var(--text-color);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* Reuse existing styles but ensure selectors match */
|
|
309
|
+
.panel-controls {
|
|
310
|
+
display: flex;
|
|
311
|
+
justify-content: flex-end;
|
|
312
|
+
margin-bottom: 0.5rem;
|
|
313
|
+
font-size: 0.85rem;
|
|
314
|
+
color: var(--text-muted);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.panel {
|
|
318
|
+
display: flex;
|
|
319
|
+
flex-direction: column;
|
|
320
|
+
min-width: 0;
|
|
321
|
+
height: 100%;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* HTML Panel Specifics */
|
|
325
|
+
.panel.html-panel {
|
|
326
|
+
height: auto;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
#htmlPreview.editor {
|
|
330
|
+
resize: vertical;
|
|
331
|
+
min-height: 50px;
|
|
332
|
+
height: 100px;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.editor-container {
|
|
336
|
+
flex: 1;
|
|
337
|
+
position: relative;
|
|
338
|
+
background: var(--secondary-bg);
|
|
339
|
+
border-radius: 6px;
|
|
340
|
+
border: 1px solid transparent;
|
|
341
|
+
transition: border-color 0.2s ease;
|
|
342
|
+
overflow: hidden;
|
|
343
|
+
/* display: flex; */
|
|
344
|
+
flex-direction: column;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.editor-container:focus-within {
|
|
348
|
+
border-color: var(--primary-color);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.editor {
|
|
352
|
+
flex: 1;
|
|
353
|
+
width: 100%;
|
|
354
|
+
height: 100%;
|
|
355
|
+
padding: 1.5rem;
|
|
356
|
+
background: transparent;
|
|
357
|
+
border: none;
|
|
358
|
+
resize: none;
|
|
359
|
+
font-family: var(--font-mono);
|
|
360
|
+
font-size: 0.7rem;
|
|
361
|
+
line-height: 1.6;
|
|
362
|
+
color: var(--text-color);
|
|
363
|
+
outline: none;
|
|
364
|
+
overflow-y: auto;
|
|
365
|
+
margin: 0;
|
|
366
|
+
white-space: pre-wrap;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/* Prism Overrides */
|
|
370
|
+
pre[class*='language-'] {
|
|
371
|
+
margin: 0 !important;
|
|
372
|
+
padding: 0 !important;
|
|
373
|
+
background: transparent !important;
|
|
374
|
+
text-shadow: none !important;
|
|
375
|
+
font-family: var(--font-mono) !important;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
code[class*='language-'],
|
|
379
|
+
pre[class*='language-'] {
|
|
380
|
+
color: var(--text-color) !important;
|
|
381
|
+
font-size: 0.7rem !important;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/* Ensure code wraps */
|
|
385
|
+
code[class*='language-'] {
|
|
386
|
+
white-space: pre-wrap !important;
|
|
387
|
+
word-break: break-word;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
#inputArea[contenteditable]:empty:before {
|
|
391
|
+
content: attr(placeholder);
|
|
392
|
+
color: var(--text-muted);
|
|
393
|
+
opacity: 0.6;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.hidden {
|
|
397
|
+
display: none;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.arrow-divider {
|
|
401
|
+
display: flex;
|
|
402
|
+
align-items: center;
|
|
403
|
+
justify-content: center;
|
|
404
|
+
color: var(--text-muted);
|
|
405
|
+
opacity: 0.5;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
@media (max-width: 768px) {
|
|
409
|
+
.arrow-divider {
|
|
410
|
+
transform: rotate(90deg);
|
|
411
|
+
padding: 1rem 0;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/* Actions */
|
|
416
|
+
.panel-actions {
|
|
417
|
+
margin-top: 1rem;
|
|
418
|
+
grid-column: 1 / -1; /* Span across both columns */
|
|
419
|
+
display: flex;
|
|
420
|
+
justify-content: flex-end;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.btn {
|
|
424
|
+
display: inline-flex;
|
|
425
|
+
align-items: center;
|
|
426
|
+
justify-content: center;
|
|
427
|
+
gap: 0.5rem;
|
|
428
|
+
padding: 0.6rem 1.2rem;
|
|
429
|
+
border-radius: 8px;
|
|
430
|
+
font-weight: 500;
|
|
431
|
+
font-size: 0.9rem;
|
|
432
|
+
cursor: pointer;
|
|
433
|
+
transition: all 0.2s ease;
|
|
434
|
+
border: none;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.btn.primary {
|
|
438
|
+
background: var(--primary-color);
|
|
439
|
+
color: white;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.btn.primary:hover {
|
|
443
|
+
background: var(--primary-hover);
|
|
444
|
+
transform: translateY(-1px);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
#copyBtn {
|
|
448
|
+
width: 100%;
|
|
449
|
+
padding: 0.8rem 1.2rem;
|
|
450
|
+
font-size: 1rem;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.btn.secondary {
|
|
454
|
+
background: var(--secondary-bg);
|
|
455
|
+
color: var(--text-color);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.btn.secondary:hover {
|
|
459
|
+
background: var(--secondary-hover);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/* Toggle Group */
|
|
463
|
+
.toggle-group {
|
|
464
|
+
display: flex;
|
|
465
|
+
background: var(--secondary-bg);
|
|
466
|
+
padding: 0.25rem;
|
|
467
|
+
border-radius: 8px;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.toggle-btn {
|
|
471
|
+
background: none;
|
|
472
|
+
border: none;
|
|
473
|
+
padding: 0.4rem 0.8rem;
|
|
474
|
+
border-radius: 6px;
|
|
475
|
+
font-size: 0.8rem;
|
|
476
|
+
color: var(--text-muted);
|
|
477
|
+
cursor: pointer;
|
|
478
|
+
transition: all 0.2s ease;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.toggle-btn.active {
|
|
482
|
+
background: var(--bg-color);
|
|
483
|
+
color: var(--text-color);
|
|
484
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/* Scrollbar */
|
|
488
|
+
::-webkit-scrollbar {
|
|
489
|
+
width: 8px;
|
|
490
|
+
height: 8px;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
::-webkit-scrollbar-track {
|
|
494
|
+
background: transparent;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
::-webkit-scrollbar-thumb {
|
|
498
|
+
background: var(--secondary-hover);
|
|
499
|
+
border-radius: 4px;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
::-webkit-scrollbar-thumb:hover {
|
|
503
|
+
background: var(--text-muted);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.radio-group {
|
|
507
|
+
display: flex;
|
|
508
|
+
font-size: 0.85rem;
|
|
509
|
+
border: 0;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.radio-group label {
|
|
513
|
+
display: flex;
|
|
514
|
+
align-items: center;
|
|
515
|
+
gap: 0.4rem;
|
|
516
|
+
cursor: pointer;
|
|
517
|
+
|
|
518
|
+
/* give it some height for background hover effect. neg margin cuz i'm lazy */
|
|
519
|
+
padding: 0.4rem 1rem;
|
|
520
|
+
margin: -0.4rem 0;
|
|
521
|
+
|
|
522
|
+
border-radius: 8px;
|
|
523
|
+
transition: all 0.2s ease;
|
|
524
|
+
|
|
525
|
+
&:first-child {
|
|
526
|
+
margin-left: 4px;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.radio-group label:hover {
|
|
531
|
+
background: var(--secondary-bg);
|
|
532
|
+
color: var(--text-color);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
.logo {
|
|
537
|
+
display: flex;
|
|
538
|
+
align-items: center;
|
|
539
|
+
gap: 12px;
|
|
540
|
+
|
|
541
|
+
svg {
|
|
542
|
+
color: var(--primary-color);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
h1 {
|
|
546
|
+
margin: 0;
|
|
547
|
+
font-size: 1.5rem;
|
|
548
|
+
font-weight: 600;
|
|
549
|
+
background: linear-gradient(135deg, var(--text-color), var(--primary-color));
|
|
550
|
+
-webkit-background-clip: text;
|
|
551
|
+
-webkit-text-fill-color: transparent;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
}
|
|
556
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { cleanHTML, removeStyleAttributes } from '../../src/cleaner.js';
|
|
4
|
+
|
|
5
|
+
test('cleaner: cleanHTML should remove disallowed tags', async () => {
|
|
6
|
+
const html = '<div><p>Hello</p><script>alert(1)</script><span>World</span></div>';
|
|
7
|
+
const cleaned = await cleanHTML(html);
|
|
8
|
+
|
|
9
|
+
// Linkedom might use uppercase for tags
|
|
10
|
+
assert.strictEqual(cleaned.toLowerCase().includes('<p>hello</p>'), true);
|
|
11
|
+
assert.strictEqual(cleaned.toLowerCase().includes('world'), true);
|
|
12
|
+
assert.strictEqual(cleaned.toLowerCase().includes('<script>'), false);
|
|
13
|
+
assert.strictEqual(cleaned.toLowerCase().includes('<div>'), false); // div is unwrapped
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('cleaner: cleanHTML should handle MDN specific cases', async () => {
|
|
17
|
+
const html = `
|
|
18
|
+
<div>
|
|
19
|
+
<button class="mdn-copy-button">Copy</button>
|
|
20
|
+
<a href="https://developer.mozilla.org/en-US/play?id=123">Play</a>
|
|
21
|
+
<a href="https://example.com">Normal</a>
|
|
22
|
+
</div>
|
|
23
|
+
`;
|
|
24
|
+
const cleaned = await cleanHTML(html);
|
|
25
|
+
|
|
26
|
+
assert.strictEqual(cleaned.includes('Copy'), false);
|
|
27
|
+
assert.strictEqual(cleaned.includes('Play'), false);
|
|
28
|
+
assert.strictEqual(cleaned.includes('Normal'), true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('cleaner: removeStyleAttributes should strip style attributes', async () => {
|
|
32
|
+
const html = '<p style="color: red;">Hello</p>';
|
|
33
|
+
const stripped = await removeStyleAttributes(html);
|
|
34
|
+
|
|
35
|
+
assert.strictEqual(stripped.toLowerCase().includes('style="color: red;"'), false);
|
|
36
|
+
assert.strictEqual(stripped.toLowerCase().includes('<p>hello</p>'), true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
test('cleaner: handles leading OL/UL tags correctly', async () => {
|
|
41
|
+
|
|
42
|
+
const html = `<meta charset='utf-8'><ol style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 0px; padding-left: 40px; list-style: outside decimal; color: rgb(32, 33, 36); font-family: Roboto, "Noto Sans", "Noto Sans JP", "Noto Sans KR", "Noto Naskh Arabic", "Noto Sans Thai", "Noto Sans Hebrew", "Noto Sans Bengali", sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><h1 class="devsite-page-title" tabindex="-1" style="box-sizing: inherit; font: 500 36px / 44px "Google Sans", "Noto Sans", "Noto Sans JP", "Noto Sans KR", "Noto Naskh Arabic", "Noto Sans Thai", "Noto Sans Hebrew", "Noto Sans Bengali", sans-serif; color: rgb(32, 33, 36); letter-spacing: normal; margin: 0px 0px 24px; overflow: visible; text-overflow: ellipsis; display: inline; vertical-align: middle;">Manage Python packages<devsite-actions data-nosnippet="" style="box-sizing: inherit; font-feature-settings: "dgun"; display: inline-flex; gap: 8px; padding-inline: 8px;"><devsite-feature-tooltip ack-key="AckCollectionsBookmarkTooltipDismiss" analytics-category="Site-Wide Custom Events" analytics-action-show="Callout Profile displayed" analytics-action-close="Callout Profile dismissed" analytics-label="Create Collection Callout" class="devsite-page-bookmark-tooltip nocontent inline-block" dismiss-button="true" id="devsite-collections-dropdown" dismiss-button-text="Dismiss" close-button-text="Got it" rendered="" current-step="0" style="--devsite-popout-top: calc(100% + 17px); --devsite-popout-width: min(50vw,320px); --devsite-feature-tooltip-text-color: #fff; position: relative; box-sizing: inherit; font-feature-settings: "dgun"; display: inline-block; --devsite-popout-offset-x: 32px;"><slot><devsite-bookmark class="show" style="box-sizing: inherit; font-feature-settings: "dgun"; --devsite-bookmark-background: 0; --devsite-bookmark-background-focus-legacy: #e8eaed; --devsite-bookmark-background-hover-legacy: #f1f3f4; --devsite-bookmark-icon-color: #5f6368; --devsite-bookmark-icon-color-saved: #1a73e8; --devsite-bookmark-icon-color-saved-hover: #174ea6; --devsite-dropdown-list-toggle-background-hover: #f1f3f4; --devsite-dropdown-list-toggle-border: 1px solid #dadce0; --devsite-dropdown-list-toggle-border-hover: 1px solid #dadce0; --devsite-dropdown-list-toggle-height: 36px; display: inline-flex; -webkit-box-align: center; align-items: center; background: none 0px 50% / auto repeat scroll padding-box border-box rgba(0, 0, 0, 0); border: 0px; box-shadow: none; cursor: pointer; height: 36px; -webkit-box-pack: center; justify-content: center; margin: 0px; padding: 0px; vertical-align: middle;"><devsite-dropdown-list aria-label="Bookmark collections drop down" ellipsis="" checkboxes="" fetchingitems="true" writable="" additemtext="New Collection" rendered="" style="--devsite-checkbox-icon-canvas-offset-x: -10px; --devsite-checkbox-icon-canvas-offset-y: -8px; --devsite-checkbox-offset-x: 4px; --devsite-checkbox-offset-y: -2px; --devsite-mdc-line-height: 50px; display: inline-flex; position: relative; vertical-align: middle; box-sizing: inherit; font-feature-settings: "dgun"; --devsite-button-box-shadow: none; visibility: visible;"><button class="toggle-button button" aria-haspopup="menu" id="dropdown-list-0-toggle" aria-controls="dropdown-list-0-dropdown" aria-expanded="false" aria-label="Open dropdown" style="align-self: auto; appearance: none; background: 0px center; border-color: rgb(218, 220, 224); border-style: solid; border-width: 1px; border-image: none 100% / 1 / 0 stretch; border-radius: 4px; box-shadow: none; box-sizing: border-box; color: rgb(95, 99, 104); cursor: pointer; display: flex; font: 500 14px / 34px "Google Sans", "Noto Sans", "Noto Sans JP", "Noto Sans KR", "Noto Naskh Arabic", "Noto Sans Thai", "Noto Sans Hebrew", "Noto Sans Bengali", sans-serif; height: 36px; letter-spacing: normal; max-width: none; min-width: auto; outline: 0px; overflow: hidden; padding: 0px 3px; text-align: center; text-decoration: none; text-overflow: ellipsis; text-transform: none; transition: background-color 0.2s, border 0.2s, box-shadow 0.2s; vertical-align: middle; white-space: nowrap; width: auto; margin: 0px; margin-inline-end: 0px; -webkit-box-align: center; -webkit-box-pack: center; align-items: center; justify-content: center;"><slot name="toggle"><span data-label="devsite-bookmark-direct-action" data-title="Save page" class="material-icons bookmark-icon bookmark-action" slot="toggle" style="-webkit-font-smoothing: antialiased; text-rendering: optimizelegibility; overflow-wrap: normal; font-style: normal; font-variant: normal; font-size-adjust: none; font-language-override: normal; font-kerning: auto; font-optical-sizing: auto; font-feature-settings: "liga"; font-variation-settings: normal; font-weight: normal; font-stretch: normal; font-size: 24px; line-height: 1; font-family: "Material Icons"; text-transform: none; box-sizing: inherit; letter-spacing: normal; display: inline-block; white-space: nowrap; direction: ltr; color: rgb(95, 99, 104); transition: color 0.2s; vertical-align: bottom;">bookmark_border</span></slot></button></devsite-dropdown-list></devsite-bookmark></slot></devsite-feature-tooltip></devsite-actions></h1><devsite-toc class="devsite-nav devsite-toc-embedded" depth="2" devsite-toc-embedded="" visible="" style="box-sizing: inherit; font-feature-settings: "dgun"; font-size: 13px; display: block; margin: 28px 0px 24px;"><ul class="devsite-nav-list" style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 0px; padding: 0px; list-style: outside none; border-inline-start: 4px solid rgb(25, 103, 210); width: auto; padding-inline-start: 12px;"><li class="devsite-nav-item devsite-nav-heading devsite-toc-toggle" style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 0px; padding: 0px; line-height: 16px; display: flex;"></li><li class="devsite-nav-item" visible="" style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 0px; padding: 0px; line-height: 16px; display: block;"></li><li class="devsite-toc-toggle" style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 0px; padding: 0px; display: flex;"></li></ul></devsite-toc><div class="devsite-article-body clearfix" style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 16px 0px 0px; padding: 0px;"><p style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 0px 0px 16px; padding: 0px; color: rgb(32, 33, 36); font-family: Roboto, "Noto Sans", "Noto Sans JP", "Noto Sans KR", "Noto Naskh Arabic", "Noto Sans Thai", "Noto Sans Hebrew", "Noto Sans Bengali", sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><a href="https://pypi.org/" class="external" style="box-sizing: inherit; font-feature-settings: "dgun"; color: rgb(26, 115, 232); outline: 0px; text-decoration: rgb(26, 115, 232); word-break: break-word;">Python Package Index</a><span> </span>(PyPI) is a public repository for Python packages. You can use Artifact Registry to create private repositories for your Python packages. These private repositories use the canonical Python repository implementation, the<span> </span><a href="https://www.python.org/dev/peps/pep-0503/" class="external" style="box-sizing: inherit; font-feature-settings: "dgun"; color: rgb(26, 115, 232); outline: 0px; text-decoration: rgb(26, 115, 232); word-break: break-word;">simple repository API</a><span> </span>(PEP 503), and work with installation tools like<span> </span><code translate="no" dir="ltr" style="box-sizing: inherit; font-feature-settings: "dgun"; background: none 0% 0% / auto repeat scroll padding-box border-box rgb(241, 243, 244); color: rgb(55, 71, 79); font-style: normal; font-variant: normal; font-weight: 500; font-stretch: 100%; font-size: 14.4px; line-height: 14.4px; font-family: "Roboto Mono", monospace; font-optical-sizing: auto; font-size-adjust: none; font-kerning: auto; font-variation-settings: normal; font-language-override: normal; padding: 1px 4px; direction: ltr !important; text-align: left !important; border-color: rgb(55, 71, 79); border-style: none; border-width: 0px; border-image: none 100% / 1 / 0 stretch; border-radius: 0px; word-break: break-word;">pip</code>.</p><h2 id="overview" data-text="Overview" tabindex="-1" role="presentation" style="box-sizing: inherit; font: 400 24px / 32px "Google Sans", "Noto Sans", "Noto Sans JP", "Noto Sans KR", "Noto Naskh Arabic", "Noto Sans Thai", "Noto Sans Hebrew", "Noto Sans Bengali", sans-serif; letter-spacing: normal; margin: 48px 0px 24px; overflow-x: clip; text-overflow: ellipsis; border-bottom: 0px none rgb(32, 33, 36); padding: 0px; color: rgb(32, 33, 36); orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span class="devsite-heading" role="heading" aria-level="2" style="box-sizing: inherit; font-feature-settings: "dgun";">Overview</span><button type="button" class="devsite-heading-link button-flat material-icons" aria-label="Copy link to this section: Overview" data-title="Copy link to this section: Overview" data-id="overview" style="box-sizing: border-box; font-feature-settings: "liga"; appearance: none; align-self: auto; background: none 0px 50% / auto repeat scroll padding-box border-box rgba(0, 0, 0, 0); border: 0px; border-radius: 4px; box-shadow: none; color: rgb(95, 99, 104); cursor: pointer; display: inline-block; font-style: normal; font-variant: normal; font-weight: normal; font-stretch: 100%; font-size: 24px; font-family: "Material Icons"; font-optical-sizing: auto; font-size-adjust: none; font-kerning: auto; font-variation-settings: normal; font-language-override: normal; height: 24px; letter-spacing: normal; line-height: 24px; margin: 0px; margin-inline-end: 0px; max-width: none; min-width: 36px; outline: 0px; overflow: hidden; padding: 0px 24px; text-align: center; text-decoration: none; text-overflow: ellipsis; text-transform: none; transition: background-color 0.2s, border 0.2s, box-shadow 0.2s; vertical-align: middle; white-space: nowrap; width: auto; overflow-wrap: normal; direction: ltr; -webkit-font-smoothing: antialiased; padding-inline: 8px; --devsite-button-white-line-height: 24px; --devsite-button-white-background-hover: transparent; opacity: 0;"></button></h2><p style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 16px 0px; padding: 0px; color: rgb(32, 33, 36); font-family: Roboto, "Noto Sans", "Noto Sans JP", "Noto Sans KR", "Noto Naskh Arabic", "Noto Sans Thai", "Noto Sans Hebrew", "Noto Sans Bengali", sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">To get familiar with Python packages in Artifact Registry, you can try the<span> </span><a href="https://docs.cloud.google.com/artifact-registry/docs/python/quickstart" style="box-sizing: inherit; font-feature-settings: "dgun"; color: rgb(26, 115, 232); outline: 0px; text-decoration: rgb(26, 115, 232); word-break: break-word;">quickstart</a>.</p><p style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 16px 0px; padding: 0px; color: rgb(32, 33, 36); font-family: Roboto, "Noto Sans", "Noto Sans JP", "Noto Sans KR", "Noto Naskh Arabic", "Noto Sans Thai", "Noto Sans Hebrew", "Noto Sans Bengali", sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">When you are ready to learn more, read the following information:</p><ol style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 0px; padding-left: 40px; list-style: outside decimal; color: rgb(32, 33, 36); font-family: Roboto, "Noto Sans", "Noto Sans JP", "Noto Sans KR", "Noto Naskh Arabic", "Noto Sans Thai", "Noto Sans Hebrew", "Noto Sans Bengali", sans-serif; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><li style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 12px 0px; padding: 0px;"><a href="https://docs.cloud.google.com/artifact-registry/docs/repositories/create-repos" style="box-sizing: inherit; font-feature-settings: "dgun"; color: rgb(26, 115, 232); outline: 0px; text-decoration: rgb(26, 115, 232); word-break: break-word;">Create a Python package repository</a><span> </span>for your packages.</li><li style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 12px 0px; padding: 0px;"><a href="https://docs.cloud.google.com/artifact-registry/docs/access-control" style="box-sizing: inherit; font-feature-settings: "dgun"; color: rgb(26, 115, 232); outline: 0px; text-decoration: rgb(26, 115, 232); word-break: break-word;">Grant permissions</a><span> </span>to the account that will connect with the repository. Service accounts for common integrations with Artifact Registry have default<span> </span><a href="https://docs.cloud.google.com/artifact-registry/docs/access-control#gcp" style="box-sizing: inherit; font-feature-settings: "dgun"; color: rgb(26, 115, 232); outline: 0px; text-decoration: rgb(26, 115, 232); word-break: break-word;">permissions</a><span> </span>for repositories in the same project.</li><li style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 12px 0px; padding: 0px;">Configure your tools:<ul style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 0px; padding-left: 40px; list-style: outside disc;"><li style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 12px 0px; padding: 0px;"><a href="https://docs.cloud.google.com/artifact-registry/docs/python/authentication" style="box-sizing: inherit; font-feature-settings: "dgun"; color: rgb(26, 115, 232); outline: 0px; text-decoration: rgb(26, 115, 232); word-break: break-word;">Configure authentication</a><span> </span>for Python clients that interact with the repository.</li><li style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 12px 0px; padding: 0px;"><a href="https://docs.cloud.google.com/artifact-registry/docs/configure-cloud-build" style="box-sizing: inherit; font-feature-settings: "dgun"; color: rgb(26, 115, 232); outline: 0px; text-decoration: rgb(26, 115, 232); word-break: break-word;">Configure Cloud Build</a><span> </span>to upload and download packages.</li><li style="box-sizing: inherit; font-feature-settings: "dgun"; margin: 12px 0px; padding: 0px;">Learn about<span> </span><a href="https://docs.cloud.google.com/artifact-registry/docs/deploy" style="box-sizing: inherit; font-feature-settings: "dgun"; color: rgb(26, 115, 232); outline: 0px; text-decoration: rgb(26, 115, 232); word-break: break-word;">deploying</a><span> </span>to Google Cloud runtime environments.</li></ul></li></ol><br class="Apple-interchange-newline">`;
|
|
43
|
+
|
|
44
|
+
const cleaned = await cleanHTML(html);
|
|
45
|
+
|
|
46
|
+
// The leading <ol> should be unwrapped because its immediate children (h1, devsite-toc, etc) are NOT <li> elements.
|
|
47
|
+
assert.strictEqual(cleaned.toLowerCase().startsWith('<ol'), false);
|
|
48
|
+
assert.strictEqual(cleaned.toLowerCase().includes('<h1'), true);
|
|
49
|
+
assert.strictEqual(cleaned.toLowerCase().includes('manage python packages'), true);
|
|
50
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { test } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { getConverter } from '../../src/converter.js';
|
|
4
|
+
|
|
5
|
+
test('converter: turndown should convert HTML to Markdown', async () => {
|
|
6
|
+
const converter = await getConverter('turndown');
|
|
7
|
+
const html = '<h1>Hello</h1>';
|
|
8
|
+
const markdown = converter.convert(html);
|
|
9
|
+
assert.strictEqual(markdown.trim(), '# Hello');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('converter: unknown converter should throw error', async () => {
|
|
13
|
+
await assert.rejects(async () => {
|
|
14
|
+
await getConverter('non-existent');
|
|
15
|
+
}, {
|
|
16
|
+
message: 'Unknown converter: non-existent. Available converters: turndown, pandoc'
|
|
17
|
+
});
|
|
18
|
+
});
|