diff --git a/app/assets/builds/spina/tailwind.css b/app/assets/builds/spina/tailwind.css index 2f3a4acdf..338a19661 100644 --- a/app/assets/builds/spina/tailwind.css +++ b/app/assets/builds/spina/tailwind.css @@ -2234,6 +2234,10 @@ trix-editor [data-trix-mutable]::selection, height: 100%; } +.max-h-80 { + max-height: 20rem; +} + .max-h-full { max-height: 100%; } diff --git a/app/assets/javascripts/spina/controllers/form_controller.js b/app/assets/javascripts/spina/controllers/form_controller.js index 8021a6b26..63967fcad 100644 --- a/app/assets/javascripts/spina/controllers/form_controller.js +++ b/app/assets/javascripts/spina/controllers/form_controller.js @@ -1,10 +1,23 @@ import { Controller } from "@hotwired/stimulus" +import debounce from "libraries/debounce" import formRequestSubmitPolyfill from "libraries/form-request-submit-polyfill" export default class extends Controller { - requestSubmit() { + submitForm = debounce(function() { this.element.requestSubmit() + }.bind(this), this.debounceTime) + + requestSubmit() { + this.submitForm() + } + + submit() { + this.submitForm() + } + + get debounceTime() { + return this.element.dataset.debounceTime || 0 } } \ No newline at end of file diff --git a/app/assets/javascripts/spina/controllers/page_select_controller.js b/app/assets/javascripts/spina/controllers/page_select_controller.js new file mode 100644 index 000000000..179f0b25c --- /dev/null +++ b/app/assets/javascripts/spina/controllers/page_select_controller.js @@ -0,0 +1,38 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + + static get targets() { + return ['input', 'label', 'search'] + } + + connect() { + // Show placeholder if there is no page selected yet + if (this.labelTarget.querySelector("turbo-frame") == undefined) { + this.clear() + } + } + + select(event) { + let button = event.currentTarget + + this.inputTarget.value = button.dataset.id + this.labelTarget.innerText = button.dataset.title + } + + clear() { + this.inputTarget.value = "" + this.labelTarget.innerHTML = ` + + ${this.element.dataset.placeholder} + + ` + } + + autofocus() { + setTimeout(function() { + this.searchTarget.focus() + }.bind(this), 100) + } + +} \ No newline at end of file diff --git a/app/assets/javascripts/spina/libraries/debounce.js b/app/assets/javascripts/spina/libraries/debounce.js new file mode 100644 index 000000000..f078d6056 --- /dev/null +++ b/app/assets/javascripts/spina/libraries/debounce.js @@ -0,0 +1,65 @@ +/** + * Returns a function, that, as long as it continues to be invoked, will not + * be triggered. The function will be called after it stops being called for + * N milliseconds. If `immediate` is passed, trigger the function on the + * leading edge, instead of the trailing. The function also has a property 'clear' + * that is a function which will clear the timer to prevent previously scheduled executions. + * + * @source underscore.js + * @see http://unscriptable.com/2009/03/20/debouncing-javascript-methods/ + * @param {Function} function to wrap + * @param {Number} timeout in ms (`100`) + * @param {Boolean} whether to execute at the beginning (`false`) + * @api public + */ +export default function debounce(func, wait, immediate){ + var timeout, args, context, timestamp, result; + if (null == wait) wait = 100; + + function later() { + var last = Date.now() - timestamp; + + if (last < wait && last >= 0) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + context = args = null; + } + } + }; + + var debounced = function(){ + context = this; + args = arguments; + timestamp = Date.now(); + var callNow = immediate && !timeout; + if (!timeout) timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + context = args = null; + } + + return result; + }; + + debounced.clear = function() { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + }; + + debounced.flush = function() { + if (timeout) { + result = func.apply(context, args); + context = args = null; + + clearTimeout(timeout); + timeout = null; + } + }; + + return debounced; +}; diff --git a/app/controllers/spina/admin/page_select_options_controller.rb b/app/controllers/spina/admin/page_select_options_controller.rb new file mode 100644 index 000000000..f63e1d65f --- /dev/null +++ b/app/controllers/spina/admin/page_select_options_controller.rb @@ -0,0 +1,24 @@ +module Spina + module Admin + class PageSelectOptionsController < AdminController + + def show + @page = Page.find(params[:id]) + end + + def index + end + + def search + if params[:resource].present? + @pages = Resource.find_by(name: params[:resource])&.pages + end + + @pages ||= Page.all + @pages = @pages.joins(:translations).where("spina_page_translations.title ILIKE :query OR materialized_path ILIKE :query", query: "%#{params[:search]}%").order(created_at: :desc).distinct.page(params[:page]).per(20) + render :index + end + + end + end +end \ No newline at end of file diff --git a/app/controllers/spina/admin/pages_controller.rb b/app/controllers/spina/admin/pages_controller.rb index bb1e643d8..f6d0df60f 100644 --- a/app/controllers/spina/admin/pages_controller.rb +++ b/app/controllers/spina/admin/pages_controller.rb @@ -108,7 +108,7 @@ def destroy redirect_to spina.admin_pages_url(resource_id: @page.resource_id) end - + private def set_locale diff --git a/app/models/spina/parts/page_link.rb b/app/models/spina/parts/page_link.rb new file mode 100644 index 000000000..e5a204dac --- /dev/null +++ b/app/models/spina/parts/page_link.rb @@ -0,0 +1,13 @@ +module Spina + module Parts + class PageLink < Base + attr_json :page_id, :integer, default: nil + + attr_accessor :options + + def content + Page.live.find_by(id: page_id) + end + end + end +end diff --git a/app/views/spina/admin/page_select_options/index.html.erb b/app/views/spina/admin/page_select_options/index.html.erb new file mode 100644 index 000000000..802b011fb --- /dev/null +++ b/app/views/spina/admin/page_select_options/index.html.erb @@ -0,0 +1,21 @@ +<%= turbo_frame_tag "page_select_options_#{params[:object_id]}" do %> + <% if params[:search].blank? %> + + <% end %> + + <% @pages.each do |page| %> + + <% end %> +<% end %> \ No newline at end of file diff --git a/app/views/spina/admin/page_select_options/show.html.erb b/app/views/spina/admin/page_select_options/show.html.erb new file mode 100644 index 000000000..81e938cf6 --- /dev/null +++ b/app/views/spina/admin/page_select_options/show.html.erb @@ -0,0 +1,3 @@ +<%= turbo_frame_tag :page_title do %> + <%= @page.title %> +<% end %> \ No newline at end of file diff --git a/app/views/spina/admin/parts/page_links/_form.html.erb b/app/views/spina/admin/parts/page_links/_form.html.erb new file mode 100644 index 000000000..b5c8633f7 --- /dev/null +++ b/app/views/spina/admin/parts/page_links/_form.html.erb @@ -0,0 +1,36 @@ +
+ +
<%= f.object.hint %>
+ +
" class="relative mt-1" data-reveal-away-value> + <%= f.hidden_field :page_id, data: {page_select_target: "input"} %> + + + +
+ +
+
+
diff --git a/config/locales/en.yml b/config/locales/en.yml index 552bef74a..90f6b8bc4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -277,6 +277,7 @@ en: save_draft: Save draft saved: Page saved saving: Saving... + select_page: Select page couldnt_be_saved: Page couldn't be saved search_engines: Search engines show_in_menu: invisible diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 18716bacc..5ec1fca22 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -244,6 +244,7 @@ nl: change_view_template: Wijzig pagina template change_view_template_confirmation: Weet je zeker dat je de pagina template wil veranderen? Inhoud die niet op de nieuwe pagina past gaat verloren. concept: concept + couldnt_be_saved: Pagina kon niet worden opgeslagen create: Maak pagina create_page: "%{template} invullen" delete: Verwijder pagina @@ -277,7 +278,7 @@ nl: save_draft: Concept opslaan saved: Pagina opgeslagen saving: " Opslaan..." - couldnt_be_saved: Pagina kon niet worden opgeslagen + select_page: Kies pagina search_engines: Zoekmachines show_in_menu: onzichtbaar skip_to_first_child: doorsturen naar 1e subpagina diff --git a/config/routes.rb b/config/routes.rb index f658f00a0..191cdfb45 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -46,12 +46,17 @@ get :edit_content get :edit_template get :children + post :sort_one + end + + collection do + post :sort end resource :move, controller: "move_pages" - - post :sort, on: :collection - post :sort_one, on: :member + end + resources :page_select_options, only: [:show, :index] do + post :search, on: :collection end resources :page_translations, only: [:destroy] resources :parent_pages diff --git a/lib/spina/engine.rb b/lib/spina/engine.rb index bdf7fafab..19ad8b6c0 100644 --- a/lib/spina/engine.rb +++ b/lib/spina/engine.rb @@ -37,7 +37,8 @@ class Engine < ::Rails::Engine Spina::Parts::ImageCollection, Spina::Parts::Repeater, Spina::Parts::Option, - Spina::Parts::Attachment + Spina::Parts::Attachment, + Spina::Parts::PageLink ) end end diff --git a/test/dummy/config/initializers/themes/demo.rb b/test/dummy/config/initializers/themes/demo.rb index bd18bc4ae..d814d7751 100644 --- a/test/dummy/config/initializers/themes/demo.rb +++ b/test/dummy/config/initializers/themes/demo.rb @@ -58,6 +58,17 @@ title: "Testrepeater", part_type: "Spina::Parts::Repeater", parts: %w[line body] + }, { + name: "page", + title: "Pagina", + part_type: "Spina::Parts::PageLink" + }, { + name: "blogpost", + title: "Blogpost", + part_type: "Spina::Parts::PageLink", + options: { + resource: "blog" + } }] theme.view_templates = [{ @@ -69,7 +80,7 @@ title: "Simple page", description: "Default layout", usage: "Use for your content", - parts: ["body", "testrepeater"] + parts: ["body", "blogpost", "page", "testrepeater"] }, { name: "demo", title: "Demo",