@fmontes/md2clip 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.
- package/README.md +56 -0
- package/md2clip.js +127 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# md2clip
|
|
2
|
+
|
|
3
|
+
Pipe markdown into your terminal, get HTML in your clipboard with proper `text/html` MIME type.
|
|
4
|
+
|
|
5
|
+
Apps like Notion, Slack, Mail, and Google Docs will paste it as rich text.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g md2clip
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
echo "**bold** and _italic_" | md2clip
|
|
17
|
+
cat README.md | md2clip
|
|
18
|
+
pbpaste | md2clip # convert clipboard markdown to HTML
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Output is also printed to stdout, so you can pipe further:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cat doc.md | md2clip | less
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Platform support
|
|
28
|
+
|
|
29
|
+
| Platform | Method | MIME type |
|
|
30
|
+
|----------|--------|-----------|
|
|
31
|
+
| macOS | Swift + NSPasteboard | `public.html` |
|
|
32
|
+
| Linux | xclip / xsel / wl-copy | `text/html` |
|
|
33
|
+
| Windows | PowerShell + System.Windows.Forms | HTML format |
|
|
34
|
+
|
|
35
|
+
### macOS requirement
|
|
36
|
+
|
|
37
|
+
Requires Xcode Command Line Tools for proper HTML MIME type:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
xcode-select --install
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Without it, falls back to `pbcopy` (plain text only).
|
|
44
|
+
|
|
45
|
+
### Linux requirement
|
|
46
|
+
|
|
47
|
+
Install one of: `xclip`, `xsel`, or `wl-copy`.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Ubuntu/Debian
|
|
51
|
+
sudo apt install xclip
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
package/md2clip.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { marked } from "marked";
|
|
4
|
+
import { spawnSync } from "child_process";
|
|
5
|
+
import { writeFileSync, unlinkSync } from "fs";
|
|
6
|
+
import { tmpdir } from "os";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
|
|
9
|
+
function getStdin() {
|
|
10
|
+
if (process.stdin.isTTY) {
|
|
11
|
+
console.error("md2clip: reads markdown from stdin\n");
|
|
12
|
+
console.error("Usage:");
|
|
13
|
+
console.error(" echo '**hello**' | md2clip");
|
|
14
|
+
console.error(" cat README.md | md2clip");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
const chunks = [];
|
|
20
|
+
process.stdin.setEncoding("utf8");
|
|
21
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
22
|
+
process.stdin.on("end", () => resolve(chunks.join("")));
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function copyAsHTML(html) {
|
|
27
|
+
const platform = process.platform;
|
|
28
|
+
|
|
29
|
+
if (platform === "darwin") {
|
|
30
|
+
copyMacOS(html);
|
|
31
|
+
} else if (platform === "linux") {
|
|
32
|
+
copyLinux(html);
|
|
33
|
+
} else if (platform === "win32") {
|
|
34
|
+
copyWindows(html);
|
|
35
|
+
} else {
|
|
36
|
+
console.error(`md2clip: unsupported platform: ${platform}`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function copyMacOS(html) {
|
|
42
|
+
// Swift sets public.html MIME type so apps receive rich HTML on paste
|
|
43
|
+
const swiftCode = `
|
|
44
|
+
import AppKit
|
|
45
|
+
let html = CommandLine.arguments[1]
|
|
46
|
+
let data = html.data(using: .utf8)!
|
|
47
|
+
let pb = NSPasteboard.general
|
|
48
|
+
pb.clearContents()
|
|
49
|
+
pb.setData(data, forType: NSPasteboard.PasteboardType("public.html"))
|
|
50
|
+
pb.setString(html, forType: .string)
|
|
51
|
+
`;
|
|
52
|
+
const swiftPath = join(tmpdir(), `_md2clip_${Date.now()}.swift`);
|
|
53
|
+
try {
|
|
54
|
+
writeFileSync(swiftPath, swiftCode);
|
|
55
|
+
const result = spawnSync("swift", [swiftPath, html], { timeout: 10000 });
|
|
56
|
+
if (result.status !== 0) {
|
|
57
|
+
throw new Error(result.stderr?.toString() || "swift failed");
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (err.code === "ENOENT") {
|
|
61
|
+
// swift not found, fall back to pbcopy (plain text)
|
|
62
|
+
console.error("Warning: swift not found, copying as plain text only.");
|
|
63
|
+
console.error("Fix: xcode-select --install");
|
|
64
|
+
spawnSync("pbcopy", [], { input: html, encoding: "utf8" });
|
|
65
|
+
} else {
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
} finally {
|
|
69
|
+
try { unlinkSync(swiftPath); } catch {}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function copyLinux(html) {
|
|
74
|
+
// Try xclip then xsel then wl-copy (Wayland)
|
|
75
|
+
const tools = [
|
|
76
|
+
{ cmd: "xclip", args: ["-selection", "clipboard", "-t", "text/html"] },
|
|
77
|
+
{ cmd: "xsel", args: ["--clipboard", "--input"] },
|
|
78
|
+
{ cmd: "wl-copy", args: ["--type", "text/html"] },
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
for (const tool of tools) {
|
|
82
|
+
const result = spawnSync(tool.cmd, tool.args, {
|
|
83
|
+
input: html,
|
|
84
|
+
encoding: "utf8",
|
|
85
|
+
timeout: 5000,
|
|
86
|
+
});
|
|
87
|
+
if (result.status === 0) return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.error("md2clip: install xclip, xsel, or wl-copy for clipboard support");
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function copyWindows(html) {
|
|
95
|
+
// PowerShell with HTML clipboard format
|
|
96
|
+
const ps = `
|
|
97
|
+
Add-Type -Assembly System.Windows.Forms
|
|
98
|
+
$html = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${Buffer.from(html).toString("base64")}'))
|
|
99
|
+
[System.Windows.Forms.Clipboard]::SetText($html, [System.Windows.Forms.TextDataFormat]::Html)
|
|
100
|
+
`;
|
|
101
|
+
const result = spawnSync("powershell", ["-Command", ps], { encoding: "utf8", timeout: 10000 });
|
|
102
|
+
if (result.status !== 0) {
|
|
103
|
+
throw new Error(result.stderr?.toString() || "powershell failed");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function main() {
|
|
108
|
+
const markdown = await getStdin();
|
|
109
|
+
|
|
110
|
+
if (!markdown.trim()) {
|
|
111
|
+
console.error("md2clip: empty input");
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const html = marked(markdown);
|
|
116
|
+
|
|
117
|
+
copyAsHTML(html);
|
|
118
|
+
|
|
119
|
+
// Also print to stdout so you can pipe further
|
|
120
|
+
process.stdout.write(html);
|
|
121
|
+
console.error("✓ Copied to clipboard as HTML");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
main().catch((err) => {
|
|
125
|
+
console.error("md2clip error:", err.message);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fmontes/md2clip",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Pipe markdown, get HTML in your clipboard with proper HTML MIME type",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"md2clip": "md2clip.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"md2clip.js"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "echo '**hello** _world_' | node md2clip.js"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"marked": "^12.0.0"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"markdown",
|
|
23
|
+
"clipboard",
|
|
24
|
+
"html",
|
|
25
|
+
"cli"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/fmontes/md2clip.git"
|
|
31
|
+
}
|
|
32
|
+
}
|