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