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 initCodeEditor();
11 initLangSelector();
12 initCode();
13 initClipboard();
14};
15
16const initCodeEditor = () => {
17 const readOnly = new URLSearchParams(window.location.search).has('readonly');
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: true,
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 });
34};
35
36const initLangSelector = () => {
37 select = new SlimSelect({
38 select: '#language',
39 data: CodeMirror.modeInfo.map((e) => ({
40 text: e.name,
41 value: slugify(e.name),
42 data: { mime: e.mime, mode: e.mode },
43 })),
44 showContent: 'down',
45 onChange: (e) => {
46 const language = e.data || { mime: null, mode: null };
47 editor.setOption('mode', language.mime);
48 CodeMirror.autoLoadMode(editor, language.mode);
49 },
50 });
51
52 select.set(decodeURIComponent(new URLSearchParams(window.location.search).get('lang') || 'plain-text'));
53};
54
55const initCode = () => {
56 const base64 = location.pathname.substr(1) || location.hash.substr(1);
57 if (base64.length === 0) {
58 return;
59 }
60 decompress(base64, (code, err) => {
61 if (err) {
62 alert('Failed to decompress data: ' + err);
63 return;
64 }
65 editor.setValue(code);
66 });
67};
68
69const initClipboard = () => {
70 clipboard = new ClipboardJS('.clipboard');
71 clipboard.on('success', () => {
72 hideCopyBar(true);
73 });
74};
75
76const generateLink = (mode) => {
77 const data = editor.getValue();
78 compress(data, (base64, err) => {
79 if (err) {
80 alert('Failed to compress data: ' + err);
81 return;
82 }
83 const url = buildUrl(base64, mode);
84 statsEl.innerHTML = `Data length: ${data.length} | Link length: ${
85 url.length
86 } | Compression ratio: ${Math.round((100 * url.length) / data.length)}%`;
87
88 showCopyBar(url);
89 });
90};
91
92// Open the "Copy" bar and select the content
93const showCopyBar = (dataToCopy) => {
94 byId('copy').classList.remove('hidden');
95 const linkInput = byId('copy-link');
96 linkInput.value = dataToCopy;
97 linkInput.focus();
98 linkInput.setSelectionRange(0, dataToCopy.length);
99};
100
101// Close the "Copy" bar
102const hideCopyBar = (success) => {
103 const copyButton = byId('copy-btn');
104 const copyBar = byId('copy');
105 if (!success) {
106 copyBar.classList.add('hidden');
107 return;
108 }
109 copyButton.innerText = 'Copied !';
110 setTimeout(() => {
111 copyBar.classList.add('hidden');
112 copyButton.innerText = 'Copy';
113 }, 800);
114};
115
116const disableLineWrapping = () => {
117 byId('disable-line-wrapping').classList.add('hidden');
118 byId('enable-line-wrapping').classList.remove('hidden');
119 editor.setOption('lineWrapping', false);
120};
121
122const enableLineWrapping = () => {
123 byId('enable-line-wrapping').classList.add('hidden');
124 byId('disable-line-wrapping').classList.remove('hidden');
125 editor.setOption('lineWrapping', true);
126};
127
128// Build a shareable URL
129const buildUrl = (rawData, mode) => {
130 const url = `${location.protocol}//${location.host}/` + rawData + `?lang=${encodeURIComponent(select.selected())}`;
131 if (mode === 'markdown') {
132 return `[NoPaste snippet](${url})`;
133 }
134 if (mode === 'iframe') {
135 const height = editor['doc'].size + 30;
136 return `<iframe width="100%" height="${height}" frameborder="0" src="${url}&readonly"></iframe>`;
137 }
138 return url;
139};
140
141// Transform a compressed base64 string into a plain text string
142const decompress = (base64, cb) => {
143 const progressBar = byId('progress');
144
145 const req = new XMLHttpRequest();
146 req.open('GET', 'data:application/octet;base64,' + base64);
147 req.responseType = 'arraybuffer';
148 req.onload = (e) => {
149 lzma.decompress(
150 new Uint8Array(e.target.response),
151 (result, err) => {
152 progressBar.style.width = '0';
153 cb(result, err);
154 },
155 (progress) => {
156 progressBar.style.width = 100 * progress + '%';
157 }
158 );
159 };
160 req.send();
161};
162
163// Transform a plain text string into a compressed base64 string
164const compress = (str, cb) => {
165 const progressBar = byId('progress');
166
167 lzma.compress(
168 str,
169 1,
170 (compressed, err) => {
171 if (err) {
172 progressBar.style.width = '0';
173 cb(compressed, err);
174 return;
175 }
176 const reader = new FileReader();
177 reader.onload = () => {
178 progressBar.style.width = '0';
179 cb(reader.result.substr(reader.result.indexOf(',') + 1));
180 };
181 reader.readAsDataURL(new Blob([new Uint8Array(compressed)]));
182 },
183 (progress) => {
184 progressBar.style.width = 100 * progress + '%';
185 }
186 );
187};
188
189const slugify = (str) =>
190 str
191 .toString()
192 .toLowerCase()
193 .replace(/\s+/g, '-')
194 .replace(/\+/g, '-p')
195 .replace(/#/g, '-sharp')
196 .replace(/[^\w\-]+/g, '');
197
198const byId = (id) => document.getElementById(id);
199
200/* Only for tests purposes */
201const testAllModes = () => {
202 for (const [index, language] of Object.entries(CodeMirror.modeInfo)) {
203 setTimeout(() => {
204 console.info(language.name);
205 select.set(slugify(language.name));
206 }, 1000 * index);
207 }
208};
209
210init();