From cf5461d96a1dace917feeacf2343a9f5045ac986 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Tue, 3 Feb 2026 17:40:13 +0300 Subject: [PATCH 01/28] fixed comment input label inconsistency between partials --- app/views/comments/_new_thread_modal.html.erb | 2 +- app/views/comments/_reply_to_thread.html.erb | 2 +- config/locales/strings/en.comments.yml | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/comments/_new_thread_modal.html.erb b/app/views/comments/_new_thread_modal.html.erb index 992933b07..097b43d7d 100644 --- a/app/views/comments/_new_thread_modal.html.erb +++ b/app/views/comments/_new_thread_modal.html.erb @@ -22,7 +22,7 @@ <%= form_tag create_comment_thread_path do %> <%= hidden_field_tag :post_id, post.id %> - <%= label_tag :body, 'Your comment', class: 'form-element' %> + <%= label_tag :body, t('comments.labels.comment_body_input'), class: 'form-element' %>
Start the thread with a comment.
<%= text_area_tag :body, '', class: 'form-element js-comment-field', diff --git a/app/views/comments/_reply_to_thread.html.erb b/app/views/comments/_reply_to_thread.html.erb index 14db862cf..9557bcdbc 100644 --- a/app/views/comments/_reply_to_thread.html.erb +++ b/app/views/comments/_reply_to_thread.html.erb @@ -48,7 +48,7 @@ id: "reply-to-thread-form-#{post.id}-#{thread.id}" do %> <%= hidden_field_tag :post_id, post.id %> <%= hidden_field_tag :inline, inline %> - <%= label_tag :content, 'Your message', class: 'form-element' %> + <%= label_tag :content, t('comments.labels.comment_body_input'), class: 'form-element' %> <%= text_area_tag :content, '', class: 'form-element js-comment-field', minlength: min_chars, diff --git a/config/locales/strings/en.comments.yml b/config/locales/strings/en.comments.yml index e6a331703..4dc769e8a 100644 --- a/config/locales/strings/en.comments.yml +++ b/config/locales/strings/en.comments.yml @@ -38,6 +38,8 @@ en: comment_not_posted: > Failed to create comment. labels: + comment_body_input: > + Your comment create_new_thread: > Start new comment thread reply_to_thread: > From 7db0e8cbdeac23411350c8e3f961c5e591f6cb4b Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Tue, 3 Feb 2026 18:18:43 +0300 Subject: [PATCH 02/28] fixed search widget's input alignment --- app/assets/stylesheets/search.scss | 23 +++++++++++++++++++++++ app/views/search/_widget.html.erb | 6 +++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/search.scss b/app/assets/stylesheets/search.scss index 8cb54160e..c40b12804 100644 --- a/app/assets/stylesheets/search.scss +++ b/app/assets/stylesheets/search.scss @@ -1,5 +1,28 @@ @import 'variables'; +.search-widget { + gap: 0.5em; + + .search-widget-input { + flex-grow: 1; + margin: 0; + } + + @media screen and (max-width: $screen-md) { + flex-direction: row; + } + + @media screen and (max-width: $screen-sm) { + align-items: start; + flex-direction: column; + gap: 0.15em; + + .search-widget-input { + width: 100%; + } + } +} + .search-filters:not([open]) { border-top: 1px solid $muted-graphic; } diff --git a/app/views/search/_widget.html.erb b/app/views/search/_widget.html.erb index 335dc1adb..0571f6bc1 100644 --- a/app/views/search/_widget.html.erb +++ b/app/views/search/_widget.html.erb @@ -1,9 +1,9 @@ -
-
+
+
<%= label_tag :search, 'Search term', class: "form-element" %> <%= search_field_tag :search, params[:search], class: 'form-element' %>
-
+
<%= submit_tag 'Search', class: 'button is-medium is-outlined', name: nil %>
From e8aa83261b0fe67c0351111cd3e01540a3717b5b Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Tue, 3 Feb 2026 18:48:04 +0300 Subject: [PATCH 03/28] added priority_order comment thrread scope & aligned expanded/collapsed thread ordering --- app/controllers/comments_controller.rb | 2 +- app/models/comment_thread.rb | 1 + app/views/posts/_expanded.html.erb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 99e3af8ac..8bddbed17 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -323,7 +323,7 @@ def post @post = Post.find(params[:post_id]) @comment_threads = CommentThread.accessible_to(current_user, @post) .where(post: @post) - .order(deleted: :asc, archived: :asc, reply_count: :desc) + .priority_order respond_to do |format| format.html { render layout: false } format.json { render json: @comment_threads } diff --git a/app/models/comment_thread.rb b/app/models/comment_thread.rb index 68d40af67..ca3a8924e 100644 --- a/app/models/comment_thread.rb +++ b/app/models/comment_thread.rb @@ -7,6 +7,7 @@ class CommentThread < ApplicationRecord has_many :thread_follower belongs_to :archived_by, class_name: 'User', optional: true + scope :priority_order, -> { order(deleted: :asc, archived: :asc, updated_at: :desc, reply_count: :desc) } scope :initially_visible, -> { where(deleted: false, archived: false).where('reply_count > 0') } scope :publicly_available, -> { where(deleted: false).where('reply_count > 0') } scope :archived, -> { where(archived: true) } diff --git a/app/views/posts/_expanded.html.erb b/app/views/posts/_expanded.html.erb index 05843506b..8bee21c4b 100644 --- a/app/views/posts/_expanded.html.erb +++ b/app/views/posts/_expanded.html.erb @@ -247,7 +247,7 @@ <% end %>
- <% comment_threads = post.comment_threads.initially_visible.order(updated_at: :desc) %> + <% comment_threads = post.comment_threads.initially_visible.priority_order %> <% public_count = comment_threads.count %> <% available_count = current_user&.post_privilege?('flag_curate', post) ? post.comment_threads.count : post.comment_threads.publicly_available.count %> From ebbc912728783df4b7bc5045aa73dadd9335b1a4 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Tue, 3 Feb 2026 19:09:14 +0300 Subject: [PATCH 04/28] fixed user profile sidebar reputation icon misalignment --- app/views/users/show.html.erb | 109 +++++++++++++++++----------------- 1 file changed, 56 insertions(+), 53 deletions(-) diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 64a2b9cf8..74a8ae1b4 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -18,35 +18,35 @@
- <% effective_profile = raw(sanitize(@user.profile&.strip || '', scrubber: scrubber)) %> - <% if effective_profile.blank? %> -

A quiet enigma. We don't know anything about <%= rtl_safe_username(@user) %> yet.

- <% elsif !user_signed_in? && !@user.community_user.privilege?('unrestricted') %> - <%= sanitize(effective_profile, attributes: %w()) %> - <% else %> - <%= effective_profile %> - <% end %> + <% effective_profile = raw(sanitize(@user.profile&.strip || '', scrubber: scrubber)) %> + <% if effective_profile.blank? %> +

A quiet enigma. We don't know anything about <%= rtl_safe_username(@user) %> yet.

+ <% elsif !user_signed_in? && !@user.community_user.privilege?('unrestricted') %> + <%= sanitize(effective_profile, attributes: %w()) %> + <% else %> + <%= effective_profile %> + <% end %>
<% unless !user_signed_in? && !@user.community_user.privilege?('unrestricted') %> <% if @user.valid_websites_for.size.positive? %> -
-

Extra fields

- - <% @user.valid_websites_for.each do |w| %> - - -
<%= w.label %> - <% if w.url[0,4] == 'http' %> - <%= link_to w.url, w.url, rel: 'nofollow' %> - <% else %> - <%= w.url %> +
+

Extra fields

+ + <% @user.valid_websites_for.each do |w| %> + + + + <% end %> - - - <% end %> -
<%= w.label %> + <% if w.url[0,4] == 'http' %> + <%= link_to w.url, w.url, rel: 'nofollow' %> + <% else %> + <%= w.url %> + <% end %> +
-
+
+
<% end %> <% end %> @@ -73,11 +73,11 @@ annotations on user privileges warnings and suspensions sent to user - <% if @user.community_user.suspended? %> - (includes lifting the suspension) - <% elsif @user.community_user.mod_warnings&.size.positive? %> - (latest <%= time_ago_in_words(@user.community_user.latest_warning) %> ago) - <% end %> + <% if @user.community_user.suspended? %> + (includes lifting the suspension) + <% elsif @user.community_user.mod_warnings&.size.positive? %> + (latest <%= time_ago_in_words(@user.community_user.latest_warning) %> ago) + <% end %> warn or suspend user
@@ -120,23 +120,23 @@ user avatar
<% if @user.staff? %> -
- Staff -
+
+ Staff +
<% elsif @user.admin? %> -
- Administrator -
+
+ Administrator +
<% elsif @user.at_least_moderator? %> -
- Moderator -
+
+ Moderator +
<% end %>
- + @@ -145,11 +145,11 @@ @@ -160,7 +160,8 @@ is_me ? 'View your received votes' : "View received votes for #{rtl_safe_username(@user)}" do %> Received votes <% end %> -
(up minus down) +
+ (up minus down) @@ -169,7 +170,9 @@ <% if is_me || current_user&.at_least_moderator? %> - + + + <% end %>
Reputation Reputation <%= @user.reputation %>
- - <%= link_to search_path(search: "user:#{@user.id} post_type:2"), + + <%= link_to search_path(search: "user:#{@user.id} post_type:2"), 'aria-label': is_me ? 'View your answers' : "View answers from #{rtl_safe_username(@user)}" do %> - Answers - <% end %> + Answers + <% end %> <%= @user.metric '2' %>
<%= @user.metric 's' %>
<%= @user.metric 'E' %>
Joined <%= @user.community_user.created_at %>
Joined <%= @user.community_user.created_at %>
@@ -178,14 +181,14 @@
<% @abilities.each do |a| %> - <% if @user.privilege?(a.internal_id) %> - - <% end %> + <% if @user.privilege?(a.internal_id) %> + + <% end %> <% end %> From 317397680ac20745c049aa24a0a26019fc99e00e Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Thu, 5 Feb 2026 12:21:01 +0300 Subject: [PATCH 10/28] settled on timestamp format 'YYYY-MM-DD hh:mm:ss UTC' --- app/assets/javascripts/qpixel_dom.js | 2 +- app/models/notification.rb | 5 ----- app/views/notifications/_notification.html.erb | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/qpixel_dom.js b/app/assets/javascripts/qpixel_dom.js index 9aa5738aa..091bdf30b 100644 --- a/app/assets/javascripts/qpixel_dom.js +++ b/app/assets/javascripts/qpixel_dom.js @@ -57,7 +57,7 @@ window.QPixel ||= {}; }, formatTimestamp: (timestamp) => { - return moment(timestamp).format('YYYY-MM-DD [at] hh:mm:ss UTC'); + return moment(timestamp).format('YYYY-MM-DD hh:mm:ss UTC'); }, getModifierState: (e) => { diff --git a/app/models/notification.rb b/app/models/notification.rb index 9883be4f6..6c3ff4f0f 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -16,9 +16,4 @@ def read? def unread? !read? end - - def rendered_timestamp - formatted = created_at.strftime('%Y-%m-%d at %H:%M:%S UTC') - CGI.unescape_html(formatted).html_safe - end end diff --git a/app/views/notifications/_notification.html.erb b/app/views/notifications/_notification.html.erb index 5bf54c562..1a9387519 100644 --- a/app/views/notifications/_notification.html.erb +++ b/app/views/notifications/_notification.html.erb @@ -20,7 +20,7 @@ alt="<% notification.community_name %> logo" /> <% end %> <%= notification.community_name %> · - <%= notification.rendered_timestamp %> + <%= notification.created_at.utc %>
<%= render_pings_text(notification.content) %> From 91f3c4de93ce61276aed88e96ad6e3fca84ae4c8 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Thu, 5 Feb 2026 13:42:55 +0300 Subject: [PATCH 11/28] restored relative time functionality --- app/assets/javascripts/relative_time.js | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 app/assets/javascripts/relative_time.js diff --git a/app/assets/javascripts/relative_time.js b/app/assets/javascripts/relative_time.js new file mode 100644 index 000000000..05b7873bc --- /dev/null +++ b/app/assets/javascripts/relative_time.js @@ -0,0 +1,35 @@ +document.addEventListener('DOMContentLoaded', () => { + const updateInterval = 6e4; // updates relative time once a minute + let lastRunAt = -1; + + /** + * @type {FrameRequestCallback} + */ + const updateRelativeTime = (timestamp) => { + const elapsed = timestamp - lastRunAt; + + if (elapsed < updateInterval && lastRunAt !== -1) { + requestAnimationFrame(updateRelativeTime); + return; + } + + document.querySelectorAll('[data-relstamp]').forEach((el) => { + if (!QPixel.DOM?.isHTMLElement(el)) { + return; + } + + const { relstamp } = el.dataset; + + if (!relstamp) { + return; + } + + el.textContent = `${QPixel.DOM.formatTimestamp(relstamp)} (${moment(relstamp).fromNow()})`; + }); + + lastRunAt = timestamp; + requestAnimationFrame(updateRelativeTime); + }; + + requestAnimationFrame(updateRelativeTime); +}); From 5bb449c3737e4e0c349d3f45b816559afa206be2 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Thu, 5 Feb 2026 13:47:48 +0300 Subject: [PATCH 12/28] added relative time to full inbox notifications --- app/views/notifications/_notification.html.erb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/notifications/_notification.html.erb b/app/views/notifications/_notification.html.erb index 1a9387519..1f0f06b49 100644 --- a/app/views/notifications/_notification.html.erb +++ b/app/views/notifications/_notification.html.erb @@ -19,7 +19,8 @@ src="<%= logo_path %>" alt="<% notification.community_name %> logo" /> <% end %> - <%= notification.community_name %> · + <%= notification.community_name %> · <%= notification.created_at.utc %>
From 26c8333f293940b5e1c3db9321a49cd611355060 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Thu, 5 Feb 2026 14:01:03 +0300 Subject: [PATCH 13/28] aded relative time to dropdown inbox notifications --- app/assets/javascripts/notifications.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/notifications.js b/app/assets/javascripts/notifications.js index 73e9667ed..4799e2c54 100644 --- a/app/assets/javascripts/notifications.js +++ b/app/assets/javascripts/notifications.js @@ -7,15 +7,26 @@ $(() => {
${notification.community_name} · - ${notification.is_read ? 'read' : `unread`} · - ${QPixel.DOM.formatTimestamp(notification.created_at)} + + ${QPixel.DOM.formatTimestamp(notification.created_at)} +
-

${notification.content}

-

+

+ + ${notification.content} + +

+

+ mark ${notification.is_read ? 'unread' : 'read'} -

+ +

`; return template; @@ -87,7 +98,6 @@ $(() => { $('.inbox-count').remove(); $('.js-notification').removeClass('is-teal').addClass('read'); - $('.js-notif-state').text('read'); $('.js-notification-toggle').html(` mark unread`); }); From 97939d6b5ea5fdfa140f2e4ac2e909044acc1fb5 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Thu, 5 Feb 2026 14:25:17 +0300 Subject: [PATCH 14/28] relative time should also be updated on mutations --- app/assets/javascripts/relative_time.js | 38 ++++++++++++++++--------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/relative_time.js b/app/assets/javascripts/relative_time.js index 05b7873bc..69d126eec 100644 --- a/app/assets/javascripts/relative_time.js +++ b/app/assets/javascripts/relative_time.js @@ -2,6 +2,23 @@ document.addEventListener('DOMContentLoaded', () => { const updateInterval = 6e4; // updates relative time once a minute let lastRunAt = -1; + /** + * @param {Node} el element to process + */ + const processNode = (el) => { + if (!QPixel.DOM?.isHTMLElement(el)) { + return; + } + + const { relstamp } = el.dataset; + + if (!relstamp) { + return; + } + + el.textContent = `${QPixel.DOM.formatTimestamp(relstamp)} (${moment(relstamp).fromNow()})`; + }; + /** * @type {FrameRequestCallback} */ @@ -13,23 +30,18 @@ document.addEventListener('DOMContentLoaded', () => { return; } - document.querySelectorAll('[data-relstamp]').forEach((el) => { - if (!QPixel.DOM?.isHTMLElement(el)) { - return; - } - - const { relstamp } = el.dataset; - - if (!relstamp) { - return; - } - - el.textContent = `${QPixel.DOM.formatTimestamp(relstamp)} (${moment(relstamp).fromNow()})`; - }); + document.querySelectorAll('[data-relstamp]').forEach(processNode); lastRunAt = timestamp; requestAnimationFrame(updateRelativeTime); }; requestAnimationFrame(updateRelativeTime); + + new MutationObserver(() => { + document.querySelectorAll('[data-relstamp]').forEach(processNode); + }).observe(document, { + attributes: true, + subtree: true, + }); }); From 924e5ddf9b97853c867ef06225107bd9cce1b7fb Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Thu, 5 Feb 2026 14:45:25 +0300 Subject: [PATCH 15/28] 'less than' sign should be escaped when used in HTML markup --- app/views/layouts/_header.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb index 534a84435..d228bede2 100644 --- a/app/views/layouts/_header.html.erb +++ b/app/views/layouts/_header.html.erb @@ -174,7 +174,7 @@ votes:4 posts with 4+ votes
- created:<1w created < 1 week ago + created:<1w created < 1 week ago
post_type:xxxx type of post From 26338fa7125cbea6920847fef2929f2c26c721e7 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Thu, 5 Feb 2026 15:18:00 +0300 Subject: [PATCH 16/28] expose MaxUploadSize site setting to the client as QPixel.MAX_UPLOAD_SIZE --- app/views/layouts/_expose.html.erb | 21 +++++++++++++++++++++ app/views/layouts/_head.html.erb | 16 +--------------- global.d.ts | 4 ++++ 3 files changed, 26 insertions(+), 15 deletions(-) create mode 100644 app/views/layouts/_expose.html.erb diff --git a/app/views/layouts/_expose.html.erb b/app/views/layouts/_expose.html.erb new file mode 100644 index 000000000..2198f99b0 --- /dev/null +++ b/app/views/layouts/_expose.html.erb @@ -0,0 +1,21 @@ + diff --git a/app/views/layouts/_head.html.erb b/app/views/layouts/_head.html.erb index 591fa2b80..700655931 100644 --- a/app/views/layouts/_head.html.erb +++ b/app/views/layouts/_head.html.erb @@ -46,21 +46,7 @@ <%= javascript_include_tag 'application' %> - +<%= render 'layouts/expose' %> <% if SiteSetting['SyntaxHighlightingEnabled'] %> diff --git a/global.d.ts b/global.d.ts index dc7bdad4f..1219f8b00 100644 --- a/global.d.ts +++ b/global.d.ts @@ -386,6 +386,10 @@ interface QPixel { * List of attributes allowed on HTML tags in posts, supplied by the server */ readonly ALLOWED_POST_ATTRS?: readonly string[] + /** + * Maximum file upload size (arbitrary unit) + */ + readonly MAX_UPLOAD_SIZE?: string // private properties _filters?: Record | null; From 062fc574b65abb66851c0926fe22d2e517b1db46 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Thu, 5 Feb 2026 15:25:02 +0300 Subject: [PATCH 17/28] post image upload should display correct max file size limit in the error notification --- app/assets/javascripts/posts.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/posts.js b/app/assets/javascripts/posts.js index 44aad0f44..0a1251c31 100644 --- a/app/assets/javascripts/posts.js +++ b/app/assets/javascripts/posts.js @@ -50,6 +50,8 @@ $(() => { const files = /** @type {HTMLInputElement} */ ($fileInput[0]).files; const form = /** @type {HTMLFormElement} */ ($tgt[0]); + const maxUploadSize = QPixel.MAX_UPLOAD_SIZE || '2MB'; + // TODO: MaxUploadSize is a site setting and can be changed if (files.length > 0 && files[0].size >= 2000000) { const isUploadModalOpened = $('#markdown-image-upload').hasClass('is-active'); @@ -58,7 +60,7 @@ $(() => { postField.val(postField.val()?.toString().replace(placeholder, '')); if (!isUploadModalOpened) { - QPixel.createNotification('danger', `Can't upload files with size more than 2MB`); + QPixel.createNotification('danger', `Can't upload files with size more than ${maxUploadSize}`); } else { $tgt.find('.js-max-size').addClass('has-color-red-700 error-shake'); From aa35c2205d048f30146607034ade73608478c05e Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Thu, 5 Feb 2026 16:20:38 +0300 Subject: [PATCH 18/28] trying to submit user avatar with size > MaxUploadSize should prevent form submission --- app/assets/javascripts/profile.js | 31 +++++++++++++++++++++++++++ app/views/users/edit_profile.html.erb | 4 ++-- 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/profile.js diff --git a/app/assets/javascripts/profile.js b/app/assets/javascripts/profile.js new file mode 100644 index 000000000..966977239 --- /dev/null +++ b/app/assets/javascripts/profile.js @@ -0,0 +1,31 @@ +document.addEventListener('DOMContentLoaded', () => { + document.querySelector('.js-submit-profile-edit')?.addEventListener('click', (ev) => { + const { target } = ev; + + if (!QPixel.DOM?.isHTMLElement(target)) { + return; + } + + const profileForm = target.closest('form'); + + profileForm?.querySelectorAll('input[type=file]').forEach((el) => { + const files = /** @type {HTMLInputElement} */ (el).files; + + // TODO: MaxUploadSize is a site setting and can be changed + if (files.length > 0 && files[0].size >= 2000000) { + if (!ev.defaultPrevented) { + ev.preventDefault(); + } + + const maxSizeCaption = profileForm?.querySelector(`.js-max-size[for='${el.id}']`); + + if (!maxSizeCaption) { + return; + } + + maxSizeCaption.classList.add('has-color-red-700', 'error-shake'); + setTimeout(() => maxSizeCaption?.classList.remove('error-shake'), 1000); + } + }); + }); +}); diff --git a/app/views/users/edit_profile.html.erb b/app/views/users/edit_profile.html.erb index 65319b03f..6deac1f15 100644 --- a/app/views/users/edit_profile.html.erb +++ b/app/views/users/edit_profile.html.erb @@ -35,7 +35,7 @@ height="64" width="64" /> <%= f.label :avatar, class: "form-element" %> -
+
An optional profile picture. Max file size <%= SiteSetting['MaxUploadSize'] %>.
<%= f.file_field :avatar, class: "form-element" %> @@ -95,7 +95,7 @@
- <%= f.submit 'Save', class: 'button is-filled' %> + <%= f.submit 'Save', class: 'button is-filled js-submit-profile-edit' %> <%= link_to 'Cancel', users_me_path, class: 'button is-muted is-outlined js-cancel-edit', From d640db375fbd08f4777793fca34708302ed78abd Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Thu, 5 Feb 2026 16:32:00 +0300 Subject: [PATCH 19/28] made new comment thread captions configurable --- app/views/comments/_new_thread_modal.html.erb | 6 +++--- config/locales/strings/en.comments.yml | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/views/comments/_new_thread_modal.html.erb b/app/views/comments/_new_thread_modal.html.erb index 097b43d7d..3d0618745 100644 --- a/app/views/comments/_new_thread_modal.html.erb +++ b/app/views/comments/_new_thread_modal.html.erb @@ -13,10 +13,10 @@ Start a new comment thread
@@ -67,23 +67,23 @@

Extra fields -- your web site, GitHub profile, social-media usernames, whatever you want.
- Only values that begin with "http" are rendered as links.

+ Only values that begin with "http" are rendered as links.

<%= f.fields_for :user_websites do |w| %> -
-
-
<%= w.text_field :label, - class: 'form-element', - autocomplete: 'off', - placeholder: 'label' %>
+
+
+
<%= w.text_field :label, + class: 'form-element', + autocomplete: 'off', + placeholder: 'label' %>
+
+
+
<%= w.text_field :url, + class: 'form-element', + autocomplete: 'off', + placeholder: 'https://...' %>
+
-
-
<%= w.text_field :url, - class: 'form-element', - autocomplete: 'off', - placeholder: 'https://...' %>
-
-
<% end %>
@@ -93,8 +93,7 @@ Your Discord user tag, username or username#1234. <%= f.text_field :discord, class: 'form-element', autocomplete: 'off', placeholder: 'username#1234' %>
- - + <%= f.submit 'Save', class: 'button is-filled js-submit-profile-edit' %> <%= link_to 'Cancel', users_me_path, From b085aeafbc3b55b4bbee18c4a410d0071a3241b9 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Sat, 7 Feb 2026 21:03:19 +0300 Subject: [PATCH 25/28] allowed seeds to autoremove site settings that are no longer present --- db/seeds.rb | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index ff74e48b3..421811728 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -134,6 +134,11 @@ def update_post(user, community, post, seed) stats end +# a map of community_id to an array of SiteSetting seed names - a partial +# solution to making our seeds mutable. For now this map facilitates removal +# of no longer present site settings during cleanup +$site_settings_map = {} + # @return [Stats] operation stats def seed_objects(type, seed) seeds = expand_communities(type, seed) @@ -155,6 +160,14 @@ def seed_objects(type, seed) ensure_system_user_abilities end + if type == SiteSetting + seeds.each do |seed| + community_id = seed[:community]&.id + $site_settings_map[community_id] ||= [] + $site_settings_map[community_id] << seed[:name] + end + end + SeedsHelper::Stats.new(created, 0, 0, skipped) end @@ -199,6 +212,29 @@ def seed_community_assets end end +def cleanup_posts + Post.where(community_id: nil).destroy_all +end + +def cleanup_site_settings + to_remove = [] + + $site_settings_map.each do |community_id, names| + stale_settings = SiteSetting.unscoped + .where(community_id: community_id) + .where.not(name: names) + to_remove.push(*stale_settings) + end + + return unless to_remove.any? + + ActiveRecord::Base.transaction do + to_remove.each(&:destroy!) + end + + puts "#{SiteSetting.model_name}: removed #{to_remove.size}" +end + sorted.each do |f, type| processed = ERB.new(File.read(f)).result(binding) data = YAML.load(processed) @@ -219,6 +255,7 @@ def seed_community_assets puts "Got error #{e}. Continuing..." end -Post.where(community_id: nil).destroy_all - seed_community_assets + +cleanup_posts +cleanup_site_settings From 3a1a937b14100b63c8163a17fe5f142e3af9217b Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Sat, 7 Feb 2026 21:04:08 +0300 Subject: [PATCH 26/28] MaxUploadSize site setting can be safely dropped now --- db/seeds/site_settings.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/db/seeds/site_settings.yml b/db/seeds/site_settings.yml index c4adc117b..6cae41ffd 100644 --- a/db/seeds/site_settings.yml +++ b/db/seeds/site_settings.yml @@ -180,15 +180,6 @@ description: > The maximum number of characters an edit comment may contain. Defaults to 255. -- name: MaxUploadSize - value: 2MB - value_type: string - category: AdvancedSettings - description: > - The maximum size of images that users may upload. Changing this setting DOES NOT change the limit - the limit is - set by your web server configuration. This setting should be set to the same value so that the correct limit - can be displayed to users. - - name: MaxRequestBodySize value: 2097152 value_type: integer From b72d7e35146c94a7df9e1786a5f64b703170c347 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Sat, 7 Feb 2026 21:27:41 +0300 Subject: [PATCH 27/28] added a note that MaxRequestBodySize's default value is equal to 2MB --- db/seeds/site_settings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/seeds/site_settings.yml b/db/seeds/site_settings.yml index 6cae41ffd..3c98ad52f 100644 --- a/db/seeds/site_settings.yml +++ b/db/seeds/site_settings.yml @@ -185,7 +185,7 @@ value_type: integer category: AdvancedSettings description: > - The maximum allowed size of request body (in bytes). This setting must be kept in sync with your web server or + The maximum allowed size of request body in bytes (2MB by default). This setting must be kept in sync with your web server or reverse proxy configuration that governs request body size limits - **do not** change it if you don't know what that means. From b72d3a40b7fe7bf81ee8ef4fb3c08a91763d5ed3 Mon Sep 17 00:00:00 2001 From: Oleg Valter Date: Sat, 7 Feb 2026 21:49:59 +0300 Subject: [PATCH 28/28] don't cleanup site settings in tests as there are fixtures --- db/seeds.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/db/seeds.rb b/db/seeds.rb index 421811728..34eca426d 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -217,6 +217,8 @@ def cleanup_posts end def cleanup_site_settings + return if Rails.env.test? + to_remove = [] $site_settings_map.each do |community_id, names|