diff --git a/frontend/wizard-vue/src/components/EditUrlModal.vue b/frontend/wizard-vue/src/components/EditUrlModal.vue index ec48d50..d5c3d5a 100644 --- a/frontend/wizard-vue/src/components/EditUrlModal.vue +++ b/frontend/wizard-vue/src/components/EditUrlModal.vue @@ -2,7 +2,7 @@ import Field from "@/components/Field.vue"; import {type Field as FieldSpec} from "@/urlmaker/specs"; -import {validateUrl} from "@/urlmaker/validators.ts"; +import {validateOr, validatePreset, validateUrl} from "@/urlmaker/validators.ts"; import Btn from "@/components/Btn.vue"; import {onMounted, onUnmounted, ref, watch} from "vue"; import Modal from "@/components/Modal.vue"; @@ -10,10 +10,10 @@ import Modal from "@/components/Modal.vue"; const field: FieldSpec = { name: '', input_type: 'url', - label: 'URL of feed for editing', + label: 'URL of feed or preset', default: '', required: true, - validate: validateUrl, + validate: validateOr(validateUrl, validatePreset), } const visible = defineModel('visible'); diff --git a/frontend/wizard-vue/src/pages/WizardPage.vue b/frontend/wizard-vue/src/pages/WizardPage.vue index 580085d..9c0c598 100644 --- a/frontend/wizard-vue/src/pages/WizardPage.vue +++ b/frontend/wizard-vue/src/pages/WizardPage.vue @@ -4,20 +4,23 @@ import {ref, watch} from "vue"; import Btn from "@/components/Btn.vue"; import Copyable from "@/components/Copyable.vue"; import EditUrlModal from "@/components/EditUrlModal.vue"; -import {decodeUrl, encodeUrl, getScreenshotUrl} from "@/urlmaker"; +import {decodePreset, decodeUrl, encodePreset, encodeUrl, getScreenshotUrl} from "@/urlmaker"; import {useWizardStore} from "@/stores/wizard.ts"; import {debounce} from "es-toolkit"; +import {validatePreset, validateUrl} from "@/urlmaker/validators.ts"; const store = useWizardStore(); const existingLink = ref(""); -const link = ref(""); +const resultLink = ref(""); +const resultPreset = ref(""); const editModalVisible = ref(false); watch(existingLink, async (value) => { if(!value) return; existingLink.value = ""; try { - store.updateSpecs(await decodeUrl(value)); + if(validateUrl(value).ok) store.updateSpecs(await decodeUrl(value)); + else if (validatePreset(value).ok) store.updateSpecs(await decodePreset(value)); } catch (e) { console.log(e); alert(`Decoding error: ${e}`); @@ -26,17 +29,18 @@ watch(existingLink, async (value) => { watch(store.specs, debounce(() => { if (store.formValid) { - generateLink(); + generate(); } else { - link.value = ""; + resultLink.value = ""; } }, 100), {immediate: true} ); -async function generateLink() { +async function generate() { try { - link.value = await encodeUrl(store.specs); + resultLink.value = await encodeUrl(store.specs); + resultPreset.value = await encodePreset(store.specs); } catch (e) { console.log(e); alert(`Encoding error: ${e}`); @@ -44,7 +48,9 @@ async function generateLink() { } function screenshot() { - window.open(getScreenshotUrl(store.specs.url)); + if(store.formValid) { + window.open(getScreenshotUrl(store.specs.url)); + } } @@ -54,9 +60,12 @@ function screenshot() { Screenshot - Edit existing task + Edit existing task / import preset Reset Form - + + + + @@ -66,13 +75,14 @@ div.wrapper { width: 100%; max-width: 600px; margin: auto; + padding-bottom: 50px; } .specs-form { margin-bottom: 15px; } -.link-view { - margin-top: 15px !important; +.link-label { + margin-top: 15px; } diff --git a/frontend/wizard-vue/src/urlmaker/index.ts b/frontend/wizard-vue/src/urlmaker/index.ts index 4477638..4ae3e86 100644 --- a/frontend/wizard-vue/src/urlmaker/index.ts +++ b/frontend/wizard-vue/src/urlmaker/index.ts @@ -3,6 +3,7 @@ import type {Specs} from "@/urlmaker/specs.ts"; const apiBase = import.meta.env.VITE_API_BASE || document.location.origin; const renderEndpoint = '/api/v1/render/'; // trailing slash const screenshotEndpoint = '/api/v1/screenshot'; // no trailing slash +export const presetPrefix = 'rssalchemy:'; export async function decodeUrl(url: string): Promise { const splitUrl = url.split(renderEndpoint); @@ -10,6 +11,18 @@ export async function decodeUrl(url: string): Promise { throw 'Split failed'; } let encodedData = splitUrl[1]; + return decodeSpecsPart(encodedData); +} + +export async function decodePreset(preset: string): Promise { + if(!preset.startsWith(presetPrefix)) { + throw 'Invalid preset'; + } + let encodedData = preset.substring(presetPrefix.length); + return decodeSpecsPart(encodedData); +} + +export async function decodeSpecsPart(encodedData: string): Promise { console.log('Data len=' + encodedData.length); const m = encodedData.match(/(\d*):?([A-Za-z0-9+/=]+)/); if(!m) { @@ -28,12 +41,20 @@ export async function decodeUrl(url: string): Promise { } export async function encodeUrl(specs: Specs): Promise { + return `${apiBase}${renderEndpoint}${await encodeSpecsPart(specs)}` +} + +export async function encodePreset(specs: Specs): Promise { + return `${presetPrefix}${await encodeSpecsPart(specs)}`; +} + +export async function encodeSpecsPart(specs: Specs): Promise { const jsonData = JSON.stringify(specs); const buf = await compress(jsonData); const encodedData = b64encode(buf); console.log('Data len=' + encodedData.length); const version = 0; - return `${apiBase}${renderEndpoint}${version}:${encodedData}` + return `${version}:${encodedData}`; } export function getScreenshotUrl(url: string): string { @@ -55,7 +76,9 @@ async function compress(s: string): Promise { let byteArray = new TextEncoder().encode(s); let cs = new CompressionStream('deflate-raw'); let writer = cs.writable.getWriter(); + // noinspection ES6MissingAwait writer.write(byteArray); + // noinspection ES6MissingAwait writer.close(); let response = new Response(cs.readable); return new Uint8Array(await response.arrayBuffer()); @@ -64,7 +87,9 @@ async function compress(s: string): Promise { async function decompress(buf: Uint8Array): Promise { let ds = new DecompressionStream('deflate-raw'); let writer = ds.writable.getWriter(); + // noinspection ES6MissingAwait writer.write(buf); + // noinspection ES6MissingAwait writer.close(); let response = new Response(ds.readable); return response.text(); diff --git a/frontend/wizard-vue/src/urlmaker/validators.ts b/frontend/wizard-vue/src/urlmaker/validators.ts index b1151d0..aaa55b0 100644 --- a/frontend/wizard-vue/src/urlmaker/validators.ts +++ b/frontend/wizard-vue/src/urlmaker/validators.ts @@ -1,3 +1,5 @@ +import {presetPrefix} from "@/urlmaker/index.ts"; + type validResult = { ok: boolean, error?: string }; export type validator = (v: string) => validResult @@ -14,6 +16,27 @@ export function validateUrl(s: string): validResult { } } +export function validatePreset(s: string): validResult { + if(!s.startsWith(presetPrefix)) { + return { + ok: false, + error: 'Not a preset' + } + } + return {ok: true} +} + +export function validateOr(...validators: validator[]): validator { + return function(s: string): validResult { + return validators.reduce((res, v) => { + let r = v(s); + if(r.ok) res.ok = true; + else res.error += r.error + '; '; + return res; + }, {ok: false, error: ''}); + } +} + export function validateSelector(s: string): validResult { try { document.createDocumentFragment().querySelector(s);