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