diff --git a/ghost/admin/app/components/members/filter.hbs b/ghost/admin/app/components/members/filter.hbs index 57a380906a..5371f924fe 100644 --- a/ghost/admin/app/components/members/filter.hbs +++ b/ghost/admin/app/components/members/filter.hbs @@ -4,7 +4,7 @@ class="gh-btn gh-btn-icon gh-btn-action-icon" data-test-button="members-filter-actions" > - + {{svg-jar "filter"}} Filter {{#if @isFiltered}} diff --git a/ghost/admin/app/components/members/filter.js b/ghost/admin/app/components/members/filter.js index 2706d47ffa..e9d15d4d05 100644 --- a/ghost/admin/app/components/members/filter.js +++ b/ghost/admin/app/components/members/filter.js @@ -190,11 +190,27 @@ export default class MembersFilter extends Component { constructor(...args) { super(...args); + this.parseDefaultFilters(); + this.fetchTiers.perform(); + } + + /** + * This method is not super clean as it uses did-update, but for now this is required to make URL changes work + * properly. + * Problem: filter parameter is changed in the members controller by modifying the URL directly + * -> the filters property is not updated in the members controller because the new parameter is not parsed again + * -> we need to listen for changes in the property and parse it again + * -> better future proof solution: move the filter parsing logic elsewhere so it can be parsed in the members controller + */ + @action + parseDefaultFilters() { if (this.args.defaultFilterParam) { this.parseNqlFilter(this.args.defaultFilterParam); - } - this.fetchTiers.perform(); + // Pass the parsed filter to the parent component + // this doesn't start a new network request, and doesn't update filterParam again + this.applyParsedFilter(); + } } @action @@ -329,6 +345,12 @@ export default class MembersFilter extends Component { value = nqlValue; } + if (typeof value === 'boolean' || typeof value === 'number') { + // Transform it to a string, to keep it compatible with the internally used value in admin + // + make sure false and 0 are truthy + value = value.toString(); + } + if (relation && value) { return new Filter({ type: key, @@ -342,14 +364,25 @@ export default class MembersFilter extends Component { parseNqlFilter(filterParam) { const validKeys = Object.keys(FILTER_RELATIONS_OPTIONS); - const filters = nql.parse(filterParam); + let filters; + + try { + filters = nql.parse(filterParam); + } catch (e) { + // Invalid nql filter + this.filters = new TrackedArray([]); + return; + } + const filterKeys = Object.keys(filters); let filterData = []; if (filterKeys?.length === 1 && validKeys.includes(filterKeys[0])) { const filterObj = this.parseNqlFilterKey(filters); - filterData = [filterObj]; + if (filterObj) { + filterData = [filterObj]; + } } else if (filters?.$and) { const andFilters = filters?.$and || []; filterData = andFilters.filter((nqlFilter) => { @@ -485,6 +518,18 @@ export default class MembersFilter extends Component { this.args.onApplyFilter(query, validFilters); } + @action + applyParsedFilter() { + const validFilters = this.filters.filter((filter) => { + if (['label', 'tier'].includes(filter.type)) { + return filter.value?.length; + } + return filter.value; + }); + + this.args.onApplyParsedFilter(validFilters); + } + @action resetFilter() { const filters = []; diff --git a/ghost/admin/app/controllers/members.js b/ghost/admin/app/controllers/members.js index 87e35838fe..4055317e5d 100644 --- a/ghost/admin/app/controllers/members.js +++ b/ghost/admin/app/controllers/members.js @@ -61,6 +61,8 @@ export default class MembersController extends Controller { @tracked _availableLabels = A([]); + @tracked parseFilterParamCounter = 0; + paidParams = PAID_PARAMS; constructor() { @@ -243,6 +245,15 @@ export default class MembersController extends Controller { this.filters = filters; } + /** + * Called to set the filters after the url filterParam has been parsed again + */ + @action + applyParsedFilter(filters) { + this.softFilters = A([]); + this.filters = filters; + } + @action applySoftFilter(filterStr, filters) { this.softFilters = filters; @@ -432,6 +443,13 @@ export default class MembersController extends Controller { this.filters = A([]); this.softFilterParam = null; this.softFilters = A([]); + } else { + this.filterParam = params.filterParam; + + // Trigger a did-update call in the filter component, so we get freshly parsed filters + // This is temporary, and a ugly pattern, but essential to make it work for now, until we moved the filter parsing logic + // out of the component + this.parseFilterParamCounter += 1; } } diff --git a/ghost/admin/app/templates/members.hbs b/ghost/admin/app/templates/members.hbs index 7e512e934a..550ee63d76 100644 --- a/ghost/admin/app/templates/members.hbs +++ b/ghost/admin/app/templates/members.hbs @@ -27,9 +27,11 @@ @onApplyFilter={{this.applyFilter}} @defaultFilterParam={{this.filterParam}} @onApplySoftFilter={{this.applySoftFilter}} + @onApplyParsedFilter={{this.applyParsedFilter}} @onResetSoftFilter={{this.resetSoftFilter}} @onResetFilter={{this.resetFilter}} @onLabelEdit={{this.editLabel}} + @parseFilterParamCounter={{this.parseFilterParamCounter}} />