all repos — NoPaste @ 5764564681738905d7c69dba74a16cbcb952874a

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