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