all repos — NoPaste @ 29b774f090102303e43cf939b38ac2083e62d9f1

Resurrected - The PussTheCat.org fork of NoPaste

scripts/script.js (view raw)

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