all repos — NoPaste @ 81991220be323f8ad2c6ba2b865376a3c4c59364

Resurrected - The PussTheCat.org fork of NoPaste

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        hideCopyBar();
 33    });
 34};
 35
 36const initLangSelector = () => {
 37    select = new SlimSelect({
 38        select: '#language',
 39        data: CodeMirror.modeInfo.map((e) => ({
 40            text: e.name,
 41            value: hash(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    // Retro-compatibility
 53    const legacyLang = new URLSearchParams(window.location.search).get('lang');
 54    if (legacyLang) {
 55        const langObj = CodeMirror.modeInfo.find((e) => slugify(e.name) === legacyLang);
 56        if (langObj) {
 57            select.set(hash(langObj.name));
 58            return;
 59        }
 60    }
 61    // Set lang selector
 62    select.set(window.location.hash.charAt(5) === '-' ? window.location.hash.substr(1, 4) : hash('Plain Text'));
 63};
 64
 65const initCode = () => {
 66    let base64 = location.pathname.substr(1) || location.hash.substr(1);
 67    if (base64.charAt(4) === '-') {
 68        base64 = base64.substr(5);
 69    }
 70    if (base64.length === 0) {
 71        return;
 72    }
 73    decompress(base64, (code, err) => {
 74        if (err) {
 75            alert('Failed to decompress data: ' + err);
 76            return;
 77        }
 78        editor.setValue(code);
 79    });
 80};
 81
 82const initClipboard = () => {
 83    clipboard = new ClipboardJS('.clipboard');
 84    clipboard.on('success', () => {
 85        hideCopyBar(true);
 86    });
 87};
 88
 89const generateLink = (mode) => {
 90    const data = editor.getValue();
 91    compress(data, (base64, err) => {
 92        if (err) {
 93            alert('Failed to compress data: ' + err);
 94            return;
 95        }
 96        const url = buildUrl(base64, mode);
 97        statsEl.innerHTML = `Data length: ${data.length} |  Link length: ${
 98            url.length
 99        } | Compression ratio: ${Math.round((100 * url.length) / data.length)}%`;
100
101        showCopyBar(url);
102    });
103};
104
105// Open the "Copy" bar and select the content
106const showCopyBar = (dataToCopy) => {
107    byId('copy').classList.remove('hidden');
108    const linkInput = byId('copy-link');
109    linkInput.value = dataToCopy;
110    linkInput.focus();
111    linkInput.setSelectionRange(0, dataToCopy.length);
112};
113
114// Close the "Copy" bar
115const hideCopyBar = (success) => {
116    const copyButton = byId('copy-btn');
117    const copyBar = byId('copy');
118    if (!success) {
119        copyBar.classList.add('hidden');
120        return;
121    }
122    copyButton.innerText = 'Copied !';
123    setTimeout(() => {
124        copyBar.classList.add('hidden');
125        copyButton.innerText = 'Copy';
126    }, 800);
127};
128
129const disableLineWrapping = () => {
130    byId('disable-line-wrapping').classList.add('hidden');
131    byId('enable-line-wrapping').classList.remove('hidden');
132    editor.setOption('lineWrapping', false);
133};
134
135const enableLineWrapping = () => {
136    byId('enable-line-wrapping').classList.add('hidden');
137    byId('disable-line-wrapping').classList.remove('hidden');
138    editor.setOption('lineWrapping', true);
139};
140
141const openInNewTab = () => {
142    window.open(location.href.replace('&readonly', ''));
143};
144
145// Build a shareable URL
146const buildUrl = (rawData, mode) => {
147    const base = `${location.protocol}//${location.host}/`;
148    const lang = hash('Plain Text') === select.selected() ? '' : select.selected() + '-';
149    const url = base + '#' + lang + rawData;
150    if (mode === 'markdown') {
151        return `[NoPaste snippet](${url})`;
152    }
153    if (mode === 'iframe') {
154        const height = Math.min(editor['doc'].height + 45, 800);
155        return `<iframe width="100%" height="${height}" frameborder="0" src="${url}"></iframe>`;
156    }
157    return url;
158};
159
160// Transform a compressed base64 string into a plain text string
161const decompress = (base64, cb) => {
162    const progressBar = byId('progress');
163
164    const req = new XMLHttpRequest();
165    req.open('GET', 'data:application/octet;base64,' + base64);
166    req.responseType = 'arraybuffer';
167    req.onload = (e) => {
168        lzma.decompress(
169            new Uint8Array(e.target.response),
170            (result, err) => {
171                progressBar.style.width = '0';
172                cb(result, err);
173            },
174            (progress) => {
175                progressBar.style.width = 100 * progress + '%';
176            }
177        );
178    };
179    req.send();
180};
181
182// Transform a plain text string into a compressed base64 string
183const compress = (str, cb) => {
184    if (str.length === 0) {
185        cb('');
186        return;
187    }
188    const progressBar = byId('progress');
189
190    lzma.compress(
191        str,
192        1,
193        (compressed, err) => {
194            if (err) {
195                progressBar.style.width = '0';
196                cb(compressed, err);
197                return;
198            }
199            const reader = new FileReader();
200            reader.onload = () => {
201                progressBar.style.width = '0';
202                cb(reader.result.substr(reader.result.indexOf(',') + 1));
203            };
204            reader.readAsDataURL(new Blob([new Uint8Array(compressed)]));
205        },
206        (progress) => {
207            progressBar.style.width = 100 * progress + '%';
208        }
209    );
210};
211
212const slugify = (str) =>
213    str
214        .toString()
215        .toLowerCase()
216        .replace(/\s+/g, '-')
217        .replace(/\+/g, '-p')
218        .replace(/#/g, '-sharp')
219        .replace(/[^\w\-]+/g, '');
220
221const byId = (id) => document.getElementById(id);
222
223const hash = function (str, seed = 0) {
224    let h1 = 0xdeadbeef ^ seed;
225    let h2 = 0x41c6ce57 ^ seed;
226    for (let i = 0, ch; i < str.length; i++) {
227        ch = str.charCodeAt(i);
228        h1 = Math.imul(h1 ^ ch, 2654435761);
229        h2 = Math.imul(h2 ^ ch, 1597334677);
230    }
231    h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
232    h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
233    const h = 4294967296 * (2097151 & h2) + (h1 >>> 0);
234    return h.toString(36).substr(0, 4).toUpperCase();
235};
236
237/* Only for tests purposes */
238const testAllModes = () => {
239    for (const [index, language] of Object.entries(CodeMirror.modeInfo)) {
240        setTimeout(() => {
241            console.info(language.name);
242            select.set(slugify(language.name));
243        }, 1000 * index);
244    }
245};
246
247/* Only for tests purposes */
248const testHashCollisions = () => {
249    const hashes = {};
250    console.time('Hash test');
251    for (const lang of CodeMirror.modeInfo) {
252        const hashed = hash(lang.name);
253        if (hashes[hashed]) {
254            console.error(`${lang.name} and ${hashes[hashed]} share the same hash: ${hashed}`);
255        }
256        hashes[hashed] = lang.name;
257    }
258    console.timeEnd('Hash test');
259};
260
261init();