index.js (view raw)
1const blob = new Blob(['importScripts("https://cdn.jsdelivr.net/npm/lzma@2.3.2/src/lzma_worker.min.js");']);
2const lzma = new LZMA(window.URL.createObjectURL(blob));
3
4let editor = null;
5let select = null;
6let clipboard = null;
7let statsEl = null;
8
9const init = () => {
10 handleLegacyUrl();
11 initCodeEditor();
12 initLangSelector();
13 initCode();
14 initClipboard();
15};
16
17const initCodeEditor = () => {
18 CodeMirror.modeURL = 'https://cdn.jsdelivr.net/npm/codemirror@5.52.0/mode/%N/%N.js';
19 editor = new CodeMirror(byId('editor'), {
20 lineNumbers: true,
21 theme: 'dracula',
22 readOnly: readOnly,
23 lineWrapping: false,
24 scrollbarStyle: 'simple',
25 });
26 if (readOnly) {
27 document.body.classList.add('readonly');
28 }
29
30 statsEl = byId('stats');
31 editor.on('change', () => {
32 statsEl.innerHTML = `Length: ${editor.getValue().length} | Lines: ${editor['doc'].size}`;
33 hideCopyBar();
34 });
35};
36
37const initLangSelector = () => {
38 select = new SlimSelect({
39 select: '#language',
40 data: CodeMirror.modeInfo.map((e) => ({
41 text: e.name,
42 value: shorten(e.name),
43 data: { mime: e.mime, mode: e.mode },
44 })),
45 showContent: 'down',
46 onChange: (e) => {
47 const language = e.data || { mime: null, mode: null };
48 editor.setOption('mode', language.mime);
49 CodeMirror.autoLoadMode(editor, language.mode);
50 },
51 });
52
53 // Set lang selector
54 const l = new URLSearchParams(window.location.search).get('l');
55 select.set(l ? decodeURIComponent(l) : shorten('Plain Text'));
56};
57
58const initCode = () => {
59 let base64 = location.hash.substr(1);
60 if (base64.length === 0) {
61 return;
62 }
63 decompress(base64, (code, err) => {
64 if (err) {
65 alert('Failed to decompress data: ' + err);
66 return;
67 }
68 editor.setValue(code);
69 });
70};
71
72const handleLegacyUrl = () => {
73 const lang = new URLSearchParams(window.location.search).get('lang');
74 const base = `${location.protocol}//${location.host}`;
75 if (location.hash.charAt(5) === '-') {
76 const hashedLang = location.hash.substr(1, 4);
77 const newLang = CodeMirror.modeInfo.find((e) => hash(e.name) === hashedLang);
78 const queryParams = newLang ? '?l=' + shorten(newLang.name) : '';
79 location.replace(`${base}/${queryParams}#${location.hash.substr(6)}`);
80 throw new Error('waiting for page to reload');
81 }
82 if (lang) {
83 location.replace(`${base}/${'?l=' + shorten(lang)}${location.hash}`);
84 throw new Error('waiting for page to reload');
85 }
86};
87
88const initClipboard = () => {
89 clipboard = new ClipboardJS('.clipboard');
90 clipboard.on('success', () => {
91 hideCopyBar(true);
92 });
93};
94
95const generateLink = (mode) => {
96 const data = editor.getValue();
97 compress(data, (base64, err) => {
98 if (err) {
99 alert('Failed to compress data: ' + err);
100 return;
101 }
102 const url = buildUrl(base64, mode);
103 statsEl.innerHTML = `Data length: ${data.length} | Link length: ${
104 url.length
105 } | Compression ratio: ${Math.round((100 * url.length) / data.length)}%`;
106
107 showCopyBar(url);
108 });
109};
110
111// Open the "Copy" bar and select the content
112const showCopyBar = (dataToCopy) => {
113 byId('copy').classList.remove('hidden');
114 const linkInput = byId('copy-link');
115 linkInput.value = dataToCopy;
116 linkInput.focus();
117 linkInput.setSelectionRange(0, dataToCopy.length);
118};
119
120// Close the "Copy" bar
121const hideCopyBar = (success) => {
122 const copyButton = byId('copy-btn');
123 const copyBar = byId('copy');
124 if (!success) {
125 copyBar.classList.add('hidden');
126 return;
127 }
128 copyButton.innerText = 'Copied !';
129 setTimeout(() => {
130 copyBar.classList.add('hidden');
131 copyButton.innerText = 'Copy';
132 }, 800);
133};
134
135const disableLineWrapping = () => {
136 byId('disable-line-wrapping').classList.add('hidden');
137 byId('enable-line-wrapping').classList.remove('hidden');
138 editor.setOption('lineWrapping', false);
139};
140
141const enableLineWrapping = () => {
142 byId('enable-line-wrapping').classList.add('hidden');
143 byId('disable-line-wrapping').classList.remove('hidden');
144 editor.setOption('lineWrapping', true);
145};
146
147const openInNewTab = () => {
148 window.open(location.href.replace('&readonly', ''));
149};
150
151// Build a shareable URL
152const buildUrl = (rawData, mode) => {
153 const base = `${location.protocol}//${location.host}/`;
154 const query = shorten('Plain Text') === select.selected() ? '' : `?l=${encodeURIComponent(select.selected())}`;
155 const url = base + query + '#' + rawData;
156 if (mode === 'markdown') {
157 return `[NoPaste snippet](${url})`;
158 }
159 if (mode === 'iframe') {
160 const height = editor['doc'].height + 45;
161 return `<iframe width="100%" height="${height}" frameborder="0" src="${url}"></iframe>`;
162 }
163 return url;
164};
165
166// Transform a compressed base64 string into a plain text string
167const decompress = (base64, cb) => {
168 const progressBar = byId('progress');
169
170 const req = new XMLHttpRequest();
171 req.open('GET', 'data:application/octet;base64,' + base64);
172 req.responseType = 'arraybuffer';
173 req.onload = (e) => {
174 lzma.decompress(
175 new Uint8Array(e.target.response),
176 (result, err) => {
177 progressBar.style.width = '0';
178 cb(result, err);
179 },
180 (progress) => {
181 progressBar.style.width = 100 * progress + '%';
182 }
183 );
184 };
185 req.send();
186};
187
188// Transform a plain text string into a compressed base64 string
189const compress = (str, cb) => {
190 if (str.length === 0) {
191 cb('');
192 return;
193 }
194 const progressBar = byId('progress');
195
196 lzma.compress(
197 str,
198 1,
199 (compressed, err) => {
200 if (err) {
201 progressBar.style.width = '0';
202 cb(compressed, err);
203 return;
204 }
205 const reader = new FileReader();
206 reader.onload = () => {
207 progressBar.style.width = '0';
208 cb(reader.result.substr(reader.result.indexOf(',') + 1));
209 };
210 reader.readAsDataURL(new Blob([new Uint8Array(compressed)]));
211 },
212 (progress) => {
213 progressBar.style.width = 100 * progress + '%';
214 }
215 );
216};
217
218const slugify = (str) =>
219 str
220 .trim()
221 .toString()
222 .toLowerCase()
223 .replace(/\s+/g, '-')
224 .replace(/\+/g, '-p')
225 .replace(/#/g, '-sharp')
226 .replace(/[^\w\-]+/g, '');
227
228const shorten = (name) => {
229 let n = slugify(name).replace('script', '-s').replace('python', 'py');
230 const nov = (s) => s[0] + s.substr(1).replace(/[aeiouy-]/g, '');
231 if (n.replace(/-/g, '').length <= 4) {
232 return n.replace(/-/g, '');
233 }
234 if (n.split('-').length >= 2) {
235 return n
236 .split('-')
237 .map((x) => nov(x.substr(0, 2)))
238 .join('')
239 .substr(0, 4);
240 }
241 n = nov(n);
242 if (n.length <= 4) {
243 return n;
244 }
245 return n.substr(0, 2) + n.substr(n.length - 2, 2);
246};
247
248const byId = (id) => document.getElementById(id);
249
250// Legacy code, only for retro-compatibility
251const hash = function (str, seed = 0) {
252 let h1 = 0xdeadbeef ^ seed;
253 let h2 = 0x41c6ce57 ^ seed;
254 for (let i = 0, ch; i < str.length; i++) {
255 ch = str.charCodeAt(i);
256 h1 = Math.imul(h1 ^ ch, 2654435761);
257 h2 = Math.imul(h2 ^ ch, 1597334677);
258 }
259 h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
260 h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
261 const h = 4294967296 * (2097151 & h2) + (h1 >>> 0);
262 return h.toString(36).substr(0, 4).toUpperCase();
263};
264
265// Only for tests purposes
266const testAllModes = () => {
267 for (const [index, language] of Object.entries(CodeMirror.modeInfo)) {
268 CodeMirror.autoLoadMode(editor, language.mode);
269 setTimeout(() => {
270 console.info(language.name);
271 select.set(shorten(language.name));
272 }, 1000 * index);
273 }
274};
275
276init();