all repos — NoPaste @ 12495971e4a060e3d678b16f8484eaf731a67f91

Resurrected - The PussTheCat.org fork of NoPaste

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();