@quark.clip/quark 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,222 @@
1
+ const { marked } = require('marked');
2
+
3
+ function isExcelTSV(text) {
4
+ const lines = text.trim().split('\n').filter(l => l.trim().length > 0);
5
+ if (lines.length < 2) return false;
6
+ const tabs = lines[0].split('\t').length;
7
+ return tabs > 1 && lines.every(l => l.split('\t').length === tabs);
8
+ }
9
+
10
+ function isCSV(text) {
11
+ const lines = text.trim().split('\n').filter(l => l.trim().length > 0);
12
+ if (lines.length < 2) return false;
13
+ const commas = lines[0].split(',').length;
14
+ return commas > 2 && lines.every(l => l.split(',').length === commas);
15
+ }
16
+
17
+ function convertToHTML(text, delimiter) {
18
+ const rows = text.trim().split('\n').filter(l => l.trim().length > 0).map(r => r.split(delimiter));
19
+ let html = '<table style="border-collapse: collapse; font-family: sans-serif; font-size: 14px;">\n';
20
+ rows.forEach((row, i) => {
21
+ html += ' <tr>\n';
22
+ row.forEach(cell => {
23
+ const tag = i === 0 ? 'th' : 'td';
24
+ const bg = i === 0 ? 'background-color: #f3f2f1;' : '';
25
+ const style = `border: 1px solid #d1d1d1; padding: 6px 12px; ${bg}`;
26
+ html += ` <${tag} style="${style}">${cell.trim()}</${tag}>\n`;
27
+ });
28
+ html += ' </tr>\n';
29
+ });
30
+ html += '</table>';
31
+ return html;
32
+ }
33
+
34
+ function stripTrackingParams(url) {
35
+ try {
36
+ const parsed = new URL(url);
37
+ const paramsToRemove = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'si', 'igshid'];
38
+ let changed = false;
39
+ paramsToRemove.forEach(param => {
40
+ if (parsed.searchParams.has(param)) {
41
+ parsed.searchParams.delete(param);
42
+ changed = true;
43
+ }
44
+ });
45
+ return changed ? parsed.toString() : url;
46
+ } catch (e) {
47
+ return url;
48
+ }
49
+ }
50
+
51
+ function fixPDFLineBreaks(text) {
52
+ return text.replace(/([^\n])\n([^\n])/g, '$1 $2');
53
+ }
54
+
55
+ function prettifyJSON(text) {
56
+ try {
57
+ const obj = JSON.parse(text);
58
+ return JSON.stringify(obj, null, 2);
59
+ } catch (e) {
60
+ return text;
61
+ }
62
+ }
63
+
64
+ function applySmartQuotes(text) {
65
+ // Detect language based on character frequency
66
+ const frenchChars = (text.match(/[éèêëàâäôöûüçœ]/gi) || []).length;
67
+ const germanChars = (text.match(/[äöüß]/gi) || []).length;
68
+ const spanishChars = (text.match(/[ñáéíóú¿¡]/gi) || []).length;
69
+
70
+ // If French is dominant
71
+ if (frenchChars > 0 && frenchChars >= germanChars && frenchChars >= spanishChars) {
72
+ return text
73
+ .replace(/(^|\s)"(.*?)"(?=\s|$|[.,!?])/g, "$1« $2 »")
74
+ .replace(/(^|\s)'(.*?)'(?=\s|$|[.,!?])/g, "$1‹ $2 ›")
75
+ .replace(/--/g, "\u2014");
76
+ }
77
+ // If German is dominant
78
+ else if (germanChars > 0 && germanChars > frenchChars && germanChars >= spanishChars) {
79
+ return text
80
+ .replace(/(^|\s)"(.*?)"(?=\s|$|[.,!?])/g, "$1„$2“")
81
+ .replace(/(^|\s)'(.*?)'(?=\s|$|[.,!?])/g, "$1‚$2‘")
82
+ .replace(/--/g, "\u2014");
83
+ }
84
+ // If Spanish is dominant
85
+ else if (spanishChars > 0 && spanishChars > frenchChars && spanishChars > germanChars) {
86
+ return text
87
+ .replace(/(^|\s)"(.*?)"(?=\s|$|[.,!?])/g, "$1«$2»")
88
+ .replace(/(^|[-\u2014\s(\["])'/g, "$1\u2018")
89
+ .replace(/'/g, "\u2019")
90
+ .replace(/--/g, "\u2014");
91
+ }
92
+ // Default English
93
+ else {
94
+ return text
95
+ .replace(/(^|[-\u2014\s(\["])'/g, "$1\u2018")
96
+ .replace(/'/g, "\u2019")
97
+ .replace(/(^|[-\u2014/\[(\u2018\s])"/g, "$1\u201c")
98
+ .replace(/"/g, "\u201d")
99
+ .replace(/--/g, "\u2014");
100
+ }
101
+ }
102
+
103
+ function renderMathFormulas(text) {
104
+ let html = text;
105
+ const blockMathRegex = /\$\$\s*([\s\S]*?)\s*\$\$/g;
106
+ const inlineMathRegex = /\$(?!\s)([^$\n]+?)(?<!\s)\$/g;
107
+
108
+ let hasMath = false;
109
+
110
+ html = html.replace(blockMathRegex, (match, formula) => {
111
+ hasMath = true;
112
+ const encoded = encodeURIComponent(formula.trim());
113
+ return `<div style="text-align: center; margin: 1em 0;"><img src="https://latex.codecogs.com/svg.image?${encoded}" alt="${formula.replace(/"/g, '&quot;')}" /></div>`;
114
+ });
115
+
116
+ html = html.replace(inlineMathRegex, (match, formula) => {
117
+ hasMath = true;
118
+ const encoded = encodeURIComponent(formula.trim());
119
+ return `<img style="vertical-align: middle;" src="https://latex.codecogs.com/svg.image?${encoded}" alt="${formula.replace(/"/g, '&quot;')}" />`;
120
+ });
121
+
122
+ return { hasMath, html };
123
+ }
124
+
125
+ function isMarkdown(text) {
126
+ // Simple heuristic: contains headings, lists, bold, links, or tables
127
+ return /^#+\s/m.test(text) ||
128
+ /\*\*[^*]+\*\*/.test(text) ||
129
+ /\[.+?\]\(.+?\)/.test(text) ||
130
+ /^\s*[-*+]\s/m.test(text) ||
131
+ /^\s*\d+\.\s/m.test(text) ||
132
+ /`[^`]+`/.test(text) ||
133
+ /^> /m.test(text) ||
134
+ (text.includes('|') && /^[-:| ]+$/m.test(text));
135
+ }
136
+
137
+ async function convertMarkdownToHTML(text) {
138
+ // First render math
139
+ const { html: mathProcessed } = renderMathFormulas(text);
140
+ // Then parse markdown
141
+ const html = await marked.parse(mathProcessed);
142
+ // Add some basic styling to tables and code blocks for clipboard pasting
143
+ return html
144
+ .replace(/<table>/g, '<table style="border-collapse: collapse; font-family: sans-serif; font-size: 14px; width: 100%;">')
145
+ .replace(/<th>/g, '<th style="border: 1px solid #d1d1d1; padding: 6px 12px; background-color: #f3f2f1; text-align: left;">')
146
+ .replace(/<td>/g, '<td style="border: 1px solid #d1d1d1; padding: 6px 12px;">')
147
+ .replace(/<code>/g, '<code style="background-color: #f4f4f4; padding: 2px 4px; border-radius: 4px; font-family: monospace;">')
148
+ .replace(/<pre><code.*?>/g, '<pre style="background-color: #f4f4f4; padding: 12px; border-radius: 8px; font-family: monospace; overflow-x: auto;"><code>');
149
+ }
150
+
151
+ function normalizeShouting(text) {
152
+ if (/^[A-Z\s.,!?'-]+$/.test(text) && text.length > 15 && text.includes(' ')) {
153
+ return text.charAt(0) + text.slice(1).toLowerCase();
154
+ }
155
+ return text;
156
+ }
157
+
158
+ // The master pipeline
159
+ async function processClipboard(text, originalHtml) {
160
+ let result = { changed: false, text: text, html: originalHtml };
161
+
162
+ // 0. Case Normalization (Shouting)
163
+ const normalizedText = normalizeShouting(text);
164
+ if (normalizedText !== text) {
165
+ text = normalizedText;
166
+ result.text = text;
167
+ result.changed = true;
168
+ }
169
+
170
+ // 0.5 Smart Quotes
171
+ if (text.includes('"') || text.includes("'")) {
172
+ if (!text.startsWith('{') && !text.startsWith('[') && !text.includes('function') && !text.includes('const ')) {
173
+ const smartText = applySmartQuotes(text);
174
+ if (smartText !== text) {
175
+ text = smartText;
176
+ result.text = text;
177
+ result.changed = true;
178
+ }
179
+ }
180
+ }
181
+
182
+ // 1. URL Tracking Stripper
183
+ if (text.startsWith('http') && text.includes('utm_')) {
184
+ const clean = stripTrackingParams(text);
185
+ if (clean !== text) return { changed: true, text: clean, html: null };
186
+ }
187
+
188
+ // 2. JSON Prettier
189
+ if ((text.startsWith('{') && text.endsWith('}')) || (text.startsWith('[') && text.endsWith(']'))) {
190
+ const pretty = prettifyJSON(text);
191
+ if (pretty !== text && pretty.includes('\n')) return { changed: true, text: pretty, html: null };
192
+ }
193
+
194
+ // 3. Excel TSV to HTML
195
+ if (isExcelTSV(text)) {
196
+ return { changed: true, text: text, html: convertToHTML(text, '\t') };
197
+ }
198
+
199
+ // 4. CSV to HTML
200
+ if (isCSV(text) && !originalHtml) {
201
+ return { changed: true, text: text, html: convertToHTML(text, ',') };
202
+ }
203
+
204
+ // 5. Full Markdown & Math to HTML
205
+ const mathResult = renderMathFormulas(text);
206
+ if ((isMarkdown(text) || mathResult.hasMath) && !originalHtml) {
207
+ const html = await convertMarkdownToHTML(text);
208
+ return { changed: true, text: text, html: html };
209
+ }
210
+
211
+ // 6. PDF Line Breaks (Only apply if it looks like a broken paragraph, heuristic)
212
+ if (text.length > 100 && text.split('\n').length > 3 && !text.includes('\t') && !originalHtml) {
213
+ const fixed = fixPDFLineBreaks(text);
214
+ if (fixed !== text && fixed.length < text.length) {
215
+ return { changed: true, text: fixed, html: null };
216
+ }
217
+ }
218
+
219
+ return result;
220
+ }
221
+
222
+ module.exports = { processClipboard };
package/dist/404.html ADDED
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Quark</title>
6
+ <script type="text/javascript">
7
+ // Single Page Apps for GitHub Pages
8
+ // MIT License
9
+ // https://github.com/rafgraph/spa-github-pages
10
+ var pathSegmentsToKeep = 1;
11
+ var l = window.location;
12
+ l.replace(
13
+ l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') +
14
+ l.pathname.split('/').slice(0, 1 + pathSegmentsToKeep).join('/') + '/?/' +
15
+ l.pathname.slice(1).split('/').slice(pathSegmentsToKeep).join('/').replace(/&/g, '~and~') +
16
+ (l.search ? '&' + l.search.slice(1).replace(/&/g, '~and~') : '') +
17
+ l.hash
18
+ );
19
+ </script>
20
+ </head>
21
+ <body>
22
+ </body>
23
+ </html>