From 576a543703a1a191923ef357dfc95805a1721eeb Mon Sep 17 00:00:00 2001 From: Joe Ma Date: Sun, 26 Mar 2023 18:54:35 +0800 Subject: [PATCH] Add modal to show paste info (web) Done paste upload logic (web) Add json response (paste) Signed-off-by: Joe Ma --- .idea/inspectionProfiles/Project_Default.xml | 2 + src/index.ts | 84 ++++++++--- tsconfig.json | 9 +- web/v1/paste.html | 6 +- web/v2/js/paste.js | 138 +++++++++++++++++-- web/v2/paste.html | 101 +++++++++++--- 6 files changed, 289 insertions(+), 51 deletions(-) diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index e6ce300..93dd4c4 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -4,10 +4,12 @@ + + \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 91b9cf8..ce44032 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,7 +23,8 @@ import dedent from 'dedent-js'; // Constants const SERVICE_URL = 'pb.nekoul.com'; -const PASTE_INDEX_HTML_URL = 'https://raw.githubusercontent.com/rikkaneko/paste/main/paste.html'; +const PASTE_INDEX_HTML_URL_v1 = 'https://raw.githubusercontent.com/rikkaneko/paste/main/web/v1/paste.html'; +const PASTE_INDEX_HTML_URL = 'https://raw.githubusercontent.com/rikkaneko/paste/main/web/v2/paste.html'; const UUID_LENGTH = 4; export interface Env { @@ -67,6 +68,22 @@ export default { }); } + if (path === '/v1' && method == 'GET') { + return await fetch(PASTE_INDEX_HTML_URL_v1, { + cf: { + cacheEverything: true, + }, + }).then(value => { + return new Response(value.body, { + // Add the correct content-type to response header + headers: { + 'content-type': 'text/html; charset=UTF-8;', + 'cache-control': 'public, max-age=172800', + }, + }); + }); + } + if (path === '/') { switch (method) { // Fetch the HTML for uploading text/file @@ -98,6 +115,7 @@ export default { let read_limit: number | undefined; let need_qrcode: boolean = false; let paste_type: string | undefined; + let reply_json: boolean = false; // Content-Type: multipart/form-data (deprecated) if (content_type.includes('multipart/form-data')) { const formdata = await request.formData(); @@ -142,17 +160,24 @@ export default { // Check if qrcode generation needed const qr = formdata.get('qrcode'); - if (typeof qr === 'string' && qr.toLowerCase() === 'true' || qr === 'on') { + if (typeof qr === 'string' && qr === '1') { need_qrcode = true; } - // Paste API v2 + // Check reply format + const json = formdata.get('json'); + if (typeof json === 'string' && json === '1') { + reply_json = true; + } + + // Paste API v2 } else { title = headers.get('x-paste-title') || undefined; mime_type = headers.get('x-paste-content-type') || undefined; password = headers.get('x-paste-pass') || undefined; paste_type = headers.get('x-paste-type') || undefined; need_qrcode = headers.get('x-paste-qr') === '1'; + reply_json = headers.get('x-json') === '1'; const count = headers.get('x-paste-read-limit') || ''; const n = parseInt(count); if (isNaN(n) || n <= 0) { @@ -235,7 +260,7 @@ export default { // Key will be expired after 28 day if unmodified ctx.waitUntil(env.PASTE_INDEX.put(uuid, JSON.stringify(descriptor), {expirationTtl: 2419200})); - return await get_paste_info(uuid, descriptor, env, is_browser, need_qrcode); + return await get_paste_info(uuid, descriptor, env, is_browser, need_qrcode, reply_json); } else { return new Response('Unable to upload the paste.\n', { status: 500, @@ -462,22 +487,49 @@ export default { }, }; -async function get_paste_info(uuid: string, descriptor: PasteIndexEntry, env: Env, use_html: boolean = true, need_qr: boolean = false): Promise { +async function get_paste_info(uuid: string, descriptor: PasteIndexEntry, env: Env, + use_html: boolean = true, need_qr: boolean = false, reply_json = false): Promise { const created = new Date(descriptor.last_modified); const expired = new Date(descriptor.last_modified + 2419200000); const link = `https://${SERVICE_URL}/${uuid}`; + const paste_info = { + uuid, + link, + link_qr: 'https://qrcode.nekoul.com/?' + new URLSearchParams({q: link, type: 'svg'}), + type: descriptor.type ?? 'paste', + title: descriptor.title?.trim(), + mime_type: descriptor.mime_type, + human_readable_size: `${to_human_readable_size(descriptor.size)}`, + size: descriptor.size, + password: !!descriptor.password, + read_count_remain: descriptor.read_count_remain, + created: created.toISOString(), + expired: expired.toISOString(), + }; + + // Reply with JSON + if (reply_json) { + return new Response(JSON.stringify(paste_info), { + headers: { + 'content-type': 'application/json; charset=utf-8', + 'cache-control': 'no-store', + }, + }); + } + + // Plain text reply let content = dedent` - id: ${uuid} + uuid: ${uuid} link: ${link} - type: ${descriptor.type ?? 'paste'} - title: ${descriptor.title?.trim() || '-'} - mime-type: ${descriptor.mime_type ?? '-'} - size: ${descriptor.size} bytes (${to_human_readable_size(descriptor.size)}) - password: ${(!!descriptor.password)} - remaining read count: ${descriptor.read_count_remain !== undefined ? - descriptor.read_count_remain ? descriptor.read_count_remain : `0 (expired)` : '-'} - created at ${created.toISOString()} - expired at ${expired.toISOString()} + type: ${paste_info.type ?? 'paste'} + title: ${paste_info.title || '-'} + mime-type: ${paste_info.mime_type ?? '-'} + size: ${paste_info.size} bytes (${paste_info.human_readable_size}) + password: ${paste_info.password} + remaining read count: ${paste_info.read_count_remain !== undefined ? + paste_info.read_count_remain ? paste_info.read_count_remain : `0 (expired)` : '-'} + created at ${paste_info.created} + expired at ${paste_info.expired} `; // Browser response @@ -492,7 +544,7 @@ async function get_paste_info(uuid: string, descriptor: PasteIndexEntry, env: En
${content}
- ${(need_qr) ? `${link}` : ''} diff --git a/tsconfig.json b/tsconfig.json index fc1cb5b..617967e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -42,12 +42,15 @@ ] /* Specify type package names to be included without being referenced in a source file. */, // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - "resolveJsonModule": true /* Enable importing .json files */, + "resolveJsonModule": true + /* Enable importing .json files */, // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ /* JavaScript Support */ - "allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */, - "checkJs": true /* Enable error reporting in type-checked JavaScript files. */, + "allowJs": true + /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */, + "checkJs": false + /* Enable error reporting in type-checked JavaScript files. */, // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ /* Emit */ diff --git a/web/v1/paste.html b/web/v1/paste.html index c281ce3..ade77d6 100644 --- a/web/v1/paste.html +++ b/web/v1/paste.html @@ -83,7 +83,7 @@ - +
@@ -169,7 +169,9 @@ document.getElementById('upload_file_form').addEventListener('submit', handle_submit_form, false);
-[Homepage][API] +[Homepage] +[API] +[Switch to new version]

© 2022 rikkaneko

diff --git a/web/v2/js/paste.js b/web/v2/js/paste.js index d8b4f4a..106a994 100644 --- a/web/v2/js/paste.js +++ b/web/v2/js/paste.js @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +const endpoint = 'https://pb.nekoul.com'; + let input_div = { file: null, text: null, @@ -28,7 +30,15 @@ let inputs = { url: null, }; -let selected_type = 'file'; +let paste_modal = { + modal: null, + uuid: null, + qrcode: null, + title: null, + expired: null, +}; + +let saved_modal = null; function validate_url(path) { let url; @@ -43,12 +53,13 @@ function validate_url(path) { function show_pop_alert(message, alert_type = 'alert-primary') { remove_pop_alert(); $('body').prepend(jQuery.parseHTML( - ``, )); + window.scrollTo(0, 0); } function remove_pop_alert() { @@ -57,6 +68,37 @@ function remove_pop_alert() { alert.remove(); } +function build_paste_modal(paste_info, show_qrcode = true) { + // Show saved modal + if (!!!paste_info && !!!saved_modal) { + console.err('Invalid call to build_paste_modal().'); + return; + } + + if (!!!paste_info) { + saved_modal.show(); + return; + } + + saved_modal = null; + paste_modal.uuid.text(paste_info.link); + paste_modal.uuid.prop('href', paste_info.link); + paste_modal.qrcode.prop('src', paste_info.link_qr); + paste_modal.qrcode.prop('alt', paste_info.link); + + // Hide/Show QRCode + if (!show_qrcode) paste_modal.qrcode.addClass('d-none'); + else paste_modal.qrcode.removeClass('d-none'); + + Object.entries(paste_info).forEach(([key, val]) => { + if (key.includes('link')) return; + $(`#paste_info_${key}`).text(val ?? '-'); + }); + let modal = new bootstrap.Modal(paste_modal.modal); + saved_modal = modal; + modal.show(); +} + $(function () { input_div.file = $('#file_upload_layout'); input_div.text = $('#text_input_layout'); @@ -64,6 +106,9 @@ $(function () { inputs.file = $('#file_upload'); inputs.text = $('#text_input'); inputs.url = $('#url_input'); + paste_modal.modal = $('#paste_modal'); + paste_modal.uuid = $('#paste_uuid'); + paste_modal.qrcode = $('#paste_qrcode'); let file_stat = $('#file_stats'); let title = $('#paste_title'); @@ -73,6 +118,15 @@ $(function () { let upload_button = $('#upload_button'); let url_validate_result = $('#url_validate_result'); let tos_btn = $('#tos_btn'); + let show_saved_btn = $('#show_saved_button'); + let go_btn = $('#go_button'); + let go_id = $('#go_paste_id'); + + // Enable bootstrap tooltips + const tooltip_trigger_list = [].slice.call($('[data-bs-toggle="tooltip"]')); + const tooltip_list = tooltip_trigger_list.map(function (e) { + return new bootstrap.Tooltip(e); + }); inputs.file.on('change', function () { inputs.file.removeClass('is-invalid'); @@ -124,6 +178,7 @@ $(function () { inputs.url.on('input', function () { inputs.url.removeClass('is-invalid'); url_validate_result.removeClass('text-danger'); + url_validate_result.text(''); if (!validate_url(this.value)) { inputs.url.addClass('is-invalid'); url_validate_result.addClass('text-danger'); @@ -131,16 +186,15 @@ $(function () { } }); - upload_button.on('click', function () { - inputs[selected_type].trigger('input'); + upload_button.on('click', async function () { const form = $('#upload_form')[0]; - const formdata = new FormData(form); - let content = {}; - formdata.forEach((val, key) => { - content[key] = val; - }); + let formdata = new FormData(form); + const type = formdata.get('paste-type'); + const content = formdata.get('u'); + const show_qrcode = formdata.get('qrcode') === '1'; - if (inputs[selected_type].hasClass('is-invalid') || !(!!content.u?.size || !!content.u?.length)) { + inputs[type].trigger('input'); + if (inputs[type].hasClass('is-invalid') || !(!!content?.size || !!content?.length)) { show_pop_alert('Please check your upload file or content', 'alert-danger'); return false; } @@ -150,13 +204,71 @@ $(function () { return false; } - // TODO Upload to pb service - show_pop_alert('Paste created!', 'alert-success'); + switch (type) { + case 'file': + formdata.set('paste-type', 'paste'); + break; + case 'text': + formdata.set('paste-type', 'paste'); + break; + case 'url': + formdata.set('paste-type', 'link'); + } + + // Remove empty entries + let filtered = new FormData(); + formdata.forEach((val, key) => { + if (!!val) filtered.set(key, val); + }); + + // Request JSON response + filtered.set('json', '1'); + upload_button.prop('disabled', true); + upload_button.text('Uploading...'); + try { + const res = await fetch(endpoint, { + method: 'POST', + body: filtered, + }); + + const paste_info = await res.json(); + + if (res.ok) { + show_pop_alert('Paste created!', 'alert-success'); + pass_input.val(''); + build_paste_modal(paste_info, show_qrcode); + show_saved_btn.prop('disabled', false); + } else { + show_pop_alert('Unable to create paste', 'alert-warning'); + } + } catch (err) { + console.log('error', err); + show_pop_alert(err.message, 'alert-danger'); + } + + upload_button.prop('disabled', false); + upload_button.text('Upload'); + }); + + show_saved_btn.on('click', function () { + if (!!!saved_modal) { + show_pop_alert('No saved paste found.', 'alert-warning'); + return; + } + saved_modal.show(); + }); + + go_btn.on('click', function () { + const uuid = go_id.val(); + if (uuid.length !== 4) { + show_pop_alert('Invalid Paste ID.', 'alert-warning'); + return; + } + window.open(`https://pb.nekoul.com/${uuid}`); }); }); function select_input_type(name) { - selected_type = name; Object.keys(input_div).forEach(key => { input_div[key].collapse('hide'); inputs[key].prop('disabled', true); diff --git a/web/v2/paste.html b/web/v2/paste.html index ef5a8cb..b6d533b 100644 --- a/web/v2/paste.html +++ b/web/v2/paste.html @@ -39,15 +39,19 @@
@@ -58,13 +62,13 @@
- - -
@@ -131,7 +135,7 @@
- + @@ -141,33 +145,37 @@ - +
+
- + +
-