Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
862e948
fix(files): add additional_html to CANCAN_MAPPINGS read group (CS-167)
jirkamotejl Mar 6, 2026
569086a
When file is updated it should be reflected on articles where this fi…
zanetagebka Mar 5, 2026
806cebf
extend fix for autocomplete with minimum query length (#548)
zanetagebka Mar 9, 2026
eff01d3
feat(aside_style): optimize and clean up css code
tovier Mar 10, 2026
7e3eff9
chore(db): migrate
mreq Mar 9, 2026
a5c7ccc
chore(gemfile): version 7.4.0
mreq Mar 9, 2026
139b5ae
fix(tiptap): new_record_alert i18n non-nominative case
mreq Mar 9, 2026
c6a8f21
chore(changelog): mv entries to released
mreq Mar 10, 2026
b28e4bd
fix(audited-dropdown): flex-wrap and full-width label/small in compon…
pj258 Mar 10, 2026
6c0b8eb
fix(tiptap):link handling - close on confirm/delete, don't autofocus …
VladaTrefil Mar 10, 2026
6a3f3c8
chore(version): 7.4.1
VladaTrefil Mar 11, 2026
6175c23
feat(scss): optimize aside css code
tovier Mar 11, 2026
e052ea2
Devel 22
jirkamotejl Mar 12, 2026
29d1857
fix(thumbnails): add try to dont_run_after_save_jobs to prevent priva…
VladaTrefil Mar 16, 2026
d7e80a5
fix(tiptap): reset white-space na .f-embed-box proti zděděnému pre-wr…
jirkamotejl Mar 16, 2026
af37dcf
Devel 23
jirkamotejl Mar 17, 2026
82a9a3f
feat(roadmap): add comprehensive roadmap document outlining planning …
ornsteinfilip Mar 19, 2026
d6258e5
fix(roadmap): update AWS-oriented provider description to include ref…
ornsteinfilip Mar 19, 2026
e82c49e
feat(video): CRA presigned S3 URLs & progress tracking + no-download …
jirkamotejl Mar 19, 2026
9b30a6e
Devel 24
jirkamotejl Mar 19, 2026
d6532d3
fix(cra): add video ID to reference_id and guard against nil encoding…
jirkamotejl Mar 19, 2026
f3d5d2a
docs: update reference_id format to include video ID
jirkamotejl Mar 19, 2026
6670e22
fix(cra): handle encoding_generation race condition and orphan detect…
jirkamotejl Mar 19, 2026
3351560
Devel 25
jirkamotejl Mar 19, 2026
b935acb
chore: update Gemfile.lock to match version 7.5.1
jirkamotejl Mar 19, 2026
ac85596
chore: run guard
jirkamotejl Mar 22, 2026
a07c9ce
chore(deps): bump actionview from 8.0.4 to 8.0.4.1 (#564)
dependabot[bot] Mar 24, 2026
c4495ba
chore(deps): bump activestorage from 8.0.4 to 8.0.4.1 (#565)
dependabot[bot] Mar 24, 2026
91da279
chore(deps): bump activesupport from 8.0.4 to 8.0.4.1 (#566)
dependabot[bot] Mar 24, 2026
570810f
chore(deps): bump json from 2.18.1 to 2.19.2 (#559)
dependabot[bot] Mar 24, 2026
b00d334
chore(deps): bump bcrypt from 3.1.20 to 3.1.22 (#561)
dependabot[bot] Mar 24, 2026
1e049e4
chore(deps-dev): bump minimatch from 3.1.2 to 3.1.5 (#551)
dependabot[bot] Mar 24, 2026
287dcde
chore(deps): bump rack from 2.2.21 to 2.2.22 (#534)
dependabot[bot] Mar 24, 2026
bcaaecd
chore(deps-dev): bump minimatch from 3.1.2 to 3.1.5 in /test/dummy (#…
dependabot[bot] Mar 24, 2026
7621692
chore(deps-dev): bump svgo from 2.8.0 to 2.8.2 in /test/dummy (#545)
dependabot[bot] Mar 24, 2026
e7fc886
chore(guard): run
mreq Mar 24, 2026
829d0fa
Merge branch 'master' into feat/aside_content_mobile_size
mreq Mar 24, 2026
04091b1
chore(changelog): add tiptap float entry
mreq Mar 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.

### Fixed

- **Autocomplete performance**: Extended minimum query length guard to `select2`, `selectize`, and `react_select` actions in `AutocompletesController`. All autocomplete endpoints now reject queries shorter than 3 characters without hitting the database, preventing cascade PostgreSQL connection pool exhaustion. Completes the fix from 7.4.0 which only covered the `show` action.
- add change event handler for select inline edits
- prevent long filenames from overflowing dropdown
- S3 ping retry loop no longer runs infinitely after 10 failures — stops retrying, shows a non-blocking flash instead of a blocking `window.alert()`, and resets the loading state
Expand Down
46 changes: 27 additions & 19 deletions app/assets/stylesheets/folio/tiptap/_styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -416,9 +416,13 @@ $f-tiptap__media-min-width--desktop: 708px !default;
}

.f-tiptap-float__aside {
margin-bottom: var(--f-tiptap-float__aside-margin-y);
position: relative;
z-index: 2;
margin-bottom: var(--f-tiptap-float__aside-margin-y);
margin-right: var(--f-tiptap-float__aside-margin-x);
margin-left: var(--f-tiptap-float__aside-offset);
width: var(--f-tiptap-float__aside-width);
float: var(--f-tiptap-float__aside-side);
}

.f-tiptap-editor & .f-tiptap-column::before,
Expand Down Expand Up @@ -555,7 +559,28 @@ $f-tiptap__media-min-width--desktop: 708px !default;
}
}

.f-tiptap-float[data-f-tiptap-float-side="right"] .f-tiptap-float__aside {
margin-right: var(--f-tiptap-float__aside-offset);
margin-left: var(--f-tiptap-float__aside-margin-x);
}

.f-tiptap-float[data-f-tiptap-float-size="small"] .f-tiptap-float__aside {
width: var(--f-tiptap-float__aside-width);
}

.f-tiptap-float[data-f-tiptap-float-size="large"] .f-tiptap-float__aside {
width: var(--f-tiptap-float__aside-width);
}

.f-tiptap-float[data-f-tiptap-float-side="right"] .f-tiptap-float__aside {
float: var(--f-tiptap-float__aside-side);
}

/* Mobile first - Aside width and spacing */
.f-tiptap-float {
--f-tiptap-float__aside-side: left;
--f-tiptap-float__aside-width: 100%;
}

@container (min-width: #{$f-tiptap__media-min-width--tablet}) {
.f-tiptap-float {
Expand All @@ -564,6 +589,7 @@ $f-tiptap__media-min-width--desktop: 708px !default;
--f-tiptap-float__aside-side: left;
--f-tiptap-float__aside-margin-x: var(--f-tiptap-float__aside-margin-x--tablet);
--f-tiptap-float__aside-offset: var(--f-tiptap-float__aside-offset--tablet);
--f-tiptap-float__aside-margin-y: var(--f-tiptap__spacer);

&::after {
content: "";
Expand All @@ -586,30 +612,12 @@ $f-tiptap__media-min-width--desktop: 708px !default;

.f-tiptap-float__aside {
float: left;
margin-right: var(--f-tiptap-float__aside-margin-x);
margin-bottom: var(--f-tiptap__spacer);
margin-left: var(--f-tiptap-float__aside-offset);
width: var(--f-tiptap-float__aside-width);
position: relative;
container-type: inline-size;
box-sizing: border-box;
min-height: 2rem;
}

.f-tiptap-float[data-f-tiptap-float-side="right"] .f-tiptap-float__aside {
float: right;
margin-right: var(--f-tiptap-float__aside-offset);
margin-left: var(--f-tiptap-float__aside-margin-x);
}

.f-tiptap-float[data-f-tiptap-float-size="small"] .f-tiptap-float__aside {
width: var(--f-tiptap-float__aside-width);
}

.f-tiptap-float[data-f-tiptap-float-size="large"] .f-tiptap-float__aside {
width: var(--f-tiptap-float__aside-width);
}

.f-tiptap-columns {
display: grid;
grid-auto-columns: 1fr;
Expand Down
23 changes: 21 additions & 2 deletions app/controllers/folio/console/api/autocompletes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,14 @@ def selectize

scope = filter_by_atom_setting_params(scope)

scope = scope.by_label_query(q) if q.present?
if q.present?
if q.length >= AUTOCOMPLETE_QUERY_MIN_LENGTH
scope = scope.by_label_query(q)
else
render_selectize_options([])
return
end
end

scope, has_type_ordering = apply_ordered_for_folio_console_selects(scope, klass)

Expand Down Expand Up @@ -197,7 +204,14 @@ def select2

scope = filter_by_atom_setting_params(scope)

scope = scope.by_label_query(q) if q.present?
if q.present?
if q.length >= AUTOCOMPLETE_QUERY_MIN_LENGTH
scope = scope.by_label_query(q)
else
render_select2_options([])
return
end
end

scope, has_type_ordering = apply_ordered_for_folio_console_selects(scope, klass)

Expand Down Expand Up @@ -237,6 +251,11 @@ def react_select
p_without = params[:without]
p_page = params[:page]&.to_i || 1

if q.present? && q.length < AUTOCOMPLETE_QUERY_MIN_LENGTH
render json: { data: [], meta: { page: 1, pages: 1, from: nil, to: nil, count: 0, next: nil } }
return
end

if class_names
# Show model names when there are multiple classes, or when a single class forces it
show_model_names = class_names.size > 1
Expand Down
26 changes: 25 additions & 1 deletion app/jobs/folio/files/after_save_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,42 @@ class Folio::Files::AfterSaveJob < Folio::ApplicationJob

# use SQL commands only!
# save/update would cause an infinite loop as this is hooked in after_save
def perform(file)
def perform(file, changed_attrs = {})
return if Rails.env.test? && !Rails.application.config.try(:folio_testing_after_save_job)

placements = file.file_placements

file.update_column(:file_placements_count, placements.size)

# Sync description/alt/headline to placements if file metadata changed
if changed_attrs.present?
sync_metadata_to_placements(file, placements, changed_attrs)
end

# touch placements to bust cache
placements.find_each do |placement|
if placement.placement
placement.placement.touch
end
end
end

private

def sync_metadata_to_placements(file, placements, changed_attrs)
if changed_attrs.key?("description")
old_desc, new_desc = changed_attrs["description"]
placements.where(description: [old_desc, nil, ""]).update_all(description: new_desc)
end

if changed_attrs.key?("alt")
old_alt, new_alt = changed_attrs["alt"]
placements.where(alt: [old_alt, nil, ""]).update_all(alt: new_alt)
end

if changed_attrs.key?("headline")
old_headline, new_headline = changed_attrs["headline"]
placements.where(title: [old_headline, nil, ""]).update_all(title: new_headline)
end
end
end
6 changes: 5 additions & 1 deletion app/models/folio/file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class Folio::File < Folio::ApplicationRecord
update_thumbnails_crop
],
read: %i[
additional_html
batch_bar
batch_download
batch_download_failure
Expand Down Expand Up @@ -237,7 +238,10 @@ def run_after_save_job
# updating placements
return if ENV["SKIP_FOLIO_FILE_AFTER_SAVE_JOB"]
return if Rails.env.test? && !Rails.application.config.try(:folio_testing_after_save_job)
Folio::Files::AfterSaveJob.perform_later(self)

changed_attrs = saved_changes.slice("description", "alt", "headline")

Folio::Files::AfterSaveJob.perform_later(self, changed_attrs)
end

def process_attached_file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,83 @@ class Folio::Console::Api::AutocompletesControllerTest < Folio::Console::BaseCon
# Clean up
Folio::Page.singleton_class.remove_method(:ordered_for_folio_console_selects)
end

test "selectize ignores short queries below minimum length" do
create(:folio_page, title: "Foo bar baz")

# 1-char query should return empty (below min length of 3)
get selectize_console_api_autocomplete_path(klass: "Folio::Page", q: "f")
json = JSON.parse(response.body)
assert_equal([], json["data"])

# 2-char query should return empty (below min length of 3)
get selectize_console_api_autocomplete_path(klass: "Folio::Page", q: "fo")
json = JSON.parse(response.body)
assert_equal([], json["data"])

# 3-char query should trigger search and return results
get selectize_console_api_autocomplete_path(klass: "Folio::Page", q: "foo")
json = JSON.parse(response.body)
assert_equal(1, json["data"].size)
assert_equal("Foo bar baz", json["data"][0]["text"])
end

test "select2 ignores short queries below minimum length" do
create(:folio_page, title: "Foo bar baz")

# 1-char query should return empty (below min length of 3)
get select2_console_api_autocomplete_path(klass: "Folio::Page", q: "f")
json = JSON.parse(response.body)
assert_equal([], json["results"])

# 2-char query should return empty (below min length of 3)
get select2_console_api_autocomplete_path(klass: "Folio::Page", q: "fo")
json = JSON.parse(response.body)
assert_equal([], json["results"])

# 3-char query should trigger search and return results
get select2_console_api_autocomplete_path(klass: "Folio::Page", q: "foo")
json = JSON.parse(response.body)
assert_equal(1, json["results"].size)
assert_equal("Foo bar baz", json["results"][0]["text"])
end

test "react_select ignores short queries below minimum length" do
create(:folio_page, title: "Foo bar baz")

# 1-char query should return empty (below min length of 3)
get react_select_console_api_autocomplete_path(class_names: "Folio::Page", q: "f")
json = JSON.parse(response.body)
assert_equal([], json["data"])
assert_equal(1, json["meta"]["page"])
assert_equal(1, json["meta"]["pages"])

# 2-char query should return empty (below min length of 3)
get react_select_console_api_autocomplete_path(class_names: "Folio::Page", q: "fo")
json = JSON.parse(response.body)
assert_equal([], json["data"])
assert_equal(1, json["meta"]["page"])
assert_equal(1, json["meta"]["pages"])

# 3-char query should trigger search and return results
get react_select_console_api_autocomplete_path(class_names: "Folio::Page", q: "foo")
json = JSON.parse(response.body)
assert_equal(1, json["data"].size)
assert_equal("Foo bar baz", json["data"][0]["text"])
end

test "react_select with multiple classes ignores short queries" do
create(:folio_page, title: "Foo page")

# 2-char query should return empty for multiple classes
get react_select_console_api_autocomplete_path(class_names: "Folio::Page", q: "fo")
json = JSON.parse(response.body)
assert_equal([], json["data"])
assert_equal(1, json["meta"]["page"])

# 3-char query should return results
get react_select_console_api_autocomplete_path(class_names: "Folio::Page", q: "foo")
json = JSON.parse(response.body)
assert json["data"].size > 0
end
end
Loading