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
-
+
Link for RSS reader:
+
+ Preset for sharing:
+
@@ -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);