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