diff --git a/app/controllers/api/submissions_controller.rb b/app/controllers/api/submissions_controller.rb index 9c7e6911b..e55cbe03d 100644 --- a/app/controllers/api/submissions_controller.rb +++ b/app/controllers/api/submissions_controller.rb @@ -173,7 +173,6 @@ module Api end def filter_submissions(submissions, params) - submissions = submissions.where(template_id: params[:template_id]) if params[:template_id].present? submissions = submissions.where(slug: params[:slug]) if params[:slug].present? if params[:template_folder].present? diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb new file mode 100644 index 000000000..1b8b1a261 --- /dev/null +++ b/app/controllers/search_controller.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +class SearchController < ApplicationController + load_and_authorize_resource :template_folder, parent: false + load_and_authorize_resource :template, parent: false + load_and_authorize_resource :submission, parent: false + load_and_authorize_resource :template, instance_name: :filter_template, id_param: :template_id, parent: true + + FOLDERS_LIMIT = 4 + LIMIT = 12 + + def index + @query = params[:q].to_s.squish + + return render_infinite_scroll(@template_folders, @templates, @submissions, @query) if turbo_infinite_scroll? + + if @query.blank? + @template_folders = @templates = @submissions = [] + elsif params[:scope] == 'folders' + @folders_pagy, @template_folders = + pagy(:countless, filter_folders(@template_folders, @templates, @query), limit: LIMIT) + + @templates = @submissions = [] + else + folders = filter_folders(@template_folders, @templates, @query) + + @pagy, @template_folders, @templates, @submissions = + if params[:scope] == 'submissions' + load_submissions_page(folders, @templates, @submissions, @query) + else + load_templates_page(folders, @templates, @submissions, @query) + end + + if @templates.blank? && @submissions.blank? && params[:template_id].blank? + @folders_pagy, @template_folders = pagy(:countless, folders, limit: LIMIT) + end + end + end + + private + + def load_submissions_page(folders, templates, submissions, query) + pagy, submissions = pagy(:countless, filter_submissions(submissions, query), limit: LIMIT) + + return [pagy, [], [], submissions] if pagy.next || params[:template_id].present? + + [pagy, folders.limit(FOLDERS_LIMIT).to_a, filter_templates(templates, query).limit(LIMIT).to_a, submissions] + end + + def load_templates_page(folders, templates, submissions, query) + pagy, templates = pagy(:countless, filter_templates(templates, query), limit: LIMIT) + + submissions = pagy.next ? [] : filter_submissions(submissions, query).limit(LIMIT).to_a + + [pagy, folders.limit(FOLDERS_LIMIT).to_a, templates, submissions] + end + + def render_infinite_scroll(folders, templates, submissions, query) + target, partial, collection = + case params[:scope] + when 'folders' + ['search_folders', 'search/folder', filter_folders(folders, templates, query)] + when 'submissions' + ['search_submissions', 'search/submission', filter_submissions(submissions, query)] + else + ['search_templates', 'search/template', filter_templates(templates, query)] + end + + pagy, records = pagy(:countless, collection, limit: LIMIT) + + render turbo_stream: [ + turbo_stream.append(target, partial:, collection: records), + turbo_stream.replace('infinite_scroll', partial: 'shared/infinite_scroll', locals: { pagy: }) + ] + end + + def filter_folders(folders, templates, query) + return folders.none if params[:archived] == 'true' + + folders = folders.where.not(id: current_account.default_template_folder.id) + + folders = folders.where(parent_folder_id: filter_folder_ids(params[:folder])) if params[:folder].present? + folders = folders.where(author_id: filter_author_id(params[:author])) if params[:author].present? + folders = TemplateFolders.filter_active_folders(folders, templates) + folders = TemplateFolders.search(folders, query) + + folders.preload(:parent_folder).order(:name) + end + + def filter_templates(templates, query) + templates = params[:archived] == 'true' ? templates.where.not(archived_at: nil) : templates.active + templates = templates.select_for_list.preload(:author, :template_accesses, folder: :parent_folder) + + templates = templates.where(folder_id: filter_folder_ids(params[:folder])) if params[:folder].present? + templates = templates.where(author_id: filter_author_id(params[:author])) if params[:author].present? + + Templates.search(current_user, templates, query).order(id: :desc) + end + + def filter_folder_ids(folder_name) + TemplateFolders.filter_by_full_name(current_account.template_folders, folder_name) + .preload(:subfolders) + .flat_map { |folder| [folder.id, *folder.subfolders.map(&:id)] } + end + + def filter_author_id(email) + current_account.users.find_by(email:)&.id || -1 + end + + def filter_submissions(submissions, query) + submissions = submissions.left_joins(:template) + + submissions = + if params[:archived] == 'true' + submissions.where.not(archived_at: nil).or(submissions.where.not(templates: { archived_at: nil })) + else + submissions.where(archived_at: nil).where(templates: { archived_at: nil }) + end + + submissions = Submissions.search(current_user, submissions, query, search_template: true) + submissions = Submissions::Filter.call(submissions, current_user, params) + + submissions = + if params[:status] == 'completed' || params[:completed_at_from].present? || params[:completed_at_to].present? + submissions.order(completed_at: :desc) + else + submissions.order(id: :desc) + end + + submissions.select_for_list.preload(:submitters, :template) + end +end diff --git a/app/javascript/elements/native_search.js b/app/javascript/elements/native_search.js index ecff58ebd..37bfe7bc1 100644 --- a/app/javascript/elements/native_search.js +++ b/app/javascript/elements/native_search.js @@ -1,27 +1,33 @@ -import { target, targetable } from '@github/catalyst/lib/targetable' - -export default targetable(class extends HTMLElement { - static [target.static] = ['form'] - +export default class extends HTMLElement { connectedCallback () { - const bridge = window.webkit?.messageHandlers?.native - - if (!bridge || !this.form) return - - const url = new URL(this.form.action) - - new FormData(this.form).forEach((value, key) => { - if (key !== 'q' && value) url.searchParams.set(key, value) - }) - - this.declared = true - - bridge.postMessage({ type: 'search', op: 'add', url: url.pathname + url.search }) + document.addEventListener('native:search', this.onSearch) } disconnectedCallback () { - if (this.declared) { - window.webkit?.messageHandlers?.native?.postMessage({ type: 'search', op: 'remove' }) - } + document.removeEventListener('native:search', this.onSearch) } -}) + + onSearch = (event) => { + const query = (event.detail?.q || '').trim() + + if (query === (this.dataset.q || '')) return + + this.dataset.q = query + + const url = new URL(this.frame.src || this.dataset.src, window.location.href) + + url.searchParams.delete('page') + + if (query) { + url.searchParams.set('q', query) + } else { + url.searchParams.delete('q') + } + + this.frame.src = url.toString() + } + + get frame () { + return this.querySelector('turbo-frame') + } +} diff --git a/app/javascript/lib/turbo_instant_click.js b/app/javascript/lib/turbo_instant_click.js index d72d4ce84..2a021e644 100644 --- a/app/javascript/lib/turbo_instant_click.js +++ b/app/javascript/lib/turbo_instant_click.js @@ -66,7 +66,7 @@ function mouseoverListener (event) { } else if (linkElement.dataset.turboFrame !== '_top') { const turboFrame = linkElement.closest('turbo-frame') - if (turboFrame) { + if (turboFrame && turboFrame.getAttribute('target') !== '_top') { requestOptions.headers['Turbo-Frame'] = turboFrame.id } } diff --git a/app/views/search/_folder.html.erb b/app/views/search/_folder.html.erb new file mode 100644 index 000000000..b8a99cb3d --- /dev/null +++ b/app/views/search/_folder.html.erb @@ -0,0 +1 @@ +<%= render 'search/row', href: folder_path(folder), icon: 'folder', title: folder.name, subtitle: folder.parent_folder&.name %> diff --git a/app/views/search/_folders.html.erb b/app/views/search/_folders.html.erb new file mode 100644 index 000000000..edcb537cd --- /dev/null +++ b/app/views/search/_folders.html.erb @@ -0,0 +1,9 @@ +<% if folders.present? %> +