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