Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,23 @@ import { AppResourcesTab, useEditorTabs } from './Tabs';
import { getScope } from './tree';
import type { ScopedAppResourceDir } from './types';
import { appResourceSubTypes } from './types';
import { replaceViewsetNameInXml } from './xmlUtils';

export const AppResourceContext = React.createContext<
SpecifyResource<SpAppResource>
>(undefined!);

const syncViewsetNameInXml = (
data: string | null | undefined,
appResource: SpecifyResource<SpAppResource | SpViewSetObject>
): string => {
const viewSet = toTable(appResource, 'SpViewSetObj');
const name = viewSet?.get('name');
if (typeof data !== 'string') return data ?? '';
if (typeof name !== 'string' || name.length === 0) return data;
return replaceViewsetNameInXml(data, name);
};

export function AppResourceEditor({
resource,
directory,
Expand Down Expand Up @@ -291,9 +303,13 @@ export function AppResourceEditor({
typeof lastDataRef.current === 'function'
? lastDataRef.current()
: lastDataRef.current;
const syncedData = syncViewsetNameInXml(
data === undefined ? resourceData.data : data,
appResource
);
const appResourceData = deserializeResource({
...resourceData,
data: data === undefined ? resourceData.data : data,
data: syncedData,
spAppResource:
toTable(appResource, 'SpAppResource')?.get(
'resource_uri'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type { AppResourcesOutlet } from './index';
import { globalResourceKey } from './tree';
import type { ScopedAppResourceDir } from './types';
import { appResourceSubTypes } from './types';
import { replaceViewsetNameInXml } from './xmlUtils';

export function AppResourceView(): JSX.Element {
return <Wrapper mode="appResources" />;
Expand Down Expand Up @@ -197,23 +198,21 @@ function useAppResource(
);
}

/*
* REFACTOR:
* Split this function up.
* Currently, the resource is not needed until subtype needs to be determined.
* All the functionality that does not depend on resource should be part of a different
* function.
*/
function useInitialData(
resource: SerializedResource<SpAppResource | SpViewSetObj>,
initialDataFrom: number | undefined,
templateFile: string | undefined
): string | false | undefined {
return useAsyncState(
React.useCallback(async () => {
const replaceViewsetName = (data: string | null | undefined): string => {
const resourceName = (resource as any)?.name ?? '';
return replaceViewsetNameInXml(data, resourceName);
};

if (typeof initialDataFrom === 'number')
return fetchResource('SpAppResourceData', initialDataFrom).then(
({ data }) => data ?? ''
({ data }) => replaceViewsetName(data ?? '')
);
else if (typeof templateFile === 'string') {
if (templateFile.includes('..'))
Expand All @@ -224,7 +223,7 @@ function useInitialData(
return ajax(`/static/config/${templateFile}`, {
headers: {},
})
.then(({ data }) => data ?? '')
.then(({ data }) => replaceViewsetName(data))
.catch(() => '');
}
const subType = f.maybe(
Expand All @@ -239,11 +238,9 @@ function useInitialData(
if (useTemplate)
return ajax(getAppResourceUrl(type.name, 'quiet'), {
headers: {},
}).then(({ data }) => data);
}).then(({ data }) => replaceViewsetName(data));
}
return false;
// Run this only once
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [initialDataFrom, templateFile]),
false
)[0];
Expand Down
21 changes: 21 additions & 0 deletions specifyweb/frontend/js_src/lib/components/AppResources/xmlUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Shared utilities for XML manipulation in AppResources
export const escapeXml = (value: string): string =>
value
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;');

// Replace the viewset name attribute in XML content
export const replaceViewsetNameInXml = (
data: string | null | undefined,
resourceName: string
): string => {
const xml = data ?? '';
if (typeof resourceName !== 'string' || resourceName.length === 0) return xml;
return xml.replace(
/(<viewset\b[^>]*\bname=)(")(.*?)\2/,
(_match, prefix, quote) =>
`${prefix}${quote}${escapeXml(resourceName)}${quote}`
);
};