1
0
Fork 0
mirror of synced 2024-09-08 21:51:58 +12:00

Ensure view names are properly encoded to handle certain special characters (#9145)

This commit is contained in:
Andrew Kingston 2022-12-22 13:09:07 +00:00 committed by GitHub
parent 3a204b72e8
commit 66674c7277
9 changed files with 32 additions and 16 deletions

View file

@ -10,6 +10,7 @@
$: views = $tables.list.flatMap(table => Object.keys(table.views || {})) $: views = $tables.list.flatMap(table => Object.keys(table.views || {}))
const saveView = async () => { const saveView = async () => {
name = name?.trim()
if (views.includes(name)) { if (views.includes(name)) {
notifications.error(`View exists with name ${name}`) notifications.error(`View exists with name ${name}`)
return return
@ -21,7 +22,7 @@
field, field,
}) })
notifications.success(`View ${name} created`) notifications.success(`View ${name} created`)
$goto(`../../view/${name}`) $goto(`../../view/${encodeURIComponent(name)}`)
} catch (error) { } catch (error) {
notifications.error("Error creating view") notifications.error("Error creating view")
} }

View file

@ -36,9 +36,8 @@
indentLevel={2} indentLevel={2}
icon="Remove" icon="Remove"
text={viewName} text={viewName}
selected={$isActive("./view/:viewName") && selected={$isActive("./view") && $views.selected?.name === viewName}
$views.selected?.name === viewName} on:click={() => $goto(`./view/${encodeURIComponent(viewName)}`)}
on:click={() => $goto(`./view/${viewName}`)}
> >
<EditViewPopover <EditViewPopover
view={{ name: viewName, ...table.views[viewName] }} view={{ name: viewName, ...table.views[viewName] }}

View file

@ -33,7 +33,8 @@
async function deleteView() { async function deleteView() {
try { try {
const isSelected = $params.viewName === $views.selectedViewName const isSelected =
decodeURIComponent($params.viewName) === $views.selectedViewName
const name = view.name const name = view.name
const id = view.tableId const id = view.tableId
await views.delete(name) await views.delete(name)

View file

@ -12,6 +12,7 @@ export const syncURLToState = options => {
store, store,
routify, routify,
beforeNavigate, beforeNavigate,
decode,
} = options || {} } = options || {}
if ( if (
!urlParam || !urlParam ||
@ -29,11 +30,23 @@ export const syncURLToState = options => {
return return
} }
// Decodes encoded URL params if required
const decodeParams = urlParams => {
if (!decode) {
return urlParams
}
let decoded = {}
Object.keys(urlParams || {}).forEach(key => {
decoded[key] = decode(urlParams[key])
})
return decoded
}
// We can't dynamically fetch the value of stateful routify stores so we need // We can't dynamically fetch the value of stateful routify stores so we need
// to just subscribe and cache the latest versions. // to just subscribe and cache the latest versions.
// We can grab their initial values as this is during component // We can grab their initial values as this is during component
// initialisation. // initialisation.
let cachedParams = get(routify.params) let cachedParams = decodeParams(get(routify.params))
let cachedGoto = get(routify.goto) let cachedGoto = get(routify.goto)
let cachedRedirect = get(routify.redirect) let cachedRedirect = get(routify.redirect)
let cachedPage = get(routify.page) let cachedPage = get(routify.page)
@ -77,7 +90,7 @@ export const syncURLToState = options => {
// Check if new value is valid // Check if new value is valid
if (validate && fallbackUrl) { if (validate && fallbackUrl) {
if (!validate(urlValue)) { if (!validate(urlValue)) {
log("Invalid URL param!") log("Invalid URL param!", urlValue)
redirectUrl(fallbackUrl) redirectUrl(fallbackUrl)
return return
} }
@ -109,7 +122,7 @@ export const syncURLToState = options => {
log(`url.${urlParam} (${urlValue}) <= state.${stateKey} (${stateValue})`) log(`url.${urlParam} (${urlValue}) <= state.${stateKey} (${stateValue})`)
if (validate && fallbackUrl) { if (validate && fallbackUrl) {
if (!validate(stateValue)) { if (!validate(stateValue)) {
log("Invalid state param!") log("Invalid state param!", stateValue)
redirectUrl(fallbackUrl) redirectUrl(fallbackUrl)
return return
} }
@ -137,6 +150,7 @@ export const syncURLToState = options => {
// Subscribe to URL changes and cache them // Subscribe to URL changes and cache them
const unsubscribeParams = routify.params.subscribe($urlParams => { const unsubscribeParams = routify.params.subscribe($urlParams => {
$urlParams = decodeParams($urlParams)
cachedParams = $urlParams cachedParams = $urlParams
mapUrlToState($urlParams) mapUrlToState($urlParams)
}) })

View file

@ -12,6 +12,7 @@
fallbackUrl: "../", fallbackUrl: "../",
store: views, store: views,
routify, routify,
decode: decodeURIComponent,
}) })
onDestroy(stopSyncing) onDestroy(stopSyncing)

View file

@ -6,9 +6,9 @@
onMount(async () => { onMount(async () => {
const { list, selected } = $views const { list, selected } = $views
if (selected) { if (selected) {
$redirect(`./${selected?.name}`) $redirect(`./${encodeURIComponent(selected?.name)}`)
} else if (list?.length) { } else if (list?.length) {
$redirect(`./${list[0].name}`) $redirect(`./${encodeURIComponent(list[0].name)}`)
} else { } else {
$redirect("../") $redirect("../")
} }

View file

@ -16,8 +16,8 @@ export const buildViewEndpoints = API => ({
params.set("group", groupBy) params.set("group", groupBy)
} }
const QUERY_VIEW_URL = field const QUERY_VIEW_URL = field
? `/api/views/${name}?${params}` ? `/api/views/${encodeURIComponent(name)}?${params}`
: `/api/views/${name}` : `/api/views/${encodeURIComponent(name)}`
return await API.get({ url: QUERY_VIEW_URL }) return await API.get({ url: QUERY_VIEW_URL })
}, },
@ -53,7 +53,7 @@ export const buildViewEndpoints = API => ({
*/ */
deleteView: async viewName => { deleteView: async viewName => {
return await API.delete({ return await API.delete({
url: `/api/views/${viewName}`, url: `/api/views/${encodeURIComponent(viewName)}`,
}) })
}, },
}) })

View file

@ -187,7 +187,7 @@ export async function save(ctx: UserCtx) {
} }
export async function fetchView(ctx: Ctx) { export async function fetchView(ctx: Ctx) {
const viewName = ctx.params.viewName const viewName = decodeURIComponent(ctx.params.viewName)
// if this is a table view being looked for just transfer to that // if this is a table view being looked for just transfer to that
if (viewName.startsWith(DocumentType.TABLE)) { if (viewName.startsWith(DocumentType.TABLE)) {

View file

@ -113,7 +113,7 @@ async function handleViewEvents(existingView: View, newView: View) {
export async function destroy(ctx: BBContext) { export async function destroy(ctx: BBContext) {
const db = context.getAppDB() const db = context.getAppDB()
const viewName = decodeURI(ctx.params.viewName) const viewName = decodeURIComponent(ctx.params.viewName)
const view = await deleteView(viewName) const view = await deleteView(viewName)
const table = await db.get(view.meta.tableId) const table = await db.get(view.meta.tableId)
delete table.views[viewName] delete table.views[viewName]
@ -124,7 +124,7 @@ export async function destroy(ctx: BBContext) {
} }
export async function exportView(ctx: BBContext) { export async function exportView(ctx: BBContext) {
const viewName = decodeURI(ctx.query.view as string) const viewName = decodeURIComponent(ctx.query.view as string)
const view = await getView(viewName) const view = await getView(viewName)
const format = ctx.query.format as string const format = ctx.query.format as string