Feature #42684 » 0001-Add-sticky-issue-header.patch
| app/assets/stylesheets/application.css | ||
|---|---|---|
| 670 | 670 |
div.issue div.subject>div>p { margin-top: 0.5em; }
|
| 671 | 671 |
div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
|
| 672 | 672 |
div.issue p.author {margin-top:0.5em; font-size: 93%}
|
| 673 |
div.issue span.private, div.journal span.private {font-size: 60%;}
|
|
| 673 |
div.issue:not(#sticky-issue-header) > span.private, div.journal span.private {font-size: 60%;}
|
|
| 674 | 674 |
div.issue .next-prev-links {color:#999;}
|
| 675 | 675 |
div.issue .attributes {margin-top: 2em;}
|
| 676 | 676 |
div.issue .attributes .attribute {padding-left:180px; clear:left; min-height: 1.8em;}
|
| ... | ... | |
| 680 | 680 |
div.issue .attribute.text_cf .value .wiki p:first-of-type {margin-top: 0;}
|
| 681 | 681 |
div.issue.overdue .due-date .value { color: #c22; }
|
| 682 | 682 |
body.controller-issues h2.inline-flex {padding-right: 0}
|
| 683 |
div#sticky-issue-header {
|
|
| 684 |
display: none; |
|
| 685 |
position: fixed; |
|
| 686 |
top: 0; |
|
| 687 |
left: 0; |
|
| 688 |
right: 0; |
|
| 689 |
background-color: white; |
|
| 690 |
border-bottom: 1px solid #d0d7de; |
|
| 691 |
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); |
|
| 692 |
font-size: 0.750rem; |
|
| 693 |
align-items: center; |
|
| 694 |
z-index: 1000; |
|
| 695 |
padding: 10px 6px; |
|
| 696 |
border-radius: 0px; |
|
| 697 |
} |
|
| 698 |
div#sticky-issue-header.is-visible {
|
|
| 699 |
display: flex; |
|
| 700 |
} |
|
| 701 |
div#sticky-issue-header .issue-heading {
|
|
| 702 |
flex-shrink: 0; |
|
| 703 |
white-space: nowrap; |
|
| 704 |
margin-right: 6px; |
|
| 705 |
} |
|
| 706 |
div#sticky-issue-header .subject {
|
|
| 707 |
font-weight: bold; |
|
| 708 |
overflow: hidden; |
|
| 709 |
text-overflow: ellipsis; |
|
| 710 |
white-space: nowrap; |
|
| 711 |
flex-grow: 1; |
|
| 712 |
} |
|
| 683 | 713 | |
| 684 | 714 |
#issue_tree table.issues, #relations table.issues {border: 0;}
|
| 685 | 715 |
#issue_tree table.issues td, #relations table.issues td {border: 0;}
|
| app/assets/stylesheets/responsive.css | ||
|---|---|---|
| 848 | 848 |
font-size: 1.1em; |
| 849 | 849 |
text-align: left; |
| 850 | 850 |
} |
| 851 | ||
| 852 |
/* Sticky issue header */ |
|
| 853 |
/* When project-jump.drdn is visible in mobile layout, offset the sticky header by its height to prevent it from being hidden. */ |
|
| 854 |
div#sticky-issue-header {
|
|
| 855 |
top: 64px; |
|
| 856 |
} |
|
| 851 | 857 |
} |
| 852 | 858 | |
| 853 | 859 |
@media all and (max-width: 599px) {
|
| app/assets/stylesheets/wiki_syntax.css | ||
|---|---|---|
| 72 | 72 |
.syntaxhl .s1 { background-color: #fff0f0 }
|
| 73 | 73 | |
| 74 | 74 |
span.more_info { font-weight: normal; }
|
| 75 | ||
| 76 |
.markdown-alert {
|
|
| 77 |
border-left: 4px solid; |
|
| 78 |
padding-left: 10px; |
|
| 79 |
margin-left: 10px; |
|
| 80 |
} |
|
| 81 |
.markdown-alert-title {
|
|
| 82 |
font-weight: bold; |
|
| 83 |
} |
|
| 84 |
.markdown-alert-tip { border-color: #5db651; }
|
|
| 85 |
.markdown-alert-tip .markdown-alert-title { color: #005f00; }
|
|
| 86 |
.markdown-alert-important { border-color: #800080; }
|
|
| 87 |
.markdown-alert-important .markdown-alert-title { color: #4b006e; }
|
|
| 88 |
.markdown-alert-caution { border-color: #c22; }
|
|
| 89 |
.markdown-alert-caution .markdown-alert-title { color: #880000; }
|
|
| 90 |
.markdown-alert-warning { border-color: #e4bc4b; }
|
|
| 91 |
.markdown-alert-warning .markdown-alert-title { color: #a7760c; }
|
|
| 92 |
.markdown-alert-note { border-color: #169; }
|
|
| 93 |
.markdown-alert-note .markdown-alert-title { color: #1e40af; }
|
|
| app/javascript/controllers/sticky_issue_header_controller.js | ||
|---|---|---|
| 1 |
import { Controller } from "@hotwired/stimulus";
|
|
| 2 | ||
| 3 |
export default class extends Controller {
|
|
| 4 |
static targets = ["original", "stickyHeader"]; |
|
| 5 | ||
| 6 |
connect() {
|
|
| 7 |
if (!this.originalTarget || !this.stickyHeaderTarget) return; |
|
| 8 | ||
| 9 |
this.observer = new IntersectionObserver( |
|
| 10 |
([entry]) => {
|
|
| 11 |
this.stickyHeaderTarget.classList.toggle("is-visible", !entry.isIntersecting);
|
|
| 12 |
}, |
|
| 13 |
{ threshold: 0 }
|
|
| 14 |
); |
|
| 15 | ||
| 16 |
this.observer.observe(this.originalTarget); |
|
| 17 |
} |
|
| 18 | ||
| 19 |
disconnect() {
|
|
| 20 |
this.observer?.disconnect(); |
|
| 21 |
} |
|
| 22 |
} |
|
| app/views/issues/show.html.erb | ||
|---|---|---|
| 37 | 37 |
<%= assignee_avatar(@issue.assigned_to, :size => "22", :class => "gravatar-child") if @issue.assigned_to %> |
| 38 | 38 |
</div> |
| 39 | 39 | |
| 40 |
<div class="subject"> |
|
| 41 |
<%= render_issue_subject_with_tree(@issue) %> |
|
| 40 |
<div data-controller="sticky-issue-header"> |
|
| 41 |
<div class="subject" data-sticky-issue-header-target="original"> |
|
| 42 |
<%= render_issue_subject_with_tree(@issue) %> |
|
| 43 |
</div> |
|
| 44 |
<div id="sticky-issue-header" data-sticky-issue-header-target="stickyHeader" class="issue"> |
|
| 45 |
<span class="issue-heading"><%= issue_heading(@issue) %>:</span> |
|
| 46 |
<span class="subject"><%= @issue.subject %></span> |
|
| 47 |
</div> |
|
| 42 | 48 |
</div> |
| 49 | ||
| 43 | 50 |
<p class="author"> |
| 44 | 51 |
<%= authoring @issue.created_on, @issue.author %>. |
| 45 | 52 |
<% if @issue.created_on != @issue.updated_on %> |
| test/system/sticky_issue_header_test.rb | ||
|---|---|---|
| 1 |
# frozen_string_literal: true |
|
| 2 | ||
| 3 |
# Redmine - project management software |
|
| 4 |
# Copyright (C) 2006- Jean-Philippe Lang |
|
| 5 |
# |
|
| 6 |
# This program is free software; you can redistribute it and/or |
|
| 7 |
# modify it under the terms of the GNU General Public License |
|
| 8 |
# as published by the Free Software Foundation; either version 2 |
|
| 9 |
# of the License, or (at your option) any later version. |
|
| 10 |
# |
|
| 11 |
# This program is distributed in the hope that it will be useful, |
|
| 12 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 14 |
# GNU General Public License for more details. |
|
| 15 |
# |
|
| 16 |
# You should have received a copy of the GNU General Public License |
|
| 17 |
# along with this program; if not, write to the Free Software |
|
| 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 19 |
require_relative '../application_system_test_case' |
|
| 20 |
class StickyIssueHeaderSystemTest < ApplicationSystemTestCase |
|
| 21 |
test "sticky issue header is hidden by default" do |
|
| 22 |
issue = Issue.find(1) |
|
| 23 |
visit issue_path(issue) |
|
| 24 | ||
| 25 |
assert_no_selector "#sticky-issue-header", text: issue.subject |
|
| 26 |
end |
|
| 27 | ||
| 28 |
test "sticky issue header appears on scroll" do |
|
| 29 |
issue = Issue.find(1) |
|
| 30 |
visit issue_path(issue) |
|
| 31 | ||
| 32 |
page.execute_script("window.scrollTo(0, 1000)")
|
|
| 33 |
assert_selector "#sticky-issue-header.is-visible", text: issue.subject |
|
| 34 | ||
| 35 |
page.execute_script("window.scrollTo(0, 0)")
|
|
| 36 |
assert_no_selector "#sticky-issue-header", text: issue.subject |
|
| 37 |
end |
|
| 38 |
end |
|