forked from extern/fediwall
Allow overriding site config URL via query parameter
Remodeled config dialog a bit and added a way to fetch site config from any url via ?load=URL parameter.
This commit is contained in:
parent
9a00596ff1
commit
cd183e14c8
@ -2,8 +2,8 @@
|
|||||||
import { sanatizeConfig, isServer, isLanguage, toQuery } from '@/config';
|
import { sanatizeConfig, isServer, isLanguage, toQuery } from '@/config';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { arrayUnique } from '@/utils';
|
import { arrayUnique } from '@/utils';
|
||||||
import { useClipboard } from '@vueuse/core'
|
|
||||||
import { type Config } from '@/types';
|
import { type Config } from '@/types';
|
||||||
|
import { siteConfigParam } from '@/config'
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
const modalDom = ref(null)
|
const modalDom = ref(null)
|
||||||
@ -96,10 +96,15 @@ const sourceCount = computed(() => {
|
|||||||
return c;
|
return c;
|
||||||
})
|
})
|
||||||
|
|
||||||
const clip = useClipboard()
|
const rateLimitRisk = computed(() => {
|
||||||
|
return sourceCount.value / config.value.interval > 1
|
||||||
|
})
|
||||||
|
|
||||||
const fullConfig = computed(() => {
|
const saveConfigLink = computed(() => {
|
||||||
return JSON.stringify(config.value)
|
const json = JSON.stringify(config.value, undefined, 2)
|
||||||
|
const bytes = new TextEncoder().encode(json)
|
||||||
|
const binString = Array.from(bytes, (x) => String.fromCodePoint(x)).join("");
|
||||||
|
return "data:application/json;charset=utf-8;base64," + btoa(binString);
|
||||||
})
|
})
|
||||||
|
|
||||||
const fullUrl = computed(() => {
|
const fullUrl = computed(() => {
|
||||||
@ -312,54 +317,45 @@ const onSubmit = () => {
|
|||||||
<div class="tab-pane" id="ctab-advanced" aria-labelledby="btab-advanced" role="tabpanel">
|
<div class="tab-pane" id="ctab-advanced" aria-labelledby="btab-advanced" role="tabpanel">
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="interval" class="form-label">Interval</label>
|
<label for="edit-interval" class="form-label">Update interval</label>
|
||||||
<div class="ms-5">
|
<div class="ms-5">
|
||||||
<input type="text" class="form-control" name="interval" v-model.lazy="formInterval">
|
<input type="text" class="form-control" name="edit-interval" v-model.lazy="formInterval">
|
||||||
<div class="form-text">Update interval in seconds.</div>
|
<div class="form-text">Number of seconds to wait between updates.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="limit" class="form-label">Limit results per source</label>
|
<label for="edit-limit" class="form-label">Results per source</label>
|
||||||
<div class="ms-5">
|
<div class="ms-5">
|
||||||
<input type="text" class="form-control" name="limit" v-model.lazy="formLimit">
|
<input type="text" class="form-control" name="edit-limit" v-model.lazy="formLimit">
|
||||||
<div class="form-text">Fetch this many new posts per request.</div>
|
<div class="form-text">Limit number of results per hashtag, account or timeline.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<h6>Save config</h6>
|
<h6>Download config</h6>
|
||||||
<div class="ms-5">
|
<div class="ms-5">
|
||||||
<div class="input-group">
|
<div class="form-text">
|
||||||
<input type="text" class="form-control" readonly :value="fullUrl">
|
You can <a :href="saveConfigLink" target="_blank" download="wall-config.json">download</a> the
|
||||||
<button v-if="clip.isSupported" class="btn btn-outline-secondary" type="button"
|
current configuration as a JSON file and either upload it as <code>wall-config.json</code>
|
||||||
@click.prevent="clip.copy(fullUrl)">Copy</button>
|
to your own self-hosted Fediwall, or host it somewhere else and load it via the
|
||||||
</div>
|
<code>?{{ siteConfigParam }}=URL</code> query parameter.
|
||||||
<div class="form-text">Bookmark or share this <a :href="fullUrl" target="_blank">Fediwall link</a>.</div>
|
</div>
|
||||||
<div class="mt-3 input-group">
|
|
||||||
<input type="text" class="form-control" readonly :value="fullConfig">
|
|
||||||
<button v-if="clip.isSupported" class="btn btn-outline-secondary" type="button"
|
|
||||||
@click.prevent="clip.copy(fullUrl)">Copy</button>
|
|
||||||
</div>
|
|
||||||
<div class="form-text">Only useful if you plan to self-host Fediwall on your own domain.</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div v-if="sourceCount / config.interval > 1" class="alert alert-warning mt-5" role="alert">
|
|
||||||
Checking {{ sourceCount }} sources every {{ formInterval }} seconds requires a high number
|
|
||||||
of API requests per second. Please reduce the number of servers, hashtags or accounts, or increase the update
|
|
||||||
interval.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="submit" class="btn btn-primary float-end" form="settings-form">Apply</button>
|
<div v-if="rateLimitRisk" class="alert alert-warning mb-3" role="alert">
|
||||||
|
Current configuration updates {{ sourceCount }} sources every {{ formInterval }} seconds and may
|
||||||
|
run into issues with rate-limited servers. Reduce the number of sources (servers, hashtags, accounts)
|
||||||
|
or increase the update interval.
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary" form="settings-form">Apply config</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -371,5 +367,6 @@ const onSubmit = () => {
|
|||||||
.form-label,
|
.form-label,
|
||||||
h6 {
|
h6 {
|
||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
}</style>
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
@ -4,8 +4,9 @@ import { fallbackConfig, siteConfigUrl } from "@/defaults";
|
|||||||
import { type Config } from '@/types';
|
import { type Config } from '@/types';
|
||||||
|
|
||||||
|
|
||||||
|
export const siteConfigParam = "load"
|
||||||
let siteConfig: Config | null = null;
|
let siteConfig: Config | undefined;
|
||||||
|
let siteConfigSource: string|undefined = undefined;
|
||||||
|
|
||||||
const themes = ["dark", "light", "auto"];
|
const themes = ["dark", "light", "auto"];
|
||||||
const boolYes = ["", "y", "yes", "true"];
|
const boolYes = ["", "y", "yes", "true"];
|
||||||
@ -183,10 +184,13 @@ export function fromQuery(query: string): Config {
|
|||||||
return sanatizeConfig(config);
|
return sanatizeConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toQuery(config: Config): string {
|
export function toQuery(config: Config, userConfig?: string): string {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
const defaults = siteConfig || fallbackConfig;
|
const defaults = siteConfig || fallbackConfig;
|
||||||
|
|
||||||
|
if (siteConfigSource && siteConfigSource !== siteConfigUrl)
|
||||||
|
params.set(siteConfigParam, siteConfigSource)
|
||||||
|
|
||||||
for (const { names, to } of parameterDefinitions) {
|
for (const { names, to } of parameterDefinitions) {
|
||||||
const value = to(config)
|
const value = to(config)
|
||||||
if (value !== to(defaults))
|
if (value !== to(defaults))
|
||||||
@ -231,6 +235,15 @@ export function sanatizeConfig(config: any): Config {
|
|||||||
return choices.includes(value) ? value : fallback;
|
return choices.includes(value) ? value : fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migrate old configuration within same minor release
|
||||||
|
if (isString(config.server)) {
|
||||||
|
console.warn("DEPRECATED: Config parameter 'server' is now an array and called 'servers'.");
|
||||||
|
(config.servers ??= []).push(config.server);
|
||||||
|
}
|
||||||
|
if (isString(config.info))
|
||||||
|
config.showinfo = config.info == "top"
|
||||||
|
|
||||||
|
|
||||||
const fallback = siteConfig || fallbackConfig;
|
const fallback = siteConfig || fallbackConfig;
|
||||||
const result: Partial<Config> = {}
|
const result: Partial<Config> = {}
|
||||||
|
|
||||||
@ -268,35 +281,28 @@ export function sanatizeConfig(config: any): Config {
|
|||||||
return result as Config;
|
return result as Config;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSideConfig() {
|
export async function loadConfig() {
|
||||||
let config;
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const loadUrl = params.get(siteConfigParam)?.trim()
|
||||||
|
|
||||||
|
const loadJson = async (url: string) => {
|
||||||
try {
|
try {
|
||||||
config = await (await fetch(siteConfigUrl)).json() || {};
|
const rs = await fetch(url, {cache: "reload", })
|
||||||
|
if(!rs.ok) throw new Error(`HTTP error! Status: ${rs.status}`);
|
||||||
|
siteConfig = sanatizeConfig(await rs.json() || {});
|
||||||
|
siteConfigSource = url
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Site config failed to load, falling back to hard-coded defaults!")
|
console.warn(`Failed to load (or parse) [${url}], falling back to defaults.`)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate old configuration within same minor release
|
|
||||||
if (isString(config.server)) {
|
|
||||||
console.warn("DEPRECATED: Config parameter 'server' is now an array and called 'servers'.");
|
|
||||||
(config.servers ??= []).push(config.server);
|
|
||||||
}
|
|
||||||
if (isString(config.info))
|
|
||||||
config.showinfo = config.info == "top"
|
|
||||||
|
|
||||||
return sanatizeConfig(config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadConfig() {
|
if (!siteConfig && loadUrl)
|
||||||
|
await loadJson(loadUrl)
|
||||||
if (!siteConfig && siteConfigUrl)
|
if (!siteConfig && siteConfigUrl)
|
||||||
siteConfig = await loadSideConfig() || null
|
await loadJson(siteConfigUrl)
|
||||||
if (!siteConfig)
|
if (!siteConfig)
|
||||||
siteConfig = sanatizeConfig(deepClone(fallbackConfig))
|
siteConfig = sanatizeConfig(deepClone(fallbackConfig))
|
||||||
|
|
||||||
if (window.location.search)
|
|
||||||
return fromQuery(window.location.search)
|
return fromQuery(window.location.search)
|
||||||
|
|
||||||
return deepClone({ ...siteConfig })
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user