From 7b70ceefbf9ea9414b8add57a813cc5db053a6f6 Mon Sep 17 00:00:00 2001 From: clragon Date: Mon, 10 Feb 2025 05:18:23 +0100 Subject: [PATCH 01/26] [UserFeedback] Fix filtering nil during restoration (#901) --- ...3_convert_staffnote_to_deleted_feedback.rb | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/db/fixes/123_convert_staffnote_to_deleted_feedback.rb b/db/fixes/123_convert_staffnote_to_deleted_feedback.rb index 420e05fa2..77c698afa 100755 --- a/db/fixes/123_convert_staffnote_to_deleted_feedback.rb +++ b/db/fixes/123_convert_staffnote_to_deleted_feedback.rb @@ -8,23 +8,26 @@ destroyed_feedback_ids = [] CurrentUser.as_system do ModAction.where(action: "user_feedback_destroy") # On July 24, 2024, we deployed the ability to soft-delete feedback records. - # We only care about restoring destroyed feedbacks that were destroyed before this date. - # Any entries after this are "real" destructions, that do not need to be restored. + # We only restore feedback that was destroyed before this date. + # Any entries after this are intentional destructions that do not need to be restored. .where("created_at < ?", CUTOFF_DATE = Date.new(2024, 8, 1)) .find_in_batches(batch_size: 10_000) do |batch| - feedback_data = batch.map do |mod_action| - record_id = mod_action.values["record_id"].to_i + feedback_data = batch.filter_map do |mod_action| + record_id = mod_action.values["record_id"] + category = mod_action.values["type"] + body = mod_action.values["reason"] + + # Old mod actions do not contain the necessary information. Skip them. + next if record_id.nil? || category.nil? || body.nil? + destroyed_feedback_ids << record_id - # old mod actions do not contain the necessary information. we skip them. - next if mod_action.values["type"].nil? || mod_action.values["reason"].nil? - { - id: record_id, + id: record_id.to_i, user_id: mod_action.values["user_id"].to_i, creator_id: User.system.id, # placeholder - category: mod_action.values["type"], - body: mod_action.values["reason"]&.strip, + category: category, + body: body.strip, created_at: Date.new(1970, 1, 1), # placeholder updated_at: mod_action.created_at, updater_id: mod_action.creator_id, From 1879a4c1f3d3b2c182ab6461c6a23873a020b483 Mon Sep 17 00:00:00 2001 From: Cinder Date: Sun, 9 Feb 2025 20:32:33 -0800 Subject: [PATCH 02/26] [CSS] Begin refactoring view-specific styles (#902) --- app/javascript/src/styles/base.scss | 4 +- app/javascript/src/styles/base/_themable.scss | 12 + .../src/styles/specific/post_index.scss | 287 ------------------ .../src/styles/views/posts/_posts.scss | 15 + .../views/posts/index/_index.desktop.scss | 32 ++ .../src/styles/views/posts/index/_index.scss | 112 +++++++ .../posts/index/partials/_fullscreen.scss | 31 ++ .../posts/index/partials/_mode_menu.scss} | 2 +- .../posts/index/partials/_wiki_excerpt.scss | 97 ++++++ 9 files changed, 302 insertions(+), 290 deletions(-) delete mode 100644 app/javascript/src/styles/specific/post_index.scss create mode 100644 app/javascript/src/styles/views/posts/_posts.scss create mode 100644 app/javascript/src/styles/views/posts/index/_index.desktop.scss create mode 100644 app/javascript/src/styles/views/posts/index/_index.scss create mode 100644 app/javascript/src/styles/views/posts/index/partials/_fullscreen.scss rename app/javascript/src/styles/{specific/post_mode_menu.scss => views/posts/index/partials/_mode_menu.scss} (99%) create mode 100644 app/javascript/src/styles/views/posts/index/partials/_wiki_excerpt.scss diff --git a/app/javascript/src/styles/base.scss b/app/javascript/src/styles/base.scss index 6ec5cfc2f..227cdab7f 100644 --- a/app/javascript/src/styles/base.scss +++ b/app/javascript/src/styles/base.scss @@ -42,6 +42,8 @@ @import "common/user_styles.scss"; @import "common/voting.scss"; +@import "views/posts/posts"; + @import "specific/admin_dashboards.scss"; @import "specific/api_keys.scss"; @import "specific/artists.scss"; @@ -68,8 +70,6 @@ @import "specific/popular.scss"; @import "specific/post_delete.scss"; @import "specific/post_flags.scss"; -@import "specific/post_index.scss"; -@import "specific/post_mode_menu.scss"; @import "specific/posts.scss"; @import "specific/post_replacements.scss"; @import "specific/post_versions.scss"; diff --git a/app/javascript/src/styles/base/_themable.scss b/app/javascript/src/styles/base/_themable.scss index 2c7dee3aa..4634774d1 100644 --- a/app/javascript/src/styles/base/_themable.scss +++ b/app/javascript/src/styles/base/_themable.scss @@ -19,3 +19,15 @@ @function themed($key) { @return var(--#{$key}); } + +@mixin with-theme($key, $value) { + body[data-th-#{$key}="#{$value}"] { + @content; + } +} + +@mixin with-setting($key, $value) { + body[data-st-#{$key}="#{$value}"] { + @content; + } +} diff --git a/app/javascript/src/styles/specific/post_index.scss b/app/javascript/src/styles/specific/post_index.scss deleted file mode 100644 index 751a66ef5..000000000 --- a/app/javascript/src/styles/specific/post_index.scss +++ /dev/null @@ -1,287 +0,0 @@ -body.c-posts.a-index, body.c-favorites.a-index { - #page { - // Override the theme to instead - // project it upon the content area - background: none; - padding: 0; - } - - // Exhibit A - // Makes the content area take up the - // full height of the page. Yes, really. - #page, #c-posts, #c-favorites, #a-index { - // I hate both this and myself - display: flex; - flex-flow: column; - flex: 1; - } -} - -.post-index { - display: grid; - - grid-template-areas: - "search " - "content" - "sidebar"; - grid-template-columns: 1fr; - grid-template-rows: min-content 1fr min-content; - - flex: 1; // See Exhibit A - - // 1. Searchbox - .search { - grid-area: search; - - padding: 0.5rem 0.25rem; - background-color: #152f56; - background-color: themed("color-foreground"); - box-shadow: inset 0px -0.25rem 0.25rem -0.25rem themed("color-background"); - - h1 { - font-size: $h3-size; - } - - .search-controls { - display: none; - flex-flow: column; - } - } - - // 2. Content - .content { - display: flex; // See Exhibit A - flex-flow: column; - - grid-area: content; - - // Imported from #page - padding: 0.5rem 0.25rem themed("content-padding-bottom"); - background-color: #152f56; - background-color: themed("color-foreground"); - background-image: themed("image-foreground"); - background-position: themed("image-foreground-position"); - background-repeat: themed("image-foreground-repeat"); - - // Quick tag edit - #edit-dialog textarea { - margin-bottom: 0.25rem; - } - - // Actual content area: - // posts and pagination - .post-index-gallery { - display: flex; - flex-flow: column; - gap: 1rem; - - flex: 1; // See Exhibit A - - .posts-container { - flex: 1; // See Exhibit A - grid-auto-rows: min-content; - } - } - } - - // 3. Sidebar - .sidebar { - grid-area: sidebar; - - display: flex; - flex-flow: column; - gap: 1em; - - padding: 0.5rem 0.25rem; - background-color: #152f56; - background-color: themed("color-foreground"); - box-shadow: inset 0px 0.25rem 0.25rem -0.25rem themed("color-background"); - - // Mode selection - #mode-box-mode, #mode-box #set-id { - width: 100%; - - // Match the searchbox - padding: 0.5em; - font-family: Verdana, sans-serif; - font-size: 1.05em; - } - } -} - - -// Desktop -.post-index { - @include window-larger-than(50rem) { - grid-template-areas: - "search content" - "sidebar content"; - grid-template-columns: 14rem 1fr; - grid-template-rows: min-content 1fr; - - .search { - box-shadow: inset -0.25rem 0px 0.25rem -0.25rem themed("color-background"); - margin-top: 0.25rem; - border-top-left-radius: 0.25rem; - padding: 0.5rem; - - .search-controls { - display: flex; - margin-top: 0.5rem; - } - } - - .sidebar { - box-shadow: inset -0.25rem 0px 0.25rem -0.25rem themed("color-background"); - margin-bottom: 0.25rem; - border-bottom-left-radius: 0.25rem; - padding: 0.5rem - } - - .content { - border-radius: 0.25rem; - } - } -} - - -// Fullscreen -body.c-posts.a-index[data-st-fullscreen="true"] { - // Desktop-only, for obvious reasons - @include window-larger-than(50rem) { - .post-index { - grid-template-areas: - "search " - "content"; - grid-template-columns: 1fr; - - .search { - display: flex; - - border-radius: 0.25rem 0.25rem 0 0; - box-shadow: inset 0px -0.25rem 0.25rem -0.25rem themed("color-background"); - - .post-search { - flex: 1; - } - - .search-controls { - display: flex; - justify-content: right; - align-self: end; - margin: 0 0 0 0.5rem; - - .st-button.w100 { - width: unset; - span { display: none; } - } - } - } - .sidebar { display: none; } - .content { - border-radius: 0 0 0.25rem 0.25rem; - } - } - } -} - - -// FEATURES -// Wiki Excerpt -.wiki-excerpt { - display: flex; - flex-flow: column; - position: relative; - - background: themed("color-section"); - border-radius: 0.25rem; - - &.hidden { display: none; } - - // header - h3 { - display: flex; - align-items: center; - - font-size: 1rem; - padding: 0.5rem; - - cursor: pointer; - - & > svg { - transition: transform 0.25s; - - height: 1.5rem; - width: 1.5rem; - } - - .wiki-excerpt-dismiss { margin-left: auto; } - } - - // body - .styled-dtext { - background: linear-gradient(to top, themed("color-section"), themed("color-text")); - -webkit-background-clip: text; - background-clip: text; - color: transparent; - - min-height: 0rem; - max-height: 0rem; - max-width: 50rem; - overflow: hidden; - padding: 0 0.5rem; - - transition: max-height 0.25s; - - // Disable links - pointer-events: none; - cursor: unset; - - a { - color: unset; - text-decoration: underline; - &::after { content: none; } - } - - p:last-child { margin-bottom: 0; } - } - - // wiki link - .wiki-excerpt-readmore { - display: flex; - justify-content: center; - align-items: center; - - position: absolute; - bottom: 0; - left: 0; - right: 0; - - height: 3rem; - max-width: 50rem; - box-sizing: border-box; - - // Makes the button appear in the middle of the animation - transition: visibility 0s 0.125s; - visibility: hidden; - - span { - padding: 0.5rem 1rem; - background: themed("color-section"); - border-radius: 0.25rem; - } - } - - &.open{ - .wiki-excerpt-toggle svg { transform: rotate(90deg); } - .styled-dtext { - max-height: 10rem; - min-height: 2rem; - } - .wiki-excerpt-readmore { visibility: visible; } - } - - &.loading { - h3::after, .styled-dtext { transition: none; } - } -} diff --git a/app/javascript/src/styles/views/posts/_posts.scss b/app/javascript/src/styles/views/posts/_posts.scss new file mode 100644 index 000000000..833ab2586 --- /dev/null +++ b/app/javascript/src/styles/views/posts/_posts.scss @@ -0,0 +1,15 @@ +// Main themes +@import "index/index"; +@include window-larger-than(50rem) { + @import "index/index.desktop"; +} + +// Features +@import "index/partials/mode_menu"; +@import "index/partials/wiki_excerpt"; + +@include window-larger-than(50rem) { + @include with-setting("fullscreen", "true") { + @import "index/partials/fullscreen"; + } +} diff --git a/app/javascript/src/styles/views/posts/index/_index.desktop.scss b/app/javascript/src/styles/views/posts/index/_index.desktop.scss new file mode 100644 index 000000000..ccb999563 --- /dev/null +++ b/app/javascript/src/styles/views/posts/index/_index.desktop.scss @@ -0,0 +1,32 @@ +// Desktop-only +// Rollout at 50rem +.post-index { + grid-template-areas: + "search content" + "sidebar content"; + grid-template-columns: 14rem 1fr; + grid-template-rows: min-content 1fr; + + .search { + box-shadow: inset -0.25rem 0px 0.25rem -0.25rem themed("color-background"); + margin-top: 0.25rem; + border-top-left-radius: 0.25rem; + padding: 0.5rem; + + .search-controls { + display: flex; + margin-top: 0.5rem; + } + } + + .sidebar { + box-shadow: inset -0.25rem 0px 0.25rem -0.25rem themed("color-background"); + margin-bottom: 0.25rem; + border-bottom-left-radius: 0.25rem; + padding: 0.5rem + } + + .content { + border-radius: 0.25rem; + } +} diff --git a/app/javascript/src/styles/views/posts/index/_index.scss b/app/javascript/src/styles/views/posts/index/_index.scss new file mode 100644 index 000000000..0d75e5c0e --- /dev/null +++ b/app/javascript/src/styles/views/posts/index/_index.scss @@ -0,0 +1,112 @@ +body.c-posts.a-index, body.c-favorites.a-index { + #page { + // Override the theme to instead + // project it upon the content area + background: none; + padding: 0; + } + + // Exhibit A + // Makes the content area take up the + // full height of the page. Yes, really. + #page, #c-posts, #c-favorites, #a-index { + // I hate both this and myself + display: flex; + flex-flow: column; + flex: 1; + } +} + + +// Post gallery +.post-index { + display: grid; + + grid-template-areas: + "search " + "content" + "sidebar"; + grid-template-columns: 1fr; + grid-template-rows: min-content 1fr min-content; + + flex: 1; // See Exhibit A + + // 1. Searchbox + .search { + grid-area: search; + + padding: 0.5rem 0.25rem; + background-color: #152f56; + background-color: themed("color-foreground"); + box-shadow: inset 0px -0.25rem 0.25rem -0.25rem themed("color-background"); + + h1 { + font-size: $h3-size; + } + + .search-controls { + display: none; + flex-flow: column; + } + } + + // 2. Content + .content { + display: flex; // See Exhibit A + flex-flow: column; + + grid-area: content; + + // Imported from #page + padding: 0.5rem 0.25rem themed("content-padding-bottom"); + background-color: #152f56; + background-color: themed("color-foreground"); + background-image: themed("image-foreground"); + background-position: themed("image-foreground-position"); + background-repeat: themed("image-foreground-repeat"); + + // Quick tag edit + #edit-dialog textarea { + margin-bottom: 0.25rem; + } + + // Actual content area: + // posts and pagination + .post-index-gallery { + display: flex; + flex-flow: column; + gap: 1rem; + + flex: 1; // See Exhibit A + + .posts-container { + flex: 1; // See Exhibit A + grid-auto-rows: min-content; + } + } + } + + // 3. Sidebar + .sidebar { + grid-area: sidebar; + + display: flex; + flex-flow: column; + gap: 1em; + + padding: 0.5rem 0.25rem; + background-color: #152f56; + background-color: themed("color-foreground"); + box-shadow: inset 0px 0.25rem 0.25rem -0.25rem themed("color-background"); + + // Mode selection + #mode-box-mode, #mode-box #set-id { + width: 100%; + + // Match the searchbox + padding: 0.5em; + font-family: Verdana, sans-serif; + font-size: 1.05em; + } + } +} diff --git a/app/javascript/src/styles/views/posts/index/partials/_fullscreen.scss b/app/javascript/src/styles/views/posts/index/partials/_fullscreen.scss new file mode 100644 index 000000000..6730b7019 --- /dev/null +++ b/app/javascript/src/styles/views/posts/index/partials/_fullscreen.scss @@ -0,0 +1,31 @@ +// Fullscreen mode +// Only relevant on desktop +.post-index { + grid-template-areas: + "search " + "content"; + grid-template-columns: 1fr; + + .search { + display: flex; + border-radius: 0.25rem 0.25rem 0 0; + box-shadow: inset 0px -0.25rem 0.25rem -0.25rem themed("color-background"); + .post-search { + flex: 1; + } + .search-controls { + display: flex; + justify-content: right; + align-self: end; + margin: 0 0 0 0.5rem; + .st-button.w100 { + width: unset; + span { display: none; } + } + } + } + .sidebar { display: none; } + .content { + border-radius: 0 0 0.25rem 0.25rem; + } +} diff --git a/app/javascript/src/styles/specific/post_mode_menu.scss b/app/javascript/src/styles/views/posts/index/partials/_mode_menu.scss similarity index 99% rename from app/javascript/src/styles/specific/post_mode_menu.scss rename to app/javascript/src/styles/views/posts/index/partials/_mode_menu.scss index 467f43954..2bf3f62b8 100644 --- a/app/javascript/src/styles/specific/post_mode_menu.scss +++ b/app/javascript/src/styles/views/posts/index/partials/_mode_menu.scss @@ -32,4 +32,4 @@ $modes: ( input { flex: 1; } button { padding: 0 0.25em; } -} \ No newline at end of file +} diff --git a/app/javascript/src/styles/views/posts/index/partials/_wiki_excerpt.scss b/app/javascript/src/styles/views/posts/index/partials/_wiki_excerpt.scss new file mode 100644 index 000000000..4160e55a6 --- /dev/null +++ b/app/javascript/src/styles/views/posts/index/partials/_wiki_excerpt.scss @@ -0,0 +1,97 @@ +.wiki-excerpt { + display: flex; + flex-flow: column; + position: relative; + + background: themed("color-section"); + border-radius: 0.25rem; + + &.hidden { display: none; } + + // header + h3 { + display: flex; + align-items: center; + + font-size: 1rem; + padding: 0.5rem; + + cursor: pointer; + + & > svg { + transition: transform 0.25s; + + height: 1.5rem; + width: 1.5rem; + } + + .wiki-excerpt-dismiss { margin-left: auto; } + } + + // body + .styled-dtext { + background: linear-gradient(to top, themed("color-section"), themed("color-text")); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + + min-height: 0rem; + max-height: 0rem; + max-width: 50rem; + overflow: hidden; + padding: 0 0.5rem; + + transition: max-height 0.25s; + + // Disable links + pointer-events: none; + cursor: unset; + + a { + color: unset; + text-decoration: underline; + &::after { content: none; } + } + + p:last-child { margin-bottom: 0; } + } + + // wiki link + .wiki-excerpt-readmore { + display: flex; + justify-content: center; + align-items: center; + + position: absolute; + bottom: 0; + left: 0; + right: 0; + + height: 3rem; + max-width: 50rem; + box-sizing: border-box; + + // Makes the button appear in the middle of the animation + transition: visibility 0s 0.125s; + visibility: hidden; + + span { + padding: 0.5rem 1rem; + background: themed("color-section"); + border-radius: 0.25rem; + } + } + + &.open{ + .wiki-excerpt-toggle svg { transform: rotate(90deg); } + .styled-dtext { + max-height: 10rem; + min-height: 2rem; + } + .wiki-excerpt-readmore { visibility: visible; } + } + + &.loading { + h3::after, .styled-dtext { transition: none; } + } +} From 6c95fdd8e376e0135ce38cb5e47832df9edcb1f1 Mon Sep 17 00:00:00 2001 From: Cinder Date: Tue, 11 Feb 2025 22:25:35 -0800 Subject: [PATCH 03/26] [Takedowns] Fix a couple of buttons linebreaking (#900) --- app/javascript/src/javascripts/takedowns.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/src/javascripts/takedowns.js b/app/javascript/src/javascripts/takedowns.js index 54335d012..437c5a19c 100644 --- a/app/javascript/src/javascripts/takedowns.js +++ b/app/javascript/src/javascripts/takedowns.js @@ -39,8 +39,8 @@ Takedown.add_posts_by_tags_preview = function (id) { $("#takedown-add-posts-tags-warning").html(preview_text).show(); $("#takedown-add-posts-tags").prop("disabled", true); $("#takedown-add-posts-tags-preview").hide(); - $("#takedown-add-posts-tags-confirm").show(); - $("#takedown-add-posts-tags-cancel").show(); + $("#takedown-add-posts-tags-confirm").css("display", "inline-block"); + $("#takedown-add-posts-tags-cancel").css("display", "inline-block"); }).fail(function (data) { Utility.error(data.responseText); }); From ac38fd11e74beb9bde7b0ef0d0067c18fea1203d Mon Sep 17 00:00:00 2001 From: Cinder Date: Tue, 11 Feb 2025 22:25:46 -0800 Subject: [PATCH 04/26] [Posts] Add an approximate result counter (#903) --- app/helpers/pagination_helper.rb | 23 +++++++++++++++++++ .../src/styles/views/posts/_posts.scss | 1 + .../views/posts/index/partials/_stats.scss | 13 +++++++++++ app/views/posts/index.html.erb | 1 + .../posts/partials/index/_stats.html.erb | 3 +++ 5 files changed, 41 insertions(+) create mode 100644 app/javascript/src/styles/views/posts/index/partials/_stats.scss create mode 100644 app/views/posts/partials/index/_stats.html.erb diff --git a/app/helpers/pagination_helper.rb b/app/helpers/pagination_helper.rb index d293d422a..919014c6c 100644 --- a/app/helpers/pagination_helper.rb +++ b/app/helpers/pagination_helper.rb @@ -1,6 +1,29 @@ # frozen_string_literal: true module PaginationHelper + def approximate_count(records) + return "" if records.pagination_mode != :numbered + + if records.total_pages > records.max_numbered_pages + pages = records.max_numbered_pages + schar = "over " + count = pages * records.records_per_page + title = "Over #{count} results found.\nActual result count may be much larger." + else + pages = records.total_pages + schar = "~" + count = pages * records.records_per_page + title = "Approximately #{count} results found.\nActual result count may differ." + end + + tag.span(class: "approximate-count", title: title, data: { count: count, pages: pages, per: records.max_numbered_pages }) do + concat schar + concat number_to_human(count, precision: 2, format: "%n%u", units: { thousand: "k" }) + concat " " + concat "result".pluralize(count) + end + end + def sequential_paginator(records) tag.nav(class: "pagination sequential", aria: { label: "Pagination" }) do return "" if records.try(:none?) diff --git a/app/javascript/src/styles/views/posts/_posts.scss b/app/javascript/src/styles/views/posts/_posts.scss index 833ab2586..0508e64b5 100644 --- a/app/javascript/src/styles/views/posts/_posts.scss +++ b/app/javascript/src/styles/views/posts/_posts.scss @@ -7,6 +7,7 @@ // Features @import "index/partials/mode_menu"; @import "index/partials/wiki_excerpt"; +@import "index/partials/stats"; @include window-larger-than(50rem) { @include with-setting("fullscreen", "true") { diff --git a/app/javascript/src/styles/views/posts/index/partials/_stats.scss b/app/javascript/src/styles/views/posts/index/partials/_stats.scss new file mode 100644 index 000000000..5f7b55196 --- /dev/null +++ b/app/javascript/src/styles/views/posts/index/partials/_stats.scss @@ -0,0 +1,13 @@ +.posts-index-stats { + display: flex; + justify-content: end; + + margin: -0.5rem 0rem 0.25rem; + + font-size: 0.75rem; + line-height: 0.75rem; + color: #fffa; + font-family: monospace; + + & > span { cursor: help; } +} diff --git a/app/views/posts/index.html.erb b/app/views/posts/index.html.erb index 1d452ab0d..f85cc74a5 100644 --- a/app/views/posts/index.html.erb +++ b/app/views/posts/index.html.erb @@ -24,6 +24,7 @@
<%= render "ads/leaderboard", tag_string: @post_set.ad_tag_string %> <%= render "posts/partials/index/edit" %> + <%= render "posts/partials/index/stats", :post_set => @post_set %> <%= render "posts/partials/index/posts", :post_set => @post_set %>
diff --git a/app/views/posts/partials/index/_stats.html.erb b/app/views/posts/partials/index/_stats.html.erb new file mode 100644 index 000000000..d788206da --- /dev/null +++ b/app/views/posts/partials/index/_stats.html.erb @@ -0,0 +1,3 @@ +
+ <%= approximate_count(post_set.posts) %> +
From 54c490b57319f95468a7d442afe4f4c157c6416c Mon Sep 17 00:00:00 2001 From: Cinder Date: Tue, 11 Feb 2025 22:25:58 -0800 Subject: [PATCH 05/26] [Pagination] Fix the keyboard shortcuts (#904) --- app/helpers/pagination_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/pagination_helper.rb b/app/helpers/pagination_helper.rb index 919014c6c..8b55b9e01 100644 --- a/app/helpers/pagination_helper.rb +++ b/app/helpers/pagination_helper.rb @@ -87,12 +87,12 @@ module PaginationHelper html = "".html_safe if disabled - html << tag.span(class: "next", id: "paginator-next", data: { shortcut: "a left" }) do + html << tag.span(class: "next", id: "paginator-next", data: { shortcut: "d right" }) do concat tag.span("Next") concat svg_icon(:chevron_right) end else - html << link_to(link, class: "next", id: "paginator-prev", rel: "next", data: { shortcut: "a left" }) do + html << link_to(link, class: "next", id: "paginator-next", rel: "next", data: { shortcut: "d right" }) do concat tag.span("Next") concat svg_icon(:chevron_right) end From bac331d1087124c6b7e23ffa7f8f9f56618d52ef Mon Sep 17 00:00:00 2001 From: Cinder Date: Tue, 11 Feb 2025 22:26:09 -0800 Subject: [PATCH 06/26] [Pagination] Restore sequential pagination labels (#905) --- app/javascript/src/styles/common/paginator.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/src/styles/common/paginator.scss b/app/javascript/src/styles/common/paginator.scss index f00e19abe..84c7084e3 100644 --- a/app/javascript/src/styles/common/paginator.scss +++ b/app/javascript/src/styles/common/paginator.scss @@ -102,7 +102,7 @@ nav.pagination { // Sequential paginator -.paginator.sequential { +nav.pagination.sequential { gap: 5rem; a span { display: block; } } From a730b46c6c296be57ed8578c7ca8a35e56f156d7 Mon Sep 17 00:00:00 2001 From: Cinder Date: Tue, 11 Feb 2025 22:26:29 -0800 Subject: [PATCH 07/26] [Posts] Replace the old Google RIS link with Lens (#906) --- app/views/posts/show.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/posts/show.html.erb b/app/views/posts/show.html.erb index 5a191268a..0233be6bf 100644 --- a/app/views/posts/show.html.erb +++ b/app/views/posts/show.html.erb @@ -40,7 +40,7 @@
  • <%= link_to "Visually similar on E6", iqdb_queries_path(search: { post_id: @post.id }), rel: "nofollow" %>
  • <% end %> <% if @post.visible? && @post.is_image? %> -
  • Reverse Google Search
  • +
  • Reverse Google Search
  • Reverse SauceNAO Search
  • Reverse Derpibooru Search
  • Reverse Kheina Search
  • From a98b97aaf6f5312ed228c6e7d056c0b46ba4dde8 Mon Sep 17 00:00:00 2001 From: Cinder Date: Tue, 11 Feb 2025 22:26:41 -0800 Subject: [PATCH 08/26] [Posts] Fix a bug caused by quotes in a wiki excerpt (#907) --- .../src/styles/views/posts/index/partials/_wiki_excerpt.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/javascript/src/styles/views/posts/index/partials/_wiki_excerpt.scss b/app/javascript/src/styles/views/posts/index/partials/_wiki_excerpt.scss index 4160e55a6..4beb26345 100644 --- a/app/javascript/src/styles/views/posts/index/partials/_wiki_excerpt.scss +++ b/app/javascript/src/styles/views/posts/index/partials/_wiki_excerpt.scss @@ -53,6 +53,10 @@ &::after { content: none; } } + // Quote visual bug + blockquote { background: unset; } + + // Remove offset caused by paragraphs p:last-child { margin-bottom: 0; } } From 436eb260e8e29537a0fbc743281d62e90f45e56e Mon Sep 17 00:00:00 2001 From: Cinder Date: Tue, 11 Feb 2025 22:26:53 -0800 Subject: [PATCH 09/26] [Footer] Adjust logo border (#908) --- app/javascript/src/styles/common/_footer.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/src/styles/common/_footer.scss b/app/javascript/src/styles/common/_footer.scss index 31c2c6935..53cf3473e 100644 --- a/app/javascript/src/styles/common/_footer.scss +++ b/app/javascript/src/styles/common/_footer.scss @@ -29,7 +29,7 @@ footer.footer-wrapper { margin-right: -3.25rem; background: themed("color-background") themed("image-background"); - border-radius: 50%; + border-radius: 25%; padding: 0.5rem; } } From aa606b7125f91fa2960e1094ae55199af3628e7f Mon Sep 17 00:00:00 2001 From: Cinder Date: Tue, 11 Feb 2025 22:27:03 -0800 Subject: [PATCH 10/26] [Posts] Add redirect support to wiki excerpts (#909) --- app/controllers/posts_controller.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 843ee2dec..0952c00c1 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -19,6 +19,12 @@ class PostsController < ApplicationController @query = tag_query.nil? ? [] : tag_query.strip.split(/ /, 2).compact_blank if @query.length == 1 @wiki_page = WikiPage.titled(@query[0]) + + # redirect? + if @wiki_page.present? && @wiki_page.parent.present? + @wiki_page = WikiPage.titled(@wiki_page.parent) + end + @wiki_text = @wiki_page.present? ? @wiki_page.body : "" if @wiki_text.present? @wiki_text = @wiki_text From 9135ccd4b9f6d359c1ba91e8184900a9fce5650a Mon Sep 17 00:00:00 2001 From: Cinder Date: Tue, 11 Feb 2025 22:27:16 -0800 Subject: [PATCH 11/26] [Posts] Replace the search submit icon with a lucide one (#910) --- app/helpers/icon_helper.rb | 1 + .../src/styles/common/post_search.scss | 22 +++++++++++++------ app/javascript/src/styles/specific/home.scss | 13 +++++++---- .../posts/partials/common/_search.html.erb | 2 +- app/views/static/home.html.erb | 2 +- 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/app/helpers/icon_helper.rb b/app/helpers/icon_helper.rb index 077752253..34bc65282 100644 --- a/app/helpers/icon_helper.rb +++ b/app/helpers/icon_helper.rb @@ -28,6 +28,7 @@ module IconHelper ellipsis: %(), # Posts + search: %(), fullscreen: %(), }.freeze diff --git a/app/javascript/src/styles/common/post_search.scss b/app/javascript/src/styles/common/post_search.scss index 68ce953d4..ede222255 100644 --- a/app/javascript/src/styles/common/post_search.scss +++ b/app/javascript/src/styles/common/post_search.scss @@ -30,7 +30,7 @@ // Disable manual resizing resize: none; - border-radius: 3px 0 0 3px; + border-radius: 0.25rem 0 0 0.25rem; box-sizing: border-box; flex: 1; @@ -43,19 +43,27 @@ font-size: unset; max-width: unset; - font-size: 1rem; - line-height: 1rem; + display: flex; + align-items: center; padding: 0.5rem; - border-radius: 0 3px 3px 0; + border-radius: 0 0.25rem 0.25rem 0; background: white; + color: black; - span { display: none; } + svg { + height: 1.5rem; + width: 1.5rem; + margin: -0.25rem 0 -0.25rem; + } } @include window-larger-than(800px) { - textarea, button[type="submit"] { - font-size: 0.85rem; + textarea { font-size: 0.85rem; } + button[type="submit"] svg { + height: 1.25rem; + width: 1.25rem; + margin: -0.125rem 0 -0.125rem; } } } diff --git a/app/javascript/src/styles/specific/home.scss b/app/javascript/src/styles/specific/home.scss index 94dd52ea2..9b6a95b85 100644 --- a/app/javascript/src/styles/specific/home.scss +++ b/app/javascript/src/styles/specific/home.scss @@ -39,7 +39,7 @@ body.c-static.a-home { input[type="text"] { flex: 1; border: 0; - border-radius: 3px 0 0 3px; + border-radius: 0.25rem 0 0 0.25rem; padding: 0.5rem; font-size: 1rem; @@ -53,9 +53,14 @@ body.c-static.a-home { button[type="submit"] { background: white; - border-radius: 0 3px 3px 0; - padding: 0 0.5em; - font-size: 1rem; + border-radius: 0 0.25rem 0.25rem 0; + padding: 0 0.5rem; + + svg { + height: 1.25rem; + width: 1.25rem; + margin: -0.125rem 0 -0.125rem; + } } } diff --git a/app/views/posts/partials/common/_search.html.erb b/app/views/posts/partials/common/_search.html.erb index 051a9a259..3c4266be7 100644 --- a/app/views/posts/partials/common/_search.html.erb +++ b/app/views/posts/partials/common/_search.html.erb @@ -10,6 +10,6 @@ <% end %> <%= text_area_tag("tags", tags, placeholder: "Search posts by tag", rows: 1, data: { shortcut: "q", autocomplete: "tag-query" }) %> - <%= tag.button(tag.i(class: "fa-solid fa-magnifying-glass"), type: "submit", title: "Search") %> + <%= tag.button(svg_icon(:search), type: "submit", title: "Search") %> <% end %> diff --git a/app/views/static/home.html.erb b/app/views/static/home.html.erb index 244aed43f..41f8e4e0f 100644 --- a/app/views/static/home.html.erb +++ b/app/views/static/home.html.erb @@ -9,7 +9,7 @@ From af5ef523166592c68189202679025b57aaf24b3b Mon Sep 17 00:00:00 2001 From: Cinder Date: Tue, 11 Feb 2025 22:27:26 -0800 Subject: [PATCH 12/26] [Posts] Add an option for a sticky searchbar (#911) --- app/helpers/icon_helper.rb | 1 + app/javascript/src/javascripts/post_search.js | 7 +++++ .../src/javascripts/utility/storage.js | 3 ++ app/javascript/src/styles/common/tables.scss | 2 -- .../src/styles/views/posts/_posts.scss | 3 ++ .../views/posts/index/_index.desktop.scss | 30 +++++++++++++++++-- .../src/styles/views/posts/index/_index.scss | 5 +--- .../posts/index/partials/_fullscreen.scss | 24 +++++++-------- .../views/posts/index/partials/_sticky.scss | 16 ++++++++++ app/views/layouts/_theme_include.html.erb | 1 + .../posts/partials/index/_controls.html.erb | 6 ++-- 11 files changed, 76 insertions(+), 22 deletions(-) create mode 100644 app/javascript/src/styles/views/posts/index/partials/_sticky.scss diff --git a/app/helpers/icon_helper.rb b/app/helpers/icon_helper.rb index 34bc65282..4a628f9ad 100644 --- a/app/helpers/icon_helper.rb +++ b/app/helpers/icon_helper.rb @@ -30,6 +30,7 @@ module IconHelper # Posts search: %(), fullscreen: %(), + anchor: %(), }.freeze def svg_icon(name, *args) diff --git a/app/javascript/src/javascripts/post_search.js b/app/javascript/src/javascripts/post_search.js index b5b3e6a17..55421cfec 100644 --- a/app/javascript/src/javascripts/post_search.js +++ b/app/javascript/src/javascripts/post_search.js @@ -67,6 +67,13 @@ PostSearch.initialize_controls = function () { $("body").attr("data-st-fullscreen", fullscreen); LStorage.Posts.Fullscreen = fullscreen; }); + + let stickySearch = LStorage.Posts.StickySearch; + $("#search-sticky").on("click", () => { + stickySearch = !stickySearch; + $("body").attr("data-st-stickysearch", stickySearch); + LStorage.Posts.StickySearch = stickySearch; + }); }; $(() => { diff --git a/app/javascript/src/javascripts/utility/storage.js b/app/javascript/src/javascripts/utility/storage.js index 62847cc6a..55c65e4ab 100644 --- a/app/javascript/src/javascripts/utility/storage.js +++ b/app/javascript/src/javascripts/utility/storage.js @@ -91,6 +91,9 @@ LStorage.Posts = { /** @returns {boolean} True if the search should be displayed in fullscreen */ Fullscreen: ["e6.posts.fusk", false], + + /** @returns {boolean} True if the search should be displayed in fullscreen */ + StickySearch: ["e6.posts.stickysearch", false], }; StorageUtils.bootstrapMany(LStorage.Posts); diff --git a/app/javascript/src/styles/common/tables.scss b/app/javascript/src/styles/common/tables.scss index e08ec9a00..6092e32fb 100644 --- a/app/javascript/src/styles/common/tables.scss +++ b/app/javascript/src/styles/common/tables.scss @@ -92,8 +92,6 @@ table.search { } table.aligned-vertical { - @extend .search; - tr { height: 1.75em; } diff --git a/app/javascript/src/styles/views/posts/_posts.scss b/app/javascript/src/styles/views/posts/_posts.scss index 0508e64b5..56c38dadb 100644 --- a/app/javascript/src/styles/views/posts/_posts.scss +++ b/app/javascript/src/styles/views/posts/_posts.scss @@ -13,4 +13,7 @@ @include with-setting("fullscreen", "true") { @import "index/partials/fullscreen"; } + @include with-setting("stickysearch", "true") { + @import "index/partials/sticky"; + } } diff --git a/app/javascript/src/styles/views/posts/index/_index.desktop.scss b/app/javascript/src/styles/views/posts/index/_index.desktop.scss index ccb999563..60f429ece 100644 --- a/app/javascript/src/styles/views/posts/index/_index.desktop.scss +++ b/app/javascript/src/styles/views/posts/index/_index.desktop.scss @@ -13,9 +13,35 @@ border-top-left-radius: 0.25rem; padding: 0.5rem; + // Align the controls properly + position: relative; + .search-controls { display: flex; - margin-top: 0.5rem; + flex-flow: row; + justify-content: right; + + position: absolute; + bottom: -1.633rem; + right: 0.25rem; + + padding: 0.25rem; + gap: 0.5rem; + + background: themed("color-foreground"); + border-radius: 0 0 0.25rem 0.25rem; + + button { + height: 1.5rem; + padding: 0; + + svg { + height: 1.25rem; + width: 1.25rem; + padding: 0.25rem; + margin: -0.125rem 0; + } + } } } @@ -23,7 +49,7 @@ box-shadow: inset -0.25rem 0px 0.25rem -0.25rem themed("color-background"); margin-bottom: 0.25rem; border-bottom-left-radius: 0.25rem; - padding: 0.5rem + padding: 0.5rem; } .content { diff --git a/app/javascript/src/styles/views/posts/index/_index.scss b/app/javascript/src/styles/views/posts/index/_index.scss index 0d75e5c0e..8c7da8df3 100644 --- a/app/javascript/src/styles/views/posts/index/_index.scss +++ b/app/javascript/src/styles/views/posts/index/_index.scss @@ -44,10 +44,7 @@ body.c-posts.a-index, body.c-favorites.a-index { font-size: $h3-size; } - .search-controls { - display: none; - flex-flow: column; - } + .search-controls { display: none; } } // 2. Content diff --git a/app/javascript/src/styles/views/posts/index/partials/_fullscreen.scss b/app/javascript/src/styles/views/posts/index/partials/_fullscreen.scss index 6730b7019..a8e68dd72 100644 --- a/app/javascript/src/styles/views/posts/index/partials/_fullscreen.scss +++ b/app/javascript/src/styles/views/posts/index/partials/_fullscreen.scss @@ -10,18 +10,18 @@ display: flex; border-radius: 0.25rem 0.25rem 0 0; box-shadow: inset 0px -0.25rem 0.25rem -0.25rem themed("color-background"); - .post-search { - flex: 1; - } - .search-controls { - display: flex; - justify-content: right; - align-self: end; - margin: 0 0 0 0.5rem; - .st-button.w100 { - width: unset; - span { display: none; } - } + + margin: 0.25rem 0 0; + padding: 0.5rem; + + flex-wrap: wrap; + justify-content: right; + + .post-search { width: 100%; } + + #search-fullscreen { + background: themed("color-button-active"); + color: black; } } .sidebar { display: none; } diff --git a/app/javascript/src/styles/views/posts/index/partials/_sticky.scss b/app/javascript/src/styles/views/posts/index/partials/_sticky.scss new file mode 100644 index 000000000..35674c99d --- /dev/null +++ b/app/javascript/src/styles/views/posts/index/partials/_sticky.scss @@ -0,0 +1,16 @@ +.post-index .search { + position: sticky; + top: 0; + + // on top of thumbnail labels + z-index: 11; + + #search-sticky { + background: themed("color-button-active"); + color: black; + } +} + +&[data-th-sheader="true"] { + .post-index .search { top: 3.75rem; } +} diff --git a/app/views/layouts/_theme_include.html.erb b/app/views/layouts/_theme_include.html.erb index bf5b3eb85..d6bcd97fa 100644 --- a/app/views/layouts/_theme_include.html.erb +++ b/app/views/layouts/_theme_include.html.erb @@ -12,6 +12,7 @@ // Settings "st-fullscreen": localStorage.getItem("e6.posts.fusk") || false, + "st-stickysearch": localStorage.getItem("e6.posts.stickysearch") || false, }; var b = document.body; diff --git a/app/views/posts/partials/index/_controls.html.erb b/app/views/posts/partials/index/_controls.html.erb index 31667a67b..34d22d434 100644 --- a/app/views/posts/partials/index/_controls.html.erb +++ b/app/views/posts/partials/index/_controls.html.erb @@ -1,6 +1,8 @@
    - +
    From 2c66906a8aaa70c00787657a5a6462be6c74d3cb Mon Sep 17 00:00:00 2001 From: Cinder Date: Tue, 11 Feb 2025 22:27:38 -0800 Subject: [PATCH 13/26] [UI] Fix the main menu layering over the post notes (#912) --- app/javascript/src/styles/common/navigation.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/src/styles/common/navigation.scss b/app/javascript/src/styles/common/navigation.scss index 160d6001a..ea3bdc8d1 100644 --- a/app/javascript/src/styles/common/navigation.scss +++ b/app/javascript/src/styles/common/navigation.scss @@ -5,7 +5,7 @@ nav.navigation { grid-template-rows: min-content min-content min-content min-content auto; width: 100%; // otherwise narrow when fixed - z-index: 20; // otherwise post labels layered above + z-index: 200; // above post labels and notes position: relative; From 3cb527d0b1017e39ef7e87c47bea2cb1437151cb Mon Sep 17 00:00:00 2001 From: Cinder Date: Tue, 11 Feb 2025 22:42:10 -0800 Subject: [PATCH 14/26] [Posts] Increase the sidebar font size on mobile. (#913) --- .../src/styles/views/posts/index/_index.desktop.scss | 2 ++ app/javascript/src/styles/views/posts/index/_index.scss | 3 +++ 2 files changed, 5 insertions(+) diff --git a/app/javascript/src/styles/views/posts/index/_index.desktop.scss b/app/javascript/src/styles/views/posts/index/_index.desktop.scss index 60f429ece..20f1a0bdd 100644 --- a/app/javascript/src/styles/views/posts/index/_index.desktop.scss +++ b/app/javascript/src/styles/views/posts/index/_index.desktop.scss @@ -50,6 +50,8 @@ margin-bottom: 0.25rem; border-bottom-left-radius: 0.25rem; padding: 0.5rem; + + font-size: 100%; } .content { diff --git a/app/javascript/src/styles/views/posts/index/_index.scss b/app/javascript/src/styles/views/posts/index/_index.scss index 8c7da8df3..9be82a05f 100644 --- a/app/javascript/src/styles/views/posts/index/_index.scss +++ b/app/javascript/src/styles/views/posts/index/_index.scss @@ -96,6 +96,9 @@ body.c-posts.a-index, body.c-favorites.a-index { background-color: themed("color-foreground"); box-shadow: inset 0px 0.25rem 0.25rem -0.25rem themed("color-background"); + // By popular demand + font-size: 150%; + // Mode selection #mode-box-mode, #mode-box #set-id { width: 100%; From 50cbc498e7f178d3c16753dfc62bf11fe40aab2f Mon Sep 17 00:00:00 2001 From: Cinder Date: Wed, 12 Feb 2025 07:01:41 -0800 Subject: [PATCH 15/26] [Themes] Add a toggle for WikiExcerpt in theme settings (#914) --- app/javascript/src/javascripts/post_search.js | 2 +- app/javascript/src/javascripts/themes.js | 48 ++++++------- .../src/javascripts/utility/storage.js | 2 +- .../src/styles/views/posts/_posts.scss | 2 +- app/views/layouts/_theme_include.html.erb | 2 +- app/views/static/theme.html.erb | 67 ++++++++++--------- 6 files changed, 65 insertions(+), 58 deletions(-) diff --git a/app/javascript/src/javascripts/post_search.js b/app/javascript/src/javascripts/post_search.js index 55421cfec..045d641d8 100644 --- a/app/javascript/src/javascripts/post_search.js +++ b/app/javascript/src/javascripts/post_search.js @@ -71,7 +71,7 @@ PostSearch.initialize_controls = function () { let stickySearch = LStorage.Posts.StickySearch; $("#search-sticky").on("click", () => { stickySearch = !stickySearch; - $("body").attr("data-st-stickysearch", stickySearch); + $("body").attr("data-st-ssearch", stickySearch); LStorage.Posts.StickySearch = stickySearch; }); }; diff --git a/app/javascript/src/javascripts/themes.js b/app/javascript/src/javascripts/themes.js index a329e109e..ccff3abeb 100644 --- a/app/javascript/src/javascripts/themes.js +++ b/app/javascript/src/javascripts/themes.js @@ -3,17 +3,23 @@ import LStorage from "./utility/storage"; const Theme = {}; -Theme.Values = ["Main", "Extra", "StickyHeader", "ForumNotif", "Palette", "Navbar", "Gestures"]; +Theme.Values = { + "Theme": ["Main", "Extra", "Palette", "StickyHeader", "Navbar", "Gestures", "ForumNotif"], + "Posts": ["WikiExcerpt", "StickySearch"], +}; -for (const one of Theme.Values) { - Object.defineProperty(Theme, one, { - get () { return LStorage.Theme[one]; }, - set (value) { - // No value checking, we die like men - LStorage.Theme[one] = value; - $("body").attr("data-th-" + one.toLowerCase(), value); - }, - }); +for (const [label, settings] of Object.entries(Theme.Values)) { + for (const one of settings) { + Object.defineProperty(Theme, one, { + get () { return LStorage.Theme[one]; }, + set (value) { + // This has the unintended side effect of setting + // attribute values that don't exist on the body. + LStorage[label][one] = value; + $("body").attr("data-th-" + one.toLowerCase(), value); + }, + }); + } } Theme.initialize_selector = function () { @@ -24,13 +30,15 @@ Theme.initialize_selector = function () { return false; } - for (const one of Theme.Values) { - $("#theme_" + one.toLowerCase()) - .val(LStorage.Theme[one] + "") - .on("change", (event) => { - const data = event.target.value; - Theme[one] = data; - }); + for (const [label, settings] of Object.entries(Theme.Values)) { + for (const one of settings) + $(`#${label}_${one}`) + .val(LStorage[label][one] + "") + .on("change", (event) => { + const data = event.target.value; + console.log("change", one, data); + Theme[one] = data; + }); } }; @@ -42,12 +50,6 @@ Theme.initialize_buttons = function () { LStorage.Site.Mascot = 0; $("#mascot-value").text(LStorage.Site.Mascot); }); - - $("#wiki-excerpt-value").text(LStorage.Posts.WikiExcerpt); - $("#wiki-excerpt-reset").on("click", () => { - LStorage.Posts.WikiExcerpt = 1; - $("#wiki-excerpt-value").text(LStorage.Posts.WikiExcerpt); - }); }; $(() => { diff --git a/app/javascript/src/javascripts/utility/storage.js b/app/javascript/src/javascripts/utility/storage.js index 55c65e4ab..d02ef5170 100644 --- a/app/javascript/src/javascripts/utility/storage.js +++ b/app/javascript/src/javascripts/utility/storage.js @@ -93,7 +93,7 @@ LStorage.Posts = { Fullscreen: ["e6.posts.fusk", false], /** @returns {boolean} True if the search should be displayed in fullscreen */ - StickySearch: ["e6.posts.stickysearch", false], + StickySearch: ["e6.posts.ssearch", false], }; StorageUtils.bootstrapMany(LStorage.Posts); diff --git a/app/javascript/src/styles/views/posts/_posts.scss b/app/javascript/src/styles/views/posts/_posts.scss index 56c38dadb..f6dcef481 100644 --- a/app/javascript/src/styles/views/posts/_posts.scss +++ b/app/javascript/src/styles/views/posts/_posts.scss @@ -13,7 +13,7 @@ @include with-setting("fullscreen", "true") { @import "index/partials/fullscreen"; } - @include with-setting("stickysearch", "true") { + @include with-setting("ssearch", "true") { @import "index/partials/sticky"; } } diff --git a/app/views/layouts/_theme_include.html.erb b/app/views/layouts/_theme_include.html.erb index d6bcd97fa..4add60a81 100644 --- a/app/views/layouts/_theme_include.html.erb +++ b/app/views/layouts/_theme_include.html.erb @@ -12,7 +12,7 @@ // Settings "st-fullscreen": localStorage.getItem("e6.posts.fusk") || false, - "st-stickysearch": localStorage.getItem("e6.posts.stickysearch") || false, + "st-ssearch": localStorage.getItem("e6.posts.ssearch") || false, }; var b = document.body; diff --git a/app/views/static/theme.html.erb b/app/views/static/theme.html.erb index 8344f356d..fd9087cf9 100644 --- a/app/views/static/theme.html.erb +++ b/app/views/static/theme.html.erb @@ -16,8 +16,8 @@ This means that they will not persist across multiple devices, or in incognito mode.

    - - @@ -25,8 +25,8 @@ - - @@ -37,42 +37,55 @@ - - - - - -

    Accessibility

    - - +

    Features

    + + + + + +

    Navigation

    - - + + + + + + + + + - -
    Swipe left for next page/image. Swipe right for previous page/image.
    - @@ -85,14 +98,6 @@ <%= svg_icon(:reset) %> Reset - - - - - <% content_for(:page_title) do %> From 911d989b95bf62dae97d6d42ed566ec4a82e319f Mon Sep 17 00:00:00 2001 From: Cinder Date: Wed, 12 Feb 2025 10:54:01 -0800 Subject: [PATCH 16/26] [Posts] Fix a layering bug in fullscreen mode (#915) --- .../src/styles/views/posts/index/partials/_fullscreen.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/javascript/src/styles/views/posts/index/partials/_fullscreen.scss b/app/javascript/src/styles/views/posts/index/partials/_fullscreen.scss index a8e68dd72..33dbd7926 100644 --- a/app/javascript/src/styles/views/posts/index/partials/_fullscreen.scss +++ b/app/javascript/src/styles/views/posts/index/partials/_fullscreen.scss @@ -16,6 +16,7 @@ flex-wrap: wrap; justify-content: right; + z-index: 11; // above posts and labels .post-search { width: 100%; } @@ -27,5 +28,6 @@ .sidebar { display: none; } .content { border-radius: 0 0 0.25rem 0.25rem; + .posts-index-stats { margin-right: 5rem; } } } From 8aa86a93506ab0bea79b336940f1c6ef2dabdb16 Mon Sep 17 00:00:00 2001 From: Cinder Date: Wed, 12 Feb 2025 10:54:18 -0800 Subject: [PATCH 17/26] [UI] Prevent the header avatar from being reloaded any time deferred posts are fetched (#916) --- app/helpers/application_helper.rb | 2 +- app/javascript/src/javascripts/thumbnails.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index d8cc321d8..2f6b5a361 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -196,7 +196,7 @@ module ApplicationHelper klass = options.delete(:class) named = options.delete(:named) - tag.a href: user_path(user), class: "simple-avatar #{klass}", data: { id: post_id, name: user.name } do + tag.a href: user_path(user), class: "simple-avatar placeholder #{klass}", data: { id: post_id, name: user.name } do tag.span(class: "simple-avatar-button") do concat tag.span(user.pretty_name, class: "simple-avatar-name") if named concat tag.span(class: "simple-avatar-image", data: { name: user.name[0].capitalize }) diff --git a/app/javascript/src/javascripts/thumbnails.js b/app/javascript/src/javascripts/thumbnails.js index e1ffeed0b..cfad7cadb 100644 --- a/app/javascript/src/javascripts/thumbnails.js +++ b/app/javascript/src/javascripts/thumbnails.js @@ -9,8 +9,9 @@ Thumbnails.initialize = function () { const replacedPosts = []; // Avatar special case - for (const post of $(".simple-avatar")) { + for (const post of $(".simple-avatar.placeholder")) { const $post = $(post); + $post.removeClass("placeholder"); const postID = $post.data("id"); if (!postID) continue; From 037cc8565524e8a4b5c5776930bd9ba49304621a Mon Sep 17 00:00:00 2001 From: Cinder Date: Wed, 12 Feb 2025 10:55:19 -0800 Subject: [PATCH 18/26] [UI] Prevent site notices from being hidden behind the header (#917) --- app/javascript/src/styles/common/notices.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/src/styles/common/notices.scss b/app/javascript/src/styles/common/notices.scss index 37ebfb812..f94727d39 100644 --- a/app/javascript/src/styles/common/notices.scss +++ b/app/javascript/src/styles/common/notices.scss @@ -16,7 +16,7 @@ div#notice { top: 1rem; left: 25%; width: 50%; - z-index: 100; + z-index: 500; color: themed("color-text"); background-color: themed("color-success"); From 4af95e56ccd739d23900ef3dfb689df6f1bc8eba Mon Sep 17 00:00:00 2001 From: Cinder Date: Fri, 14 Feb 2025 19:03:40 -0800 Subject: [PATCH 19/26] [Posts] Tweak the sidebar styles (#921) --- .../styles/views/posts/index/_index.desktop.scss | 15 ++++++--------- .../src/styles/views/posts/index/_index.scss | 6 +----- .../views/posts/index/partials/_fullscreen.scss | 2 ++ .../views/posts/index/partials/_sticky.scss | 3 +++ 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/app/javascript/src/styles/views/posts/index/_index.desktop.scss b/app/javascript/src/styles/views/posts/index/_index.desktop.scss index 20f1a0bdd..115ae2aef 100644 --- a/app/javascript/src/styles/views/posts/index/_index.desktop.scss +++ b/app/javascript/src/styles/views/posts/index/_index.desktop.scss @@ -4,14 +4,12 @@ grid-template-areas: "search content" "sidebar content"; - grid-template-columns: 14rem 1fr; + grid-template-columns: 15rem 1fr; grid-template-rows: min-content 1fr; .search { box-shadow: inset -0.25rem 0px 0.25rem -0.25rem themed("color-background"); - margin-top: 0.25rem; - border-top-left-radius: 0.25rem; - padding: 0.5rem; + padding: 0.5rem 0.75rem 0.5rem 0.5rem; // Align the controls properly position: relative; @@ -23,7 +21,7 @@ position: absolute; bottom: -1.633rem; - right: 0.25rem; + right: 0.5rem; padding: 0.25rem; gap: 0.5rem; @@ -47,14 +45,13 @@ .sidebar { box-shadow: inset -0.25rem 0px 0.25rem -0.25rem themed("color-background"); - margin-bottom: 0.25rem; - border-bottom-left-radius: 0.25rem; - padding: 0.5rem; + padding: 0.5rem 0.75rem 0.5rem 0.5rem; font-size: 100%; } .content { - border-radius: 0.25rem; + border-radius: 0 0.25rem 0.25rem 0; + padding: 0.5rem 0.75rem themed("content-padding-bottom"); } } diff --git a/app/javascript/src/styles/views/posts/index/_index.scss b/app/javascript/src/styles/views/posts/index/_index.scss index 9be82a05f..bfd376b51 100644 --- a/app/javascript/src/styles/views/posts/index/_index.scss +++ b/app/javascript/src/styles/views/posts/index/_index.scss @@ -2,7 +2,7 @@ body.c-posts.a-index, body.c-favorites.a-index { #page { // Override the theme to instead // project it upon the content area - background: none; + background: var(--color-foreground); padding: 0; } @@ -36,8 +36,6 @@ body.c-posts.a-index, body.c-favorites.a-index { grid-area: search; padding: 0.5rem 0.25rem; - background-color: #152f56; - background-color: themed("color-foreground"); box-shadow: inset 0px -0.25rem 0.25rem -0.25rem themed("color-background"); h1 { @@ -92,8 +90,6 @@ body.c-posts.a-index, body.c-favorites.a-index { gap: 1em; padding: 0.5rem 0.25rem; - background-color: #152f56; - background-color: themed("color-foreground"); box-shadow: inset 0px 0.25rem 0.25rem -0.25rem themed("color-background"); // By popular demand diff --git a/app/javascript/src/styles/views/posts/index/partials/_fullscreen.scss b/app/javascript/src/styles/views/posts/index/partials/_fullscreen.scss index 33dbd7926..3d3279d5c 100644 --- a/app/javascript/src/styles/views/posts/index/partials/_fullscreen.scss +++ b/app/javascript/src/styles/views/posts/index/partials/_fullscreen.scss @@ -24,6 +24,8 @@ background: themed("color-button-active"); color: black; } + + .search-controls { right: 0.25rem; } } .sidebar { display: none; } .content { diff --git a/app/javascript/src/styles/views/posts/index/partials/_sticky.scss b/app/javascript/src/styles/views/posts/index/partials/_sticky.scss index 35674c99d..45514b76c 100644 --- a/app/javascript/src/styles/views/posts/index/partials/_sticky.scss +++ b/app/javascript/src/styles/views/posts/index/partials/_sticky.scss @@ -2,6 +2,9 @@ position: sticky; top: 0; + background: themed("color-foreground"); + border-radius: 0.25rem; + // on top of thumbnail labels z-index: 11; From 7b7482b9499e23d2f652a943f9eef179ab5989b8 Mon Sep 17 00:00:00 2001 From: Catting <5874051+mm12@users.noreply.github.com> Date: Wed, 19 Feb 2025 09:24:15 -0600 Subject: [PATCH 20/26] [Artist] Change search field 'linked' from true/any to true/false/any (#923) --- app/models/artist.rb | 2 ++ app/views/artists/_search.html.erb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/artist.rb b/app/models/artist.rb index 135ecc399..e51ce6a99 100644 --- a/app/models/artist.rb +++ b/app/models/artist.rb @@ -483,6 +483,8 @@ class Artist < ApplicationRecord if params[:is_linked].to_s.truthy? q = q.where("linked_user_id IS NOT NULL") + elsif params[:is_linked].to_s.falsy? + q = q.where("linked_user_id IS NULL") end case params[:order] diff --git a/app/views/artists/_search.html.erb b/app/views/artists/_search.html.erb index bb239b0d7..ae7e13233 100644 --- a/app/views/artists/_search.html.erb +++ b/app/views/artists/_search.html.erb @@ -3,6 +3,6 @@ <%= f.input :url_matches, label: "URL", as: :string %> <%= f.user :creator %> <%= f.input :has_tag, label: "Has tag?", collection: [["Yes", true], ["No", false]], include_blank: true %> - <%= f.input :is_linked, label: "Linked Only", as: :boolean %> + <%= f.input :is_linked, label: "Linked?", collection: [["Yes", true], ["No", false]], include_blank: true %> <%= f.input :order, collection: [["Recently created", "created_at"], ["Last updated", "updated_at"], ["Name", "name"], ["Post count", "post_count"]] %> <% end %> From 0f8272ede44819261ca15fa418da4161cc1987b8 Mon Sep 17 00:00:00 2001 From: Cinder Date: Wed, 19 Feb 2025 07:25:42 -0800 Subject: [PATCH 21/26] [UI] Add an extra hidden profile link to the primary menu (#918) --- app/helpers/icon_helper.rb | 1 + app/javascript/src/styles/common/navigation.scss | 2 ++ app/views/layouts/_main_links.html.erb | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/app/helpers/icon_helper.rb b/app/helpers/icon_helper.rb index 4a628f9ad..fc5bc05fa 100644 --- a/app/helpers/icon_helper.rb +++ b/app/helpers/icon_helper.rb @@ -17,6 +17,7 @@ module IconHelper swatch: %(), settings: %(), log_in: %(), + user: %(), # Utility times: %(), diff --git a/app/javascript/src/styles/common/navigation.scss b/app/javascript/src/styles/common/navigation.scss index ea3bdc8d1..582caaa0b 100644 --- a/app/javascript/src/styles/common/navigation.scss +++ b/app/javascript/src/styles/common/navigation.scss @@ -187,6 +187,8 @@ nav.navigation { padding: 1rem 0.5rem; } li.current a { background-color: themed("color-foreground"); } + + li.nav-hidden { display: none; } } .nav-secondary { diff --git a/app/views/layouts/_main_links.html.erb b/app/views/layouts/_main_links.html.erb index 58373063a..47e689677 100644 --- a/app/views/layouts/_main_links.html.erb +++ b/app/views/layouts/_main_links.html.erb @@ -1,3 +1,8 @@ +<% if CurrentUser.is_anonymous? %> + <%= decorated_nav_link_to("Log In", :log_in, new_session_path, class: "nav-hidden") %> +<% else %> + <%= decorated_nav_link_to("Profile", :user, user_path(CurrentUser.user), class: "nav-hidden") %> +<% end %> <%= decorated_nav_link_to("Artists", :brush, artists_path) %> <%= decorated_nav_link_to("Posts", :images, posts_path) %> <%= decorated_nav_link_to("Pools", :library, gallery_pools_path) %> From 445049a89d33e56245553230d868b310dc92a298 Mon Sep 17 00:00:00 2001 From: Cinder Date: Wed, 19 Feb 2025 07:25:59 -0800 Subject: [PATCH 22/26] [Themes] Add font options (#919) --- app/javascript/src/javascripts/themes.js | 2 +- .../src/javascripts/utility/storage.js | 3 + app/javascript/src/styles/base.scss | 1 + app/javascript/src/styles/base/_fonts.scss | 161 ++++++++++++++++++ app/javascript/src/styles/base/_vars.scss | 2 +- .../src/styles/common/post_search.scss | 2 +- .../src/styles/views/posts/index/_index.scss | 2 +- app/views/layouts/_theme_include.html.erb | 1 + app/views/static/theme.html.erb | 13 ++ public/fonts/Lato/Lato-Bold.woff | Bin 0 -> 36732 bytes public/fonts/Lato/Lato-Bold.woff2 | Bin 0 -> 28596 bytes public/fonts/Lato/Lato-BoldItalic.woff | Bin 0 -> 39376 bytes public/fonts/Lato/Lato-BoldItalic.woff2 | Bin 0 -> 30560 bytes public/fonts/Lato/Lato-Italic.woff | Bin 0 -> 39104 bytes public/fonts/Lato/Lato-Italic.woff2 | Bin 0 -> 30308 bytes public/fonts/Lato/Lato-Regular.woff | Bin 0 -> 37524 bytes public/fonts/Lato/Lato-Regular.woff2 | Bin 0 -> 29256 bytes public/fonts/Lexend/Lexend-Bold.woff | Bin 0 -> 45344 bytes public/fonts/Lexend/Lexend-Bold.woff2 | Bin 0 -> 32664 bytes public/fonts/Lexend/Lexend-BoldItalic.woff | Bin 0 -> 57368 bytes public/fonts/Lexend/Lexend-BoldItalic.woff2 | Bin 0 -> 39172 bytes public/fonts/Lexend/Lexend-Italic.woff | Bin 0 -> 67816 bytes public/fonts/Lexend/Lexend-Italic.woff2 | Bin 0 -> 44496 bytes public/fonts/Lexend/Lexend-Regular.woff | Bin 0 -> 43980 bytes public/fonts/Lexend/Lexend-Regular.woff2 | Bin 0 -> 31744 bytes .../fonts/OpenDyslexic/OpenDyslexic-Bold.woff | Bin 0 -> 151148 bytes .../OpenDyslexic/OpenDyslexic-Bold.woff2 | Bin 0 -> 95724 bytes .../OpenDyslexic/OpenDyslexic-BoldItalic.woff | Bin 0 -> 154704 bytes .../OpenDyslexic-BoldItalic.woff2 | Bin 0 -> 98316 bytes .../OpenDyslexic/OpenDyslexic-Italic.woff | Bin 0 -> 153900 bytes .../OpenDyslexic/OpenDyslexic-Italic.woff2 | Bin 0 -> 97856 bytes .../OpenDyslexic/OpenDyslexic-Regular.woff | Bin 0 -> 147252 bytes .../OpenDyslexic/OpenDyslexic-Regular.woff2 | Bin 0 -> 93940 bytes public/fonts/OpenSans/OpenSans-Bold.woff | Bin 0 -> 78100 bytes public/fonts/OpenSans/OpenSans-Bold.woff2 | Bin 0 -> 58152 bytes .../fonts/OpenSans/OpenSans-BoldItalic.woff | Bin 0 -> 81320 bytes .../fonts/OpenSans/OpenSans-BoldItalic.woff2 | Bin 0 -> 60184 bytes public/fonts/OpenSans/OpenSans-Italic.woff | Bin 0 -> 83972 bytes public/fonts/OpenSans/OpenSans-Italic.woff2 | Bin 0 -> 62776 bytes public/fonts/OpenSans/OpenSans-Regular.woff | Bin 0 -> 80224 bytes public/fonts/OpenSans/OpenSans-Regular.woff2 | Bin 0 -> 60132 bytes 41 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 app/javascript/src/styles/base/_fonts.scss create mode 100644 public/fonts/Lato/Lato-Bold.woff create mode 100644 public/fonts/Lato/Lato-Bold.woff2 create mode 100644 public/fonts/Lato/Lato-BoldItalic.woff create mode 100644 public/fonts/Lato/Lato-BoldItalic.woff2 create mode 100644 public/fonts/Lato/Lato-Italic.woff create mode 100644 public/fonts/Lato/Lato-Italic.woff2 create mode 100644 public/fonts/Lato/Lato-Regular.woff create mode 100644 public/fonts/Lato/Lato-Regular.woff2 create mode 100644 public/fonts/Lexend/Lexend-Bold.woff create mode 100644 public/fonts/Lexend/Lexend-Bold.woff2 create mode 100644 public/fonts/Lexend/Lexend-BoldItalic.woff create mode 100644 public/fonts/Lexend/Lexend-BoldItalic.woff2 create mode 100644 public/fonts/Lexend/Lexend-Italic.woff create mode 100644 public/fonts/Lexend/Lexend-Italic.woff2 create mode 100644 public/fonts/Lexend/Lexend-Regular.woff create mode 100644 public/fonts/Lexend/Lexend-Regular.woff2 create mode 100644 public/fonts/OpenDyslexic/OpenDyslexic-Bold.woff create mode 100644 public/fonts/OpenDyslexic/OpenDyslexic-Bold.woff2 create mode 100644 public/fonts/OpenDyslexic/OpenDyslexic-BoldItalic.woff create mode 100644 public/fonts/OpenDyslexic/OpenDyslexic-BoldItalic.woff2 create mode 100644 public/fonts/OpenDyslexic/OpenDyslexic-Italic.woff create mode 100644 public/fonts/OpenDyslexic/OpenDyslexic-Italic.woff2 create mode 100644 public/fonts/OpenDyslexic/OpenDyslexic-Regular.woff create mode 100644 public/fonts/OpenDyslexic/OpenDyslexic-Regular.woff2 create mode 100644 public/fonts/OpenSans/OpenSans-Bold.woff create mode 100644 public/fonts/OpenSans/OpenSans-Bold.woff2 create mode 100644 public/fonts/OpenSans/OpenSans-BoldItalic.woff create mode 100644 public/fonts/OpenSans/OpenSans-BoldItalic.woff2 create mode 100644 public/fonts/OpenSans/OpenSans-Italic.woff create mode 100644 public/fonts/OpenSans/OpenSans-Italic.woff2 create mode 100644 public/fonts/OpenSans/OpenSans-Regular.woff create mode 100644 public/fonts/OpenSans/OpenSans-Regular.woff2 diff --git a/app/javascript/src/javascripts/themes.js b/app/javascript/src/javascripts/themes.js index ccff3abeb..d57c28153 100644 --- a/app/javascript/src/javascripts/themes.js +++ b/app/javascript/src/javascripts/themes.js @@ -4,7 +4,7 @@ import LStorage from "./utility/storage"; const Theme = {}; Theme.Values = { - "Theme": ["Main", "Extra", "Palette", "StickyHeader", "Navbar", "Gestures", "ForumNotif"], + "Theme": ["Main", "Extra", "Palette", "Font", "StickyHeader", "Navbar", "Gestures", "ForumNotif"], "Posts": ["WikiExcerpt", "StickySearch"], }; diff --git a/app/javascript/src/javascripts/utility/storage.js b/app/javascript/src/javascripts/utility/storage.js index d02ef5170..601b98d60 100644 --- a/app/javascript/src/javascripts/utility/storage.js +++ b/app/javascript/src/javascripts/utility/storage.js @@ -57,6 +57,9 @@ LStorage.Theme = { /** @returns {string} Colorblind-friendly palette (default / deut / trit) */ Palette: ["theme-palette", "default"], + /** @returns {string} Font family (verdana / leto / lexend / dyslexic ) */ + Font: ["theme-font", "Verdana"], + /** @returns {string} Position of the navbar on the post page (top / bottom / both / none) */ Navbar: ["theme-nav", "top"], diff --git a/app/javascript/src/styles/base.scss b/app/javascript/src/styles/base.scss index 227cdab7f..2cb54aaad 100644 --- a/app/javascript/src/styles/base.scss +++ b/app/javascript/src/styles/base.scss @@ -8,6 +8,7 @@ @import "base/base"; @import "base/links"; @import "base/fontawesome"; +@import "base/fonts"; @import "common/standard_variables"; @import "common/standard_elements"; diff --git a/app/javascript/src/styles/base/_fonts.scss b/app/javascript/src/styles/base/_fonts.scss new file mode 100644 index 000000000..511b14388 --- /dev/null +++ b/app/javascript/src/styles/base/_fonts.scss @@ -0,0 +1,161 @@ +// Picker +body { + &[data-th-font="Lato"] { font-family: "Lato", $base_font_family; } + &[data-th-font="Lexend"] { font-family: "Lexend", $base_font_family; } + &[data-th-font="Monospace"] { font-family: monospace, monospace; } + &[data-th-font="OpenDyslexic"] { font-family: "OpenDyslexic", $base_font_family; } + &[data-th-font="OpenSans"] { font-family: "OpenSans", $base_font_family; } + &[data-th-font="ComicSans"] { font-family: "Comic Sans MS", "Comic Sans", cursive; } +} + +/** Font Definitions **/ +// Lato +@font-face { + font-family: "Lato"; + src: url("/public/fonts/Lato/Lato-Regular.woff2") format("woff2"), + url("/public/fonts/Lato/Lato-Regular.woff") format("woff"); + font-weight: normal; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Lato"; + src: url("/public/fonts/Lato/Lato-Bold.woff2") format("woff2"), + url("/public/fonts/Lato/Lato-Bold.woff") format("woff"); + font-weight: bold; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Lato"; + src: url("/public/fonts/Lato/Lato-Italic.woff2") format("woff2"), + url("/public/fonts/Lato/Lato-Italic.woff") format("woff"); + font-weight: normal; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: "Lato"; + src: url("/public/fonts/Lato/Lato-BoldItalic.woff2") format("woff2"), + url("/public/fonts/Lato/Lato-BoldItalic.woff") format("woff"); + font-weight: bold; + font-style: italic; + font-display: swap; +} + + +// Lexend +@font-face { + font-family: "Lexend"; + src: url("/public/fonts/Lexend/Lexend-Regular.woff2") format("woff2"), + url("/public/fonts/Lexend/Lexend-Regular.woff") format("woff"); + font-weight: normal; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Lexend"; + src: url("/public/fonts/Lexend/Lexend-Bold.woff2") format("woff2"), + url("/public/fonts/Lexend/Lexend-Bold.woff") format("woff"); + font-weight: bold; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Lexend"; + src: url("/public/fonts/Lexend/Lexend-Italic.woff2") format("woff2"), + url("/public/fonts/Lexend/Lexend-Italic.woff") format("woff"); + font-weight: normal; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: "Lexend"; + src: url("/public/fonts/Lexend/Lexend-BoldItalic.woff2") format("woff2"), + url("/public/fonts/Lexend/Lexend-BoldItalic.woff") format("woff"); + font-weight: bold; + font-style: italic; + font-display: swap; +} + + +// OpenDyslexic +@font-face { + font-family: "OpenDyslexic"; + src: url("/public/fonts/OpenDyslexic/OpenDyslexic-Regular.woff2") format("woff2"), + url("/public/fonts/OpenDyslexic/OpenDyslexic-Regular.woff") format("woff"); + font-weight: normal; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "OpenDyslexic"; + src: url("/public/fonts/OpenDyslexic/OpenDyslexic-Bold.woff2") format("woff2"), + url("/public/fonts/OpenDyslexic/OpenDyslexic-Bold.woff") format("woff"); + font-weight: bold; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "OpenDyslexic"; + src: url("/public/fonts/OpenDyslexic/OpenDyslexic-Italic.woff2") format("woff2"), + url("/public/fonts/OpenDyslexic/OpenDyslexic-Italic.woff") format("woff"); + font-weight: normal; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: "OpenDyslexic"; + src: url("/public/fonts/OpenDyslexic/OpenDyslexic-BoldItalic.woff2") format("woff2"), + url("/public/fonts/OpenDyslexic/OpenDyslexic-BoldItalic.woff") format("woff"); + font-weight: bold; + font-style: italic; + font-display: swap; +} + + +// OpenSans +@font-face { + font-family: "OpenSans"; + src: url("/public/fonts/OpenSans/OpenSans-Regular.woff2") format("woff2"), + url("/public/fonts/OpenSans/OpenSans-Regular.woff") format("woff"); + font-weight: normal; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "OpenSans"; + src: url("/public/fonts/OpenSans/OpenSans-Bold.woff2") format("woff2"), + url("/public/fonts/OpenSans/OpenSans-Bold.woff") format("woff"); + font-weight: bold; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "OpenSans"; + src: url("/public/fonts/OpenSans/OpenSans-Italic.woff2") format("woff2"), + url("/public/fonts/OpenSans/OpenSans-Italic.woff") format("woff"); + font-weight: normal; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: "OpenSans"; + src: url("/public/fonts/OpenSans/OpenSans-BoldItalic.woff2") format("woff2"), + url("/public/fonts/OpenSans/OpenSans-BoldItalic.woff") format("woff"); + font-weight: bold; + font-style: italic; + font-display: swap; +} diff --git a/app/javascript/src/styles/base/_vars.scss b/app/javascript/src/styles/base/_vars.scss index 9d48be57e..c8f7ae949 100644 --- a/app/javascript/src/styles/base/_vars.scss +++ b/app/javascript/src/styles/base/_vars.scss @@ -20,7 +20,7 @@ $dtext_h3_size: 1.6em; $dtext_h4_size: 1.4em; $dtext_h5_size: 1.2em; $dtext_h6_size: 1em; -$base_font_family: Verdana, sans-serif; +$base_font_family: Verdana, Geneva, sans-serif; $box-shadow-size: 2px 2px 5px; diff --git a/app/javascript/src/styles/common/post_search.scss b/app/javascript/src/styles/common/post_search.scss index ede222255..f9655f607 100644 --- a/app/javascript/src/styles/common/post_search.scss +++ b/app/javascript/src/styles/common/post_search.scss @@ -13,7 +13,7 @@ textarea { // Override default texarea styles - font-family: Verdana, sans-serif; + font-family: $base_font_family; font-size: 1rem; line-height: 1rem; padding: 0.5rem 0 0.5rem 0.5rem; diff --git a/app/javascript/src/styles/views/posts/index/_index.scss b/app/javascript/src/styles/views/posts/index/_index.scss index bfd376b51..0f24b040d 100644 --- a/app/javascript/src/styles/views/posts/index/_index.scss +++ b/app/javascript/src/styles/views/posts/index/_index.scss @@ -101,7 +101,7 @@ body.c-posts.a-index, body.c-favorites.a-index { // Match the searchbox padding: 0.5em; - font-family: Verdana, sans-serif; + font-family: $base_font_family; font-size: 1.05em; } } diff --git a/app/views/layouts/_theme_include.html.erb b/app/views/layouts/_theme_include.html.erb index 4add60a81..b4b9e144c 100644 --- a/app/views/layouts/_theme_include.html.erb +++ b/app/views/layouts/_theme_include.html.erb @@ -8,6 +8,7 @@ "th-sheader": localStorage.getItem("theme-sheader") || false, "th-forumnotif": localStorage.getItem("theme-forumnotif") || false, "th-palette": localStorage.getItem("theme-palette") || "default", + "th-font": localStorage.getItem("theme-font") || "Verdana", "th-nav": localStorage.getItem("theme-nav") || "top", // Settings diff --git a/app/views/static/theme.html.erb b/app/views/static/theme.html.erb index fd9087cf9..283ba896b 100644 --- a/app/views/static/theme.html.erb +++ b/app/views/static/theme.html.erb @@ -45,6 +45,18 @@ + + + +

    Features

    +

    Navigation

    + -
    -
    - + <%= custom_form_for @user do |form| %> + + <%= render "users/partials/edit/basic", form: form %> + <%= render "users/partials/edit/advanced", form: form %> + <%= render "users/partials/edit/blacklist", form: form %> + + <%= form.button :submit, "Save Settings" %> + + + <% end %> +
    -

    <%= link_to "Request a name change", new_user_name_change_request_path %>

    - - -
    - -

    - <% if CurrentUser.user.email.present? %> - <%= CurrentUser.user.email %> - <% else %> - blank - <% end %> - – - <%= link_to "Change your email", new_maintenance_user_email_change_path %> -

    -
    - - <%= f.input :avatar_id, as: :string, label: "Avatar Post ID" %> - - <%= f.input :profile_about, as: :dtext, label: "About Me", limit: Danbooru.config.user_about_max_size, allow_color: true %> - - <%= f.input :profile_artinfo, as: :dtext, label: "Commission Info", limit: Danbooru.config.user_about_max_size, allow_color: true %> - - <%= f.input :time_zone, :include_blank => false %> - - <%= f.input :receive_email_notifications, :as => :select, :include_blank => false, :collection => [["Yes", "true"], ["No", "false"]] %> - - <%= f.input :comment_threshold, :hint => "Comments below this score will be hidden by default." %> - - <%= f.input :default_image_size, :hint => "Show original image size, scaled to fit, scaled to fit vertically, or show resized #{Danbooru.config.large_image_width} pixel sample version.", :label => "Default image width", :collection => [["Original", "original"], ["Fit (Horizontal)", "fit"], ["Fit (Vertical)", "fitv"], ["Sample (#{Danbooru.config.large_image_width}px)", "large"]], :include_blank => false %> - - <%= f.input :per_page, :label => "Posts per page", :as => :select, :collection => (25..250), :include_blank => false %> - - <%= f.input :enable_safe_mode, :label => "Safe mode", :hint => "Show only safe images. Hide questionable and explicit images.", :as => :select, :include_blank => false, :collection => [["Yes", "true"], ["No", "false"]] %> - - <%= f.input :blacklisted_tags, :hint => "Put any tag combinations you never want to see here. Each combination should go on a separate line. View help.".html_safe, autocomplete: "tag-query", input_html: { size: "40x5" } %> - - <%= f.input :blacklist_users, hint: "Hide comments, blips and forum posts from users that have been blacklisted, in addition to posts.", as: :select, include_blank: false, collection: [["Yes", "true"], ["No", "false"]] %> -
    - -
    - <%= f.input :style_usernames, :as => :select, :label => "Colored usernames", - :hint => raw("Color each user's name depending on their level. See #{link_to 'the legend', wiki_page_path(id: 'e621:colored_usernames')} for what the colors are."), - :include_blank => false, :collection => [["Yes", "true"], ["No", "false"]] %> - - <%= f.input :enable_keyboard_navigation, :as => :select, :include_blank => false, :label => "Enable keyboard shortcuts", :collection => [["Yes", "true"], ["No", "false"]], - hint: raw("Enables the use of keyboard shortcuts for a majority of site actions related to posts. A list of keyboard shortcuts is available #{link_to 'here', keyboard_shortcuts_path}.")%> - - <%= f.input :enable_auto_complete, :as => :select, :collection => [["Yes", "true"], ["No", "false"]], :include_blank => false, - hint: "Enables auto-completion on most tag and user entry fields." %> - - <%= f.input :enable_privacy_mode, :as => :select, :collection => [["No", "false"], ["Yes", "true"]], :include_blank => false, - hint: "Prevent showing your favorites to others users (except staff)." %> - - <%= f.input :show_post_statistics, as: :select, collection: [["No", "false"],["Yes", "true"]], include_blank: false, - hint: "Show post statistics below posts on search pages." %> - - <%= f.input :description_collapsed_initially, as: :select, collection: [["No", "false"],["Yes", "true"]], include_blank: false, - hint: "Don't expand post descriptions on page load." %> - - <%= f.input :hide_comments, as: :select, collection: [["No", "false"],["Yes", "true"]], include_blank: false, - hint: "Do not show the comments section on post pages." %> - - <% unless CurrentUser.is_janitor? %> - <%= f.input :disable_user_dmails, label: "Disable DMails", hint: "Prevent other users from sending you DMails. You will be prevented from sending DMails to non-staff members while this option is enabled. Staff are always allowed to send you DMails.", - :as => :select, :collection => [["No", "false"], ["Yes", "true"]], :include_blank => false %> - <% end %> - - <%= f.input :disable_cropped_thumbnails, :as => :select, :collection => [["No", "false"], ["Yes", "true"]], :include_blank => false, - hint: "Disables displaying cropped thumbnails on the mobile layout of the site in favor of scaled thumbnails. Has no effect on the desktop site." %> - - <%= f.input :show_hidden_comments, label: "Show Own Hidden Comments", :as => :select, :collection => [["No", "false"], ["Yes", "true"]], :include_blank => false, - hint: "Show your own hidden comments on comment pages." %> - - <% if @user.post_upload_count >= 10 %> - <%= f.input :enable_compact_uploader, label: "Enable Compact Uploader", as: :select, - collection: [["No", "false"], ["Yes", "true"]], include_blank: false, - hint: "Enables a more compact and less guided post uploader." %> - <% end %> - -
    - - <%= hidden_field_tag "user[dmail_filter_attributes][id]", @user.dmail_filter.try(:id) %> - <%= text_field_tag "user[dmail_filter_attributes][words]", @user.dmail_filter.try(:words), :id => "user_dmail_filter_attributes_words", :class => "text optional", :size => 40 %> - A list of banned words (space delimited). Any dmail you receive with a banned word will automatically be deleted. -
    - - <%= f.input :favorite_tags, :label => "Frequent tags", :hint => "A list of tags that you use often. They will appear when using the list of Related Tags.", autocomplete: "tag-query", input_html: { rows: 5 } %> - - <%= f.input :disable_responsive_mode, :as => :select, :collection => [["No", "false"], ["Yes", "true"]], :include_blank => false, :hint => "Disable alternative layout for mobile and tablet." %> - - <%= f.input :custom_style, :label => "Custom CSS style".html_safe, :hint => "Style to apply to the whole site.", :input_html => {:size => "40x5"} %> -
    - - <%= f.button :submit, "Submit" %> - <% end %> - - + <% content_for(:page_title) do %> Settings <% end %> -<% content_for(:html_header) do %> - <%= javascript_tag nonce: true do -%> - $(function() { - $("#advanced-settings-section").hide(); - $("#edit-options a:not(#delete-account):not(#change-password)").on("click", function(e) { - var $target = $(e.target); - $("h2 a").removeClass("active"); - $("#basic-settings-section,#advanced-settings-section").hide(); - $target.addClass("active"); - $($target.attr("href") + "-section").show(); - e.preventDefault(); - }); - }); - <% end -%> -<% end %> - <%= render "secondary_links" %> diff --git a/app/views/users/partials/edit/_advanced.html.erb b/app/views/users/partials/edit/_advanced.html.erb new file mode 100644 index 000000000..98f4eba42 --- /dev/null +++ b/app/views/users/partials/edit/_advanced.html.erb @@ -0,0 +1,166 @@ +<% tab_name = "advanced" %> + + +Accessibility + + + Enable Keyboard Shortcuts + + <%= form.input_field :enable_keyboard_navigation, as: :boolean, class: "st-toggle" %> + <%= form.label :enable_keyboard_navigation, "!", class: "st-toggle" %> + + + The full list of shortcuts is available <%= link_to "here", keyboard_shortcuts_path %>. + + + + + Enable Auto Complete + + <%= form.input_field :enable_auto_complete, as: :boolean, class: "st-toggle" %> + <%= form.label :enable_auto_complete, "!", class: "st-toggle" %> + + + Tag and user name suggestions. + + + + + Colored Usernames + + <%= form.input_field :style_usernames, as: :boolean, class: "st-toggle" %> + <%= form.label :style_usernames, "!", class: "st-toggle" %> + + + Color names depending on the user's level. + + + + + +Privacy & Messaging + + + Hide Favorites + + <%= form.input_field :enable_privacy_mode, as: :boolean, class: "st-toggle" %> + <%= form.label :enable_privacy_mode, "!", class: "st-toggle" %> + + + Prevent your favorites from being publicly visible. + + + + + Disable DMails + + <%= form.input_field :disable_user_dmails, as: :boolean, class: "st-toggle", disabled: CurrentUser.is_staff? %> + <%= form.label :disable_user_dmails, "!", class: "st-toggle" %> + + + Prevent other users from sending you DMails. + <% if CurrentUser.is_staff? %> +
    Staff members are not allowed to disable DMails. + <% end %> +
    +
    + + + DMail Filters + + <%= hidden_field_tag "user[dmail_filter_attributes][id]", @user.dmail_filter.try(:id) %> + <%= text_field_tag "user[dmail_filter_attributes][words]", @user.dmail_filter.try(:words), id: "user_dmail_filter_attributes_words", class: "text optional", size: 40 %> + + + A list of banned words (space delimited). Any dmail you receive with a banned word will automatically be deleted. + + + + + +Posts & Tags + + + Show Statistics + + <%= form.input_field :show_post_statistics, as: :boolean, class: "st-toggle" %> + <%= form.label :show_post_statistics, "!", class: "st-toggle" %> + + + Show post stats below posts on search pages. + + + + + Collapse Descriptions + + <%= form.input_field :description_collapsed_initially, as: :boolean, class: "st-toggle" %> + <%= form.label :description_collapsed_initially, "!", class: "st-toggle" %> + + + Do not expand post descriptions on page load. + + + + + Frequent Tags + + <%= form.input_field :favorite_tags, label: false, autocomplete: "tag-query", rows: 5 %> + + + A list of tags that you use often.
    They will appear when using the list of Related Tags. +
    +
    + +<% if @user.post_upload_count >= 10 %> + + Compact Uploader + + <%= form.input_field :enable_compact_uploader, as: :boolean, class: "st-toggle" %> + <%= form.label :enable_compact_uploader, "!", class: "st-toggle" %> + + + A more compact and less guided post uploader. + + +<% end %> + + + +Mobile / Tablet + + + Disable Responsive Mode + + <%= form.input_field :disable_responsive_mode, as: :boolean, class: "st-toggle" %> + <%= form.label :disable_responsive_mode, "!", class: "st-toggle" %> + + + Disable alternative layout for mobile and tablet. + + + + + Disable Cropped Thumbnails + + <%= form.input_field :disable_cropped_thumbnails, as: :boolean, class: "st-toggle" %> + <%= form.label :disable_cropped_thumbnails, "!", class: "st-toggle" %> + + + No effect on the desktop site. + + + + + +Customization + + + Custom CSS + + <%= form.input_field :custom_style, label: false, rows: 8 %> + + + Apply CSS Styles to the whole website. + + diff --git a/app/views/users/partials/edit/_basic.html.erb b/app/views/users/partials/edit/_basic.html.erb new file mode 100644 index 000000000..d1af69828 --- /dev/null +++ b/app/views/users/partials/edit/_basic.html.erb @@ -0,0 +1,155 @@ +<% tab_name = "basic" %> + + +Account + + + Username + + <%= link_to "Edit", new_user_name_change_request_path, class: "st-button" %> + + + + + Email + + " disabled><%= link_to "Edit", new_maintenance_user_email_change_path, class: "st-button" %> + + + + + + + <%= link_to "Change password", edit_user_password_path(user_id: @user.id), class: "st-button" %> + <%= link_to "Delete account", maintenance_user_deletion_path, class: "st-button danger" %> + + + + + Time Zone + + <%= form.input_field :time_zone, + label: false, + include_blank: false + %> + + + + + Email Notifications + + <%= form.input_field :receive_email_notifications, as: :boolean, class: "st-toggle" %> + <%= form.label :receive_email_notifications, "!", class: "st-toggle" %> + + + + + + + + +Profile + + + <%= form.label :avatar_id, "Avatar Post ID" %> + + <%= form.input_field :avatar_id, as: :string, label: false %> + + + The image with this ID will be set as your avatar. + + + + + <%= form.label :profile_about, "About Me" %> + + <%= form.input_field :profile_about, + as: :dtext, + label: false, + rows: 8, + limit: Danbooru.config.user_about_max_size, + allow_color: true + %> + + + + + <%= form.label :profile_artinfo, "Commission Info" %> + + <%= form.input_field :profile_artinfo, + as: :dtext, + label: false, + rows: 8, + limit: Danbooru.config.user_about_max_size, + allow_color: true + %> + + + + + +Posts + + + <%= form.label :default_image_size, "Default image width" %> + + <%= form.input_field :default_image_size, + label: false, + collection: [["Original", "original"], ["Fit (Horizontal)", "fit"], ["Fit (Vertical)", "fitv"], ["Sample (#{Danbooru.config.large_image_width}px)", "large"]], + include_blank: false + %> + + + Show original image size, scaled to fit, scaled to fit vertically, or show resized 850 pixel sample version. + + + + + <%= form.label :per_page, "Posts per page" %> + + <%= form.input_field :per_page, label: false, as: :select, collection: (25..250).step(25), include_blank: false %> + + + + + <%= form.label :comment_threshold %> + + <%= form.input_field :comment_threshold, label: false %> + + + Comments below this score will be hidden by default. + + + + + <%= form.label :hide_comments %> + + <%= form.input_field :hide_comments, as: :boolean, class: "st-toggle" %> + <%= form.label :hide_comments, "!", class: "st-toggle" %> + + + Do not show the comments section on post pages. + + + + + <%= form.label :show_hidden_comments, "Show own hidden comments" %> + + <%= form.input_field :show_hidden_comments, as: :boolean, class: "st-toggle" %> + <%= form.label :show_hidden_comments, "!", class: "st-toggle" %> + + + Show your hidden comments on comment pages. + + + + + <%= form.label :enable_safe_mode, "Safe mode" %> + + <%= form.input_field :enable_safe_mode, as: :boolean, class: "st-toggle" %> + <%= form.label :enable_safe_mode, "!", class: "st-toggle" %> + + + Only show images rated safe. + + diff --git a/app/views/users/partials/edit/_blacklist.html.erb b/app/views/users/partials/edit/_blacklist.html.erb new file mode 100644 index 000000000..388591267 --- /dev/null +++ b/app/views/users/partials/edit/_blacklist.html.erb @@ -0,0 +1,25 @@ +<% tab_name = "blacklist" %> + + +Blacklist + + + Blacklisted Tags + + <%= form.input_field :blacklisted_tags, label: false, autocomplete: "tag-query", rows: 8 %> + + + Put any tag combinations you never want to see here. Each combination should go on a separate line. View help. + + + + + Blacklist Users + + <%= form.input_field :blacklist_users, as: :boolean, class: "st-toggle" %> + <%= form.label :blacklist_users, "!", class: "st-toggle" %> + + + Hide comments, blips and forum posts from users that have been blacklisted, in addition to posts. + + From 6aa9678da630378773e7f314acbb465e67dd5aa3 Mon Sep 17 00:00:00 2001 From: Cinder Date: Wed, 26 Feb 2025 02:26:39 -0800 Subject: [PATCH 24/26] [Misc] Redesign the toggle switches (#927) --- .../src/styles/common/_standard_elements.scss | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/app/javascript/src/styles/common/_standard_elements.scss b/app/javascript/src/styles/common/_standard_elements.scss index 686aa32f4..d4a9d6d5b 100644 --- a/app/javascript/src/styles/common/_standard_elements.scss +++ b/app/javascript/src/styles/common/_standard_elements.scss @@ -81,29 +81,29 @@ label.st-toggle { display: flex; width: 2.5rem; - height: 1.5rem; + height: 0.75rem; box-sizing: border-box; position: relative; background-color: themed("color-foreground"); - transition: background-color 500ms; + // transition: background-color 200ms; border-radius: 0.25rem; cursor: pointer; font-size: 0; - box-shadow: inset 0 0 0.25rem themed("color-background"); + box-shadow: inset 0 0 0.25rem #00000060; &::after { content: ""; width: 1rem; height: 1rem; position: absolute; - top: 0.25rem; - left: 0.25rem; + top: -0.125rem; + left: 0rem; - background: themed("color-text-muted"); + background: themed("color-link"); border-radius: 0.25rem; - transition: left 100ms; + transition: left 100ms, background-color 200ms; } } input[type="checkbox"][disabled].st-toggle + label.st-toggle { @@ -112,6 +112,10 @@ input[type="checkbox"][disabled].st-toggle + label.st-toggle { } input[type="checkbox"].st-toggle { display: none; } input[type="checkbox"].st-toggle:checked + label.st-toggle { - background-color: palette("background-green-d5"); - &::after { left: 1.25rem; } + // background-color: palette("background-green-d5"); + &::after { + content: ""; + left: 1.5rem; + background: themed("color-link-active"); + } } From e304030e8ae25e13df8812af361c7ddbed267c82 Mon Sep 17 00:00:00 2001 From: clragon Date: Wed, 26 Feb 2025 14:02:53 +0100 Subject: [PATCH 25/26] [WikiPages] Add editing tag category (#920) Co-authored-by: Donovan Daniels --- app/controllers/wiki_pages_controller.rb | 3 +- app/models/tag.rb | 6 +- app/models/wiki_page.rb | 137 ++++++++++++++---- app/models/wiki_page_version.rb | 1 - app/views/wiki_page_versions/diff.html.erb | 24 ++- app/views/wiki_page_versions/show.html.erb | 6 +- app/views/wiki_pages/_form.html.erb | 20 ++- .../wiki_pages/_secondary_links.html.erb | 9 +- app/views/wiki_pages/edit.html.erb | 6 +- app/views/wiki_pages/show.html.erb | 14 +- test/functional/wiki_pages_controller_test.rb | 95 ++++++++++-- 11 files changed, 233 insertions(+), 88 deletions(-) diff --git a/app/controllers/wiki_pages_controller.rb b/app/controllers/wiki_pages_controller.rb index d586e4d9b..74b533b2c 100644 --- a/app/controllers/wiki_pages_controller.rb +++ b/app/controllers/wiki_pages_controller.rb @@ -123,9 +123,10 @@ class WikiPagesController < ApplicationController end def wiki_page_params(context) - permitted_params = %i[body edit_reason] + permitted_params = %i[body category_id edit_reason] permitted_params += %i[parent] if CurrentUser.is_privileged? permitted_params += %i[is_locked is_deleted skip_secondary_validations] if CurrentUser.is_janitor? + permitted_params += %i[category_is_locked] if CurrentUser.is_admin? permitted_params += %i[title] if context == :create || CurrentUser.is_janitor? params.fetch(:wiki_page, {}).permit(permitted_params) diff --git a/app/models/tag.rb b/app/models/tag.rb index 62db4a89c..fc304d381 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -80,10 +80,14 @@ class Tag < ApplicationRecord def category_for(tag_name) Cache.fetch("tc:#{tag_name}") do - Tag.where(name: tag_name).pick(:category).to_i + category_for!(tag_name).to_i end end + def category_for!(tag_name) + Tag.where(name: tag_name).pick(:category) + end + def categories_for(tag_names, disable_cache: false) if disable_cache tag_cats = {} diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index a0ae4c1cd..a5c6d551e 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -1,14 +1,21 @@ # frozen_string_literal: true class WikiPage < ApplicationRecord - class RevertError < Exception ; end + class RevertError < Exception; end before_validation :normalize_title before_validation :normalize_other_names before_validation :normalize_parent + before_save :log_changes + before_save :update_tag, if: :tag_changed? + before_destroy :validate_not_used_as_help_page + before_destroy :log_destroy after_save :create_version + after_save :update_help_page, if: :saved_change_to_title? + normalizes :body, with: ->(body) { body.gsub("\r\n", "\n") } - validates :title, uniqueness: { :case_sensitive => false } + + validates :title, uniqueness: { case_sensitive: false } validates :title, presence: true validates :title, tag_name: true, if: :title_changed? validates :body, presence: { unless: -> { is_deleted? || other_names.present? || parent.present? } } @@ -19,12 +26,8 @@ class WikiPage < ApplicationRecord validate :validate_redirect validate :validate_not_locked - before_destroy :validate_not_used_as_help_page - before_destroy :log_destroy - before_save :log_changes - after_save :update_help_page, if: :saved_change_to_title? - attr_accessor :skip_secondary_validations, :edit_reason + array_attribute :other_names belongs_to_creator belongs_to_updater @@ -33,19 +36,19 @@ class WikiPage < ApplicationRecord has_many :versions, -> { order("wiki_page_versions.id ASC") }, class_name: "WikiPageVersion", dependent: :destroy has_one :help_page, foreign_key: "wiki_page", primary_key: "title" - def log_destroy - ModAction.log(:wiki_page_delete, {wiki_page: title, wiki_page_id: id}) - end - def log_changes if title_changed? && !new_record? - ModAction.log(:wiki_page_rename, {new_title: title, old_title: title_was}) + ModAction.log(:wiki_page_rename, { new_title: title, old_title: title_was }) end if is_locked_changed? - ModAction.log(is_locked ? :wiki_page_lock : :wiki_page_unlock, {wiki_page: title}) + ModAction.log(is_locked ? :wiki_page_lock : :wiki_page_unlock, { wiki_page: title }) end end + def log_destroy + ModAction.log(:wiki_page_delete, { wiki_page: title, wiki_page_id: id }) + end + module SearchMethods def titled(title) find_by(title: WikiPage.normalize_name(title)) @@ -123,7 +126,7 @@ class WikiPage < ApplicationRecord module ApiMethods def method_attributes - super + [:creator_name, :category_id] + super + %i[creator_name category_id] end end @@ -140,9 +143,92 @@ class WikiPage < ApplicationRecord end end + module TagMethods + def tag + @tag ||= super + end + + def category_id + return @category_id if instance_variable_defined?(:@category_id) + @category_id = tag&.category + end + + def category_id=(value) + return if value.blank? || value.to_i == category_id + category_id_will_change! + @category_id = value.to_i + end + + def category_is_locked + return @category_is_locked if instance_variable_defined?(:@category_is_locked) + @category_is_locked = tag&.is_locked || false + end + + def category_is_locked=(value) + return if value == category_is_locked + category_is_locked_will_change! + @category_is_locked = value + end + + def category_id_changed? + attribute_changed?("category_id") + end + + def category_id_will_change! + attribute_will_change!("category_id") + end + + def category_is_locked_changed? + attribute_changed?("category_is_locked") + end + + def category_is_locked_will_change! + attribute_will_change!("category_is_locked") + end + + def tag_update_map + {}.tap do |updates| + updates[:category] = @category_id if category_id_changed? + updates[:is_locked] = @category_is_locked if category_is_locked_changed? + end + end + + def tag_changed? + tag_update_map.present? + end + + def update_tag + updates = tag_update_map + @tag = Tag.find_or_create_by_name(title) + + return if updates.empty? + unless @tag.category_editable_by?(CurrentUser.user) + reload_tag_attributes + errors.add(:category_id, "Cannot be changed") + throw(:abort) + end + + @tag.update(updates) + @tag.save + + if @tag.invalid? + errors.add(:category_id, @tag.errors.full_messages.join(", ")) + throw(:abort) + end + + reload_tag_attributes + end + + def reload_tag_attributes + remove_instance_variable(:@category_id) if instance_variable_defined?(:@category_id) + remove_instance_variable(:@category_is_locked) if instance_variable_defined?(:@category_is_locked) + end + end + extend SearchMethods include ApiMethods include HelpPageMethods + include TagMethods def user_not_limited allowed = CurrentUser.can_wiki_edit_with_reason @@ -156,7 +242,7 @@ class WikiPage < ApplicationRecord def validate_not_locked if is_locked? && !CurrentUser.is_janitor? errors.add(:is_locked, "and cannot be updated") - return false + false end end @@ -203,7 +289,12 @@ class WikiPage < ApplicationRecord end def normalize_title - self.title = title.downcase.tr(" ", "_") + title = self.title.downcase.tr(" ", "_") + if title =~ /\A(#{Tag.categories.regexp}):(.+)\Z/ + self.category_id = Tag.categories.value_for($1) + title = $2 + end + self.title = title end def normalize_other_names @@ -226,16 +317,12 @@ class WikiPage < ApplicationRecord @skip_secondary_validations = value.to_s.truthy? end - def category_id - Tag.category_for(title) - end - def pretty_title - title&.tr("_", " ") || '' + title&.tr("_", " ") || "" end def pretty_title_with_category - return pretty_title if category_id == 0 + return pretty_title if category_id.blank? || category_id == 0 "#{Tag.category_for_value(category_id)}: #{pretty_title}" end @@ -274,10 +361,6 @@ class WikiPage < ApplicationRecord else match end - end.map {|x| x.downcase.tr(" ", "_").to_s}.uniq - end - - def visible? - true + end.map { |x| x.downcase.tr(" ", "_").to_s }.uniq end end diff --git a/app/models/wiki_page_version.rb b/app/models/wiki_page_version.rb index e0e42536e..39830e612 100644 --- a/app/models/wiki_page_version.rb +++ b/app/models/wiki_page_version.rb @@ -6,7 +6,6 @@ class WikiPageVersion < ApplicationRecord belongs_to_updater user_status_counter :wiki_edit_count, foreign_key: :updater_id belongs_to :artist, optional: true - delegate :visible?, to: :wiki_page module SearchMethods def for_user(user_id) diff --git a/app/views/wiki_page_versions/diff.html.erb b/app/views/wiki_page_versions/diff.html.erb index 0d739e392..141136533 100644 --- a/app/views/wiki_page_versions/diff.html.erb +++ b/app/views/wiki_page_versions/diff.html.erb @@ -2,23 +2,19 @@

    Wiki Page: <%= @thispage.title %>

    - <% if @thispage.visible? %> -

    Showing differences between <%= compact_time @thispage.updated_at %> (<%= link_to_user @thispage.updater %>) and <%= compact_time @otherpage.updated_at %> (<%= link_to_user @otherpage.updater %>)

    +

    Showing differences between <%= compact_time @thispage.updated_at %> (<%= link_to_user @thispage.updater %>) and <%= compact_time @otherpage.updated_at %> (<%= link_to_user @otherpage.updater %>)

    - <% if @thispage.parent != @otherpage.parent %> -
    - Page redirect changed - from <%= @thispage.parent.blank? ? "none" : link_to(@thispage.parent, show_or_new_wiki_pages_path(title: @thispage.parent)) %> - to <%= @otherpage.parent.blank? ? "none" : link_to(@otherpage.parent, show_or_new_wiki_pages_path(title: @otherpage.parent)) %>. -
    - <% end %> - -
    - <%= text_diff(@thispage.body, @otherpage.body) %> + <% if @thispage.parent != @otherpage.parent %> +
    + Page redirect changed + from <%= @thispage.parent.blank? ? "none" : link_to(@thispage.parent, show_or_new_wiki_pages_path(title: @thispage.parent)) %> + to <%= @otherpage.parent.blank? ? "none" : link_to(@otherpage.parent, show_or_new_wiki_pages_path(title: @otherpage.parent)) %>.
    - <% else %> -

    The artist requested removal of this page.

    <% end %> + +
    + <%= text_diff(@thispage.body, @otherpage.body) %> +
    diff --git a/app/views/wiki_page_versions/show.html.erb b/app/views/wiki_page_versions/show.html.erb index 3e5553358..a8d575b16 100644 --- a/app/views/wiki_page_versions/show.html.erb +++ b/app/views/wiki_page_versions/show.html.erb @@ -10,11 +10,7 @@ <% end %>
    - <% if @wiki_page_version.visible? %> - <%= format_text(@wiki_page_version.body) %> - <% else %> -

    The artist has requested removal of this page.

    - <% end %> + <%= format_text(@wiki_page_version.body) %>
    diff --git a/app/views/wiki_pages/_form.html.erb b/app/views/wiki_pages/_form.html.erb index f7d8ebf5c..280c64258 100644 --- a/app/views/wiki_pages/_form.html.erb +++ b/app/views/wiki_pages/_form.html.erb @@ -12,22 +12,28 @@ <%= f.input :body, as: :dtext, limit: Danbooru.config.wiki_page_max_size, allow_color: true %> + <%= f.input :category_id, + label: "Tag Category", + collection: TagCategory::CANONICAL_MAPPING.to_a, + include_blank: true, + disabled: @wiki_page.category_is_locked && !CurrentUser.is_admin? %> + + <% if CurrentUser.is_admin? %> + <%= f.input :category_is_locked, label: "Lock Category", as: :boolean %> + <% end %> + <%= f.input :parent, label: "Redirects to", autocomplete: "wiki-page", input_html: { disabled: !CurrentUser.is_privileged? } %> - <% if CurrentUser.is_janitor? && @wiki_page.is_deleted? %> - <%= f.input :is_deleted, :label => "Deleted", :hint => "Uncheck to restore this wiki page" %> - <% end %> - <% if CurrentUser.is_janitor? %> - <%= f.input :is_locked, :label => "Locked" %> + <%= f.input :is_locked, :label => "Lock Page" %> <% end %> - <%= f.input :edit_reason, label: "Edit Reason" %> - <% if CurrentUser.is_janitor? && @wiki_page.errors[:title]&.any? { |error| error.include?("Move the posts and update any wikis linking to this page first.") } %> <%= f.input :skip_secondary_validations, as: :boolean, label: "Force rename", hint: "Ignore the renaming requirements" %> <% end %> + <%= f.input :edit_reason, label: "Edit Reason" %> + <%= f.button :submit, "Submit" %> <% end %> diff --git a/app/views/wiki_pages/_secondary_links.html.erb b/app/views/wiki_pages/_secondary_links.html.erb index 47dc049bd..4eaa3112d 100644 --- a/app/views/wiki_pages/_secondary_links.html.erb +++ b/app/views/wiki_pages/_secondary_links.html.erb @@ -17,11 +17,8 @@ <% if @wiki_page.tag.present? %> <%= subnav_link_to "Posts (#{@wiki_page.tag.post_count})", posts_path(tags: @wiki_page.title) %> - <% if CurrentUser.is_member? %> - <%= subnav_link_to "Edit Tag Type", edit_tag_path(@wiki_page.tag) %> - <% if CurrentUser.is_janitor? %> - <%= subnav_link_to "Fix Tag Count", new_tag_correction_path(tag_id: @wiki_page.tag.id) %> - <% end %> + <% if CurrentUser.is_janitor?%> + <%= subnav_link_to "Fix Tag Count", new_tag_correction_path(tag_id: @wiki_page.tag.id) %> <% end %> <% end %> @@ -40,6 +37,8 @@ <%= subnav_link_to "Report", new_ticket_path(disp_id: @wiki_page.id, qtype: "wiki") %> <% end %> <% end %> + + <% elsif @wiki_page_version %>
  • diff --git a/app/views/wiki_pages/edit.html.erb b/app/views/wiki_pages/edit.html.erb index 63b9f3549..131e2c181 100644 --- a/app/views/wiki_pages/edit.html.erb +++ b/app/views/wiki_pages/edit.html.erb @@ -5,11 +5,7 @@

    Edit Wiki

    - <% if @wiki_page.visible? %> - <%= render "form" %> - <% else %> -

    The artist requested removal of this page.

    - <% end %> + <%= render "form" %> <%= wiki_page_alias_and_implication_list(@wiki_page)%> <%= wiki_page_post_previews(@wiki_page) %> diff --git a/app/views/wiki_pages/show.html.erb b/app/views/wiki_pages/show.html.erb index 389451b0f..8e7e954b8 100644 --- a/app/views/wiki_pages/show.html.erb +++ b/app/views/wiki_pages/show.html.erb @@ -21,17 +21,13 @@ <% end %>
    - <% if wiki_content.visible? %> - <%= format_text(wiki_content.body, allow_color: true, max_thumbs: 75) %> + <%= format_text(wiki_content.body, allow_color: true, max_thumbs: 75) %> - <% if wiki_content.artist %> -

    <%= link_to "View artist", wiki_content.artist %>

    - <% end %> - - <%= wiki_page_alias_and_implication_list(wiki_content) %> - <% else %> -

    This artist has requested removal of their information.

    + <% if wiki_content.artist %> +

    <%= link_to "View artist", wiki_content.artist %>

    <% end %> + + <%= wiki_page_alias_and_implication_list(wiki_content) %>
    <%= wiki_page_post_previews(wiki_content) %> diff --git a/test/functional/wiki_pages_controller_test.rb b/test/functional/wiki_pages_controller_test.rb index ca29c2462..15ddf5dfe 100644 --- a/test/functional/wiki_pages_controller_test.rb +++ b/test/functional/wiki_pages_controller_test.rb @@ -12,8 +12,8 @@ class WikiPagesControllerTest < ActionDispatch::IntegrationTest context "index action" do setup do as(@user) do - @wiki_page_abc = create(:wiki_page, :title => "abc") - @wiki_page_def = create(:wiki_page, :title => "def") + @wiki_page_abc = create(:wiki_page, title: "abc") + @wiki_page_def = create(:wiki_page, title: "def") end end @@ -23,12 +23,12 @@ class WikiPagesControllerTest < ActionDispatch::IntegrationTest end should "list all wiki_pages (with search)" do - get wiki_pages_path, params: {:search => {:title => "abc"}} + get wiki_pages_path, params: { search: { title: "abc" } } assert_redirected_to(wiki_page_path(@wiki_page_abc)) end should "list wiki_pages without tags with order=post_count" do - get wiki_pages_path, params: {:search => {:title => "abc", :order => "post_count"}} + get wiki_pages_path, params: { search: { title: "abc", order: "post_count" } } assert_redirected_to(wiki_page_path(@wiki_page_abc)) end end @@ -46,7 +46,7 @@ class WikiPagesControllerTest < ActionDispatch::IntegrationTest end should "render for a title" do - get wiki_page_path(:id => @wiki_page.title) + get wiki_page_path(id: @wiki_page.title) assert_response :success end @@ -64,7 +64,7 @@ class WikiPagesControllerTest < ActionDispatch::IntegrationTest as(@user) do @wiki_page.update(title: "-aaa") end - get wiki_page_path(:id => @wiki_page.id) + get wiki_page_path(id: @wiki_page.id) assert_response :success end end @@ -89,7 +89,7 @@ class WikiPagesControllerTest < ActionDispatch::IntegrationTest context "new action" do should "render" do - get_auth new_wiki_page_path, @mod, params: { wiki_page: { title: "test" }} + get_auth new_wiki_page_path, @mod, params: { wiki_page: { title: "test" } } assert_response :success end end @@ -108,7 +108,55 @@ class WikiPagesControllerTest < ActionDispatch::IntegrationTest context "create action" do should "create a wiki_page" do assert_difference("WikiPage.count", 1) do - post_auth wiki_pages_path, @user, params: {:wiki_page => {:title => "abc", :body => "abc"}} + post_auth wiki_pages_path, @user, params: { wiki_page: { title: "abc", body: "abc" } } + end + end + + context "with prefix" do + should "work" do + assert_difference(%w[WikiPage.count Tag.count], 1) do + post_auth wiki_pages_path, @user, params: { wiki_page: { title: "character:abc", body: "abc" } } + end + @wiki = WikiPage.last + assert_equal("abc", @wiki.title) + assert_equal(Tag.categories.character, @wiki.category_id) + end + + should "not work for disallowed prefixes" do + assert_no_difference("WikiPage.count") do + post_auth wiki_pages_path, @user, params: { wiki_page: { title: "lore:abc", body: "abc" } } + end + end + + should "not work for tags over the threshold" do + @tag = create(:tag, post_count: 500) + assert_no_difference("WikiPage.count") do + post_auth wiki_pages_path, @user, params: { wiki_page: { title: "character:#{@tag.name}", body: "abc" } } + end + end + end + + context "with category_id" do + should "work" do + assert_difference(%w[WikiPage.count Tag.count], 1) do + post_auth wiki_pages_path, @user, params: { wiki_page: { title: "abc", body: "abc", category_id: Tag.categories.character } } + end + @wiki = WikiPage.last + assert_equal("abc", @wiki.title) + assert_equal(Tag.categories.character, @wiki.category_id) + end + + should "not work for disallowed categories" do + assert_no_difference("WikiPage.count") do + post_auth wiki_pages_path, @user, params: { wiki_page: { title: "abc", body: "abc", category_id: Tag.categories.lore } } + end + end + + should "not work for tags over the threshold" do + @tag = create(:tag, post_count: 500) + assert_no_difference("WikiPage.count") do + post_auth wiki_pages_path, @user, params: { wiki_page: { title: @tag.name, body: "abc", category_id: Tag.categories.character } } + end end end end @@ -122,18 +170,18 @@ class WikiPagesControllerTest < ActionDispatch::IntegrationTest end should "update a wiki_page" do - put_auth wiki_page_path(@wiki_page), @user, params: {:wiki_page => {:body => "xyz"}} + put_auth wiki_page_path(@wiki_page), @user, params: { wiki_page: { body: "xyz" } } @wiki_page.reload assert_equal("xyz", @wiki_page.body) end should "not rename a wiki page with a non-empty tag" do - put_auth wiki_page_path(@wiki_page), @user, params: {:wiki_page => {:title => "bar"}} + put_auth wiki_page_path(@wiki_page), @user, params: { wiki_page: { title: "bar"}} assert_equal("foo", @wiki_page.reload.title) end should "rename a wiki page with a non-empty tag if secondary validations are skipped" do - put_auth wiki_page_path(@wiki_page), @mod, params: {:wiki_page => {:title => "bar", :skip_secondary_validations => "1"}} + put_auth wiki_page_path(@wiki_page), @mod, params: { wiki_page: { title: "bar", skip_secondary_validations: "1" } } assert_equal("bar", @wiki_page.reload.title) end @@ -141,6 +189,27 @@ class WikiPagesControllerTest < ActionDispatch::IntegrationTest put_auth wiki_page_path(@wiki_page), @user, params: {wiki_page: { is_deleted: true }} assert_equal(false, @wiki_page.reload.is_deleted?) end + + context "with category_id" do + should "work" do + put_auth wiki_page_path(@wiki_page), @user, params: { wiki_page: { category_id: Tag.categories.character } } + @wiki_page.reload + assert_equal(Tag.categories.character, @wiki_page.category_id) + end + + should "not work for disallowed categories" do + put_auth wiki_page_path(@wiki_page), @user, params: { wiki_page: { category_id: Tag.categories.lore } } + @wiki_page.reload + assert_equal(Tag.categories.general, @wiki_page.category_id) + end + + should "not work for tags over the threshold" do + @tag.update_column(:post_count, 500) + put_auth wiki_page_path(@wiki_page), @user, params: { wiki_page: { category_id: Tag.categories.character } } + @wiki_page.reload + assert_equal(Tag.categories.general, @wiki_page.category_id) + end + end end context "destroy action" do @@ -172,7 +241,7 @@ class WikiPagesControllerTest < ActionDispatch::IntegrationTest should "revert to a previous version" do version = @wiki_page.versions.first assert_equal("1", version.body) - put_auth revert_wiki_page_path(@wiki_page), @user, params: {:version_id => version.id} + put_auth revert_wiki_page_path(@wiki_page), @user, params: { version_id: version.id } @wiki_page.reload assert_equal("1", @wiki_page.body) end @@ -182,7 +251,7 @@ class WikiPagesControllerTest < ActionDispatch::IntegrationTest @wiki_page_2 = create(:wiki_page) end - put_auth revert_wiki_page_path(@wiki_page), @user, params: { :version_id => @wiki_page_2.versions.first.id } + put_auth revert_wiki_page_path(@wiki_page), @user, params: { version_id: @wiki_page_2.versions.first.id } @wiki_page.reload assert_not_equal(@wiki_page.body, @wiki_page_2.body) From 0dd17474aa69bddd1f00b29c25f699ae1ee69793 Mon Sep 17 00:00:00 2001 From: Cinder Date: Wed, 26 Feb 2025 05:34:43 -0800 Subject: [PATCH 26/26] [Users] Rework the profile page (#931) --- app/helpers/application_helper.rb | 19 +-- app/helpers/icon_helper.rb | 10 ++ app/helpers/users_helper.rb | 43 +++++ app/javascript/src/javascripts/thumbnails.js | 8 +- app/javascript/src/javascripts/users.js | 25 +++ .../src/javascripts/utility/storage.js | 11 ++ app/javascript/src/styles/base.scss | 1 + .../styles/common/_standard_variables.scss | 4 +- .../src/styles/common/navigation.scss | 22 +-- app/javascript/src/styles/specific/users.scss | 102 +----------- .../views/application/_application.scss | 3 + .../src/styles/views/application/_avatar.scss | 41 +++++ .../views/application/_feedback_badge.scss | 103 ++++++++++++ .../views/application/_level_badge.scss | 40 +++++ .../src/styles/views/users/_users.scss | 1 + .../src/styles/views/users/edit/_edit.scss | 5 +- .../src/styles/views/users/show/_show.scss | 10 ++ .../views/users/show/partials/_about.scss | 44 +++++ .../users/show/partials/_ban_banner.scss | 9 + .../views/users/show/partials/_card.scss | 87 ++++++++++ .../users/show/partials/_post_summary.scss | 107 ++++++++++++ .../users/show/partials/_profile_section.scss | 35 ++++ .../users/show/partials/_staff_info.scss | 16 ++ .../views/users/show/partials/_user_info.scss | 71 ++++++++ app/models/user.rb | 27 +++ app/presenters/user_presenter.rb | 5 + app/views/application/_feedback_badge.erb | 34 ++++ app/views/application/_profile_avatar.erb | 11 ++ app/views/layouts/_nav.html.erb | 6 +- .../staff_notes/partials/_for_user.html.erb | 21 +-- app/views/users/_about.html.erb | 14 -- app/views/users/_statistics.html.erb | 156 ------------------ app/views/users/partials/show/_about.html.erb | 17 ++ .../users/partials/show/_ban_banner.html.erb | 5 + app/views/users/partials/show/_card.html.erb | 20 +++ .../users/partials/show/_mentions.html.erb | 0 .../show}/_post_summary.html.erb | 6 +- .../users/partials/show/_staff_info.html.erb | 34 ++++ .../users/partials/show/_user_info.html.erb | 129 +++++++++++++++ app/views/users/show.html.erb | 39 ++++- 40 files changed, 1016 insertions(+), 325 deletions(-) create mode 100644 app/javascript/src/javascripts/users.js create mode 100644 app/javascript/src/styles/views/application/_application.scss create mode 100644 app/javascript/src/styles/views/application/_avatar.scss create mode 100644 app/javascript/src/styles/views/application/_feedback_badge.scss create mode 100644 app/javascript/src/styles/views/application/_level_badge.scss create mode 100644 app/javascript/src/styles/views/users/show/_show.scss create mode 100644 app/javascript/src/styles/views/users/show/partials/_about.scss create mode 100644 app/javascript/src/styles/views/users/show/partials/_ban_banner.scss create mode 100644 app/javascript/src/styles/views/users/show/partials/_card.scss create mode 100644 app/javascript/src/styles/views/users/show/partials/_post_summary.scss create mode 100644 app/javascript/src/styles/views/users/show/partials/_profile_section.scss create mode 100644 app/javascript/src/styles/views/users/show/partials/_staff_info.scss create mode 100644 app/javascript/src/styles/views/users/show/partials/_user_info.scss create mode 100644 app/views/application/_feedback_badge.erb create mode 100644 app/views/application/_profile_avatar.erb delete mode 100644 app/views/users/_about.html.erb delete mode 100644 app/views/users/_statistics.html.erb create mode 100644 app/views/users/partials/show/_about.html.erb create mode 100644 app/views/users/partials/show/_ban_banner.html.erb create mode 100644 app/views/users/partials/show/_card.html.erb create mode 100644 app/views/users/partials/show/_mentions.html.erb rename app/views/users/{ => partials/show}/_post_summary.html.erb (89%) create mode 100644 app/views/users/partials/show/_staff_info.html.erb create mode 100644 app/views/users/partials/show/_user_info.html.erb diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 2f6b5a361..38c7f195c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -121,6 +121,10 @@ module ApplicationHelper time_tag(time.strftime("%Y-%m-%d %H:%M"), time) end + def compact_date(time) + time_tag(time.strftime("%Y-%m-%d"), time) + end + def external_link_to(url, truncate: nil, strip_scheme: false, link_options: {}) text = url text = text.gsub(%r!\Ahttps?://!i, "") if strip_scheme @@ -189,21 +193,6 @@ module ApplicationHelper end end - def simple_avatar(user, **options) - return "" if user.nil? - post_id = user.avatar_id - deferred_post_ids.add(post_id) if post_id - - klass = options.delete(:class) - named = options.delete(:named) - tag.a href: user_path(user), class: "simple-avatar placeholder #{klass}", data: { id: post_id, name: user.name } do - tag.span(class: "simple-avatar-button") do - concat tag.span(user.pretty_name, class: "simple-avatar-name") if named - concat tag.span(class: "simple-avatar-image", data: { name: user.name[0].capitalize }) - end - end - end - def unread_dmails(user) if user.has_mail? "(#{user.unread_dmail_count})" diff --git a/app/helpers/icon_helper.rb b/app/helpers/icon_helper.rb index fc5bc05fa..e9f2c77e6 100644 --- a/app/helpers/icon_helper.rb +++ b/app/helpers/icon_helper.rb @@ -20,8 +20,18 @@ module IconHelper user: %(), # Utility + plus: %(), times: %(), reset: %(), + replace: %(), + upload: %(), + stamp: %(), + power: %(), + circle_help: %(), + notepad: %(), + flag_left: %(), + ticket: %(), + key_square: %(), # Pagination chevron_left: %(), diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 161233348..a0e8abe2b 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -11,4 +11,47 @@ module UsersHelper domain = email.split("@").last link_to "»", users_path(search: { email_matches: "*@#{domain}" }) end + + def simple_avatar(user, **options) + return "" if user.nil? + post_id = user.avatar_id + deferred_post_ids.add(post_id) if post_id + + klass = options.delete(:class) + named = options.delete(:named) + tag.a href: user_path(user), class: "simple-avatar placeholder #{klass}", data: { id: post_id, name: user.name } do + tag.span(class: "avatar-button") do + concat tag.span(user.pretty_name, class: "avatar-name") if named + concat tag.span(class: "avatar-image", data: { name: user.name[0].capitalize }) + end + end + end + + def profile_avatar(user, **options) + return if user.nil? + post_id = user.avatar_id + deferred_post_ids.add(post_id) if post_id + + klass = options.delete(:class) + + render "/application/profile_avatar", user: user, post_id: post_id, klass: klass + end + + def user_level_badge(user) + return if user.nil? + + tag.span(class: "level-badge level-#{user.level_string.downcase}") do + user.level_string.upcase + end + end + + def user_feedback_badge(user) + return if user.nil? + + feedbacks = user.feedback_pieces + deleted = CurrentUser.user.is_staff? ? feedbacks[:deleted] : 0 + active = feedbacks[:positive] + feedbacks[:neutral] + feedbacks[:negative] + + render "/application/feedback_badge", user: user, positive: feedbacks[:positive], neutral: feedbacks[:neutral], negative: feedbacks[:negative], deleted: deleted, active: active + end end diff --git a/app/javascript/src/javascripts/thumbnails.js b/app/javascript/src/javascripts/thumbnails.js index cfad7cadb..958ee9f1b 100644 --- a/app/javascript/src/javascripts/thumbnails.js +++ b/app/javascript/src/javascripts/thumbnails.js @@ -9,7 +9,7 @@ Thumbnails.initialize = function () { const replacedPosts = []; // Avatar special case - for (const post of $(".simple-avatar.placeholder")) { + for (const post of $(".simple-avatar.placeholder, .profile-avatar.placeholder")) { const $post = $(post); $post.removeClass("placeholder"); @@ -21,7 +21,11 @@ Thumbnails.initialize = function () { $("") .attr("src", postData["preview_url"]) - .appendTo($post.find("span.simple-avatar-image")); + .appendTo($post.find("span.avatar-image")); + + if ($post.hasClass("profile-avatar")) + $post.attr("href", "/posts/" + postID); + continue; } diff --git a/app/javascript/src/javascripts/users.js b/app/javascript/src/javascripts/users.js new file mode 100644 index 000000000..c95c0b7c3 --- /dev/null +++ b/app/javascript/src/javascripts/users.js @@ -0,0 +1,25 @@ +import LStorage from "./utility/storage"; + +const Users = {}; + +Users.init_section = function ($wrapper) { + const $header = $wrapper.find(".profile-section-header").first(); + const $body = $(".profile-section-body").first(); + const name = $wrapper.attr("name"); + if (!name || !$header.length || !$body.length) return; + + let state = LStorage.Users[name]; + if (state) $wrapper.removeClass("hidden"); + + $header.on("click", () => { + $wrapper.toggleClass("hidden", state); + + state = !state; + LStorage.Users[name] = state; + }); +}; + +$(() => { + for (const one of $((".profile-section"))) + Users.init_section($((one))); +}); diff --git a/app/javascript/src/javascripts/utility/storage.js b/app/javascript/src/javascripts/utility/storage.js index 601b98d60..bd81923c4 100644 --- a/app/javascript/src/javascripts/utility/storage.js +++ b/app/javascript/src/javascripts/utility/storage.js @@ -190,6 +190,17 @@ LStorage.Blacklist = { StorageUtils.bootstrapSome(LStorage.Blacklist, ["Collapsed"]); +// Users page config +LStorage.Users = { + /** @returns {boolean} True to show staff stats, false to hide them */ + StaffStats: ["e6.users.staffstats", false], + + /** @returns {boolean} True to show user stats, false to hide them */ + StaffNotes: ["e6.users.staffnotes", false], +}; +StorageUtils.bootstrapMany(LStorage.Users); + + /** * Patches the add, delete, and clear methods for the filter cache set. * Otherwise, modifying the set with these methods would not update the local storage diff --git a/app/javascript/src/styles/base.scss b/app/javascript/src/styles/base.scss index 80a7cebe8..2a7a3db2d 100644 --- a/app/javascript/src/styles/base.scss +++ b/app/javascript/src/styles/base.scss @@ -43,6 +43,7 @@ @import "common/user_styles.scss"; @import "common/voting.scss"; +@import "views/application/application"; @import "views/posts/posts"; @import "views/users/users"; diff --git a/app/javascript/src/styles/common/_standard_variables.scss b/app/javascript/src/styles/common/_standard_variables.scss index 8504ec0b5..1dcadec00 100644 --- a/app/javascript/src/styles/common/_standard_variables.scss +++ b/app/javascript/src/styles/common/_standard_variables.scss @@ -17,5 +17,5 @@ $st-values: ( @function padding($value) { @return st-value(($value)); } @mixin st-padding($value) { padding: padding(($value)); } -@function radius($value) { @return st-value($value); } -@mixin st-radius($value) { border-radius: radius($value); } +@function radius($value: 025) { @return st-value($value); } +@mixin st-radius($value: 025) { border-radius: radius($value); } diff --git a/app/javascript/src/styles/common/navigation.scss b/app/javascript/src/styles/common/navigation.scss index 582caaa0b..54c417d12 100644 --- a/app/javascript/src/styles/common/navigation.scss +++ b/app/javascript/src/styles/common/navigation.scss @@ -76,11 +76,11 @@ nav.navigation { } a.simple-avatar { - .simple-avatar-button { + .avatar-button { padding: 0; gap: 0; - .simple-avatar-name { + .avatar-name { padding: 0.5rem; @include window-smaller-than(32rem) { @@ -88,7 +88,7 @@ nav.navigation { } } - .simple-avatar-image { + .avatar-image { display: flex; justify-content: center; align-items: center; @@ -119,7 +119,7 @@ nav.navigation { } @include window-smaller-than(32rem) { - &.sign-in .simple-avatar-image { + &.sign-in .avatar-image { background: themed("color-foreground"); } } @@ -495,18 +495,18 @@ nav.navigation, html.nav-toggled nav.navigation { padding: 0; height: 100%; - .simple-avatar-button { + .avatar-button { background: none; color: inherit; align-items: start; font-size: 0.875rem; line-height: 0.875rem; - .simple-avatar-name { + .avatar-name { padding: 0 0.5rem; } - .simple-avatar-image { + .avatar-image { height: 3rem; width: 3rem; background: themed("color-foreground"); @@ -528,9 +528,9 @@ nav.navigation, html.nav-toggled nav.navigation { } // Stage 2: account label - .collapse-2 .simple-avatar-name { display: none; } + .collapse-2 .avatar-name { display: none; } @include window-larger-than(65rem) { - .collapse-2 .simple-avatar-name { display: unset; } + .collapse-2 .avatar-name { display: unset; } } } } @@ -564,10 +564,10 @@ body.c-static.a-home { position: static; height: unset; padding: 0; - .simple-avatar-button { + .avatar-button { height: unset; align-items: center; - .simple-avatar-image { display: none; } + .avatar-image { display: none; } } } } diff --git a/app/javascript/src/styles/specific/users.scss b/app/javascript/src/styles/specific/users.scss index c84173453..84b7eff06 100644 --- a/app/javascript/src/styles/specific/users.scss +++ b/app/javascript/src/styles/specific/users.scss @@ -15,7 +15,7 @@ div#c-users { gap: 1em; & > div { - max-width: 1600px; + max-width: 100rem; box-sizing: border-box; } @@ -76,107 +76,7 @@ div#c-users { // Middle section: uploads and favorites .blacklist-ui { padding: 0; } - .posts-section { - display: flex; - flex-flow: column; - gap: 1em; - .profile-sample { - display: grid; - grid-template: "p-header" - "p-links" - "p-posts"; - gap: 0.5em 0; - - @include window-larger-than(800px) { - grid-template: "p-header p-links" - "p-posts p-posts"; - grid-template-columns: 12em 1fr; - gap: 0 0.5em; - } - } - - .profile-sample-header a, .profile-sample-links a { - display: block; - box-sizing: border-box; - align-content: center; - text-align: center; - - height: 100%; - padding: 0.5em; - border-radius: 6px; - background-color: themed("color-section"); - - &:hover { - background-color: themed("color-section-lighten-5"); - } - &:focus, &:active { - outline: 0; - color: themed("color-link-active"); - } - } - - .profile-sample-header { - grid-area: p-header; - display: flex; - - a { - font-size: 1.25em; - font-weight: bold; - width: 100%; - } - - @include window-larger-than(800px) { - a { border-radius: 6px 6px 0 0; } - } - } - - .profile-sample-links { - grid-area: p-links; - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 0.5rem; - text-align: center; - - span { - padding: 0.5em; - color: themed("color-text-muted"); - } - - .spacer { display: none; } - .profile-comments-link { - grid-row: 1 / 3; - grid-column: 3; - } - - @include window-larger-than(800px) { - display: flex; - a { height: min-content; } - .spacer { display: block; } - } - } - - .profile-sample-posts { - grid-area: p-posts; - display: flex; - overflow: auto; - justify-content: center; - flex-wrap: wrap; - - gap: 1em; - padding: 0.5em; - - background: var(--color-section); - border-radius: 6px; - - // Desktop - @include window-larger-than(800px) { - flex-wrap: nowrap; - border-top-left-radius: 0; - justify-content: flex-start; - } - } - } // Bottom section: about me and commission info diff --git a/app/javascript/src/styles/views/application/_application.scss b/app/javascript/src/styles/views/application/_application.scss new file mode 100644 index 000000000..287a1081c --- /dev/null +++ b/app/javascript/src/styles/views/application/_application.scss @@ -0,0 +1,3 @@ +@import "avatar"; +@import "feedback_badge"; +@import "level_badge"; diff --git a/app/javascript/src/styles/views/application/_avatar.scss b/app/javascript/src/styles/views/application/_avatar.scss new file mode 100644 index 000000000..fc73dd740 --- /dev/null +++ b/app/javascript/src/styles/views/application/_avatar.scss @@ -0,0 +1,41 @@ +.profile-avatar { + .avatar-image { + display: flex; + width: 5rem; + height: 5rem; + + position: relative; + + background: themed("color-section"); + @include st-radius; + + // Letter if no avatar image + &::after { + content: attr(data-initial); + + display: flex; + justify-content: center; + align-items: center; + + font-size: 5rem; + font-weight: bold; + color: themed("color-foreground"); + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + } + + // On top of the letter + img { + width: 100%; + height: 100%; + object-fit: cover; + z-index: 1; + + @include st-radius; + } + } +} diff --git a/app/javascript/src/styles/views/application/_feedback_badge.scss b/app/javascript/src/styles/views/application/_feedback_badge.scss new file mode 100644 index 000000000..f796149ea --- /dev/null +++ b/app/javascript/src/styles/views/application/_feedback_badge.scss @@ -0,0 +1,103 @@ +$hex-size: 1.25rem; + +.user-record { + display: flex; + + height: $hex-size * 0.6; // 0.75rem + width: $hex-size; // 1.25rem + margin: ($hex-size / 5) 0; // 0.25rem 0 + + justify-content: center; + align-items: center; + font-size: ($hex-size / 5 * 4) * 0.9; + svg { width: ($hex-size / 5 * 4) * 0.9; } + + background: palette("plain-black"); + color: white; + + // Place corners under the text + position: relative; + z-index: 1; + + &::before, &::after { + content: ""; + + position: absolute; + right: 0; + left: 0; + + border-left: ($hex-size / 2) solid transparent; + border-right: ($hex-size / 2) solid transparent; + + z-index: -1; + } + + &::before { + top: -($hex-size / 5); + border-bottom: ($hex-size / 5) solid palette("plain-black"); + } + + &::after { + bottom: -($hex-size / 5); + border-top: ($hex-size / 5) solid palette("plain-black"); + } + + + // Variations + &.deleted { + background: palette("background-yellow"); + &::before { border-bottom-color: palette("background-yellow"); } + &::after { border-top-color: palette("background-yellow"); } + } + + &.negative { + background: palette("background-red"); + &::before { border-bottom-color: palette("background-red"); } + &::after { border-top-color: palette("background-red"); } + } + + &.neutral { + background: palette("background-grey"); + &::before { border-bottom-color: palette("background-grey"); } + &::after { border-top-color: palette("background-grey"); } + } + + &.positive { + background: palette("background-green"); + &::before { border-bottom-color: palette("background-green"); } + &::after { border-top-color: palette("background-green"); } + } +} + + +.user-records-list { + display: flex; + flex-flow: row; + gap: 0.25rem; + + &:hover .user-record { + &.deleted { + background: palette("background-yellow-d5"); + &::before { border-bottom-color: palette("background-yellow-d5"); } + &::after { border-top-color: palette("background-yellow-d5"); } + } + + &.negative { + background: palette("background-red-d5"); + &::before { border-bottom-color: palette("background-red-d5"); } + &::after { border-top-color: palette("background-red-d5"); } + } + + &.neutral { + background: palette("background-grey-d5"); + &::before { border-bottom-color: palette("background-grey-d5"); } + &::after { border-top-color: palette("background-grey-d5"); } + } + + &.positive { + background: palette("background-green-d5"); + &::before { border-bottom-color: palette("background-green-d5"); } + &::after { border-top-color: palette("background-green-d5"); } + } + } +} diff --git a/app/javascript/src/styles/views/application/_level_badge.scss b/app/javascript/src/styles/views/application/_level_badge.scss new file mode 100644 index 000000000..b61941516 --- /dev/null +++ b/app/javascript/src/styles/views/application/_level_badge.scss @@ -0,0 +1,40 @@ +$user-levels: ( + "unactivated" + "blocked" + "member" + "privileged" + "former-staff" + "janitor" + "moderator" + "admin" +); +$user-level-text: ( + "moderator": #ffffff, + "janitor": #ffffff, +); + +.level-badge { + font-weight: bold; + font-size: 0.65rem; + color: black; + + background: themed("color-user-member"); + padding: 0.1rem 0.25rem; + @include st-radius; + + @each $level in $user-levels { + &.level-#{$level} { + background: themed("color-user-" + $level); + } + } + @each $level, $color in $user-level-text { + &.level-#{$level} { + color: $color; + } + } + + &.level-unactivated { + background: themed("palette-background-red"); + color: #ffffff; + } +} diff --git a/app/javascript/src/styles/views/users/_users.scss b/app/javascript/src/styles/views/users/_users.scss index 1fe06b79f..46f889bd9 100644 --- a/app/javascript/src/styles/views/users/_users.scss +++ b/app/javascript/src/styles/views/users/_users.scss @@ -1 +1,2 @@ +@import "show/show"; @import "edit/edit"; diff --git a/app/javascript/src/styles/views/users/edit/_edit.scss b/app/javascript/src/styles/views/users/edit/_edit.scss index 128c1d984..785c99a0b 100644 --- a/app/javascript/src/styles/views/users/edit/_edit.scss +++ b/app/javascript/src/styles/views/users/edit/_edit.scss @@ -106,8 +106,9 @@ tabs-content { } tab-entry { - display: none; - &.active { display: grid; } + display: none !important; + &.active { display: grid !important; } + &.flex.active { display: flex !important; } grid-template-areas: "head" "body" "hint"; grid-template-columns: 1fr; diff --git a/app/javascript/src/styles/views/users/show/_show.scss b/app/javascript/src/styles/views/users/show/_show.scss new file mode 100644 index 000000000..67421bc05 --- /dev/null +++ b/app/javascript/src/styles/views/users/show/_show.scss @@ -0,0 +1,10 @@ +body.c-users.a-show { + + @import "partials/about"; + @import "partials/ban_banner"; + @import "partials/card"; + @import "partials/post_summary"; + @import "partials/profile_section"; + @import "partials/staff_info"; + @import "partials/user_info"; +} diff --git a/app/javascript/src/styles/views/users/show/partials/_about.scss b/app/javascript/src/styles/views/users/show/partials/_about.scss new file mode 100644 index 000000000..027388d94 --- /dev/null +++ b/app/javascript/src/styles/views/users/show/partials/_about.scss @@ -0,0 +1,44 @@ +tabs-menu, tabs-content { + max-width: 100rem; +} + +#profile-tabs[data-has-about="false"][data-has-artinfo="false"] { + display: none; +} + +.profile-about-section, .profile-artinfo-section { + flex-flow: column; + align-items: start; + + tab-head { + font-weight: bold; + font-size: 1rem; + } +} + +@include window-larger-than(50rem) { + tabs-menu { display: none; } + tabs-content { + display: grid; + grid-template-columns: 15rem 1fr; + grid-template-rows: min-content min-content; + gap: 1rem; + } + + .profile-user-info { + display: grid !important; + grid-template-areas: unset; + grid-row: 1 / 4; + height: min-content; + } + + .profile-about-section { + display: flex !important; + grid-column: 2 / -1; + } + + .profile-artinfo-section { + display: flex !important; + grid-column: 2 / -1; + } +} \ No newline at end of file diff --git a/app/javascript/src/styles/views/users/show/partials/_ban_banner.scss b/app/javascript/src/styles/views/users/show/partials/_ban_banner.scss new file mode 100644 index 000000000..5f867e327 --- /dev/null +++ b/app/javascript/src/styles/views/users/show/partials/_ban_banner.scss @@ -0,0 +1,9 @@ +.profile-ban { + background: var(--palette-background-red); + padding: 0.5rem; + @include st-radius; + + .styled-dtext p:last-child { + margin-bottom: 0; + } +} diff --git a/app/javascript/src/styles/views/users/show/partials/_card.scss b/app/javascript/src/styles/views/users/show/partials/_card.scss new file mode 100644 index 000000000..b3d3f03b0 --- /dev/null +++ b/app/javascript/src/styles/views/users/show/partials/_card.scss @@ -0,0 +1,87 @@ +.profile-card { + display: grid; + grid-template-columns: min-content auto; + gap: 1rem; + + // Info block + .profile-info { + display: flex; + flex-flow: column; + justify-content: center; + gap: 0.25rem; + } + + .profile-name { + display: flex; + flex-flow: row; + gap: 0.5rem; + align-items: center; + + a { + color: themed("color-text"); + font-size: 1.25rem; + } + } + + .profile-joined { + font-size: 0.75rem; + color: themed("color-text-muted"); + margin-top: -0.25rem; + } +} + +.profile-quickie { + display: flex; + gap: 0.5rem; + + margin-top: 1rem; + + .entry { + display: flex; + flex-flow: column; + align-items: center; + } + + .entry > a { + display: grid; + grid-template-columns: min-content 1fr; + grid-template-areas: + "icon header" + "icon title "; + column-gap: 0.25rem; + align-items: center; + + width: 7rem; + padding: 0.5rem; + font-size: 1rem; + + @include st-radius; + background: themed("color-section"); + + &:hover { background: themed("color-section-lighten-5"); } + + .entry-icon { + grid-area: icon; + height: 1.5rem; + width: 1.5rem; + color: themed("color-link-active"); + } + .entry-header { + grid-area: header; + font-size: 1.1rem; + line-height: 1.1rem; + white-space: nowrap; + } + .entry-title { + grid-area: title; + font-size: 0.9rem; + line-height: 0.9rem; + white-space: nowrap; + } + } + + .entry-extra { + a, a:visited { color: themed("color-text-muted"); } + a:hover, a:active { color: themed("color-link-active"); } + } +} diff --git a/app/javascript/src/styles/views/users/show/partials/_post_summary.scss b/app/javascript/src/styles/views/users/show/partials/_post_summary.scss new file mode 100644 index 000000000..c4fbe6183 --- /dev/null +++ b/app/javascript/src/styles/views/users/show/partials/_post_summary.scss @@ -0,0 +1,107 @@ +.posts-section { + display: flex; + flex-flow: column; + gap: 1em; + + // Hack to fix spacing on mobile + margin-top: 1rem; + @include window-larger-than(50rem) { + margin-top: 0; + } + + .profile-sample { + display: grid; + grid-template: "p-header" + "p-links" + "p-posts"; + gap: 0.5em 0; + + @include window-larger-than(50rem) { + grid-template: "p-header p-links" + "p-posts p-posts"; + grid-template-columns: 12em 1fr; + gap: 0 0.5em; + } + } + + .profile-sample-header a, .profile-sample-links a { + display: block; + box-sizing: border-box; + align-content: center; + text-align: center; + + height: 100%; + padding: 0.5em; + border-radius: 6px; + background-color: themed("color-section"); + + &:hover { + background-color: themed("color-section-lighten-5"); + } + &:focus, &:active { + outline: 0; + color: themed("color-link-active"); + } + } + + .profile-sample-header { + grid-area: p-header; + display: flex; + + a { + font-size: 1.25em; + font-weight: bold; + width: 100%; + } + + @include window-larger-than(800px) { + a { border-radius: 6px 6px 0 0; } + } + } + + .profile-sample-links { + grid-area: p-links; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 0.5rem; + text-align: center; + + span { + padding: 0.5em; + color: themed("color-text-muted"); + } + + .spacer { display: none; } + .profile-comments-link { + grid-row: 1 / 3; + grid-column: 3; + } + + @include window-larger-than(50rem) { + display: flex; + a { height: min-content; } + .spacer { display: block; } + } + } + + .profile-sample-posts { + grid-area: p-posts; + display: flex; + overflow: auto; + justify-content: center; + flex-wrap: wrap; + + gap: 1em; + padding: 0.5em; + + background: var(--color-section); + border-radius: 6px; + + // Desktop + @include window-larger-than(50rem) { + flex-wrap: nowrap; + border-top-left-radius: 0; + justify-content: flex-start; + } + } +} diff --git a/app/javascript/src/styles/views/users/show/partials/_profile_section.scss b/app/javascript/src/styles/views/users/show/partials/_profile_section.scss new file mode 100644 index 000000000..2cbbeefd4 --- /dev/null +++ b/app/javascript/src/styles/views/users/show/partials/_profile_section.scss @@ -0,0 +1,35 @@ +.profile-section { + background: themed("color-section"); + + .profile-section-header { + background: themed("color-section-lighten-5"); + @include st-radius; + padding: 0.25rem 0.5rem; + cursor: pointer; + + &::before { + content: "⮟"; + display: inline-flex; + transition: transform 200ms; + margin-right: 0.25rem; + } + } + + .profile-section-body { + padding: 0.25rem 0.5rem; + border-radius: 0 0 radius() radius(); + overflow: hidden; + } + + // Collapsed + &.hidden { + .profile-section-header::before { + transform: rotate(-90deg); + } + + .profile-section-body { + padding: 0 0.5rem; + height: 0; + } + } +} diff --git a/app/javascript/src/styles/views/users/show/partials/_staff_info.scss b/app/javascript/src/styles/views/users/show/partials/_staff_info.scss new file mode 100644 index 000000000..096717352 --- /dev/null +++ b/app/javascript/src/styles/views/users/show/partials/_staff_info.scss @@ -0,0 +1,16 @@ +.profile-staff-info { + display: grid; + grid-template-columns: min-content 1fr; + gap: 0.25rem 1rem; + + h4 { + white-space: nowrap; + } + + .block { grid-column: 1 / -1; } + h4.block { margin-bottom: -0.25rem; } + + @include window-larger-than(50rem) { + grid-template-columns: min-content 1fr min-content 1fr; + } +} diff --git a/app/javascript/src/styles/views/users/show/partials/_user_info.scss b/app/javascript/src/styles/views/users/show/partials/_user_info.scss new file mode 100644 index 000000000..8d744b44f --- /dev/null +++ b/app/javascript/src/styles/views/users/show/partials/_user_info.scss @@ -0,0 +1,71 @@ +.profile-user-info { + display: grid; + grid-template-columns: 1fr; + grid-template-rows: repeat(auto-fit, minmax(min-content, 5rem)); + background: themed("color-section"); + @include st-radius; + + @include window-larger-than(38rem) { grid-template-columns: 1fr 1fr; } + @include window-larger-than(50rem) { grid-template-columns: 1fr; } + + .profile-line { + display: grid; + grid-template-columns: 1fr min-content; + padding: 1rem 0.5rem; + border-bottom: 2px solid themed("color-foreground"); + + h4 { + // Not necessary, but it aligns + // with the -extra line better + display: flex; + align-items: center; + gap: 0.25em; + + svg { + width: 1em; + height: 1em; + vertical-align: middle; + } + } + + &-number { + white-space: nowrap; + } + + &-extra { + grid-column: 1 / -1; + font-size: 90%; + color: themed("color-text-muted"); + + margin-left: 1.25em; + } + + // Stats display section + &-show { + display: flex; + flex-wrap: wrap; + gap: 0.5rem 2rem; + margin-top: 0.5rem; + + .entry { + text-align: center; + width: 5rem; + & > span { color: themed("color-text-muted"); } + } + } + + // Dot-separated, less important + &-list { + display: flex; + gap: 0.25em; + + a:not(:last-child)::after { + content: "•"; + margin-left: 0.25rem; + color: themed("color-text-muted"); + } + } + + &:last-child { border-bottom: none; } + } +} diff --git a/app/models/user.rb b/app/models/user.rb index 0ad1ecde7..871c61196 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -574,6 +574,11 @@ class User < ApplicationRecord base_upload_limit + (pieces[:approved] / 10) - (pieces[:deleted] / 4) - pieces[:pending] end + def upload_limit_max + pieces = upload_limit_pieces + base_upload_limit + (pieces[:approved] / 10) - (pieces[:deleted] / 4) + end + def upload_limit_pieces @upload_limit_pieces ||= begin deleted_count = Post.deleted.for_user(id).count @@ -739,6 +744,28 @@ class User < ApplicationRecord user_status.ticket_count end + def feedback_pieces + @feedback_pieces ||= begin + count = { + deleted: 0, + negative: 0, + neutral: 0, + positive: 0, + } + + feedback.each do |one| + if one.is_deleted + count[:deleted] += 1 + next + end + + count[one.category.to_sym] += 1 + end + + count + end + end + def positive_feedback_count feedback.active.positive.count end diff --git a/app/presenters/user_presenter.rb b/app/presenters/user_presenter.rb index 5a4ab4662..4c7260f67 100644 --- a/app/presenters/user_presenter.rb +++ b/app/presenters/user_presenter.rb @@ -53,6 +53,11 @@ class UserPresenter = #{user.upload_limit}}.html_safe end + def upload_limit_short + return "none" if user.can_upload_free? + "#{user.upload_limit} / #{user.upload_limit_max}" + end + def uploads posts = Post.tag_match("user:#{user.name}").limit(8) PostsDecorator.decorate_collection(posts) diff --git a/app/views/application/_feedback_badge.erb b/app/views/application/_feedback_badge.erb new file mode 100644 index 000000000..47375347a --- /dev/null +++ b/app/views/application/_feedback_badge.erb @@ -0,0 +1,34 @@ + + <% if deleted > 0 %> + <%= deleted %> + <% end %> + + <% if negative > 0 %> + <%= negative %> + <% end %> + + <% if neutral > 0 %> + <%= neutral %> + <% end %> + + <% if positive > 0 %> + <%= positive %> + <% end %> + + +<% if CurrentUser.is_moderator? && CurrentUser.user != user && active == 0 %> + " + class="user-records-list" + title="New Feedback" + > + <%= svg_icon(:plus) %> + +<% end %> \ No newline at end of file diff --git a/app/views/application/_profile_avatar.erb b/app/views/application/_profile_avatar.erb new file mode 100644 index 000000000..584e1255b --- /dev/null +++ b/app/views/application/_profile_avatar.erb @@ -0,0 +1,11 @@ +" + data-id="<%= post_id %>" + data-name="<%= user.name %>" +> + + diff --git a/app/views/layouts/_nav.html.erb b/app/views/layouts/_nav.html.erb index d306a09b1..d04d9ff6b 100644 --- a/app/views/layouts/_nav.html.erb +++ b/app/views/layouts/_nav.html.erb @@ -20,11 +20,11 @@ <% if CurrentUser.is_anonymous? %> -
    -
    - Staff Notes (<%= user.staff_notes.count %>) -
    -

    <%= link_to "Staff Notes", staff_notes_path(search: { user_id: user.id }) %>

    - <%= render "staff_notes/partials/list_of_notes", staff_notes: user.staff_notes.limit(15), show_receiver_name: false %> -
    -

    <%= link_to "Create »", new_staff_note_path(search: { user_id: user.id }), class: "expand-new-staff-note" %>

    - <%= render "staff_notes/partials/new", user: user, staff_note: StaffNote.new(user_id: user.id), hidden: true %> -
    -
    -
    + <% end %> diff --git a/app/views/users/_about.html.erb b/app/views/users/_about.html.erb deleted file mode 100644 index faa72ecad..000000000 --- a/app/views/users/_about.html.erb +++ /dev/null @@ -1,14 +0,0 @@ -
    - <% if user.profile_about.present? %> -
    -

    About

    -
    <%= format_text(user.profile_about, allow_color: true) %>
    -
    - <% end %> - <% if user.profile_artinfo.present? %> -
    -

    Artist Information

    -
    <%= format_text(user.profile_artinfo, allow_color: true) %>
    -
    - <% end %> -
    diff --git a/app/views/users/_statistics.html.erb b/app/views/users/_statistics.html.erb deleted file mode 100644 index 5c230162c..000000000 --- a/app/views/users/_statistics.html.erb +++ /dev/null @@ -1,156 +0,0 @@ -
    -
    - <%= user_avatar @user %> -
    -
    -

    <%= link_to_user @user %>

    -
    -
    - Join Date - <%= compact_time @user.created_at %> - - Level - <%= "(Unactivated)" unless user.is_verified? %> <%= presenter.level %> - - <% if user.is_banned? && user.recent_ban %> - Ban reason - <%= format_text presenter.ban_reason %> - <% end %> - - Posts - - <%= presenter.active_upload_count(self) %> - [<%= link_to "pending", posts_path(tags: "user:#{user.name} status:pending") %>] - (<%= link_to "comments on", comments_path(group_by: :comment, search: {poster_id: user.id}) %>) - <% if CurrentUser.is_moderator? %> - (<%= link_to "votes", action: "index", controller: "post_votes", search: { user_name: user.name } %>) - <% end %> - - - Deleted - - <%= presenter.deleted_upload_count(self) %> - - - Replaced - - <%= presenter.replaced_upload_count(self) %> - [<%= link_to "pending", post_replacements_path(search: { creator_name: user.name }) %>] - - - Rejected - <%= presenter.rejected_replacements_count(self) %> - - Favorites - - <%= presenter.favorite_count(self) %> - - - Forum Posts - - <%= presenter.forum_post_count(self) %> - (<%= link_to "mentions", forum_posts_path(search: { body_matches: user.name }) %>) - - - Comments - - <%= presenter.comment_count(self) %> on <%= presenter.commented_posts_count(self) %> posts - (<%= link_to "mentions", comments_path(group_by: :comment, search:{ body_matches: user.name }) %>) - <% if CurrentUser.is_moderator? %> - (<%= link_to "votes", action: "index", controller: "comment_votes", search: { user_name: user.name } %>) - <% end %> - - - <% if user.can_approve_posts? || Post.where(approver: user).exists? %> - Approvals - <%= presenter.approval_count(self) %> - <% end %> - - <% if CurrentUser.user.id == user.id || CurrentUser.is_janitor? %> - <% if presenter.previous_names(self).present? %> - Previous Names - <%= presenter.previous_names(self) %> -> <%= user.name %> - <% end %> - <% end %> - - <% if CurrentUser.is_admin? %> - Email - - <%= user.email %> - <%= email_domain_search(user.email) %> - - Last IP - <%= link_to_ip(user.last_ip_addr) %> - <% end %> -
    - -
    - Feedback - - <%= presenter.feedbacks %> - <%= link_to("List", user_feedbacks_path(search: { user_id: @user.id })) %> - <% if CurrentUser.is_moderator? && @user.feedback.active.count == 0 %> - | <%= link_to("Create", new_user_feedback_path(user_feedback: { user_id: @user.id, category: "neutral" })) %> - <% end %> - - - Permissions - <%= presenter.permissions %> - - Upload Limit - - <%= presenter.upload_limit(self) %> - <% if CurrentUser.user.id == user.id %> - (<%= link_to "help", upload_limit_users_path %>) - <% else %> - (<%= link_to "help", wiki_page_path(id: "upload_limit") %>) - <% end %> - - - Post Changes - - <%= presenter.post_version_count(self) %> - <% if CurrentUser.is_moderator? && UserRevert.can_revert?(user) %> - [<%= link_to "revert all", new_user_revert_path(user_id: user.id) %>] - <% end %> - - - Wiki Changes - <%= presenter.wiki_page_version_count(self) %> - - Note Changes - <%= presenter.note_version_count(self) %> on <%= presenter.noted_posts_count(self) %> posts - - Artist Changes - <%= presenter.artist_version_count(self) %> - - Pool Changes - <%= presenter.pool_version_count(self) %> - - <% if CurrentUser.user.id == user.id || CurrentUser.is_janitor? %> - Flags - <%= presenter.flag_count(self) %> - <% end %> - - <% if CurrentUser.user.id == user.id || CurrentUser.is_moderator? %> - Tickets - - <%= presenter.ticket_count(self) %> - <% if CurrentUser.is_moderator? %> - [<%= link_to "pending", tickets_path(search: { creator_id: user.id, status: "pending" }) %>] - [<%= link_to "accused", tickets_path(search: { accused_id: user.id }) %>] - <% end %> - - <% end %> - - <% if CurrentUser.id == user.id %> - API Key - - <%= link_to (CurrentUser.api_key ? "View" : "Generate"), user_api_key_path(CurrentUser.user) %> - (<%= link_to "help", help_page_path(id: "api") %>) - - <% end %> -
    -
    -
    -
    diff --git a/app/views/users/partials/show/_about.html.erb b/app/views/users/partials/show/_about.html.erb new file mode 100644 index 000000000..636f651dd --- /dev/null +++ b/app/views/users/partials/show/_about.html.erb @@ -0,0 +1,17 @@ +<% if has_about %> + + About + + <%= format_text(user.profile_about, allow_color: true) %> + + +<% end %> + +<% if has_artinfo %> + + Artist Information + + <%= format_text(user.profile_artinfo, allow_color: true) %> + + +<% end %> diff --git a/app/views/users/partials/show/_ban_banner.html.erb b/app/views/users/partials/show/_ban_banner.html.erb new file mode 100644 index 000000000..a6ffe4218 --- /dev/null +++ b/app/views/users/partials/show/_ban_banner.html.erb @@ -0,0 +1,5 @@ +<% if user.is_banned? && user.recent_ban %> +
    + <%= format_text presenter.ban_reason %> +
    +<% end %> diff --git a/app/views/users/partials/show/_card.html.erb b/app/views/users/partials/show/_card.html.erb new file mode 100644 index 000000000..e635ce1f0 --- /dev/null +++ b/app/views/users/partials/show/_card.html.erb @@ -0,0 +1,20 @@ +
    +
    + <%= profile_avatar(@user) %> +
    +
    + + <%= link_to_user(@user) %> + <%= user_feedback_badge(@user) %> + + + Joined <%= compact_date @user.created_at %> + + + <%= user_level_badge(@user) %> + <% unless @user.is_verified? %> + UNACTIVATED + <% end %> + +
    +
    diff --git a/app/views/users/partials/show/_mentions.html.erb b/app/views/users/partials/show/_mentions.html.erb new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/users/_post_summary.html.erb b/app/views/users/partials/show/_post_summary.html.erb similarity index 89% rename from app/views/users/_post_summary.html.erb rename to app/views/users/partials/show/_post_summary.html.erb index 2c4873a71..886cf76f5 100644 --- a/app/views/users/_post_summary.html.erb +++ b/app/views/users/partials/show/_post_summary.html.erb @@ -4,7 +4,7 @@
    - <%= link_to "Uploads", posts_path(:tags => "user:#{user.name}"), class: "title" %> + <%= link_to "Uploads", posts_path(tags: "user:#{user.name}"), class: "title" %>