diff --git a/src/components/CustomListEditor.tsx b/src/components/CustomListEditor.tsx index 0676b2057..5d1f2ef87 100644 --- a/src/components/CustomListEditor.tsx +++ b/src/components/CustomListEditor.tsx @@ -23,6 +23,7 @@ import TextWithEditMode from "./TextWithEditMode"; import ShareIcon from "./icons/ShareIcon"; type CustomListEditorProps = { + autoUpdateStatus?: string; collections?: AdminCollectionData[]; entries?: CustomListEditorEntriesData; entryPoints?: string[]; @@ -32,6 +33,7 @@ type CustomListEditorProps = { isFetchingMoreSearchResults: boolean; isLoaded?: boolean; isModified?: boolean; + isSearchModified?: boolean; isOwner?: boolean; isShared?: boolean; isSharePending?: boolean; @@ -73,6 +75,7 @@ type CustomListEditorProps = { }; export default function CustomListEditor({ + autoUpdateStatus, collections, entries, entryPoints, @@ -82,6 +85,7 @@ export default function CustomListEditor({ isFetchingMoreSearchResults, isLoaded, isModified, + isSearchModified, isOwner, isShared, isSharePending, @@ -261,6 +265,7 @@ export default function CustomListEditor({ updateProperty?.("autoUpdate", value)} updateSearchParam={updateSearchParam} @@ -279,8 +284,10 @@ export default function CustomListEditor({ { const CustomListEntriesEditor = ({ autoUpdate, - entries, - entryCount, + autoUpdateStatus, + entries = [], + entryCount = 0, isFetchingMoreCustomListEntries, isFetchingSearchResults, isFetchingMoreSearchResults, isOwner, + isSearchModified, listId, opdsFeedUrl, searchResults, @@ -134,7 +138,7 @@ const CustomListEntriesEditor = ({ const readOnly = !isOwner || autoUpdate; - let searchResultList = null; + let searchResultList: JSX.Element | null = null; if (isOwner) { searchResultList = ( @@ -210,7 +214,7 @@ const CustomListEntriesEditor = ({
{book.title}
- {book.authors.join(", ")} + {book.authors?.join(", ")}
@@ -256,125 +260,163 @@ const CustomListEntriesEditor = ({ ); } - let entryList = null; - - if (!autoUpdate) { - const visibleEntryCount = entries.length; - const startNum = visibleEntryCount > 0 ? 1 : 0; - const endNum = visibleEntryCount; - const booksText = entryCount === 1 ? "book" : "books"; - - const entryListDisplay = - entryCount > 0 - ? `Displaying ${startNum} - ${endNum} of ${entryCount} ${booksText}` - : "No books in this list"; - - entryList = ( -
-
-

List Entries: {entryListDisplay}

+ const visibleEntryCount = entries.length; + const startNum = visibleEntryCount > 0 ? 1 : 0; + const endNum = visibleEntryCount; + const booksText = entryCount === 1 ? "book" : "books"; + + const entryListDisplay = + entryCount > 0 + ? `Displaying ${startNum} - ${endNum} of ${entryCount} ${booksText}` + : "No books in this list"; + + let autoUpdateStatusName = null; + let autoUpdateStatusDescription = null; + + if (!listId) { + autoUpdateStatusName = "New"; + autoUpdateStatusDescription = + "This is a new list. Once the initial search criteria have been saved, the system will begin to populate its entries; however, the list might not be fully populated until the next scheduled update."; + } else if (isSearchModified) { + autoUpdateStatusName = "Search criteria modified"; + autoUpdateStatusDescription = + "There are unsaved changes to the search criteria for this list. Once the changes have been saved, the new search criteria will be used to repopulate the list during the next scheduled update."; + } else { + if (autoUpdateStatus === "init") { + autoUpdateStatusName = "Initializing"; + autoUpdateStatusDescription = + "This list was created recently. The system has partially populated the list using the configured search criteria and will fully populate the list during the next scheduled update."; + } else if (autoUpdateStatus === "repopulate") { + autoUpdateStatusName = "Repopulating"; + autoUpdateStatusDescription = + "The search criteria for this list were changed recently, but the entries have not yet been updated. The new search criteria will be used to repopulate the list during the next scheduled update."; + } else if (autoUpdateStatus === "updated") { + autoUpdateStatusName = "Updated"; + autoUpdateStatusDescription = + "This list was fully populated during the last scheduled update, using the configured search criteria and the titles that were available at the time. New titles matching the criteria will be added to the list during the next scheduled update."; + } else if (!autoUpdateStatus) { + autoUpdateStatusName = "Changing to automatic"; + autoUpdateStatusDescription = + "This list was populated manually, but is being changed to be updated automatically. The configured search criteria will be used to repopulate the list during the next scheduled update."; + } else { + autoUpdateStatusName = autoUpdateStatus; + } + } - {!readOnly && entries?.length > 0 && ( -
- Remove all currently visible items from list: + const entryList = ( +
+
+

List Entries: {entryListDisplay}

-
+ + + )} - {!readOnly &&

Drag search results here to add them to the list.

} - - - {(provided, snapshot) => ( -
    0 && ( +
    + Remove all currently visible items from list: + +
    + + {!readOnly &&

    Drag search results here to add them to the list.

    } + + + {(provided, snapshot) => ( +
      + {entries?.map((book) => ( + + {(provided, snapshot) => ( +
    • +
      + {!readOnly && } - {getMediumSVG(getMedium(book))} - -
      - {renderCatalogLink(book, opdsFeedUrl)} - - {!readOnly && ( -
    • - )} -
      - ))} + {getMediumSVG(getMedium(book))} - {provided.placeholder} -
    - )} -
    +
    + {renderCatalogLink(book, opdsFeedUrl)} - {loadMoreEntries && ( - + {!readOnly && ( +
    +
+ + {provided.placeholder} + + )} + + ))} + + {provided.placeholder} + )} -
- ); - } + + + {loadMoreEntries && ( + + )} +
+ ); return ( diff --git a/src/components/CustomListSearch.tsx b/src/components/CustomListSearch.tsx index be1b293c6..9cb3f5ce3 100644 --- a/src/components/CustomListSearch.tsx +++ b/src/components/CustomListSearch.tsx @@ -12,6 +12,7 @@ export interface CustomListSearchProps { isOwner?: boolean; languages: LanguagesData; library: LibraryData; + listId: string; searchParams: CustomListEditorSearchParams; showAutoUpdate?: boolean; startingTitle?: string; @@ -44,6 +45,7 @@ const CustomListSearch = ({ entryPoints, isOwner, library, + listId, searchParams, showAutoUpdate, startingTitle, @@ -170,7 +172,7 @@ const CustomListSearch = ({
{ "http://schema.org/EBook"; }); - it("does not render list entries when autoUpdate is true", () => { + it("renders list entries", () => { const wrapper = mount( { const entriesContainer = wrapper.find(".custom-list-entries"); - expect(entriesContainer.length).to.equal(0); + expect(entriesContainer.length).to.equal(1); + + const droppable = entriesContainer.find(Droppable); + + expect(droppable.length).to.equal(1); + + const entries = droppable.find(Draggable); + + expect(entries.length).to.equal(2); + + expect(entries.at(0).text()).to.contain("entry A"); + expect(entries.at(0).text()).to.contain("author A"); + expect(entries.at(1).text()).to.contain("entry B"); + expect(entries.at(1).text()).to.contain("author B1, author B2"); + + const display = wrapper.find(".custom-list-entries h4"); + + expect(display.text()).to.equal( + "List Entries: Displaying 1 - 2 of 2 books" + ); }); - it("renders list entries when autoUpdate is false", () => { + it("makes list entries read only if autoUpdate is true", () => { const wrapper = mount( { ); const entriesContainer = wrapper.find(".custom-list-entries"); + const removeAllButton = entriesContainer.find(".droppable-header button"); - expect(entriesContainer.length).to.equal(1); + expect(removeAllButton.length).to.equal(0); - const droppable = entriesContainer.find(Droppable); + const removeEntryButtons = entriesContainer.find( + ".custom-list-entry button" + ); - expect(droppable.length).to.equal(1); + expect(removeEntryButtons.length).to.equal(0); + }); - const entries = droppable.find(Draggable); + it("renders an auto update status if autoUpdate is true", () => { + const wrapper = mount( + , + { context: fullContext, childContextTypes } + ); - expect(entries.length).to.equal(2); + let status; - expect(entries.at(0).text()).to.contain("entry A"); - expect(entries.at(0).text()).to.contain("author A"); - expect(entries.at(1).text()).to.contain("entry B"); - expect(entries.at(1).text()).to.contain("author B1, author B2"); + status = wrapper.find(".custom-list-entries .auto-update-status-name"); + expect(status.text()).to.equal("Status: New"); - const display = wrapper.find(".custom-list-entries h4"); + wrapper.setProps({ isSearchModified: true }); - expect(display.text()).to.equal( - "List Entries: Displaying 1 - 2 of 2 books" - ); + status = wrapper.find(".custom-list-entries .auto-update-status-name"); + expect(status.text()).to.equal("Status: New"); + + wrapper.setProps({ + listId: "123", + isSearchModified: false, + autoUpdateStatus: "init", + }); + + status = wrapper.find(".custom-list-entries .auto-update-status-name"); + expect(status.text()).to.equal("Status: Initializing"); + + wrapper.setProps({ isSearchModified: true }); + + status = wrapper.find(".custom-list-entries .auto-update-status-name"); + expect(status.text()).to.equal("Status: Search criteria modified"); + + wrapper.setProps({ isSearchModified: false, autoUpdateStatus: "updated" }); + + status = wrapper.find(".custom-list-entries .auto-update-status-name"); + expect(status.text()).to.equal("Status: Updated"); + + wrapper.setProps({ isSearchModified: true }); + + status = wrapper.find(".custom-list-entries .auto-update-status-name"); + expect(status.text()).to.equal("Status: Search criteria modified"); + + wrapper.setProps({ + isSearchModified: false, + autoUpdateStatus: "repopulate", + }); + + status = wrapper.find(".custom-list-entries .auto-update-status-name"); + expect(status.text()).to.equal("Status: Repopulating"); + + wrapper.setProps({ isSearchModified: true }); + + status = wrapper.find(".custom-list-entries .auto-update-status-name"); + expect(status.text()).to.equal("Status: Search criteria modified"); + + wrapper.setProps({ autoUpdate: false }); + + status = wrapper.find(".custom-list-entries .auto-update-status-name"); + + expect(status.length).to.equal(0); }); it("renders a link to view each entry", () => { diff --git a/src/components/__tests__/CustomListSearch-test.tsx b/src/components/__tests__/CustomListSearch-test.tsx index f62e0af0f..7551ecccb 100644 --- a/src/components/__tests__/CustomListSearch-test.tsx +++ b/src/components/__tests__/CustomListSearch-test.tsx @@ -57,6 +57,7 @@ describe("CustomListSearch", () => { isOwner={true} languages={languages} library={library} + listId="123" search={search} searchParams={searchParams} updateAutoUpdate={updateAutoUpdate} @@ -221,6 +222,7 @@ describe("CustomListSearch", () => { entryPoints={entryPoints} languages={languages} library={library} + listId="123" search={search} searchParams={searchParams} startingTitle="test" @@ -260,6 +262,7 @@ describe("CustomListSearch", () => { isOwner={true} languages={languages} library={library} + listId={null} search={search} searchParams={searchParams} showAutoUpdate={true} @@ -300,6 +303,38 @@ describe("CustomListSearch", () => { isOwner={false} languages={languages} library={library} + listId={null} + search={search} + searchParams={searchParams} + showAutoUpdate={true} + updateAutoUpdate={updateAutoUpdate} + updateSearchParam={updateSearchParam} + /> + ); + + const autoUpdateOptions = wrapper + .find(".auto-update") + .find(".form-group"); + + const autoUpdateOn = autoUpdateOptions.at(0); + const autoUpdateOnRadio = autoUpdateOn.find("input"); + + expect(autoUpdateOnRadio.props().disabled).to.be.true; + + const autoUpdateOff = autoUpdateOptions.at(1); + const autoUpdateOffRadio = autoUpdateOff.find("input"); + + expect(autoUpdateOffRadio.props().disabled).to.be.true; + }); + + it("disables the radio buttons for auto update when listId is not null", () => { + wrapper = mount( + { entryPoints={entryPoints} languages={languages} library={library} + listId="123" search={search} searchParams={searchParams} showAutoUpdate={true} diff --git a/src/reducers/__tests__/customListEditor-test.ts b/src/reducers/__tests__/customListEditor-test.ts index 27309087f..a8430ac08 100644 --- a/src/reducers/__tests__/customListEditor-test.ts +++ b/src/reducers/__tests__/customListEditor-test.ts @@ -783,13 +783,14 @@ describe("custom list editor reducer", () => { expect(nextState.searchParams.current.language).to.equal("eng"); }); - it("updates isValid and isModified", () => { + it("updates isValid, isModified, and isSearchModified", () => { const nextState = reducer(state, { type: ActionCreator.UPDATE_CUSTOM_LIST_EDITOR_SEARCH_PARAM, name: "entryPoint", value: "Book", }); + expect(nextState.isSearchModified).to.equal(true); expect(nextState.isModified).to.equal(true); expect(nextState.isValid).to.equal(false); }); @@ -1136,7 +1137,7 @@ describe("custom list editor reducer", () => { ).to.equal("92"); }); - it("updates isValid and isModified", () => { + it("updates isValid, isModified, and isSearchModified", () => { const state = { ...initialState, properties: { @@ -1155,6 +1156,7 @@ describe("custom list editor reducer", () => { query: valueQuery, }); + expect(nextState.isSearchModified).to.equal(true); expect(nextState.isModified).to.equal(true); expect(nextState.isValid).to.equal(true); }); @@ -1256,7 +1258,7 @@ describe("custom list editor reducer", () => { expect(nextState).to.deep.equal(state); }); - it("updates isValid and isModified", () => { + it("updates isValid, isModified, and isSearchModified", () => { const namedState = { ...state, properties: { @@ -1276,6 +1278,7 @@ describe("custom list editor reducer", () => { bool: "or", }); + expect(nextState.isSearchModified).to.equal(true); expect(nextState.isModified).to.equal(true); expect(nextState.isValid).to.equal(true); }); @@ -1391,7 +1394,7 @@ describe("custom list editor reducer", () => { }); }); - it("updates isValid and isModified", () => { + it("updates isValid, isModified, isSearchModified", () => { const namedState = { ...state, properties: { @@ -1411,6 +1414,7 @@ describe("custom list editor reducer", () => { targetId: "95", }); + expect(nextState.isSearchModified).to.equal(true); expect(nextState.isModified).to.equal(true); expect(nextState.isValid).to.equal(true); }); @@ -1536,7 +1540,7 @@ describe("custom list editor reducer", () => { ).to.equal(undefined); }); - it("updates isValid and isModified", () => { + it("updates isValid, isModified, and isSearchModified", () => { const state = { ...initialState, properties: { @@ -1548,6 +1552,21 @@ describe("custom list editor reducer", () => { }, searchParams: { ...initialState.searchParams, + baseline: { + ...initialState.searchParams.baseline, + advanced: { + ...initialState.searchParams.baseline.advanced, + include: { + ...initialState.searchParams.baseline.advanced.include, + query: { + id: "92", + key: "title", + value: "bar", + }, + selectedQueryId: "92", + }, + }, + }, current: { ...initialState.searchParams.current, advanced: { @@ -1574,6 +1593,7 @@ describe("custom list editor reducer", () => { id: "92", }); + expect(nextState.isSearchModified).to.equal(true); expect(nextState.isModified).to.equal(true); expect(nextState.isValid).to.equal(false); }); diff --git a/src/reducers/customListEditor.ts b/src/reducers/customListEditor.ts index 50cc4567c..ef20bb98f 100644 --- a/src/reducers/customListEditor.ts +++ b/src/reducers/customListEditor.ts @@ -544,6 +544,11 @@ export interface CustomListEditorState { */ id: number; + /** + * The update status of the list, if it is auto updating. + */ + autoUpdateStatus: string; + /** * Flag indicating if the auto updating lists feature is enabled. */ @@ -600,6 +605,12 @@ export interface CustomListEditorState { */ isModified: boolean; + /** + * The modified state of the search parameters; true if the search has been changed since the + * last save, false otherwise. + */ + isSearchModified: boolean; + /** * An error message, if an error has occurred. */ @@ -634,6 +645,7 @@ const initialSearchParams = { */ export const initialState: CustomListEditorState = { id: null, + autoUpdateStatus: "", isAutoUpdateEnabled: false, isLoaded: false, isOwner: true, @@ -657,6 +669,7 @@ export const initialState: CustomListEditorState = { }, isValid: false, isModified: false, + isSearchModified: false, error: null, }; @@ -703,6 +716,22 @@ const isSearchModified = (state: CustomListEditorState): boolean => { return baselineSearchUrl !== currentSearchUrl; }; +/** + * Checks if the search parameters in a custom list editor state have been modified and stores + * the result in the state. + * + * @param state The custom list editor state + * @returns A new custom list editor state, with the isSearchModified property updated with the + * result. All other properties of the returned state are identical to the input state. + */ +const checkSearchModified = ( + state: CustomListEditorState +): CustomListEditorState => { + return produce(state, (draftState) => { + draftState.isSearchModified = isSearchModified(draftState); + }); +}; + /** * Determines if a custom list editor contains data has been modified since it was last saved, * given its state. @@ -720,14 +749,15 @@ const isModified = (state: CustomListEditorState): boolean => { Object.keys(removed).length > 0 || baseline.name !== current.name || baseline.autoUpdate !== current.autoUpdate || - (current.autoUpdate && isSearchModified(state)) || + (current.autoUpdate && state.isSearchModified) || baseline.collections.length !== current.collections.length || !baseline.collections.every((id) => current.collections.includes(id)) ); }; /** - * Validates the data in a custom list editor state, and checks if the data has been modified. + * Validates the data in a custom list editor state, and checks if the data has been modified, and + * stores the results in the state. * * @param state The custom list editor state * @returns A new custom list editor state, with the isValid and isModified properties updated @@ -805,6 +835,11 @@ const initialStateForList = ( draftState.isOwner = customList.is_owner; draftState.isShared = customList.is_shared; + draftState.autoUpdateStatus = + isAutoUpdateEnabled && !!customList.auto_update + ? customList.auto_update_status + : ""; + const initialProperties = { name: customList.name || "", collections: customList.collections.map((collection) => collection.id), @@ -1042,9 +1077,11 @@ const handleUpdateCustomListEditorSearchParam = validatedHandler( (state: CustomListEditorState, action): CustomListEditorState => { const { name, value } = action; - return produce(state, (draftState) => { - draftState.searchParams.current[name] = value; - }); + return checkSearchModified( + produce(state, (draftState) => { + draftState.searchParams.current[name] = value; + }) + ); } ); @@ -1279,28 +1316,30 @@ const getDefaultBooleanOperator = (builderName: string) => { */ const handleAddCustomListEditorAdvSearchQuery = validatedHandler( (state: CustomListEditorState, action): CustomListEditorState => { - return produce(state, (draftState) => { - const { builderName, query } = action; - const builder = draftState.searchParams.current.advanced[builderName]; - const { query: currentQuery, selectedQueryId } = builder; + return checkSearchModified( + produce(state, (draftState) => { + const { builderName, query } = action; + const builder = draftState.searchParams.current.advanced[builderName]; + const { query: currentQuery, selectedQueryId } = builder; - const newQuery = { - ...query, - id: newQueryId(), - }; + const newQuery = { + ...query, + id: newQueryId(), + }; - if (!currentQuery) { - builder.query = newQuery; - builder.selectedQueryId = newQuery.id; - } else { - builder.query = addDescendantQuery( - currentQuery, - selectedQueryId || currentQuery.id, - newQuery, - getDefaultBooleanOperator(builderName) - ); - } - }); + if (!currentQuery) { + builder.query = newQuery; + builder.selectedQueryId = newQuery.id; + } else { + builder.query = addDescendantQuery( + currentQuery, + selectedQueryId || currentQuery.id, + newQuery, + getDefaultBooleanOperator(builderName) + ); + } + }) + ); } ); @@ -1317,25 +1356,27 @@ const handleAddCustomListEditorAdvSearchQuery = validatedHandler( */ const handleUpdateCustomListEditorAdvSearchQueryBoolean = validatedHandler( (state: CustomListEditorState, action): CustomListEditorState => { - return produce(state, (draftState) => { - const { builderName, id, bool } = action; - const builder = draftState.searchParams.current.advanced[builderName]; - const { query: currentQuery } = builder; - const targetQuery = findDescendantQuery(currentQuery, id); - - if ( - targetQuery && - (targetQuery.and || targetQuery.or) && - !targetQuery[bool] - ) { - const oppositeBool = bool === "and" ? "or" : "and"; - const children = targetQuery[oppositeBool]; - - delete targetQuery[oppositeBool]; - - targetQuery[bool] = children; - } - }); + return checkSearchModified( + produce(state, (draftState) => { + const { builderName, id, bool } = action; + const builder = draftState.searchParams.current.advanced[builderName]; + const { query: currentQuery } = builder; + const targetQuery = findDescendantQuery(currentQuery, id); + + if ( + targetQuery && + (targetQuery.and || targetQuery.or) && + !targetQuery[bool] + ) { + const oppositeBool = bool === "and" ? "or" : "and"; + const children = targetQuery[oppositeBool]; + + delete targetQuery[oppositeBool]; + + targetQuery[bool] = children; + } + }) + ); } ); @@ -1361,29 +1402,31 @@ const handleUpdateCustomListEditorAdvSearchQueryBoolean = validatedHandler( */ const handleMoveCustomListEditorAdvSearchQuery = validatedHandler( (state: CustomListEditorState, action): CustomListEditorState => { - return produce(state, (draftState) => { - const { builderName, id, targetId } = action; - const builder = draftState.searchParams.current.advanced[builderName]; - const { query: currentQuery } = builder; - const query = findDescendantQuery(currentQuery, id); - - const newQuery = { - ...query, - id: newQueryId(), - }; + return checkSearchModified( + produce(state, (draftState) => { + const { builderName, id, targetId } = action; + const builder = draftState.searchParams.current.advanced[builderName]; + const { query: currentQuery } = builder; + const query = findDescendantQuery(currentQuery, id); + + const newQuery = { + ...query, + id: newQueryId(), + }; - const afterAddQuery = addDescendantQuery( - currentQuery, - targetId, - newQuery, - getDefaultBooleanOperator(builderName) - ); + const afterAddQuery = addDescendantQuery( + currentQuery, + targetId, + newQuery, + getDefaultBooleanOperator(builderName) + ); - const afterRemoveQuery = removeDescendantQuery(afterAddQuery, id); + const afterRemoveQuery = removeDescendantQuery(afterAddQuery, id); - builder.query = afterRemoveQuery; - builder.selectedQueryId = targetId; - }); + builder.query = afterRemoveQuery; + builder.selectedQueryId = targetId; + }) + ); } ); @@ -1399,35 +1442,37 @@ const handleMoveCustomListEditorAdvSearchQuery = validatedHandler( */ const handleRemoveCustomListEditorAdvSearchQuery = validatedHandler( (state: CustomListEditorState, action): CustomListEditorState => { - return produce(state, (draftState) => { - const { builderName, id } = action; - const builder = draftState.searchParams.current.advanced[builderName]; - const { query: currentQuery } = builder; + return checkSearchModified( + produce(state, (draftState) => { + const { builderName, id } = action; + const builder = draftState.searchParams.current.advanced[builderName]; + const { query: currentQuery } = builder; - const afterRemoveQuery = removeDescendantQuery(currentQuery, id); + const afterRemoveQuery = removeDescendantQuery(currentQuery, id); - if (afterRemoveQuery !== currentQuery) { - builder.query = afterRemoveQuery; + if (afterRemoveQuery !== currentQuery) { + builder.query = afterRemoveQuery; - // It is possible that removeDescendantQuery removed more than just the one query; for - // example, if the removed query was the child of an and/or query, and removing it left only - // one other child query, then the remaining child would have been lifted out of the parent, - // and the parent would have been deleted as well. For this reason, we can't assume that the - // parent of the removed query still exists; we have to find the nearest ancestor of the - // removed query that remains in the tree, to make that the new selected query. + // It is possible that removeDescendantQuery removed more than just the one query; for + // example, if the removed query was the child of an and/or query, and removing it left only + // one other child query, then the remaining child would have been lifted out of the parent, + // and the parent would have been deleted as well. For this reason, we can't assume that the + // parent of the removed query still exists; we have to find the nearest ancestor of the + // removed query that remains in the tree, to make that the new selected query. - const path = findDescendantQueryPath(currentQuery, id); + const path = findDescendantQueryPath(currentQuery, id); - path.pop(); - path.reverse(); + path.pop(); + path.reverse(); - const ancestorId = path.find((id) => - findDescendantQuery(afterRemoveQuery, id) - ); + const ancestorId = path.find((id) => + findDescendantQuery(afterRemoveQuery, id) + ); - builder.selectedQueryId = ancestorId; - } - }); + builder.selectedQueryId = ancestorId; + } + }) + ); } ); diff --git a/src/stylesheets/custom_list_editor.scss b/src/stylesheets/custom_list_editor.scss index 6ee24bb58..71f706914 100644 --- a/src/stylesheets/custom_list_editor.scss +++ b/src/stylesheets/custom_list_editor.scss @@ -105,6 +105,7 @@ aside { font-style: italic; + font-size: .9em; } } } @@ -220,6 +221,7 @@ aside { font-style: italic; + font-size: .9em; } } } diff --git a/src/stylesheets/custom_list_entries_editor.scss b/src/stylesheets/custom_list_entries_editor.scss index e98397e43..f250cc000 100644 --- a/src/stylesheets/custom_list_entries_editor.scss +++ b/src/stylesheets/custom_list_entries_editor.scss @@ -19,6 +19,11 @@ display: inline-block; } + .auto-update-status-desc { + font-style: italic; + font-size: .9em; + } + button { float: right;