mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Enhancement: default to title/content search, allow choosing full search link from global search (#6805)
This commit is contained in:
		| @@ -201,7 +201,23 @@ | ||||
|  | ||||
|         <div class="row mb-3"> | ||||
|           <div class="offset-md-3 col"> | ||||
|             <pngx-input-check i18n-title title="Search database only (do not include advanced search results)" formControlName="searchDbOnly"></pngx-input-check> | ||||
|             <pngx-input-check i18n-title title="Do not include advanced search results" formControlName="searchDbOnly"></pngx-input-check> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|         <div class="row mb-3"> | ||||
|           <div class="offset-md-3 col"> | ||||
|             <div class="row"> | ||||
|               <div class="col-md-2 col-form-label pt-0"> | ||||
|                 <span i18n>Full search links to</span> | ||||
|               </div> | ||||
|               <div class="col"> | ||||
|                 <select class="form-select" formControlName="searchLink"> | ||||
|                   <option [ngValue]="GlobalSearchType.TITLE_CONTENT" i18n>Title and content search</option> | ||||
|                   <option [ngValue]="GlobalSearchType.ADVANCED" i18n>Advanced search</option> | ||||
|                 </select> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|   | ||||
| @@ -309,7 +309,7 @@ describe('SettingsComponent', () => { | ||||
|     expect(toastErrorSpy).toHaveBeenCalled() | ||||
|     expect(storeSpy).toHaveBeenCalled() | ||||
|     expect(appearanceSettingsSpy).not.toHaveBeenCalled() | ||||
|     expect(setSpy).toHaveBeenCalledTimes(26) | ||||
|     expect(setSpy).toHaveBeenCalledTimes(27) | ||||
|  | ||||
|     // succeed | ||||
|     storeSpy.mockReturnValueOnce(of(true)) | ||||
|   | ||||
| @@ -27,7 +27,7 @@ import { | ||||
| } from 'rxjs' | ||||
| import { Group } from 'src/app/data/group' | ||||
| import { SavedView } from 'src/app/data/saved-view' | ||||
| import { SETTINGS_KEYS } from 'src/app/data/ui-settings' | ||||
| import { GlobalSearchType, SETTINGS_KEYS } from 'src/app/data/ui-settings' | ||||
| import { User } from 'src/app/data/user' | ||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||
| import { | ||||
| @@ -101,6 +101,7 @@ export class SettingsComponent | ||||
|     defaultPermsEditGroups: new FormControl(null), | ||||
|     documentEditingRemoveInboxTags: new FormControl(null), | ||||
|     searchDbOnly: new FormControl(null), | ||||
|     searchLink: new FormControl(null), | ||||
|  | ||||
|     notificationsConsumerNewDocument: new FormControl(null), | ||||
|     notificationsConsumerSuccess: new FormControl(null), | ||||
| @@ -129,6 +130,8 @@ export class SettingsComponent | ||||
|  | ||||
|   public systemStatus: SystemStatus | ||||
|  | ||||
|   public readonly GlobalSearchType = GlobalSearchType | ||||
|  | ||||
|   get systemStatusHasErrors(): boolean { | ||||
|     return ( | ||||
|       this.systemStatus.database.status === SystemStatusItemStatus.ERROR || | ||||
| @@ -306,6 +309,7 @@ export class SettingsComponent | ||||
|         SETTINGS_KEYS.DOCUMENT_EDITING_REMOVE_INBOX_TAGS | ||||
|       ), | ||||
|       searchDbOnly: this.settings.get(SETTINGS_KEYS.SEARCH_DB_ONLY), | ||||
|       searchLink: this.settings.get(SETTINGS_KEYS.SEARCH_FULL_TYPE), | ||||
|       savedViews: {}, | ||||
|     } | ||||
|   } | ||||
| @@ -539,6 +543,10 @@ export class SettingsComponent | ||||
|       SETTINGS_KEYS.SEARCH_DB_ONLY, | ||||
|       this.settingsForm.value.searchDbOnly | ||||
|     ) | ||||
|     this.settings.set( | ||||
|       SETTINGS_KEYS.SEARCH_FULL_TYPE, | ||||
|       this.settingsForm.value.searchLink | ||||
|     ) | ||||
|     this.settings.setLanguage(this.settingsForm.value.displayLanguage) | ||||
|     this.settings | ||||
|       .storeSettings() | ||||
|   | ||||
| @@ -19,8 +19,12 @@ | ||||
|                 </div> | ||||
|             </div> | ||||
|             @if (query) { | ||||
|                 <button class="btn btn-sm btn-outline-secondary" type="button" (click)="runAdvanedSearch()"> | ||||
|                     <ng-container i18n>Advanced search</ng-container> | ||||
|                 <button class="btn btn-sm btn-outline-secondary" type="button" (click)="runFullSearch()"> | ||||
|                     @if (useAdvancedForFullSearch) { | ||||
|                         <ng-container i18n>Advanced search</ng-container> | ||||
|                     } @else { | ||||
|                         <ng-container i18n>Search</ng-container> | ||||
|                     } | ||||
|                     <i-bs width="1em" height="1em" name="arrow-right-short"></i-bs> | ||||
|                 </button> | ||||
|             } | ||||
|   | ||||
| @@ -25,6 +25,7 @@ import { | ||||
|   FILTER_HAS_DOCUMENT_TYPE_ANY, | ||||
|   FILTER_HAS_STORAGE_PATH_ANY, | ||||
|   FILTER_HAS_TAGS_ALL, | ||||
|   FILTER_TITLE_CONTENT, | ||||
| } from 'src/app/data/filter-rule-type' | ||||
| import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons' | ||||
| import { DocumentService } from 'src/app/services/rest/document.service' | ||||
| @@ -37,6 +38,8 @@ import { ElementRef } from '@angular/core' | ||||
| import { ToastService } from 'src/app/services/toast.service' | ||||
| import { DataType } from 'src/app/data/datatype' | ||||
| import { queryParamsFromFilterRules } from 'src/app/utils/query-params' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { GlobalSearchType, SETTINGS_KEYS } from 'src/app/data/ui-settings' | ||||
|  | ||||
| const searchResults = { | ||||
|   total: 11, | ||||
| @@ -130,6 +133,7 @@ describe('GlobalSearchComponent', () => { | ||||
|   let documentService: DocumentService | ||||
|   let documentListViewService: DocumentListViewService | ||||
|   let toastService: ToastService | ||||
|   let settingsService: SettingsService | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     await TestBed.configureTestingModule({ | ||||
| @@ -150,6 +154,7 @@ describe('GlobalSearchComponent', () => { | ||||
|     documentService = TestBed.inject(DocumentService) | ||||
|     documentListViewService = TestBed.inject(DocumentListViewService) | ||||
|     toastService = TestBed.inject(ToastService) | ||||
|     settingsService = TestBed.inject(SettingsService) | ||||
|  | ||||
|     fixture = TestBed.createComponent(GlobalSearchComponent) | ||||
|     component = fixture.componentInstance | ||||
| @@ -262,7 +267,7 @@ describe('GlobalSearchComponent', () => { | ||||
|     component.searchResults = searchResults as any | ||||
|     component.resultsDropdown.open() | ||||
|     component.query = 'test' | ||||
|     const advancedSearchSpy = jest.spyOn(component, 'runAdvanedSearch') | ||||
|     const advancedSearchSpy = jest.spyOn(component, 'runFullSearch') | ||||
|     component.searchInputKeyDown(new KeyboardEvent('keydown', { key: 'Enter' })) | ||||
|     expect(advancedSearchSpy).toHaveBeenCalled() | ||||
|   }) | ||||
| @@ -499,15 +504,6 @@ describe('GlobalSearchComponent', () => { | ||||
|     expect(focusSpy).toHaveBeenCalled() | ||||
|   }) | ||||
|  | ||||
|   it('should support explicit advanced search', () => { | ||||
|     const qfSpy = jest.spyOn(documentListViewService, 'quickFilter') | ||||
|     component.query = 'test' | ||||
|     component.runAdvanedSearch() | ||||
|     expect(qfSpy).toHaveBeenCalledWith([ | ||||
|       { rule_type: FILTER_FULLTEXT_QUERY, value: 'test' }, | ||||
|     ]) | ||||
|   }) | ||||
|  | ||||
|   it('should support open in new window', () => { | ||||
|     const openSpy = jest.spyOn(window, 'open') | ||||
|     const event = new Event('click') | ||||
| @@ -528,4 +524,23 @@ describe('GlobalSearchComponent', () => { | ||||
|     button.dispatchEvent(keyboardEvent) | ||||
|     expect(dispatchSpy).toHaveBeenCalledTimes(2) // once for keydown, second for click | ||||
|   }) | ||||
|  | ||||
|   it('should support title content search and advanced search', () => { | ||||
|     const qfSpy = jest.spyOn(documentListViewService, 'quickFilter') | ||||
|     component.query = 'test' | ||||
|     component.runFullSearch() | ||||
|     expect(qfSpy).toHaveBeenCalledWith([ | ||||
|       { rule_type: FILTER_TITLE_CONTENT, value: 'test' }, | ||||
|     ]) | ||||
|  | ||||
|     settingsService.set( | ||||
|       SETTINGS_KEYS.SEARCH_FULL_TYPE, | ||||
|       GlobalSearchType.ADVANCED | ||||
|     ) | ||||
|     component.query = 'test' | ||||
|     component.runFullSearch() | ||||
|     expect(qfSpy).toHaveBeenCalledWith([ | ||||
|       { rule_type: FILTER_FULLTEXT_QUERY, value: 'test' }, | ||||
|     ]) | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -15,6 +15,7 @@ import { | ||||
|   FILTER_HAS_DOCUMENT_TYPE_ANY, | ||||
|   FILTER_HAS_STORAGE_PATH_ANY, | ||||
|   FILTER_HAS_TAGS_ALL, | ||||
|   FILTER_TITLE_CONTENT, | ||||
| } from 'src/app/data/filter-rule-type' | ||||
| import { DataType } from 'src/app/data/datatype' | ||||
| import { ObjectWithId } from 'src/app/data/object-with-id' | ||||
| @@ -42,6 +43,8 @@ import { UserEditDialogComponent } from '../../common/edit-dialog/user-edit-dial | ||||
| import { WorkflowEditDialogComponent } from '../../common/edit-dialog/workflow-edit-dialog/workflow-edit-dialog.component' | ||||
| import { HotKeyService } from 'src/app/services/hot-key.service' | ||||
| import { paramsFromViewState } from 'src/app/utils/query-params' | ||||
| import { SettingsService } from 'src/app/services/settings.service' | ||||
| import { GlobalSearchType, SETTINGS_KEYS } from 'src/app/data/ui-settings' | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'pngx-global-search', | ||||
| @@ -63,6 +66,13 @@ export class GlobalSearchComponent implements OnInit { | ||||
|   @ViewChildren('primaryButton') primaryButtons: QueryList<ElementRef> | ||||
|   @ViewChildren('secondaryButton') secondaryButtons: QueryList<ElementRef> | ||||
|  | ||||
|   get useAdvancedForFullSearch(): boolean { | ||||
|     return ( | ||||
|       this.settingsService.get(SETTINGS_KEYS.SEARCH_FULL_TYPE) === | ||||
|       GlobalSearchType.ADVANCED | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   constructor( | ||||
|     public searchService: SearchService, | ||||
|     private router: Router, | ||||
| @@ -71,7 +81,8 @@ export class GlobalSearchComponent implements OnInit { | ||||
|     private documentListViewService: DocumentListViewService, | ||||
|     private permissionsService: PermissionsService, | ||||
|     private toastService: ToastService, | ||||
|     private hotkeyService: HotKeyService | ||||
|     private hotkeyService: HotKeyService, | ||||
|     private settingsService: SettingsService | ||||
|   ) { | ||||
|     this.queryDebounce = new Subject<string>() | ||||
|  | ||||
| @@ -282,7 +293,7 @@ export class GlobalSearchComponent implements OnInit { | ||||
|         this.primaryButtons.first.nativeElement.click() | ||||
|         this.searchInput.nativeElement.blur() | ||||
|       } else if (this.query?.length) { | ||||
|         this.runAdvanedSearch() | ||||
|         this.runFullSearch() | ||||
|         this.reset(true) | ||||
|       } | ||||
|     } else if (event.key === 'Escape' && !this.resultsDropdown.isOpen()) { | ||||
| @@ -378,9 +389,12 @@ export class GlobalSearchComponent implements OnInit { | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   public runAdvanedSearch() { | ||||
|   public runFullSearch() { | ||||
|     const ruleType = this.useAdvancedForFullSearch | ||||
|       ? FILTER_FULLTEXT_QUERY | ||||
|       : FILTER_TITLE_CONTENT | ||||
|     this.documentListViewService.quickFilter([ | ||||
|       { rule_type: FILTER_FULLTEXT_QUERY, value: this.query }, | ||||
|       { rule_type: ruleType, value: this.query }, | ||||
|     ]) | ||||
|     this.reset(true) | ||||
|   } | ||||
|   | ||||
| @@ -12,6 +12,11 @@ export interface UiSetting { | ||||
|   default: any | ||||
| } | ||||
|  | ||||
| export enum GlobalSearchType { | ||||
|   ADVANCED = 'advanced', | ||||
|   TITLE_CONTENT = 'title-content', | ||||
| } | ||||
|  | ||||
| export const SETTINGS_KEYS = { | ||||
|   LANGUAGE: 'language', | ||||
|   APP_LOGO: 'app_logo', | ||||
| @@ -57,6 +62,7 @@ export const SETTINGS_KEYS = { | ||||
|   DOCUMENT_EDITING_REMOVE_INBOX_TAGS: | ||||
|     'general-settings:document-editing:remove-inbox-tags', | ||||
|   SEARCH_DB_ONLY: 'general-settings:search:db-only', | ||||
|   SEARCH_FULL_TYPE: 'general-settings:search:more-link', | ||||
| } | ||||
|  | ||||
| export const SETTINGS: UiSetting[] = [ | ||||
| @@ -225,4 +231,9 @@ export const SETTINGS: UiSetting[] = [ | ||||
|     type: 'boolean', | ||||
|     default: false, | ||||
|   }, | ||||
|   { | ||||
|     key: SETTINGS_KEYS.SEARCH_FULL_TYPE, | ||||
|     type: 'string', | ||||
|     default: GlobalSearchType.TITLE_CONTENT, | ||||
|   }, | ||||
| ] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon