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.
@@ -0,0 +1,23 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { convert } from '../../src/markpaste.js';
4
+
5
+ test('library: convert should use turndown by default', async () => {
6
+ const html = '<h1>Hello</h1>';
7
+ const markdown = await convert(html);
8
+ assert.strictEqual(markdown.trim(), '# Hello');
9
+ });
10
+
11
+ test('library: convert should support pandoc', async () => {
12
+ const html = '<p>Hello <b>World</b></p>';
13
+ const markdown = await convert(html, { converter: 'pandoc' });
14
+ assert.strictEqual(markdown.includes('Hello'), true);
15
+ assert.strictEqual(markdown.includes('World'), true);
16
+ });
17
+
18
+ test('library: convert should support disabling cleaning', async () => {
19
+ const html = '<div style="color: red;"><p>Hello</p></div>';
20
+ // If clean is false, it uses removeStyleAttributes which unwraps but might keep some structure
21
+ const markdown = await convert(html, { clean: false });
22
+ assert.strictEqual(markdown.includes('Hello'), true);
23
+ });
@@ -0,0 +1,13 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { getConverter } from '../../src/converter.js';
4
+
5
+ test('pandoc: converter should convert HTML to Markdown', async () => {
6
+ const converter = await getConverter('pandoc');
7
+ const html = '<h1>Hello</h1>';
8
+ const markdown = converter.convert(html);
9
+
10
+ assert.strictEqual(markdown.includes('Hello'), true);
11
+ // Pandoc usually adds its own flavor of Markdown
12
+ assert.strictEqual(markdown.includes('#'), true);
13
+ });
@@ -0,0 +1,75 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ test('should load the page without errors or 404s', async ({ page }) => {
4
+ const failedRequests: string[] = [];
5
+ const consoleErrors: string[] = [];
6
+
7
+ // Track failed network requests
8
+ page.on('requestfailed', request => {
9
+ failedRequests.push(`${request.url()}: ${request.failure()?.errorText}`);
10
+ });
11
+
12
+ // Track 4xx/5xx responses
13
+ page.on('response', response => {
14
+ if (response.status() >= 400) {
15
+ failedRequests.push(`${response.url()}: ${response.status()}`);
16
+ }
17
+ });
18
+
19
+ // Track console errors
20
+ page.on('console', msg => {
21
+ if (msg.type() === 'error') {
22
+ consoleErrors.push(msg.text());
23
+ }
24
+ });
25
+
26
+ page.on('pageerror', error => {
27
+ consoleErrors.push(error.message);
28
+ });
29
+
30
+ await page.goto('http://127.0.0.1:8081/index.html');
31
+
32
+ // Verify no network failures
33
+ expect(failedRequests, `Found failed network requests: ${failedRequests.join(', ')}`).toHaveLength(0);
34
+
35
+ // Verify no console errors
36
+ expect(consoleErrors, `Found console errors: ${consoleErrors.join(', ')}`).toHaveLength(0);
37
+
38
+ // Basic check that the page rendered
39
+ await expect(page.locator('h1')).toHaveText('MarkPaste');
40
+ });
41
+
42
+ test('should paste HTML and update all 2 markdown outputs', async ({ page }) => {
43
+ await page.goto('http://127.0.0.1:8081/index.html');
44
+
45
+ const inputArea = page.locator('#inputArea');
46
+ const testHtml = '<h1>Test Title</h1><p>Hello <strong>world</strong></p>';
47
+
48
+ // Simulate paste event
49
+ await page.evaluate((html) => {
50
+ const el = document.querySelector('#inputArea');
51
+ if (!el) return;
52
+ const dataTransfer = new DataTransfer();
53
+ dataTransfer.setData('text/html', html);
54
+ const event = new ClipboardEvent('paste', {
55
+ clipboardData: dataTransfer,
56
+ bubbles: true,
57
+ cancelable: true,
58
+ });
59
+ el.dispatchEvent(event);
60
+ }, testHtml);
61
+
62
+ // Expected markdown (approximate, since different converters might vary slightly)
63
+ // Turndown: # Test Title\n\nHello **world**
64
+ // Pandoc: # Test Title\n\nHello **world**
65
+
66
+ const outputTurndown = page.locator('#outputCodeTurndown');
67
+ const outputPandoc = page.locator('#outputCodePandoc');
68
+
69
+ // Wait for all outputs to contain expected content
70
+ await expect(outputTurndown).toContainText('# Test Title');
71
+ await expect(outputTurndown).toContainText('Hello **world**');
72
+
73
+ await expect(outputPandoc).toContainText('# Test Title');
74
+ await expect(outputPandoc).toContainText('Hello **world**');
75
+ });
@@ -0,0 +1,32 @@
1
+ import {test, expect} from '@playwright/test';
2
+
3
+ test.describe('Cleaner functionality', () => {
4
+ test.beforeEach(async ({page}) => {
5
+ await page.goto('http://127.0.0.1:8081/index.html');
6
+ });
7
+
8
+ test('should remove MDN copy button and play links', async ({page}) => {
9
+ const html = `
10
+ <div>
11
+ <p>Example code:</p>
12
+ <button class="mdn-copy-button">Copy</button>
13
+ <pre><code>console.log('hello');</code></pre>
14
+ <a href="https://developer.mozilla.org/en-US/play?id=123">Play in MDN</a>
15
+ <a href="https://example.com">Normal Link</a>
16
+ </div>
17
+ `;
18
+
19
+ await page.evaluate(html => {
20
+ const inputArea = document.getElementById('inputArea');
21
+ inputArea.innerHTML = html;
22
+ inputArea.dispatchEvent(new Event('input', {bubbles: true}));
23
+ }, html);
24
+
25
+ const outputCode = page.locator('#outputCodeTurndown');
26
+
27
+ await expect(outputCode).not.toContainText('Copy');
28
+ await expect(outputCode).not.toContainText('Play in MDN');
29
+ await expect(outputCode).toContainText('Normal Link');
30
+ await expect(outputCode).toContainText("console.log('hello');");
31
+ });
32
+ });
@@ -0,0 +1,117 @@
1
+ import {test, expect} from '@playwright/test';
2
+
3
+ test.describe('MarkPaste functionality', () => {
4
+ test.beforeEach(async ({page}) => {
5
+ await page.goto('http://127.0.0.1:8081/index.html');
6
+ });
7
+
8
+ test('should convert basic rich text to markdown', async ({page}) => {
9
+ const html = '<p>Hello <b>world</b></p>';
10
+ await page.evaluate(html => {
11
+ const inputArea = document.getElementById('inputArea');
12
+ inputArea.innerHTML = html;
13
+ inputArea.dispatchEvent(new Event('input', {bubbles: true}));
14
+ }, html);
15
+
16
+ const outputCode = page.locator('#outputCodeTurndown');
17
+ await expect(outputCode).toHaveText('Hello **world**');
18
+ });
19
+
20
+ test('should handle the tricky case from repro.html', async ({page}) => {
21
+ const html = `
22
+ <meta charset='utf-8'>
23
+ <p>The<span> </span>
24
+ <code class="w3-codespan">debugger</code>
25
+ <span> </span>keyword stops the execution of JavaScript, and calls (if available) the debugging function.</p>
26
+ `;
27
+
28
+ await page.evaluate(html => {
29
+ const inputArea = document.getElementById('inputArea');
30
+ inputArea.innerHTML = html;
31
+ inputArea.dispatchEvent(new Event('input', {bubbles: true}));
32
+ }, html);
33
+
34
+ const outputCode = page.locator('#outputCodeTurndown');
35
+ await expect(outputCode).toHaveText('The `debugger` keyword stops the execution of JavaScript, and calls (if available) the debugging function.');
36
+
37
+ const htmlCode = page.locator('#htmlCode');
38
+ const cleanedHtml = await htmlCode.innerText();
39
+ expect(cleanedHtml).toMatch(/The\s*<code[^>]*>debugger<\/code>/);
40
+ expect(cleanedHtml).not.toContain('The<code>debugger</code>');
41
+ });
42
+
43
+ test('should run all converters simultaneously', async ({page}) => {
44
+ const html = '<h3>Hello World</h3>';
45
+ await page.evaluate(html => {
46
+ const inputArea = document.getElementById('inputArea');
47
+ inputArea.innerHTML = html;
48
+ inputArea.dispatchEvent(new Event('input', {bubbles: true}));
49
+ }, html);
50
+
51
+ const turndownOutput = page.locator('#outputCodeTurndown');
52
+ await expect(turndownOutput).toHaveText('### Hello World');
53
+
54
+ const pandocOutput = page.locator('#outputCodePandoc');
55
+ // Pandoc might take a moment
56
+ await expect(pandocOutput).toContainText('Hello World');
57
+ });
58
+
59
+ // SKIP this for now. needs a human to look into why its failing.
60
+ test.skip('should toggle HTML cleaning', async ({page}) => {
61
+ const html = '<div><p>Hello</p><style>body{color:red;}</style><script>alert("xss")</script></div>';
62
+
63
+ await page.evaluate(html => {
64
+ const inputArea = document.getElementById('inputArea');
65
+ inputArea.innerHTML = html;
66
+ inputArea.dispatchEvent(new Event('input', {bubbles: true}));
67
+ }, html);
68
+
69
+ const outputCode = page.locator('#outputCodeTurndown');
70
+ const htmlCode = page.locator('#htmlCode');
71
+
72
+ // Initially, "Clean HTML" is checked
73
+ await page.waitForFunction(() => document.getElementById('outputCodeTurndown').textContent.includes('Hello'));
74
+ expect(await htmlCode.textContent()).not.toContain('<div>');
75
+ expect(await htmlCode.textContent()).not.toContain('<script>');
76
+
77
+ // Uncheck "Clean HTML"
78
+ await page.locator('#cleanHtmlToggle').uncheck();
79
+ // Wait for update
80
+ await page.waitForTimeout(100);
81
+
82
+ expect(await htmlCode.textContent()).toContain('<div>');
83
+ expect(await htmlCode.textContent()).not.toContain('<script>');
84
+ });
85
+
86
+ test('should retain table structure', async ({page}) => {
87
+ const html = `
88
+ <table>
89
+ <thead>
90
+ <tr>
91
+ <th>Header 1</th>
92
+ <th>Header 2</th>
93
+ </tr>
94
+ </thead>
95
+ <tbody>
96
+ <tr>
97
+ <td>Row 1, Cell 1</td>
98
+ <td>Row 1, Cell 2</td>
99
+ </tr>
100
+ </tbody>
101
+ </table>
102
+ `;
103
+
104
+ await page.evaluate(html => {
105
+ const inputArea = document.getElementById('inputArea');
106
+ inputArea.innerHTML = html;
107
+ inputArea.dispatchEvent(new Event('input', {bubbles: true}));
108
+ }, html);
109
+
110
+ const htmlCode = page.locator('#htmlCode');
111
+
112
+ await expect(htmlCode).toContainText('<table');
113
+ await expect(htmlCode).toContainText('<tr');
114
+ await expect(htmlCode).toContainText('<td');
115
+ await expect(htmlCode).toContainText('<th');
116
+ });
117
+ });
Binary file
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "esnext",
4
+ "module": "nodenext",
5
+ "erasableSyntaxOnly": true,
6
+ "verbatimModuleSyntax": true,
7
+ "allowImportingTsExtensions": true,
8
+ "rewriteRelativeImportExtensions": true,
9
+
10
+ "noEmit": true,
11
+ "allowJs": true,
12
+ "checkJs": true,
13
+ "skipLibCheck": true
14
+ },
15
+ "include": ["src/**/*.js", "types/**/*.d.ts", "test/**/*.ts", "test/**/*.js"],
16
+ "exclude": ["node_modules/linkedom"]
17
+ }
@@ -0,0 +1,7 @@
1
+ import WASI, {WASIProcExit} from './wasi.js';
2
+ export {WASI, WASIProcExit};
3
+ export {Fd, Inode} from './fd.js';
4
+ export {File, Directory, OpenFile, OpenDirectory, PreopenDirectory, ConsoleStdout} from './fs_mem.js';
5
+ export {SyncOPFSFile, OpenSyncOPFSFile} from './fs_opfs.js';
6
+ export {strace} from './strace.js';
7
+ export * as wasi from './wasi_defs.js';
@@ -0,0 +1,47 @@
1
+ import type {Grammar} from 'prismjs';
2
+ import type TurndownService from 'turndown';
3
+
4
+ declare global {
5
+ interface Window {
6
+ Prism: {
7
+ highlightElement(element: Element): void;
8
+ languages: Grammar;
9
+ };
10
+ TurndownService: typeof TurndownService;
11
+ toMarkdown: (html: string) => string;
12
+ turndownPluginGfm: {
13
+ gfm: TurndownService.Plugin | TurndownService.Plugin[];
14
+ };
15
+ }
16
+
17
+ // bling.js
18
+ interface Node {
19
+ on(name: string, fn: EventListenerOrEventListenerObject): void;
20
+ }
21
+
22
+ interface Window {
23
+ on(name: string, fn: EventListenerOrEventListenerObject): void;
24
+ $<T extends string>(query: T, context?: ParentNode): import('typed-query-selector/parser.js').ParseSelector<T, Element>;
25
+ $$<T extends string>(query: T, context?: ParentNode): NodeListOf<import('typed-query-selector/parser.js').ParseSelector<T, Element>>;
26
+ }
27
+
28
+ interface NodeList extends Array<Node> {
29
+ on(name: string, fn: EventListenerOrEventListenerObject): void;
30
+ }
31
+
32
+
33
+ // idle detector
34
+
35
+ interface IdleDetector {
36
+ addEventListener(type: "change", listener: (this: IdleDetector, ev: { userState: "active" | "idle", screenState: "locked" | "unlocked" }) => unknown, options?: boolean | AddEventListenerOptions): void;
37
+ start(options: { threshold: number; signal?: AbortSignal }): Promise<void>;
38
+ screenState: "locked" | "unlocked";
39
+ userState: "active" | "idle";
40
+ }
41
+
42
+ declare const IdleDetector: {
43
+ new(): IdleDetector;
44
+ requestPermission(): Promise<"granted" | "denied">;
45
+ };
46
+ }
47
+ /// <reference path="pandoc-wasm.d.ts" />
@@ -0,0 +1,11 @@
1
+ export interface PandocWasmExports extends WebAssembly.Exports {
2
+ __wasm_call_ctors: () => void;
3
+ memory: WebAssembly.Memory;
4
+ malloc: (size: number) => number;
5
+ hs_init_with_rtsopts: (argc_ptr: number, argv_ptr: number) => void;
6
+ wasm_main: (args_ptr: number, args_len: number) => void;
7
+ }
8
+
9
+ export interface PandocWasmInstance extends WebAssembly.Instance {
10
+ exports: PandocWasmExports;
11
+ }