From f97acd01e96d963108ed10d70ae02af25a6960ee Mon Sep 17 00:00:00 2001 From: Sean Yeoh Date: Mon, 1 Nov 2021 21:32:56 +0800 Subject: [PATCH 001/388] Validate that issue cannot be on a different project than node's project --- app/models/evidence.rb | 12 ++++++++++ spec/models/evidence_spec.rb | 44 ++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/app/models/evidence.rb b/app/models/evidence.rb index 1b6a770dc..f3187055b 100644 --- a/app/models/evidence.rb +++ b/app/models/evidence.rb @@ -20,6 +20,8 @@ class Evidence < ApplicationRecord validates :issue, presence: true, associated: true validates :node, presence: true, associated: true + validate :issue_cannot_be_on_another_project + # -- Scopes --------------------------------------------------------------- @@ -34,4 +36,14 @@ def local_fields 'Title' => issue.try(:title) } end + + private + + def issue_cannot_be_on_another_project + return unless node && issue + + if node.project.id != issue.project.id + errors.add(:issue, 'cannot be on another project') + end + end end diff --git a/spec/models/evidence_spec.rb b/spec/models/evidence_spec.rb index 1cc0ffd1d..f8bb8e19c 100644 --- a/spec/models/evidence_spec.rb +++ b/spec/models/evidence_spec.rb @@ -72,4 +72,48 @@ expect(@fields['Label']).to eq('Node Label') end end + + describe '#issue_cannot_be_on_another_project' do + let(:node) { create(:node) } + let(:issue) { create(:issue) } + + + context 'when node is not present' do + it 'does not trigger validation' do + evidence = Evidence.new(issue: issue) + evidence.valid? + expect(evidence.errors.full_messages).to_not include('Issue cannot be on another project') + end + end + + context 'when issue is not present' do + it 'does not trigger validation' do + evidence = Evidence.new(node: node) + evidence.valid? + expect(evidence.errors.full_messages).to_not include('Issue cannot be on another project') + end + end + + context 'when node and issue is in the same project' do + it 'is valid' do + evidence = Evidence.new(node: node, issue: issue) + expect(evidence.valid?).to eq(true) + end + end + + context 'when node and issue is on a different project' do + let(:issue_on_another_project) { (create(:issue, node: node_on_another_project)) } + let(:node_on_another_project) do + node = create(:node) + allow(node).to receive(:project).and_return(Project.new(id: 2, name: 'Another Project')) + node + end + + it 'is invalid' do + evidence = Evidence.new(node: node, issue: issue_on_another_project) + expect(evidence.valid?).to eq(false) + expect(evidence.errors.full_messages).to include('Issue cannot be on another project') + end + end + end end From 4e6cd5b1e4222e717d0fab3f0144f606d82e6dd0 Mon Sep 17 00:00:00 2001 From: Sean Yeoh Date: Mon, 1 Nov 2021 22:04:36 +0800 Subject: [PATCH 002/388] Fix evidence factory --- spec/factories/evidence.rb | 7 ++++++- spec/models/evidence_spec.rb | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/spec/factories/evidence.rb b/spec/factories/evidence.rb index 9683edd02..1b08d1424 100644 --- a/spec/factories/evidence.rb +++ b/spec/factories/evidence.rb @@ -2,7 +2,12 @@ factory :evidence do content { "#[EvidenceBlock1]#\nThis particular instance is terrible!\n\n" } author { "factory_bot" } - association :issue association :node + + after(:build) do |evidence| + unless evidence.issue + evidence.issue = create(:issue, node: evidence.node.project.issue_library) + end + end end end diff --git a/spec/models/evidence_spec.rb b/spec/models/evidence_spec.rb index f8bb8e19c..f988961c2 100644 --- a/spec/models/evidence_spec.rb +++ b/spec/models/evidence_spec.rb @@ -11,9 +11,10 @@ it { should validate_presence_of :node } describe 'on create' do + let(:issue) { create(:issue, project: node.project) } + let(:node) { create(:node) } + let(:subscribable) { build(:evidence, issue: issue, author: user.email, node: node) } let(:user) { create(:user) } - let(:issue) { create(:issue) } - let(:subscribable) { build(:evidence, issue: issue, author: user.email) } it_behaves_like 'a subscribable model' end @@ -74,9 +75,8 @@ end describe '#issue_cannot_be_on_another_project' do + let(:issue) { create(:issue, project: node.project.issue_library) } let(:node) { create(:node) } - let(:issue) { create(:issue) } - context 'when node is not present' do it 'does not trigger validation' do From 4087edcaa082133a9d6f2b05192039ca72a673b2 Mon Sep 17 00:00:00 2001 From: Sean Yeoh Date: Mon, 1 Nov 2021 22:07:19 +0800 Subject: [PATCH 003/388] Add project to issue factory object --- spec/models/evidence_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/evidence_spec.rb b/spec/models/evidence_spec.rb index f988961c2..bdd88c89e 100644 --- a/spec/models/evidence_spec.rb +++ b/spec/models/evidence_spec.rb @@ -75,7 +75,7 @@ end describe '#issue_cannot_be_on_another_project' do - let(:issue) { create(:issue, project: node.project.issue_library) } + let(:issue) { create(:issue, node: node.project.issue_library, project: node.project) } let(:node) { create(:node) } context 'when node is not present' do From 351a08da25666685e0d52a77e3697b28cd25f42b Mon Sep 17 00:00:00 2001 From: Sean Yeoh Date: Tue, 2 Nov 2021 19:21:51 +0800 Subject: [PATCH 004/388] Add changelog entry --- CHANGELOG | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 20f906ac2..2ee19778f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,14 +22,14 @@ - [future tense verb] [integration enhancement] - [integration bug fixes]: - [future tense verb] [integration bug fix] - - Reporting enhancements: + - Reporting enhancements:Security Fixes - [report type]: - [future tense verb] [reporting enhancement] - REST/JSON API enhancements: - [API entity]: - [future tense verb] [API enhancement] - Security Fixes: - - High: (Authenticated|Unauthenticated) (admin|author|contributor) [vulnerability description] + - High: Authenticated author creating an evidence with an issue outside of the project will be able to read the issue's content - Medium: (Authenticated|Unauthenticated) (admin|author|contributor) [vulnerability description] - Low: (Authenticated|Unauthenticated) (admin|author|contributor) [vulnerability description] From 96ea6b5551adf82421690b6c329db92f922fe9af Mon Sep 17 00:00:00 2001 From: Sean Yeoh Date: Tue, 2 Nov 2021 19:25:21 +0800 Subject: [PATCH 005/388] Fix typo --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2ee19778f..03e274e8c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,7 +22,7 @@ - [future tense verb] [integration enhancement] - [integration bug fixes]: - [future tense verb] [integration bug fix] - - Reporting enhancements:Security Fixes + - Reporting enhancements: - [report type]: - [future tense verb] [reporting enhancement] - REST/JSON API enhancements: From d50bf5ce5de8694999c18ca1b9c812083d6fb2c0 Mon Sep 17 00:00:00 2001 From: Sean Yeoh Date: Tue, 2 Nov 2021 19:26:03 +0800 Subject: [PATCH 006/388] Update changelog entry --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 03e274e8c..be78c1b42 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,7 +29,7 @@ - [API entity]: - [future tense verb] [API enhancement] - Security Fixes: - - High: Authenticated author creating an evidence with an issue outside of the project will be able to read the issue's content + - High: Authenticated author creating an evidence with an issue outside of the project can read the issue's content - Medium: (Authenticated|Unauthenticated) (admin|author|contributor) [vulnerability description] - Low: (Authenticated|Unauthenticated) (admin|author|contributor) [vulnerability description] From 8664a009cf38b9f6788623d061030edc4a09de65 Mon Sep 17 00:00:00 2001 From: Sean Yeoh Date: Wed, 3 Nov 2021 17:35:02 +0800 Subject: [PATCH 007/388] Backport spec changes --- spec/models/node_spec.rb | 2 +- spec/models/recoverable_revision_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/node_spec.rb b/spec/models/node_spec.rb index 1f8cda821..f1a964044 100644 --- a/spec/models/node_spec.rb +++ b/spec/models/node_spec.rb @@ -51,7 +51,7 @@ it 'returns unique issues even if node and issue are associated through multiple evidence' do node = create(:node) - issue = create(:issue) + issue = create(:issue, node: node.project.issue_library) create(:evidence, node: node, issue: issue) create(:evidence, node: node, issue: issue) diff --git a/spec/models/recoverable_revision_spec.rb b/spec/models/recoverable_revision_spec.rb index 15ffb96b2..b19887f0f 100644 --- a/spec/models/recoverable_revision_spec.rb +++ b/spec/models/recoverable_revision_spec.rb @@ -44,7 +44,7 @@ describe "recovering an Evidence whose Issue has been deleted" do it "recovers the Issue as well" do issue = create(:issue, node: @project.issue_library) - evidence = create(:evidence, issue: issue) + evidence = create(:evidence, issue: issue, node: create(:node, project: @project)) evidence.destroy issue.destroy From bae7c7779caf0d7d374604ddbdb09c965f4bcbd0 Mon Sep 17 00:00:00 2001 From: Sean Yeoh Date: Mon, 8 Nov 2021 22:11:30 +0800 Subject: [PATCH 008/388] Rename validation method to something friendlier --- app/models/evidence.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/evidence.rb b/app/models/evidence.rb index f3187055b..c25f3f823 100644 --- a/app/models/evidence.rb +++ b/app/models/evidence.rb @@ -20,7 +20,7 @@ class Evidence < ApplicationRecord validates :issue, presence: true, associated: true validates :node, presence: true, associated: true - validate :issue_cannot_be_on_another_project + validate :relations_scoped_to_project # -- Scopes --------------------------------------------------------------- @@ -39,11 +39,11 @@ def local_fields private - def issue_cannot_be_on_another_project + def relations_scoped_to_project return unless node && issue if node.project.id != issue.project.id - errors.add(:issue, 'cannot be on another project') + errors.add(:issue, 'must be within the project') end end end From 709b8569b45f2f9f1d0745accf46a322233e0a42 Mon Sep 17 00:00:00 2001 From: Sean Yeoh Date: Mon, 8 Nov 2021 22:29:31 +0800 Subject: [PATCH 009/388] Delete project call to node --- app/models/evidence.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/evidence.rb b/app/models/evidence.rb index c25f3f823..d513caab4 100644 --- a/app/models/evidence.rb +++ b/app/models/evidence.rb @@ -42,7 +42,7 @@ def local_fields def relations_scoped_to_project return unless node && issue - if node.project.id != issue.project.id + if project.id != issue.project.id errors.add(:issue, 'must be within the project') end end From 8fbb7e83792760ad00d73935ccdbd2fbbd4f7c45 Mon Sep 17 00:00:00 2001 From: Sean Yeoh Date: Mon, 8 Nov 2021 22:33:13 +0800 Subject: [PATCH 010/388] Update spec expectation message --- spec/models/evidence_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/models/evidence_spec.rb b/spec/models/evidence_spec.rb index bdd88c89e..8b12a29cc 100644 --- a/spec/models/evidence_spec.rb +++ b/spec/models/evidence_spec.rb @@ -82,7 +82,7 @@ it 'does not trigger validation' do evidence = Evidence.new(issue: issue) evidence.valid? - expect(evidence.errors.full_messages).to_not include('Issue cannot be on another project') + expect(evidence.errors.full_messages).to_not include('Issue must be within the project') end end @@ -90,7 +90,7 @@ it 'does not trigger validation' do evidence = Evidence.new(node: node) evidence.valid? - expect(evidence.errors.full_messages).to_not include('Issue cannot be on another project') + expect(evidence.errors.full_messages).to_not include('Issue must be within the project') end end @@ -112,7 +112,7 @@ it 'is invalid' do evidence = Evidence.new(node: node, issue: issue_on_another_project) expect(evidence.valid?).to eq(false) - expect(evidence.errors.full_messages).to include('Issue cannot be on another project') + expect(evidence.errors.full_messages).to include('Issue must be within the project') end end end From 1d47707edb7192250a7eb73b431ccadf7385dc12 Mon Sep 17 00:00:00 2001 From: Sean Yeoh Date: Tue, 9 Nov 2021 22:02:49 +0800 Subject: [PATCH 011/388] Improve changelog language. Rename validation method name. --- CHANGELOG | 2 +- app/models/evidence.rb | 6 +++--- spec/models/evidence_spec.rb | 23 +++-------------------- 3 files changed, 7 insertions(+), 24 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index be78c1b42..fd7fb45ac 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,7 +29,7 @@ - [API entity]: - [future tense verb] [API enhancement] - Security Fixes: - - High: Authenticated author creating an evidence with an issue outside of the project can read the issue's content + - High: Authenticated (author) broken access control in reading unauthorized issue content - Medium: (Authenticated|Unauthenticated) (admin|author|contributor) [vulnerability description] - Low: (Authenticated|Unauthenticated) (admin|author|contributor) [vulnerability description] diff --git a/app/models/evidence.rb b/app/models/evidence.rb index d513caab4..e9f9002cf 100644 --- a/app/models/evidence.rb +++ b/app/models/evidence.rb @@ -20,7 +20,7 @@ class Evidence < ApplicationRecord validates :issue, presence: true, associated: true validates :node, presence: true, associated: true - validate :relations_scoped_to_project + validate :validate_issue_project # -- Scopes --------------------------------------------------------------- @@ -39,11 +39,11 @@ def local_fields private - def relations_scoped_to_project + def validate_issue_project return unless node && issue if project.id != issue.project.id - errors.add(:issue, 'must be within the project') + errors.add(:issue, 'is invalid') end end end diff --git a/spec/models/evidence_spec.rb b/spec/models/evidence_spec.rb index 8b12a29cc..410db96a8 100644 --- a/spec/models/evidence_spec.rb +++ b/spec/models/evidence_spec.rb @@ -74,34 +74,18 @@ end end - describe '#issue_cannot_be_on_another_project' do + describe '#validate_issue_project' do let(:issue) { create(:issue, node: node.project.issue_library, project: node.project) } let(:node) { create(:node) } - context 'when node is not present' do - it 'does not trigger validation' do - evidence = Evidence.new(issue: issue) - evidence.valid? - expect(evidence.errors.full_messages).to_not include('Issue must be within the project') - end - end - - context 'when issue is not present' do - it 'does not trigger validation' do - evidence = Evidence.new(node: node) - evidence.valid? - expect(evidence.errors.full_messages).to_not include('Issue must be within the project') - end - end - - context 'when node and issue is in the same project' do + context 'when issue is in the same project' do it 'is valid' do evidence = Evidence.new(node: node, issue: issue) expect(evidence.valid?).to eq(true) end end - context 'when node and issue is on a different project' do + context 'when issue is on a different project' do let(:issue_on_another_project) { (create(:issue, node: node_on_another_project)) } let(:node_on_another_project) do node = create(:node) @@ -112,7 +96,6 @@ it 'is invalid' do evidence = Evidence.new(node: node, issue: issue_on_another_project) expect(evidence.valid?).to eq(false) - expect(evidence.errors.full_messages).to include('Issue must be within the project') end end end From 4173c39a9b48e61ba9aa2593313894acfd213750 Mon Sep 17 00:00:00 2001 From: Sean Yeoh Date: Wed, 10 Nov 2021 19:27:33 +0800 Subject: [PATCH 012/388] Remove redundant language in changelog --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index fd7fb45ac..db1cb6704 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,7 +29,7 @@ - [API entity]: - [future tense verb] [API enhancement] - Security Fixes: - - High: Authenticated (author) broken access control in reading unauthorized issue content + - High: Authenticated author broken access control: read access to issue content - Medium: (Authenticated|Unauthenticated) (admin|author|contributor) [vulnerability description] - Low: (Authenticated|Unauthenticated) (admin|author|contributor) [vulnerability description] From 344876c4da75efde6527b158199b07c5ac5084fe Mon Sep 17 00:00:00 2001 From: Matt Budz Date: Fri, 24 Mar 2023 15:18:00 +0100 Subject: [PATCH 013/388] track activity when the state is changed --- app/controllers/concerns/activity_tracking.rb | 4 ++++ app/controllers/qa/issues_controller.rb | 6 +++++- app/models/activity.rb | 6 +++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/controllers/concerns/activity_tracking.rb b/app/controllers/concerns/activity_tracking.rb index 3029440f0..4261d882d 100644 --- a/app/controllers/concerns/activity_tracking.rb +++ b/app/controllers/concerns/activity_tracking.rb @@ -30,4 +30,8 @@ def track_recovered(trackable, user: current_user, project: current_project) def track_updated(trackable, user: current_user, project: current_project) track_activity(trackable, :update, user, project) end + + def track_updated_state(trackable, user: current_user, project: current_project) + track_activity(trackable, :update_state, user, project) + end end diff --git a/app/controllers/qa/issues_controller.rb b/app/controllers/qa/issues_controller.rb index 4eadd6f4d..acfc2c6cc 100644 --- a/app/controllers/qa/issues_controller.rb +++ b/app/controllers/qa/issues_controller.rb @@ -1,4 +1,5 @@ class QA::IssuesController < AuthenticatedController + include ActivityTracking include LiquidEnabledResource include ProjectScoped @@ -20,10 +21,13 @@ def edit end def update - @issues = current_project.issues.ready_for_review.where(id: params[:ids]) + @issues = current_project.issues.where(id: params[:ids]) respond_to do |format| if @issues.update_all(state: @state, updated_at: Time.now) + @issues.each do |issue| + track_updated_state(issue) + end format.html { redirect_to project_qa_issues_path(current_project), notice: 'State updated successfully.' } format.json { head :ok } else diff --git a/app/models/activity.rb b/app/models/activity.rb index c0965d91f..a2d689210 100644 --- a/app/models/activity.rb +++ b/app/models/activity.rb @@ -17,14 +17,14 @@ def project=(new_project); end validates_presence_of :action, :trackable_id, :trackable_type, :user - VALID_ACTIONS = %w[create update destroy recover] + VALID_ACTIONS = %w[create destroy recover update update_state] validates_inclusion_of :action, in: VALID_ACTIONS # -- Scopes --------------------------------------------------------------- scope :latest, -> do - includes(:trackable).order("activities.created_at DESC").limit(20) + includes(:trackable).order('activities.created_at DESC').limit(20) end # -- Callbacks ------------------------------------------------------------ @@ -45,7 +45,7 @@ def project=(new_project); end # FIXME - ISSUE/NOTE INHERITANCE def trackable=(new_trackable) super - self.trackable_type = "Issue" if new_trackable.is_a?(Issue) + self.trackable_type = 'Issue' if new_trackable.is_a?(Issue) new_trackable end From 10b111781718d68e093db163dc0930c2fd188b10 Mon Sep 17 00:00:00 2001 From: Matt Budz Date: Fri, 24 Mar 2023 15:18:24 +0100 Subject: [PATCH 014/388] show state change activities in the activity feed --- app/presenters/activity_presenter.rb | 7 +++++-- app/views/issues/activities/_issue.html.erb | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/presenters/activity_presenter.rb b/app/presenters/activity_presenter.rb index 6c38cbd00..4cdc33a93 100644 --- a/app/presenters/activity_presenter.rb +++ b/app/presenters/activity_presenter.rb @@ -79,8 +79,11 @@ def render_title # but this may change if we add activities whose action is an irregular # verb. def verb - if activity.action == 'destroy' + case activity.action + when 'destroy' 'deleted' + when 'update_state' + 'updated' else activity.action.sub(/e?\z/, 'ed') end @@ -111,7 +114,7 @@ def partial_paths end def render_partial - locals = {activity: activity, presenter: self} + locals = { activity: activity, presenter: self } locals[trackable_name] = activity.trackable render partial_path, locals end diff --git a/app/views/issues/activities/_issue.html.erb b/app/views/issues/activities/_issue.html.erb index 00c1694e4..b09602df5 100644 --- a/app/views/issues/activities/_issue.html.erb +++ b/app/views/issues/activities/_issue.html.erb @@ -1,6 +1,6 @@ <% if issue %> <% if issue.title? %> - <%= presenter.verb %> the <%= link_to issue.title, [current_project, issue] %> issue. + <%= presenter.verb %> the <%= link_to issue.title, [current_project, issue] %> issue<%= " to #{issue.state.humanize.downcase}" if activity.action == 'update_state' %>. <% else %> <%= presenter.verb %> <%= link_to "Issue ##{issue.id}", [current_project, issue] %>. <% end %> From 7d697f8f910b3450a51682c3b8c595a829ee6857 Mon Sep 17 00:00:00 2001 From: Matt Budz Date: Fri, 24 Mar 2023 15:27:41 +0100 Subject: [PATCH 015/388] update copy --- app/views/issues/activities/_issue.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/issues/activities/_issue.html.erb b/app/views/issues/activities/_issue.html.erb index b09602df5..b3080afc0 100644 --- a/app/views/issues/activities/_issue.html.erb +++ b/app/views/issues/activities/_issue.html.erb @@ -1,6 +1,6 @@ <% if issue %> <% if issue.title? %> - <%= presenter.verb %> the <%= link_to issue.title, [current_project, issue] %> issue<%= " to #{issue.state.humanize.downcase}" if activity.action == 'update_state' %>. + <%= presenter.verb %> the <%= link_to issue.title, [current_project, issue] %> <%= activity.action == 'update_state' ? "issue's state to #{issue.state.humanize.downcase}" : 'issue' %>. <% else %> <%= presenter.verb %> <%= link_to "Issue ##{issue.id}", [current_project, issue] %>. <% end %> From f23d5ecccb642b409dc3ae165c08b0766a43268e Mon Sep 17 00:00:00 2001 From: Matt Budz Date: Fri, 24 Mar 2023 17:47:32 +0100 Subject: [PATCH 016/388] initial spec updates --- spec/support/qa_shared_examples.rb | 68 ++++++++++++++++++------------ 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/spec/support/qa_shared_examples.rb b/spec/support/qa_shared_examples.rb index 6dd3fb397..b34bb66aa 100644 --- a/spec/support/qa_shared_examples.rb +++ b/spec/support/qa_shared_examples.rb @@ -1,6 +1,9 @@ shared_examples 'qa pages' do |item_type| describe 'index page', js: true do + MODEL = item_type.to_s.classify.constantize + STATES = ['Draft', 'Published'] + before do visit polymorphic_path([current_project, :qa, item_type.to_s.pluralize.to_sym]) end @@ -48,45 +51,56 @@ end it 'updates the list of records with the state' do - within '.dataTables_wrapper' do - @original_row_count = page.all('tbody tr').count - page.find('td.select-checkbox', match: :first).click - - click_button('State') - click_link('Published') + STATES.each do |state| + record = MODEL.where(state: 'ready_for_review').first + visit polymorphic_path([current_project, :qa, item_type.to_s.pluralize.to_sym]) + + within '.dataTables_wrapper' do + @original_row_count = page.all('tbody tr').count + page.find('td.select-checkbox', match: :first).click + + click_button('State') + expect { click_link state }.to have_enqueued_job(ActivityTrackingJob).with( + action: 'update_state', + project_id: current_project.id, + trackable_id: record.id, + trackable_type: record.class.to_s, + user_id: @logged_in_as.id + ) + + expect(current_path).to eq polymorphic_path([current_project, :qa, item_type.to_s.pluralize.to_sym]) + expect(page.all('tbody tr').count).to eq(@original_row_count - 1) + expect(page).to have_selector('.alert-success', text: 'State updated successfully.') + expect(record.reload.state).to eq state.downcase.gsub(' ', '_') + end end - - page.find('.alert') - - expect(page.all('tbody tr').count).to eq(@original_row_count - 1) - expect(page).to have_selector('.alert-success', text: 'State updated successfully.') end end end describe 'show page' do - before do - visit polymorphic_path([current_project, :qa, records.first]) - end - it 'shows the record\'s content' do + visit polymorphic_path([current_project, :qa, records.first]) expect(page).to have_content(records.first.title) end - it 'updates the state to draft' do - click_button 'Draft' + it 'updates the state' do + STATES.each do |state| + record = MODEL.where(state: 'ready_for_review').first + visit polymorphic_path([current_project, :qa, record]) - expect(current_path).to eq polymorphic_path([current_project, :qa, item_type.to_s.pluralize.to_sym]) - expect(page).to have_selector('.alert-success', text: 'State updated successfully.') - expect(records.first.reload.draft?).to eq true - end + expect { click_button state }.to have_enqueued_job(ActivityTrackingJob).with( + action: 'update_state', + project_id: current_project.id, + trackable_id: record.id, + trackable_type: record.class.to_s, + user_id: @logged_in_as.id + ) - it 'updates the state to published' do - click_button 'Published' - - expect(current_path).to eq polymorphic_path([current_project, :qa, item_type.to_s.pluralize.to_sym]) - expect(page).to have_selector('.alert-success', text: 'State updated successfully.') - expect(records.first.reload.published?).to eq true + expect(current_path).to eq polymorphic_path([current_project, :qa, item_type.to_s.pluralize.to_sym]) + expect(page).to have_selector('.alert-success', text: 'State updated successfully.') + expect(record.reload.state).to eq state.downcase.gsub(' ', '_') + end end end From ea3d3e07a1576d487ce0a8547a8df37b71570ab2 Mon Sep 17 00:00:00 2001 From: Matt Budz Date: Fri, 24 Mar 2023 18:05:07 +0100 Subject: [PATCH 017/388] fix failing spec --- spec/support/qa_shared_examples.rb | 38 ++++++++++++++++-------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/spec/support/qa_shared_examples.rb b/spec/support/qa_shared_examples.rb index b34bb66aa..02feacd03 100644 --- a/spec/support/qa_shared_examples.rb +++ b/spec/support/qa_shared_examples.rb @@ -55,24 +55,26 @@ record = MODEL.where(state: 'ready_for_review').first visit polymorphic_path([current_project, :qa, item_type.to_s.pluralize.to_sym]) - within '.dataTables_wrapper' do - @original_row_count = page.all('tbody tr').count - page.find('td.select-checkbox', match: :first).click - - click_button('State') - expect { click_link state }.to have_enqueued_job(ActivityTrackingJob).with( - action: 'update_state', - project_id: current_project.id, - trackable_id: record.id, - trackable_type: record.class.to_s, - user_id: @logged_in_as.id - ) - - expect(current_path).to eq polymorphic_path([current_project, :qa, item_type.to_s.pluralize.to_sym]) - expect(page.all('tbody tr').count).to eq(@original_row_count - 1) - expect(page).to have_selector('.alert-success', text: 'State updated successfully.') - expect(record.reload.state).to eq state.downcase.gsub(' ', '_') - end + @original_row_count = page.all('tbody tr').count + page.find('td.select-checkbox', match: :first).click + + click_button('State') + expect do + click_link state + # Wait for action to complete + page.find('.alert') + end.to have_enqueued_job(ActivityTrackingJob).with( + action: 'update_state', + project_id: current_project.id, + trackable_id: record.id, + trackable_type: record.class.to_s, + user_id: @logged_in_as.id + ) + + expect(current_path).to eq polymorphic_path([current_project, :qa, item_type.to_s.pluralize.to_sym]) + expect(page.all('tbody tr').count).to eq(@original_row_count - 1) + expect(page).to have_selector('.alert-success', text: 'State updated successfully.') + expect(record.reload.state).to eq state.downcase.gsub(' ', '_') end end end From c5b6cc5b598e5be21a101040b204450d65ec68a2 Mon Sep 17 00:00:00 2001 From: Matt Budz Date: Fri, 24 Mar 2023 18:12:51 +0100 Subject: [PATCH 018/388] rubocop --- app/presenters/activity_presenter.rb | 41 ++++++++++++++-------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/app/presenters/activity_presenter.rb b/app/presenters/activity_presenter.rb index 4cdc33a93..2e1130aae 100644 --- a/app/presenters/activity_presenter.rb +++ b/app/presenters/activity_presenter.rb @@ -41,22 +41,23 @@ def created_at_ago def icon icon_css = %w{activity-icon fa} - icon_css << case activity.trackable_type - when 'Board', 'List', 'Card' - 'fa-trello' - when 'Comment' - 'fa-comment' - when 'Evidence' - 'fa-flag' - when 'Issue' - 'fa-bug' - when 'Node' - 'fa-folder-o' - when 'Note' - 'fa-file-text-o' - else - '' - end + icon_css << + case activity.trackable_type + when 'Board', 'List', 'Card' + 'fa-trello' + when 'Comment' + 'fa-comment' + when 'Evidence' + 'fa-flag' + when 'Issue' + 'fa-bug' + when 'Node' + 'fa-folder-o' + when 'Note' + 'fa-file-text-o' + else + '' + end h.content_tag :span, nil, class: icon_css end @@ -128,9 +129,9 @@ def trackable_name def trackable_title @title ||= if activity.trackable.respond_to?(:title) && activity.trackable.title? - activity.trackable.title - elsif activity.trackable.respond_to?(:label) && activity.trackable.label? - activity.trackable.label - end + activity.trackable.title + elsif activity.trackable.respond_to?(:label) && activity.trackable.label? + activity.trackable.label + end end end From ba80ecc3a9167582c3d75004f476079135ee7332 Mon Sep 17 00:00:00 2001 From: Matt Budz Date: Tue, 28 Mar 2023 14:32:38 +0200 Subject: [PATCH 019/388] use `let` vs defining constants --- spec/support/qa_shared_examples.rb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/spec/support/qa_shared_examples.rb b/spec/support/qa_shared_examples.rb index 02feacd03..5a010b2f1 100644 --- a/spec/support/qa_shared_examples.rb +++ b/spec/support/qa_shared_examples.rb @@ -1,9 +1,8 @@ shared_examples 'qa pages' do |item_type| + let(:model) { item_type.to_s.classify.constantize } + let(:states) { ['Draft', 'Published'] } describe 'index page', js: true do - MODEL = item_type.to_s.classify.constantize - STATES = ['Draft', 'Published'] - before do visit polymorphic_path([current_project, :qa, item_type.to_s.pluralize.to_sym]) end @@ -51,8 +50,8 @@ end it 'updates the list of records with the state' do - STATES.each do |state| - record = MODEL.where(state: 'ready_for_review').first + states.each do |state| + record = model.where(state: 'ready_for_review').first visit polymorphic_path([current_project, :qa, item_type.to_s.pluralize.to_sym]) @original_row_count = page.all('tbody tr').count @@ -87,8 +86,8 @@ end it 'updates the state' do - STATES.each do |state| - record = MODEL.where(state: 'ready_for_review').first + states.each do |state| + record = model.where(state: 'ready_for_review').first visit polymorphic_path([current_project, :qa, record]) expect { click_button state }.to have_enqueued_job(ActivityTrackingJob).with( From ab8de9fe10a5eb3c5835c6a5b0076e4b3295594c Mon Sep 17 00:00:00 2001 From: Matt Budz Date: Tue, 28 Mar 2023 14:54:30 +0200 Subject: [PATCH 020/388] make the specs a bit more DRY --- spec/support/qa_shared_examples.rb | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/spec/support/qa_shared_examples.rb b/spec/support/qa_shared_examples.rb index 5a010b2f1..4d763019e 100644 --- a/spec/support/qa_shared_examples.rb +++ b/spec/support/qa_shared_examples.rb @@ -1,4 +1,14 @@ shared_examples 'qa pages' do |item_type| + def job_params(record) + { + action: 'update_state', + project_id: current_project.id, + trackable_id: record.id, + trackable_type: record.class.to_s, + user_id: @logged_in_as.id + } + end + let(:model) { item_type.to_s.classify.constantize } let(:states) { ['Draft', 'Published'] } @@ -62,13 +72,7 @@ click_link state # Wait for action to complete page.find('.alert') - end.to have_enqueued_job(ActivityTrackingJob).with( - action: 'update_state', - project_id: current_project.id, - trackable_id: record.id, - trackable_type: record.class.to_s, - user_id: @logged_in_as.id - ) + end.to have_enqueued_job(ActivityTrackingJob).with(job_params(record)) expect(current_path).to eq polymorphic_path([current_project, :qa, item_type.to_s.pluralize.to_sym]) expect(page.all('tbody tr').count).to eq(@original_row_count - 1) @@ -90,13 +94,7 @@ record = model.where(state: 'ready_for_review').first visit polymorphic_path([current_project, :qa, record]) - expect { click_button state }.to have_enqueued_job(ActivityTrackingJob).with( - action: 'update_state', - project_id: current_project.id, - trackable_id: record.id, - trackable_type: record.class.to_s, - user_id: @logged_in_as.id - ) + expect { click_button state }.to have_enqueued_job(ActivityTrackingJob).with(job_params(record)) expect(current_path).to eq polymorphic_path([current_project, :qa, item_type.to_s.pluralize.to_sym]) expect(page).to have_selector('.alert-success', text: 'State updated successfully.') From 2eb43bb254d4312e485664688e66b33903810024 Mon Sep 17 00:00:00 2001 From: Matt Budz Date: Thu, 30 Mar 2023 15:48:10 +0200 Subject: [PATCH 021/388] formatting --- app/presenters/activity_presenter.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/presenters/activity_presenter.rb b/app/presenters/activity_presenter.rb index 2e1130aae..28d60cdfc 100644 --- a/app/presenters/activity_presenter.rb +++ b/app/presenters/activity_presenter.rb @@ -128,7 +128,8 @@ def trackable_name end def trackable_title - @title ||= if activity.trackable.respond_to?(:title) && activity.trackable.title? + @title ||= + if activity.trackable.respond_to?(:title) && activity.trackable.title? activity.trackable.title elsif activity.trackable.respond_to?(:label) && activity.trackable.label? activity.trackable.label From a35c81004225d9be54dbd0ba8b4f39b6c1ed14d2 Mon Sep 17 00:00:00 2001 From: Matt Budz Date: Thu, 30 Mar 2023 15:49:34 +0200 Subject: [PATCH 022/388] move method to bottom of file for consistency --- spec/support/qa_shared_examples.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/support/qa_shared_examples.rb b/spec/support/qa_shared_examples.rb index 4d763019e..32cd00767 100644 --- a/spec/support/qa_shared_examples.rb +++ b/spec/support/qa_shared_examples.rb @@ -1,14 +1,4 @@ shared_examples 'qa pages' do |item_type| - def job_params(record) - { - action: 'update_state', - project_id: current_project.id, - trackable_id: record.id, - trackable_type: record.class.to_s, - user_id: @logged_in_as.id - } - end - let(:model) { item_type.to_s.classify.constantize } let(:states) { ['Draft', 'Published'] } @@ -129,4 +119,14 @@ def job_params(record) expect(current_path).to eq polymorphic_path([current_project, :qa, records.first]) end end + + def job_params(record) + { + action: 'update_state', + project_id: current_project.id, + trackable_id: record.id, + trackable_type: record.class.to_s, + user_id: @logged_in_as.id + } + end end From 3fe1b1d6ed7f00e24e3e2beabf4b4bfbaeb4c013 Mon Sep 17 00:00:00 2001 From: Matt Budz Date: Wed, 19 Apr 2023 20:00:08 +0200 Subject: [PATCH 023/388] fix `500`when updating state in `QA::Issues#edit` --- app/controllers/qa/issues_controller.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/controllers/qa/issues_controller.rb b/app/controllers/qa/issues_controller.rb index 9f7db697d..4adc7e0ec 100644 --- a/app/controllers/qa/issues_controller.rb +++ b/app/controllers/qa/issues_controller.rb @@ -3,16 +3,19 @@ class QA::IssuesController < AuthenticatedController include ProjectScoped before_action :set_issues - before_action :set_issue, only: [:edit, :show, :update] + before_action :set_issue, only: [:edit, :update] before_action :store_location, only: [:index, :show] before_action :validate_state, only: [:multiple_update, :update] def index - @issues = current_project.issues.ready_for_review @all_columns = @default_columns = ['Title'] end - def show; end + def show + @issue = current_project.issues.find(params[:id]) + + redirect_to project_qa_issues_path(current_project) unless @issue.ready_for_review? + end def edit @form_cancel_path = project_qa_issue_path(current_project, @issue) From 5cd8e228798c05b1998d1bb0d249be3165d517e4 Mon Sep 17 00:00:00 2001 From: Matt Budz Date: Wed, 19 Apr 2023 20:00:16 +0200 Subject: [PATCH 024/388] update CHANGELOG --- CHANGELOG | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 35430edfe..77d4e5413 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,8 +4,7 @@ - Upgraded gems: - [gem] - Bugs fixes: - - [entity]: - - [future tense verb] [bug fix] + - QA: Redirect to correct view when changing states on QA edit views - Bug tracker items: - [item] - New integrations: From 40919cd4cd6f6212691b2879bda93a633b267e26 Mon Sep 17 00:00:00 2001 From: Matt Budz Date: Tue, 25 Apr 2023 11:55:37 +0200 Subject: [PATCH 025/388] add specs --- spec/support/qa_shared_examples.rb | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/spec/support/qa_shared_examples.rb b/spec/support/qa_shared_examples.rb index 6dd3fb397..b8537e6af 100644 --- a/spec/support/qa_shared_examples.rb +++ b/spec/support/qa_shared_examples.rb @@ -12,7 +12,7 @@ end end - it 'redirects the user back after updating the record' do + it 'redirects the user back to #index after updating the record' do find('.dataTable tbody tr:first-of-type').hover click_link 'Edit' @@ -99,7 +99,7 @@ end end - it 'redirects the user back after updating the record' do + it 'redirects the user back to #show after updating the record' do expect(current_path).to eq polymorphic_path([:edit, current_project, :qa, records.first]) click_button "Update #{item_type.to_s.titleize}" @@ -108,12 +108,24 @@ expect(page).to have_selector('.alert-success', text: "#{item_type.to_s.humanize} updated.") end - it 'redirects the user back after cancelling' do + it 'redirects the user back to #show after cancelling' do expect(current_path).to eq polymorphic_path([:edit, current_project, :qa, records.first]) click_link 'Cancel' expect(current_path).to eq polymorphic_path([current_project, :qa, records.first]) end + + it 'redirects the user to #index after a state change' do + expect(current_path).to eq polymorphic_path([:edit, current_project, :qa, records.first]) + + click_button 'Toggle Dropdown' + choose "#{item_type}_state_published" + + click_button "Update #{item_type.to_s.titleize}" + + expect(current_path).to eq polymorphic_path([current_project, :qa, item_type.to_s.pluralize.to_sym]) + expect(page).to have_selector('.alert-success', text: "#{item_type.to_s.humanize} updated.") + end end end From 9c64e1f3a01ab73c24883de09e45095b50a4bc4a Mon Sep 17 00:00:00 2001 From: Matt Budz Date: Thu, 27 Apr 2023 16:43:14 +0200 Subject: [PATCH 026/388] `track_updated_state` -> `track_state_change` --- app/controllers/concerns/activity_tracking.rb | 4 ++-- app/controllers/qa/issues_controller.rb | 3 ++- app/models/activity.rb | 2 +- app/presenters/activity_presenter.rb | 2 +- app/views/issues/activities/_issue.html.erb | 2 +- spec/support/qa_shared_examples.rb | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/controllers/concerns/activity_tracking.rb b/app/controllers/concerns/activity_tracking.rb index 4261d882d..06b8dfa6c 100644 --- a/app/controllers/concerns/activity_tracking.rb +++ b/app/controllers/concerns/activity_tracking.rb @@ -31,7 +31,7 @@ def track_updated(trackable, user: current_user, project: current_project) track_activity(trackable, :update, user, project) end - def track_updated_state(trackable, user: current_user, project: current_project) - track_activity(trackable, :update_state, user, project) + def track_state_change(trackable, user: current_user, project: current_project) + track_activity(trackable, :state_change, user, project) end end diff --git a/app/controllers/qa/issues_controller.rb b/app/controllers/qa/issues_controller.rb index 8065d91cf..3f27e847f 100644 --- a/app/controllers/qa/issues_controller.rb +++ b/app/controllers/qa/issues_controller.rb @@ -22,6 +22,7 @@ def edit def update if @issue.update(state: @state, updated_at: Time.now) + track_state_change(@issue) redirect_to project_qa_issues_path(current_project), notice: 'State updated successfully.' else render :show, alert: @issue.errors.full_messages.join('; ') @@ -35,7 +36,7 @@ def multiple_update if @issues.update_all(state: @state, updated_at: Time.now) @issues.each do |issue| - track_updated_state(issue) + track_state_change(issue) end format.html { redirect_to_target_or_default project_qa_issues_path(current_project), notice: 'State updated successfully.' } diff --git a/app/models/activity.rb b/app/models/activity.rb index a2d689210..cbd1e52fa 100644 --- a/app/models/activity.rb +++ b/app/models/activity.rb @@ -17,7 +17,7 @@ def project=(new_project); end validates_presence_of :action, :trackable_id, :trackable_type, :user - VALID_ACTIONS = %w[create destroy recover update update_state] + VALID_ACTIONS = %w[create destroy recover state_change update] validates_inclusion_of :action, in: VALID_ACTIONS diff --git a/app/presenters/activity_presenter.rb b/app/presenters/activity_presenter.rb index 28d60cdfc..da5e020a2 100644 --- a/app/presenters/activity_presenter.rb +++ b/app/presenters/activity_presenter.rb @@ -83,7 +83,7 @@ def verb case activity.action when 'destroy' 'deleted' - when 'update_state' + when 'state_change' 'updated' else activity.action.sub(/e?\z/, 'ed') diff --git a/app/views/issues/activities/_issue.html.erb b/app/views/issues/activities/_issue.html.erb index b3080afc0..d42e48bb2 100644 --- a/app/views/issues/activities/_issue.html.erb +++ b/app/views/issues/activities/_issue.html.erb @@ -1,6 +1,6 @@ <% if issue %> <% if issue.title? %> - <%= presenter.verb %> the <%= link_to issue.title, [current_project, issue] %> <%= activity.action == 'update_state' ? "issue's state to #{issue.state.humanize.downcase}" : 'issue' %>. + <%= presenter.verb %> the <%= link_to issue.title, [current_project, issue] %> <%= activity.action == 'state_change' ? "issue's state to #{issue.state.humanize.downcase}" : 'issue' %>. <% else %> <%= presenter.verb %> <%= link_to "Issue ##{issue.id}", [current_project, issue] %>. <% end %> diff --git a/spec/support/qa_shared_examples.rb b/spec/support/qa_shared_examples.rb index 32cd00767..5eac40437 100644 --- a/spec/support/qa_shared_examples.rb +++ b/spec/support/qa_shared_examples.rb @@ -122,7 +122,7 @@ def job_params(record) { - action: 'update_state', + action: 'state_change', project_id: current_project.id, trackable_id: record.id, trackable_type: record.class.to_s, From 9fe0c9f7d6a8be24f96c4672ea5725f8f2c948e8 Mon Sep 17 00:00:00 2001 From: Matt Budz Date: Fri, 28 Apr 2023 16:16:09 +0200 Subject: [PATCH 027/388] add bootstrap 5 sass assets (temporarily) --- app/assets/stylesheets/tylium.scss | 52 +- .../vendor/bootstrap/_accordion.scss | 149 ++ .../stylesheets/vendor/bootstrap/_alert.scss | 71 + .../stylesheets/vendor/bootstrap/_badge.scss | 38 + .../vendor/bootstrap/_breadcrumb.scss | 40 + .../vendor/bootstrap/_button-group.scss | 142 ++ .../vendor/bootstrap/_buttons.scss | 207 +++ .../stylesheets/vendor/bootstrap/_card.scss | 234 +++ .../vendor/bootstrap/_carousel.scss | 226 +++ .../stylesheets/vendor/bootstrap/_close.scss | 40 + .../vendor/bootstrap/_containers.scss | 41 + .../vendor/bootstrap/_dropdown.scss | 249 +++ .../stylesheets/vendor/bootstrap/_forms.scss | 9 + .../vendor/bootstrap/_functions.scss | 302 +++ .../stylesheets/vendor/bootstrap/_grid.scss | 33 + .../vendor/bootstrap/_helpers.scss | 10 + .../stylesheets/vendor/bootstrap/_images.scss | 42 + .../vendor/bootstrap/_list-group.scss | 192 ++ .../stylesheets/vendor/bootstrap/_maps.scss | 54 + .../stylesheets/vendor/bootstrap/_mixins.scss | 43 + .../stylesheets/vendor/bootstrap/_modal.scss | 237 +++ .../stylesheets/vendor/bootstrap/_nav.scss | 172 ++ .../stylesheets/vendor/bootstrap/_navbar.scss | 278 +++ .../vendor/bootstrap/_offcanvas.scss | 144 ++ .../vendor/bootstrap/_pagination.scss | 109 ++ .../vendor/bootstrap/_placeholders.scss | 51 + .../vendor/bootstrap/_popover.scss | 196 ++ .../vendor/bootstrap/_progress.scss | 59 + .../stylesheets/vendor/bootstrap/_reboot.scss | 610 ++++++ .../stylesheets/vendor/bootstrap/_root.scss | 73 + .../vendor/bootstrap/_spinners.scss | 85 + .../stylesheets/vendor/bootstrap/_tables.scss | 164 ++ .../stylesheets/vendor/bootstrap/_toasts.scss | 73 + .../vendor/bootstrap/_tooltip.scss | 120 ++ .../vendor/bootstrap/_transitions.scss | 27 + .../stylesheets/vendor/bootstrap/_type.scss | 106 ++ .../vendor/bootstrap/_utilities.scss | 647 +++++++ .../vendor/bootstrap/_variables.scss | 1634 +++++++++++++++++ .../vendor/bootstrap/bootstrap-grid.scss | 64 + .../vendor/bootstrap/bootstrap-reboot.scss | 9 + .../vendor/bootstrap/bootstrap-utilities.scss | 18 + .../vendor/bootstrap/bootstrap.scss | 51 + .../bootstrap/forms/_floating-labels.scss | 75 + .../vendor/bootstrap/forms/_form-check.scss | 175 ++ .../vendor/bootstrap/forms/_form-control.scss | 194 ++ .../vendor/bootstrap/forms/_form-range.scss | 91 + .../vendor/bootstrap/forms/_form-select.scss | 71 + .../vendor/bootstrap/forms/_form-text.scss | 11 + .../vendor/bootstrap/forms/_input-group.scss | 132 ++ .../vendor/bootstrap/forms/_labels.scss | 36 + .../vendor/bootstrap/forms/_validation.scss | 12 + .../vendor/bootstrap/helpers/_clearfix.scss | 3 + .../vendor/bootstrap/helpers/_color-bg.scss | 10 + .../bootstrap/helpers/_colored-links.scss | 12 + .../vendor/bootstrap/helpers/_position.scss | 36 + .../vendor/bootstrap/helpers/_ratio.scss | 26 + .../vendor/bootstrap/helpers/_stacks.scss | 15 + .../bootstrap/helpers/_stretched-link.scss | 15 + .../bootstrap/helpers/_text-truncation.scss | 7 + .../bootstrap/helpers/_visually-hidden.scss | 8 + .../vendor/bootstrap/helpers/_vr.scss | 8 + .../vendor/bootstrap/mixins/_alert.scss | 15 + .../vendor/bootstrap/mixins/_backdrop.scss | 14 + .../vendor/bootstrap/mixins/_banner.scss | 9 + .../bootstrap/mixins/_border-radius.scss | 78 + .../vendor/bootstrap/mixins/_box-shadow.scss | 18 + .../vendor/bootstrap/mixins/_breakpoints.scss | 127 ++ .../vendor/bootstrap/mixins/_buttons.scss | 70 + .../vendor/bootstrap/mixins/_caret.scss | 64 + .../vendor/bootstrap/mixins/_clearfix.scss | 9 + .../bootstrap/mixins/_color-scheme.scss | 7 + .../vendor/bootstrap/mixins/_container.scss | 11 + .../vendor/bootstrap/mixins/_deprecate.scss | 10 + .../vendor/bootstrap/mixins/_forms.scss | 152 ++ .../vendor/bootstrap/mixins/_gradients.scss | 47 + .../vendor/bootstrap/mixins/_grid.scss | 151 ++ .../vendor/bootstrap/mixins/_image.scss | 16 + .../vendor/bootstrap/mixins/_list-group.scss | 24 + .../vendor/bootstrap/mixins/_lists.scss | 7 + .../vendor/bootstrap/mixins/_pagination.scss | 10 + .../vendor/bootstrap/mixins/_reset-text.scss | 17 + .../vendor/bootstrap/mixins/_resize.scss | 6 + .../bootstrap/mixins/_table-variants.scss | 24 + .../bootstrap/mixins/_text-truncate.scss | 8 + .../vendor/bootstrap/mixins/_transition.scss | 26 + .../vendor/bootstrap/mixins/_utilities.scss | 97 + .../bootstrap/mixins/_visually-hidden.scss | 29 + .../vendor/bootstrap/utilities/_api.scss | 47 + .../vendor/bootstrap/vendor/_rfs.scss | 354 ++++ 89 files changed, 9460 insertions(+), 25 deletions(-) create mode 100644 app/assets/stylesheets/vendor/bootstrap/_accordion.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_alert.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_badge.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_breadcrumb.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_button-group.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_buttons.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_card.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_carousel.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_close.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_containers.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_dropdown.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_forms.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_functions.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_grid.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_helpers.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_images.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_list-group.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_maps.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_mixins.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_modal.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_nav.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_navbar.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_offcanvas.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_pagination.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_placeholders.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_popover.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_progress.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_reboot.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_root.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_spinners.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_tables.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_toasts.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_tooltip.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_transitions.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_type.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_utilities.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/_variables.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/bootstrap-grid.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/bootstrap-reboot.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/bootstrap-utilities.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/bootstrap.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/forms/_floating-labels.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/forms/_form-check.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/forms/_form-control.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/forms/_form-range.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/forms/_form-select.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/forms/_form-text.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/forms/_input-group.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/forms/_labels.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/forms/_validation.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/helpers/_clearfix.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/helpers/_color-bg.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/helpers/_colored-links.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/helpers/_position.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/helpers/_ratio.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/helpers/_stacks.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/helpers/_stretched-link.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/helpers/_text-truncation.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/helpers/_visually-hidden.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/helpers/_vr.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_alert.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_backdrop.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_banner.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_border-radius.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_box-shadow.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_breakpoints.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_buttons.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_caret.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_clearfix.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_color-scheme.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_container.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_deprecate.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_forms.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_gradients.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_grid.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_image.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_list-group.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_lists.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_pagination.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_reset-text.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_resize.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_table-variants.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_text-truncate.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_transition.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_utilities.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/mixins/_visually-hidden.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/utilities/_api.scss create mode 100644 app/assets/stylesheets/vendor/bootstrap/vendor/_rfs.scss diff --git a/app/assets/stylesheets/tylium.scss b/app/assets/stylesheets/tylium.scss index db4de03d2..1e4cbd01b 100644 --- a/app/assets/stylesheets/tylium.scss +++ b/app/assets/stylesheets/tylium.scss @@ -1,40 +1,42 @@ // vendor stylesheets -@import "bootstrap/functions"; -@import "bootstrap/variables"; -@import "tylium/custom_grid"; -@import "bootstrap"; +// @import "bootstrap/functions"; +// @import "bootstrap/variables"; +// @import "tylium/custom_grid"; +// @import "bootstrap"; -@import "font-awesome.min"; -@import "jquery.fileupload-ui"; +@import 'vendor/bootstrap/bootstrap'; -@import "vendor/datatables.min"; +@import 'font-awesome.min'; +@import 'jquery.fileupload-ui'; + +@import 'vendor/datatables.min'; // theme-level variables -@import "tylium/variables"; +@import 'tylium/variables'; // shared styles -@import "shared/activities"; -@import "shared/comments"; -@import "shared/datatables"; -@import "shared/editor_toolbar"; -@import "shared/empty_state.scss"; -@import "shared/inline_editable"; -@import "shared/mixins"; -@import "shared/noscript"; -@import "shared/notifications"; -@import "shared/pagination"; -@import "shared/revisions"; -@import "shared/textile.scss"; -@import "shared/validations"; +@import 'shared/activities'; +@import 'shared/comments'; +@import 'shared/datatables'; +@import 'shared/editor_toolbar'; +@import 'shared/empty_state.scss'; +@import 'shared/inline_editable'; +@import 'shared/mixins'; +@import 'shared/noscript'; +@import 'shared/notifications'; +@import 'shared/pagination'; +@import 'shared/revisions'; +@import 'shared/textile.scss'; +@import 'shared/validations'; // styles for base HTML elements like body, h1, etc. -@import "tylium/base"; +@import 'tylium/base'; // styles for managing the layout in the screen (columns, floats, etc.) -@import "tylium/layout"; +@import 'tylium/layout'; // specific styles for the different views -@import "tylium/modules"; +@import 'tylium/modules'; // engines css -@import "tylium/engines"; +@import 'tylium/engines'; diff --git a/app/assets/stylesheets/vendor/bootstrap/_accordion.scss b/app/assets/stylesheets/vendor/bootstrap/_accordion.scss new file mode 100644 index 000000000..f09601bab --- /dev/null +++ b/app/assets/stylesheets/vendor/bootstrap/_accordion.scss @@ -0,0 +1,149 @@ +// +// Base styles +// + +.accordion { + // scss-docs-start accordion-css-vars + --#{$prefix}accordion-color: #{$accordion-color}; + --#{$prefix}accordion-bg: #{$accordion-bg}; + --#{$prefix}accordion-transition: #{$accordion-transition}; + --#{$prefix}accordion-border-color: #{$accordion-border-color}; + --#{$prefix}accordion-border-width: #{$accordion-border-width}; + --#{$prefix}accordion-border-radius: #{$accordion-border-radius}; + --#{$prefix}accordion-inner-border-radius: #{$accordion-inner-border-radius}; + --#{$prefix}accordion-btn-padding-x: #{$accordion-button-padding-x}; + --#{$prefix}accordion-btn-padding-y: #{$accordion-button-padding-y}; + --#{$prefix}accordion-btn-color: #{$accordion-button-color}; + --#{$prefix}accordion-btn-bg: #{$accordion-button-bg}; + --#{$prefix}accordion-btn-icon: #{escape-svg($accordion-button-icon)}; + --#{$prefix}accordion-btn-icon-width: #{$accordion-icon-width}; + --#{$prefix}accordion-btn-icon-transform: #{$accordion-icon-transform}; + --#{$prefix}accordion-btn-icon-transition: #{$accordion-icon-transition}; + --#{$prefix}accordion-btn-active-icon: #{escape-svg($accordion-button-active-icon)}; + --#{$prefix}accordion-btn-focus-border-color: #{$accordion-button-focus-border-color}; + --#{$prefix}accordion-btn-focus-box-shadow: #{$accordion-button-focus-box-shadow}; + --#{$prefix}accordion-body-padding-x: #{$accordion-body-padding-x}; + --#{$prefix}accordion-body-padding-y: #{$accordion-body-padding-y}; + --#{$prefix}accordion-active-color: #{$accordion-button-active-color}; + --#{$prefix}accordion-active-bg: #{$accordion-button-active-bg}; + // scss-docs-end accordion-css-vars +} + +.accordion-button { + position: relative; + display: flex; + align-items: center; + width: 100%; + padding: var(--#{$prefix}accordion-btn-padding-y) var(--#{$prefix}accordion-btn-padding-x); + @include font-size($font-size-base); + color: var(--#{$prefix}accordion-btn-color); + text-align: left; // Reset button style + background-color: var(--#{$prefix}accordion-btn-bg); + border: 0; + @include border-radius(0); + overflow-anchor: none; + @include transition(var(--#{$prefix}accordion-transition)); + + &:not(.collapsed) { + color: var(--#{$prefix}accordion-active-color); + background-color: var(--#{$prefix}accordion-active-bg); + box-shadow: inset 0 calc(-1 * var(--#{$prefix}accordion-border-width)) 0 var(--#{$prefix}accordion-border-color); // stylelint-disable-line function-disallowed-list + + &::after { + background-image: var(--#{$prefix}accordion-btn-active-icon); + transform: var(--#{$prefix}accordion-btn-icon-transform); + } + } + + // Accordion icon + &::after { + flex-shrink: 0; + width: var(--#{$prefix}accordion-btn-icon-width); + height: var(--#{$prefix}accordion-btn-icon-width); + margin-left: auto; + content: ""; + background-image: var(--#{$prefix}accordion-btn-icon); + background-repeat: no-repeat; + background-size: var(--#{$prefix}accordion-btn-icon-width); + @include transition(var(--#{$prefix}accordion-btn-icon-transition)); + } + + &:hover { + z-index: 2; + } + + &:focus { + z-index: 3; + border-color: var(--#{$prefix}accordion-btn-focus-border-color); + outline: 0; + box-shadow: var(--#{$prefix}accordion-btn-focus-box-shadow); + } +} + +.accordion-header { + margin-bottom: 0; +} + +.accordion-item { + color: var(--#{$prefix}accordion-color); + background-color: var(--#{$prefix}accordion-bg); + border: var(--#{$prefix}accordion-border-width) solid var(--#{$prefix}accordion-border-color); + + &:first-of-type { + @include border-top-radius(var(--#{$prefix}accordion-border-radius)); + + .accordion-button { + @include border-top-radius(var(--#{$prefix}accordion-inner-border-radius)); + } + } + + &:not(:first-of-type) { + border-top: 0; + } + + // Only set a border-radius on the last item if the accordion is collapsed + &:last-of-type { + @include border-bottom-radius(var(--#{$prefix}accordion-border-radius)); + + .accordion-button { + &.collapsed { + @include border-bottom-radius(var(--#{$prefix}accordion-inner-border-radius)); + } + } + + .accordion-collapse { + @include border-bottom-radius(var(--#{$prefix}accordion-border-radius)); + } + } +} + +.accordion-body { + padding: var(--#{$prefix}accordion-body-padding-y) var(--#{$prefix}accordion-body-padding-x); +} + + +// Flush accordion items +// +// Remove borders and border-radius to keep accordion items edge-to-edge. + +.accordion-flush { + .accordion-collapse { + border-width: 0; + } + + .accordion-item { + border-right: 0; + border-left: 0; + @include border-radius(0); + + &:first-child { border-top: 0; } + &:last-child { border-bottom: 0; } + + .accordion-button { + &, + &.collapsed { + @include border-radius(0); + } + } + } +} diff --git a/app/assets/stylesheets/vendor/bootstrap/_alert.scss b/app/assets/stylesheets/vendor/bootstrap/_alert.scss new file mode 100644 index 000000000..c8bc91b42 --- /dev/null +++ b/app/assets/stylesheets/vendor/bootstrap/_alert.scss @@ -0,0 +1,71 @@ +// +// Base styles +// + +.alert { + // scss-docs-start alert-css-vars + --#{$prefix}alert-bg: transparent; + --#{$prefix}alert-padding-x: #{$alert-padding-x}; + --#{$prefix}alert-padding-y: #{$alert-padding-y}; + --#{$prefix}alert-margin-bottom: #{$alert-margin-bottom}; + --#{$prefix}alert-color: inherit; + --#{$prefix}alert-border-color: transparent; + --#{$prefix}alert-border: #{$alert-border-width} solid var(--#{$prefix}alert-border-color); + --#{$prefix}alert-border-radius: #{$alert-border-radius}; + // scss-docs-end alert-css-vars + + position: relative; + padding: var(--#{$prefix}alert-padding-y) var(--#{$prefix}alert-padding-x); + margin-bottom: var(--#{$prefix}alert-margin-bottom); + color: var(--#{$prefix}alert-color); + background-color: var(--#{$prefix}alert-bg); + border: var(--#{$prefix}alert-border); + @include border-radius(var(--#{$prefix}alert-border-radius)); +} + +// Headings for larger alerts +.alert-heading { + // Specified to prevent conflicts of changing $headings-color + color: inherit; +} + +// Provide class for links that match alerts +.alert-link { + font-weight: $alert-link-font-weight; +} + + +// Dismissible alerts +// +// Expand the right padding and account for the close button's positioning. + +.alert-dismissible { + padding-right: $alert-dismissible-padding-r; + + // Adjust close link position + .btn-close { + position: absolute; + top: 0; + right: 0; + z-index: $stretched-link-z-index + 1; + padding: $alert-padding-y * 1.25 $alert-padding-x; + } +} + + +// scss-docs-start alert-modifiers +// Generate contextual modifier classes for colorizing the alert. + +@each $state, $value in $theme-colors { + $alert-background: shift-color($value, $alert-bg-scale); + $alert-border: shift-color($value, $alert-border-scale); + $alert-color: shift-color($value, $alert-color-scale); + + @if (contrast-ratio($alert-background, $alert-color) < $min-contrast-ratio) { + $alert-color: mix($value, color-contrast($alert-background), abs($alert-color-scale)); + } + .alert-#{$state} { + @include alert-variant($alert-background, $alert-border, $alert-color); + } +} +// scss-docs-end alert-modifiers diff --git a/app/assets/stylesheets/vendor/bootstrap/_badge.scss b/app/assets/stylesheets/vendor/bootstrap/_badge.scss new file mode 100644 index 000000000..cc3d26955 --- /dev/null +++ b/app/assets/stylesheets/vendor/bootstrap/_badge.scss @@ -0,0 +1,38 @@ +// Base class +// +// Requires one of the contextual, color modifier classes for `color` and +// `background-color`. + +.badge { + // scss-docs-start badge-css-vars + --#{$prefix}badge-padding-x: #{$badge-padding-x}; + --#{$prefix}badge-padding-y: #{$badge-padding-y}; + @include rfs($badge-font-size, --#{$prefix}badge-font-size); + --#{$prefix}badge-font-weight: #{$badge-font-weight}; + --#{$prefix}badge-color: #{$badge-color}; + --#{$prefix}badge-border-radius: #{$badge-border-radius}; + // scss-docs-end badge-css-vars + + display: inline-block; + padding: var(--#{$prefix}badge-padding-y) var(--#{$prefix}badge-padding-x); + @include font-size(var(--#{$prefix}badge-font-size)); + font-weight: var(--#{$prefix}badge-font-weight); + line-height: 1; + color: var(--#{$prefix}badge-color); + text-align: center; + white-space: nowrap; + vertical-align: baseline; + @include border-radius(var(--#{$prefix}badge-border-radius)); + @include gradient-bg(); + + // Empty badges collapse automatically + &:empty { + display: none; + } +} + +// Quick fix for badges in buttons +.btn .badge { + position: relative; + top: -1px; +} diff --git a/app/assets/stylesheets/vendor/bootstrap/_breadcrumb.scss b/app/assets/stylesheets/vendor/bootstrap/_breadcrumb.scss new file mode 100644 index 000000000..b8252ff21 --- /dev/null +++ b/app/assets/stylesheets/vendor/bootstrap/_breadcrumb.scss @@ -0,0 +1,40 @@ +.breadcrumb { + // scss-docs-start breadcrumb-css-vars + --#{$prefix}breadcrumb-padding-x: #{$breadcrumb-padding-x}; + --#{$prefix}breadcrumb-padding-y: #{$breadcrumb-padding-y}; + --#{$prefix}breadcrumb-margin-bottom: #{$breadcrumb-margin-bottom}; + @include rfs($breadcrumb-font-size, --#{$prefix}breadcrumb-font-size); + --#{$prefix}breadcrumb-bg: #{$breadcrumb-bg}; + --#{$prefix}breadcrumb-border-radius: #{$breadcrumb-border-radius}; + --#{$prefix}breadcrumb-divider-color: #{$breadcrumb-divider-color}; + --#{$prefix}breadcrumb-item-padding-x: #{$breadcrumb-item-padding-x}; + --#{$prefix}breadcrumb-item-active-color: #{$breadcrumb-active-color}; + // scss-docs-end breadcrumb-css-vars + + display: flex; + flex-wrap: wrap; + padding: var(--#{$prefix}breadcrumb-padding-y) var(--#{$prefix}breadcrumb-padding-x); + margin-bottom: var(--#{$prefix}breadcrumb-margin-bottom); + @include font-size(var(--#{$prefix}breadcrumb-font-size)); + list-style: none; + background-color: var(--#{$prefix}breadcrumb-bg); + @include border-radius(var(--#{$prefix}breadcrumb-border-radius)); +} + +.breadcrumb-item { + // The separator between breadcrumbs (by default, a forward-slash: "/") + + .breadcrumb-item { + padding-left: var(--#{$prefix}breadcrumb-item-padding-x); + + &::before { + float: left; // Suppress inline spacings and underlining of the separator + padding-right: var(--#{$prefix}breadcrumb-item-padding-x); + color: var(--#{$prefix}breadcrumb-divider-color); + content: var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider)) #{"/* rtl:"} var(--#{$prefix}breadcrumb-divider, escape-svg($breadcrumb-divider-flipped)) #{"*/"}; + } + } + + &.active { + color: var(--#{$prefix}breadcrumb-item-active-color); + } +} diff --git a/app/assets/stylesheets/vendor/bootstrap/_button-group.scss b/app/assets/stylesheets/vendor/bootstrap/_button-group.scss new file mode 100644 index 000000000..79b100cbf --- /dev/null +++ b/app/assets/stylesheets/vendor/bootstrap/_button-group.scss @@ -0,0 +1,142 @@ +// Make the div behave like a button +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-flex; + vertical-align: middle; // match .btn alignment given font-size hack above + + > .btn { + position: relative; + flex: 1 1 auto; + } + + // Bring the hover, focused, and "active" buttons to the front to overlay + // the borders properly + > .btn-check:checked + .btn, + > .btn-check:focus + .btn, + > .btn:hover, + > .btn:focus, + > .btn:active, + > .btn.active { + z-index: 1; + } +} + +// Optional: Group multiple button groups together for a toolbar +.btn-toolbar { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; + + .input-group { + width: auto; + } +} + +.btn-group { + @include border-radius($btn-border-radius); + + // Prevent double borders when buttons are next to each other + > :not(.btn-check:first-child) + .btn, + > .btn-group:not(:first-child) { + margin-left: -$btn-border-width; + } + + // Reset rounded corners + > .btn:not(:last-child):not(.dropdown-toggle), + > .btn.dropdown-toggle-split:first-child, + > .btn-group:not(:last-child) > .btn { + @include border-end-radius(0); + } + + // The left radius should be 0 if the button is: + // - the "third or more" child + // - the second child and the previous element isn't `.btn-check` (making it the first child visually) + // - part of a btn-group which isn't the first child + > .btn:nth-child(n + 3), + > :not(.btn-check) + .btn, + > .btn-group:not(:first-child) > .btn { + @include border-start-radius(0); + } +} + +// Sizing +// +// Remix the default button sizing classes into new ones for easier manipulation. + +.btn-group-sm > .btn { @extend .btn-sm; } +.btn-group-lg > .btn { @extend .btn-lg; } + + +// +// Split button dropdowns +// + +.dropdown-toggle-split { + padding-right: $btn-padding-x * .75; + padding-left: $btn-padding-x * .75; + + &::after, + .dropup &::after, + .dropend &::after { + margin-left: 0; + } + + .dropstart &::before { + margin-right: 0; + } +} + +.btn-sm + .dropdown-toggle-split { + padding-right: $btn-padding-x-sm * .75; + padding-left: $btn-padding-x-sm * .75; +} + +.btn-lg + .dropdown-toggle-split { + padding-right: $btn-padding-x-lg * .75; + padding-left: $btn-padding-x-lg * .75; +} + + +// The clickable button for toggling the menu +// Set the same inset shadow as the :active state +.btn-group.show .dropdown-toggle { + @include box-shadow($btn-active-box-shadow); + + // Show no shadow for `.btn-link` since it has no other button styles. + &.btn-link { + @include box-shadow(none); + } +} + + +// +// Vertical button groups +// + +.btn-group-vertical { + flex-direction: column; + align-items: flex-start; + justify-content: center; + + > .btn, + > .btn-group { + width: 100%; + } + + > .btn:not(:first-child), + > .btn-group:not(:first-child) { + margin-top: -$btn-border-width; + } + + // Reset rounded corners + > .btn:not(:last-child):not(.dropdown-toggle), + > .btn-group:not(:last-child) > .btn { + @include border-bottom-radius(0); + } + + > .btn ~ .btn, + > .btn-group:not(:first-child) > .btn { + @include border-top-radius(0); + } +} diff --git a/app/assets/stylesheets/vendor/bootstrap/_buttons.scss b/app/assets/stylesheets/vendor/bootstrap/_buttons.scss new file mode 100644 index 000000000..f2c4c13a9 --- /dev/null +++ b/app/assets/stylesheets/vendor/bootstrap/_buttons.scss @@ -0,0 +1,207 @@ +// +// Base styles +// + +.btn { + // scss-docs-start btn-css-vars + --#{$prefix}btn-padding-x: #{$btn-padding-x}; + --#{$prefix}btn-padding-y: #{$btn-padding-y}; + --#{$prefix}btn-font-family: #{$btn-font-family}; + @include rfs($btn-font-size, --#{$prefix}btn-font-size); + --#{$prefix}btn-font-weight: #{$btn-font-weight}; + --#{$prefix}btn-line-height: #{$btn-line-height}; + --#{$prefix}btn-color: #{$body-color}; + --#{$prefix}btn-bg: transparent; + --#{$prefix}btn-border-width: #{$btn-border-width}; + --#{$prefix}btn-border-color: transparent; + --#{$prefix}btn-border-radius: #{$btn-border-radius}; + --#{$prefix}btn-hover-border-color: transparent; + --#{$prefix}btn-box-shadow: #{$btn-box-shadow}; + --#{$prefix}btn-disabled-opacity: #{$btn-disabled-opacity}; + --#{$prefix}btn-focus-box-shadow: 0 0 0 #{$btn-focus-width} rgba(var(--#{$prefix}btn-focus-shadow-rgb), .5); + // scss-docs-end btn-css-vars + + display: inline-block; + padding: var(--#{$prefix}btn-padding-y) var(--#{$prefix}btn-padding-x); + font-family: var(--#{$prefix}btn-font-family); + @include font-size(var(--#{$prefix}btn-font-size)); + font-weight: var(--#{$prefix}btn-font-weight); + line-height: var(--#{$prefix}btn-line-height); + color: var(--#{$prefix}btn-color); + text-align: center; + text-decoration: if($link-decoration == none, null, none); + white-space: $btn-white-space; + vertical-align: middle; + cursor: if($enable-button-pointers, pointer, null); + user-select: none; + border: var(--#{$prefix}btn-border-width) solid var(--#{$prefix}btn-border-color); + @include border-radius(var(--#{$prefix}btn-border-radius)); + @include gradient-bg(var(--#{$prefix}btn-bg)); + @include box-shadow(var(--#{$prefix}btn-box-shadow)); + @include transition($btn-transition); + + &:hover { + color: var(--#{$prefix}btn-hover-color); + text-decoration: if($link-hover-decoration == underline, none, null); + background-color: var(--#{$prefix}btn-hover-bg); + border-color: var(--#{$prefix}btn-hover-border-color); + } + + .btn-check + &:hover { + // override for the checkbox/radio buttons + color: var(--#{$prefix}btn-color); + background-color: var(--#{$prefix}btn-bg); + border-color: var(--#{$prefix}btn-border-color); + } + + &:focus-visible { + color: var(--#{$prefix}btn-hover-color); + @include gradient-bg(var(--#{$prefix}btn-hover-bg)); + border-color: var(--#{$prefix}btn-hover-border-color); + outline: 0; + // Avoid using mixin so we can pass custom focus shadow properly + @if $enable-shadows { + box-shadow: var(--#{$prefix}btn-box-shadow), var(--#{$prefix}btn-focus-box-shadow); + } @else { + box-shadow: var(--#{$prefix}btn-focus-box-shadow); + } + } + + .btn-check:focus-visible + & { + border-color: var(--#{$prefix}btn-hover-border-color); + outline: 0; + // Avoid using mixin so we can pass custom focus shadow properly + @if $enable-shadows { + box-shadow: var(--#{$prefix}btn-box-shadow), var(--#{$prefix}btn-focus-box-shadow); + } @else { + box-shadow: var(--#{$prefix}btn-focus-box-shadow); + } + } + + .btn-check:checked + &, + :not(.btn-check) + &:active, + &:first-child:active, + &.active, + &.show { + color: var(--#{$prefix}btn-active-color); + background-color: var(--#{$prefix}btn-active-bg); + // Remove CSS gradients if they're enabled + background-image: if($enable-gradients, none, null); + border-color: var(--#{$prefix}btn-active-border-color); + @include box-shadow(var(--#{$prefix}btn-active-shadow)); + + &:focus-visible { + // Avoid using mixin so we can pass custom focus shadow properly + @if $enable-shadows { + box-shadow: var(--#{$prefix}btn-active-shadow), var(--#{$prefix}btn-focus-box-shadow); + } @else { + box-shadow: var(--#{$prefix}btn-focus-box-shadow); + } + } + } + + &:disabled, + &.disabled, + fieldset:disabled & { + color: var(--#{$prefix}btn-disabled-color); + pointer-events: none; + background-color: var(--#{$prefix}btn-disabled-bg); + background-image: if($enable-gradients, none, null); + border-color: var(--#{$prefix}btn-disabled-border-color); + opacity: var(--#{$prefix}btn-disabled-opacity); + @include box-shadow(none); + } +} + + +// +// Alternate buttons +// + +// scss-docs-start btn-variant-loops +@each $color, $value in $theme-colors { + .btn-#{$color} { + @if $color == "light" { + @include button-variant( + $value, + $value, + $hover-background: shade-color($value, $btn-hover-bg-shade-amount), + $hover-border: shade-color($value, $btn-hover-border-shade-amount), + $active-background: shade-color($value, $btn-active-bg-shade-amount), + $active-border: shade-color($value, $btn-active-border-shade-amount) + ); + } @else if $color == "dark" { + @include button-variant( + $value, + $value, + $hover-background: tint-color($value, $btn-hover-bg-tint-amount), + $hover-border: tint-color($value, $btn-hover-border-tint-amount), + $active-background: tint-color($value, $btn-active-bg-tint-amount), + $active-border: tint-color($value, $btn-active-border-tint-amount) + ); + } @else { + @include button-variant($value, $value); + } + } +} + +@each $color, $value in $theme-colors { + .btn-outline-#{$color} { + @include button-outline-variant($value); + } +} +// scss-docs-end btn-variant-loops + + +// +// Link buttons +// + +// Make a button look and behave like a link +.btn-link { + --#{$prefix}btn-font-weight: #{$font-weight-normal}; + --#{$prefix}btn-color: #{$btn-link-color}; + --#{$prefix}btn-bg: transparent; + --#{$prefix}btn-border-color: transparent; + --#{$prefix}btn-hover-color: #{$btn-link-hover-color}; + --#{$prefix}btn-hover-border-color: transparent; + --#{$prefix}btn-active-color: #{$btn-link-hover-color}; + --#{$prefix}btn-active-border-color: transparent; + --#{$prefix}btn-disabled-color: #{$btn-link-disabled-color}; + --#{$prefix}btn-disabled-border-color: transparent; + --#{$prefix}btn-box-shadow: none; + --#{$prefix}btn-focus-shadow-rgb: #{to-rgb(mix(color-contrast($primary), $primary, 15%))}; + + text-decoration: $link-decoration; + @if $enable-gradients { + background-image: none; + } + + &:hover, + &:focus-visible { + text-decoration: $link-hover-decoration; + } + + &:focus-visible { + color: var(--#{$prefix}btn-color); + } + + &:hover { + color: var(--#{$prefix}btn-hover-color); + } + + // No need for an active state here +} + + +// +// Button Sizes +// + +.btn-lg { + @include button-size($btn-padding-y-lg, $btn-padding-x-lg, $btn-font-size-lg, $btn-border-radius-lg); +} + +.btn-sm { + @include button-size($btn-padding-y-sm, $btn-padding-x-sm, $btn-font-size-sm, $btn-border-radius-sm); +} diff --git a/app/assets/stylesheets/vendor/bootstrap/_card.scss b/app/assets/stylesheets/vendor/bootstrap/_card.scss new file mode 100644 index 000000000..ce8c02f1f --- /dev/null +++ b/app/assets/stylesheets/vendor/bootstrap/_card.scss @@ -0,0 +1,234 @@ +// +// Base styles +// + +.card { + // scss-docs-start card-css-vars + --#{$prefix}card-spacer-y: #{$card-spacer-y}; + --#{$prefix}card-spacer-x: #{$card-spacer-x}; + --#{$prefix}card-title-spacer-y: #{$card-title-spacer-y}; + --#{$prefix}card-border-width: #{$card-border-width}; + --#{$prefix}card-border-color: #{$card-border-color}; + --#{$prefix}card-border-radius: #{$card-border-radius}; + --#{$prefix}card-box-shadow: #{$card-box-shadow}; + --#{$prefix}card-inner-border-radius: #{$card-inner-border-radius}; + --#{$prefix}card-cap-padding-y: #{$card-cap-padding-y}; + --#{$prefix}card-cap-padding-x: #{$card-cap-padding-x}; + --#{$prefix}card-cap-bg: #{$card-cap-bg}; + --#{$prefix}card-cap-color: #{$card-cap-color}; + --#{$prefix}card-height: #{$card-height}; + --#{$prefix}card-color: #{$card-color}; + --#{$prefix}card-bg: #{$card-bg}; + --#{$prefix}card-img-overlay-padding: #{$card-img-overlay-padding}; + --#{$prefix}card-group-margin: #{$card-group-margin}; + // scss-docs-end card-css-vars + + position: relative; + display: flex; + flex-direction: column; + min-width: 0; // See https://github.com/twbs/bootstrap/pull/22740#issuecomment-305868106 + height: var(--#{$prefix}card-height); + word-wrap: break-word; + background-color: var(--#{$prefix}card-bg); + background-clip: border-box; + border: var(--#{$prefix}card-border-width) solid var(--#{$prefix}card-border-color); + @include border-radius(var(--#{$prefix}card-border-radius)); + @include box-shadow(var(--#{$prefix}card-box-shadow)); + + > hr { + margin-right: 0; + margin-left: 0; + } + + > .list-group { + border-top: inherit; + border-bottom: inherit; + + &:first-child { + border-top-width: 0; + @include border-top-radius(var(--#{$prefix}card-inner-border-radius)); + } + + &:last-child { + border-bottom-width: 0; + @include border-bottom-radius(var(--#{$prefix}card-inner-border-radius)); + } + } + + // Due to specificity of the above selector (`.card > .list-group`), we must + // use a child selector here to prevent double borders. + > .card-header + .list-group, + > .list-group + .card-footer { + border-top: 0; + } +} + +.card-body { + // Enable `flex-grow: 1` for decks and groups so that card blocks take up + // as much space as possible, ensuring footers are aligned to the bottom. + flex: 1 1 auto; + padding: var(--#{$prefix}card-spacer-y) var(--#{$prefix}card-spacer-x); + color: var(--#{$prefix}card-color); +} + +.card-title { + margin-bottom: var(--#{$prefix}card-title-spacer-y); +} + +.card-subtitle { + margin-top: calc(-.5 * var(--#{$prefix}card-title-spacer-y)); // stylelint-disable-line function-disallowed-list + margin-bottom: 0; +} + +.card-text:last-child { + margin-bottom: 0; +} + +.card-link { + &:hover { + text-decoration: if($link-hover-decoration == underline, none, null); + } + + + .card-link { + margin-left: var(--#{$prefix}card-spacer-x); + } +} + +// +// Optional textual caps +// + +.card-header { + padding: var(--#{$prefix}card-cap-padding-y) var(--#{$prefix}card-cap-padding-x); + margin-bottom: 0; // Removes the default margin-bottom of + color: var(--#{$prefix}card-cap-color); + background-color: var(--#{$prefix}card-cap-bg); + border-bottom: var(--#{$prefix}card-border-width) solid var(--#{$prefix}card-border-color); + + &:first-child { + @include border-radius(var(--#{$prefix}card-inner-border-radius) var(--#{$prefix}card-inner-border-radius) 0 0); + } +} + +.card-footer { + padding: var(--#{$prefix}card-cap-padding-y) var(--#{$prefix}card-cap-padding-x); + color: var(--#{$prefix}card-cap-color); + background-color: var(--#{$prefix}card-cap-bg); + border-top: var(--#{$prefix}card-border-width) solid var(--#{$prefix}card-border-color); + + &:last-child { + @include border-radius(0 0 var(--#{$prefix}card-inner-border-radius) var(--#{$prefix}card-inner-border-radius)); + } +} + + +// +// Header navs +// + +.card-header-tabs { + margin-right: calc(-.5 * var(--#{$prefix}card-cap-padding-x)); // stylelint-disable-line function-disallowed-list + margin-bottom: calc(-1 * var(--#{$prefix}card-cap-padding-y)); // stylelint-disable-line function-disallowed-list + margin-left: calc(-.5 * var(--#{$prefix}card-cap-padding-x)); // stylelint-disable-line function-disallowed-list + border-bottom: 0; + + .nav-link.active { + background-color: var(--#{$prefix}card-bg); + border-bottom-color: var(--#{$prefix}card-bg); + } +} + +.card-header-pills { + margin-right: calc(-.5 * var(--#{$prefix}card-cap-padding-x)); // stylelint-disable-line function-disallowed-list + margin-left: calc(-.5 * var(--#{$prefix}card-cap-padding-x)); // stylelint-disable-line function-disallowed-list +} + +// Card image +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: var(--#{$prefix}card-img-overlay-padding); + @include border-radius(var(--#{$prefix}card-inner-border-radius)); +} + +.card-img, +.card-img-top, +.card-img-bottom { + width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch +} + +.card-img, +.card-img-top { + @include border-top-radius(var(--#{$prefix}card-inner-border-radius)); +} + +.card-img, +.card-img-bottom { + @include border-bottom-radius(var(--#{$prefix}card-inner-border-radius)); +} + + +// +// Card groups +// + +.card-group { + // The child selector allows nested `.card` within `.card-group` + // to display properly. + > .card { + margin-bottom: var(--#{$prefix}card-group-margin); + } + + @include media-breakpoint-up(sm) { + display: flex; + flex-flow: row wrap; + // The child selector allows nested `.card` within `.card-group` + // to display properly. + > .card { + // Flexbugs #4: https://github.com/philipwalton/flexbugs#flexbug-4 + flex: 1 0 0%; + margin-bottom: 0; + + + .card { + margin-left: 0; + border-left: 0; + } + + // Handle rounded corners + @if $enable-rounded { + &:not(:last-child) { + @include border-end-radius(0); + + .card-img-top, + .card-header { + // stylelint-disable-next-line property-disallowed-list + border-top-right-radius: 0; + } + .card-img-bottom, + .card-footer { + // stylelint-disable-next-line property-disallowed-list + border-bottom-right-radius: 0; + } + } + + &:not(:first-child) { + @include border-start-radius(0); + + .card-img-top, + .card-header { + // stylelint-disable-next-line property-disallowed-list + border-top-left-radius: 0; + } + .card-img-bottom, + .card-footer { + // stylelint-disable-next-line property-disallowed-list + border-bottom-left-radius: 0; + } + } + } + } + } +} diff --git a/app/assets/stylesheets/vendor/bootstrap/_carousel.scss b/app/assets/stylesheets/vendor/bootstrap/_carousel.scss new file mode 100644 index 000000000..858b83634 --- /dev/null +++ b/app/assets/stylesheets/vendor/bootstrap/_carousel.scss @@ -0,0 +1,226 @@ +// Notes on the classes: +// +// 1. .carousel.pointer-event should ideally be pan-y (to allow for users to scroll vertically) +// even when their scroll action started on a carousel, but for compatibility (with Firefox) +// we're preventing all actions instead +// 2. The .carousel-item-start and .carousel-item-end is used to indicate where +// the active slide is heading. +// 3. .active.carousel-item is the current slide. +// 4. .active.carousel-item-start and .active.carousel-item-end is the current +// slide in its in-transition state. Only one of these occurs at a time. +// 5. .carousel-item-next.carousel-item-start and .carousel-item-prev.carousel-item-end +// is the upcoming slide in transition. + +.carousel { + position: relative; +} + +.carousel.pointer-event { + touch-action: pan-y; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; + @include clearfix(); +} + +.carousel-item { + position: relative; + display: none; + float: left; + width: 100%; + margin-right: -100%; + backface-visibility: hidden; + @include transition($carousel-transition); +} + +.carousel-item.active, +.carousel-item-next, +.carousel-item-prev { + display: block; +} + +.carousel-item-next:not(.carousel-item-start), +.active.carousel-item-end { + transform: translateX(100%); +} + +.carousel-item-prev:not(.carousel-item-end), +.active.carousel-item-start { + transform: translateX(-100%); +} + + +// +// Alternate transitions +// + +.carousel-fade { + .carousel-item { + opacity: 0; + transition-property: opacity; + transform: none; + } + + .carousel-item.active, + .carousel-item-next.carousel-item-start, + .carousel-item-prev.carousel-item-end { + z-index: 1; + opacity: 1; + } + + .active.carousel-item-start, + .active.carousel-item-end { + z-index: 0; + opacity: 0; + @include transition(opacity 0s $carousel-transition-duration); + } +} + + +// +// Left/right controls for nav +// + +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 0; + bottom: 0; + z-index: 1; + // Use flex for alignment (1-3) + display: flex; // 1. allow flex styles + align-items: center; // 2. vertically center contents + justify-content: center; // 3. horizontally center contents + width: $carousel-control-width; + padding: 0; + color: $carousel-control-color; + text-align: center; + background: none; + border: 0; + opacity: $carousel-control-opacity; + @include transition($carousel-control-transition); + + // Hover/focus state + &:hover, + &:focus { + color: $carousel-control-color; + text-decoration: none; + outline: 0; + opacity: $carousel-control-hover-opacity; + } +} +.carousel-control-prev { + left: 0; + background-image: if($enable-gradients, linear-gradient(90deg, rgba($black, .25), rgba($black, .001)), null); +} +.carousel-control-next { + right: 0; + background-image: if($enable-gradients, linear-gradient(270deg, rgba($black, .25), rgba($black, .001)), null); +} + +// Icons for within +.carousel-control-prev-icon, +.carousel-control-next-icon { + display: inline-block; + width: $carousel-control-icon-width; + height: $carousel-control-icon-width; + background-repeat: no-repeat; + background-position: 50%; + background-size: 100% 100%; +} + +/* rtl:options: { + "autoRename": true, + "stringMap":[ { + "name" : "prev-next", + "search" : "prev", + "replace" : "next" + } ] +} */ +.carousel-control-prev-icon { + background-image: escape-svg($carousel-control-prev-icon-bg); +} +.carousel-control-next-icon { + background-image: escape-svg($carousel-control-next-icon-bg); +} + +// Optional indicator pips/controls +// +// Add a container (such as a list) with the following class and add an item (ideally a focusable control, +// like a button) with data-bs-target for each slide your carousel holds. + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 2; + display: flex; + justify-content: center; + padding: 0; + // Use the .carousel-control's width as margin so we don't overlay those + margin-right: $carousel-control-width; + margin-bottom: 1rem; + margin-left: $carousel-control-width; + list-style: none; + + [data-bs-target] { + box-sizing: content-box; + flex: 0 1 auto; + width: $carousel-indicator-width; + height: $carousel-indicator-height; + padding: 0; + margin-right: $carousel-indicator-spacer; + margin-left: $carousel-indicator-spacer; + text-indent: -999px; + cursor: pointer; + background-color: $carousel-indicator-active-bg; + background-clip: padding-box; + border: 0; + // Use transparent borders to increase the hit area by 10px on top and bottom. + border-top: $carousel-indicator-hit-area-height solid transparent; + border-bottom: $carousel-indicator-hit-area-height solid transparent; + opacity: $carousel-indicator-opacity; + @include transition($carousel-indicator-transition); + } + + .active { + opacity: $carousel-indicator-active-opacity; + } +} + + +// Optional captions +// +// + +.carousel-caption { + position: absolute; + right: (100% - $carousel-caption-width) * .5; + bottom: $carousel-caption-spacer; + left: (100% - $carousel-caption-width) * .5; + padding-top: $carousel-caption-padding-y; + padding-bottom: $carousel-caption-padding-y; + color: $carousel-caption-color; + text-align: center; +} + +// Dark mode carousel + +.carousel-dark { + .carousel-control-prev-icon, + .carousel-control-next-icon { + filter: $carousel-dark-control-icon-filter; + } + + .carousel-indicators [data-bs-target] { + background-color: $carousel-dark-indicator-active-bg; + } + + .carousel-caption { + color: $carousel-dark-caption-color; + } +} diff --git a/app/assets/stylesheets/vendor/bootstrap/_close.scss b/app/assets/stylesheets/vendor/bootstrap/_close.scss new file mode 100644 index 000000000..a0813de8d --- /dev/null +++ b/app/assets/stylesheets/vendor/bootstrap/_close.scss @@ -0,0 +1,40 @@ +// Transparent background and border properties included for button version. +// iOS requires the button element instead of an anchor tag. +// If you want the anchor version, it requires `href="#"`. +// See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile + +.btn-close { + box-sizing: content-box; + width: $btn-close-width; + height: $btn-close-height; + padding: $btn-close-padding-y $btn-close-padding-x; + color: $btn-close-color; + background: transparent escape-svg($btn-close-bg) center / $btn-close-width auto no-repeat; // include transparent for button elements + border: 0; // for button elements + @include border-radius(); + opacity: $btn-close-opacity; + + // Override 's hover style + &:hover { + color: $btn-close-color; + text-decoration: none; + opacity: $btn-close-hover-opacity; + } + + &:focus { + outline: 0; + box-shadow: $btn-close-focus-shadow; + opacity: $btn-close-focus-opacity; + } + + &:disabled, + &.disabled { + pointer-events: none; + user-select: none; + opacity: $btn-close-disabled-opacity; + } +} + +.btn-close-white { + filter: $btn-close-white-filter; +} diff --git a/app/assets/stylesheets/vendor/bootstrap/_containers.scss b/app/assets/stylesheets/vendor/bootstrap/_containers.scss new file mode 100644 index 000000000..83b31381b --- /dev/null +++ b/app/assets/stylesheets/vendor/bootstrap/_containers.scss @@ -0,0 +1,41 @@ +// Container widths +// +// Set the container width, and override it for fixed navbars in media queries. + +@if $enable-container-classes { + // Single container class with breakpoint max-widths + .container, + // 100% wide container at all breakpoints + .container-fluid { + @include make-container(); + } + + // Responsive containers that are 100% wide until a breakpoint + @each $breakpoint, $container-max-width in $container-max-widths { + .container-#{$breakpoint} { + @extend .container-fluid; + } + + @include media-breakpoint-up($breakpoint, $grid-breakpoints) { + %responsive-container-#{$breakpoint} { + max-width: $container-max-width; + } + + // Extend each breakpoint which is smaller or equal to the current breakpoint + $extend-breakpoint: true; + + @each $name, $width in $grid-breakpoints { + @if ($extend-breakpoint) { + .container#{breakpoint-infix($name, $grid-breakpoints)} { + @extend %responsive-container-#{$breakpoint}; + } + + // Once the current breakpoint is reached, stop extending + @if ($breakpoint == $name) { + $extend-breakpoint: false; + } + } + } + } + } +} diff --git a/app/assets/stylesheets/vendor/bootstrap/_dropdown.scss b/app/assets/stylesheets/vendor/bootstrap/_dropdown.scss new file mode 100644 index 000000000..8899d25a0 --- /dev/null +++ b/app/assets/stylesheets/vendor/bootstrap/_dropdown.scss @@ -0,0 +1,249 @@ +// The dropdown wrapper (`
`) +.dropup, +.dropend, +.dropdown, +.dropstart, +.dropup-center, +.dropdown-center { + position: relative; +} + +.dropdown-toggle { + white-space: nowrap; + + // Generate the caret automatically + @include caret(); +} + +// The dropdown menu +.dropdown-menu { + // scss-docs-start dropdown-css-vars + --#{$prefix}dropdown-zindex: #{$zindex-dropdown}; + --#{$prefix}dropdown-min-width: #{$dropdown-min-width}; + --#{$prefix}dropdown-padding-x: #{$dropdown-padding-x}; + --#{$prefix}dropdown-padding-y: #{$dropdown-padding-y}; + --#{$prefix}dropdown-spacer: #{$dropdown-spacer}; + @include rfs($dropdown-font-size, --#{$prefix}dropdown-font-size); + --#{$prefix}dropdown-color: #{$dropdown-color}; + --#{$prefix}dropdown-bg: #{$dropdown-bg}; + --#{$prefix}dropdown-border-color: #{$dropdown-border-color}; + --#{$prefix}dropdown-border-radius: #{$dropdown-border-radius}; + --#{$prefix}dropdown-border-width: #{$dropdown-border-width}; + --#{$prefix}dropdown-inner-border-radius: #{$dropdown-inner-border-radius}; + --#{$prefix}dropdown-divider-bg: #{$dropdown-divider-bg}; + --#{$prefix}dropdown-divider-margin-y: #{$dropdown-divider-margin-y}; + --#{$prefix}dropdown-box-shadow: #{$dropdown-box-shadow}; + --#{$prefix}dropdown-link-color: #{$dropdown-link-color}; + --#{$prefix}dropdown-link-hover-color: #{$dropdown-link-hover-color}; + --#{$prefix}dropdown-link-hover-bg: #{$dropdown-link-hover-bg}; + --#{$prefix}dropdown-link-active-color: #{$dropdown-link-active-color}; + --#{$prefix}dropdown-link-active-bg: #{$dropdown-link-active-bg}; + --#{$prefix}dropdown-link-disabled-color: #{$dropdown-link-disabled-color}; + --#{$prefix}dropdown-item-padding-x: #{$dropdown-item-padding-x}; + --#{$prefix}dropdown-item-padding-y: #{$dropdown-item-padding-y}; + --#{$prefix}dropdown-header-color: #{$dropdown-header-color}; + --#{$prefix}dropdown-header-padding-x: #{$dropdown-header-padding-x}; + --#{$prefix}dropdown-header-padding-y: #{$dropdown-header-padding-y}; + // scss-docs-end dropdown-css-vars + + position: absolute; + z-index: var(--#{$prefix}dropdown-zindex); + display: none; // none by default, but block on "open" of the menu + min-width: var(--#{$prefix}dropdown-min-width); + padding: var(--#{$prefix}dropdown-padding-y) var(--#{$prefix}dropdown-padding-x); + margin: 0; // Override default margin of ul + @include font-size(var(--#{$prefix}dropdown-font-size)); + color: var(--#{$prefix}dropdown-color); + text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer) + list-style: none; + background-color: var(--#{$prefix}dropdown-bg); + background-clip: padding-box; + border: var(--#{$prefix}dropdown-border-width) solid var(--#{$prefix}dropdown-border-color); + @include border-radius(var(--#{$prefix}dropdown-border-radius)); + @include box-shadow(var(--#{$prefix}dropdown-box-shadow)); + + &[data-bs-popper] { + top: 100%; + left: 0; + margin-top: var(--#{$prefix}dropdown-spacer); + } + + @if $dropdown-padding-y == 0 { + > .dropdown-item:first-child, + > li:first-child .dropdown-item { + @include border-top-radius(var(--#{$prefix}dropdown-inner-border-radius)); + } + > .dropdown-item:last-child, + > li:last-child .dropdown-item { + @include border-bottom-radius(var(--#{$prefix}dropdown-inner-border-radius)); + } + + } +} + +// scss-docs-start responsive-breakpoints +// We deliberately hardcode the `bs-` prefix because we check +// this custom property in JS to determine Popper's positioning + +@each $breakpoint in map-keys($grid-breakpoints) { + @include media-breakpoint-up($breakpoint) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + .dropdown-menu#{$infix}-start { + --bs-position: start; + + &[data-bs-popper] { + right: auto; + left: 0; + } + } + + .dropdown-menu#{$infix}-end { + --bs-position: end; + + &[data-bs-popper] { + right: 0; + left: auto; + } + } + } +} +// scss-docs-end responsive-breakpoints + +// Allow for dropdowns to go bottom up (aka, dropup-menu) +// Just add .dropup after the standard .dropdown class and you're set. +.dropup { + .dropdown-menu[data-bs-popper] { + top: auto; + bottom: 100%; + margin-top: 0; + margin-bottom: var(--#{$prefix}dropdown-spacer); + } + + .dropdown-toggle { + @include caret(up); + } +} + +.dropend { + .dropdown-menu[data-bs-popper] { + top: 0; + right: auto; + left: 100%; + margin-top: 0; + margin-left: var(--#{$prefix}dropdown-spacer); + } + + .dropdown-toggle { + @include caret(end); + &::after { + vertical-align: 0; + } + } +} + +.dropstart { + .dropdown-menu[data-bs-popper] { + top: 0; + right: 100%; + left: auto; + margin-top: 0; + margin-right: var(--#{$prefix}dropdown-spacer); + } + + .dropdown-toggle { + @include caret(start); + &::before { + vertical-align: 0; + } + } +} + + +// Dividers (basically an `
`) within the dropdown +.dropdown-divider { + height: 0; + margin: var(--#{$prefix}dropdown-divider-margin-y) 0; + overflow: hidden; + border-top: 1px solid var(--#{$prefix}dropdown-divider-bg); + opacity: 1; // Revisit in v6 to de-dupe styles that conflict with
element +} + +// Links, buttons, and more within the dropdown menu +// +// `
diff --git a/app/views/layouts/tylium/_navbar.html.erb b/app/views/layouts/tylium/_navbar.html.erb index 36a91eaec..55be0125f 100644 --- a/app/views/layouts/tylium/_navbar.html.erb +++ b/app/views/layouts/tylium/_navbar.html.erb @@ -1,69 +1,71 @@ <% end %> -
-
-
+
+

You're merging <%= @issues.count %> Issues into a target Issue

<%= form_tag project_merge_index_path(current_project) do %> @@ -91,5 +90,4 @@
<% end %>
-
diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb index 038fbb77d..23694898c 100644 --- a/app/views/layouts/mailer.html.erb +++ b/app/views/layouts/mailer.html.erb @@ -1,3 +1,5 @@ +<% css_color = defined?(Dradis::Pro) ? '#298DCC' : '#378137' %> + @@ -11,7 +13,7 @@ background-color: #fafafa; } a { - color: #378137; /* #298DCC in pro */ + color: <%= css_color %>; text-decoration: none; } a:hover { @@ -53,15 +55,16 @@ <% if defined?(Dradis::Pro) %> - - - + + + <% end %> + @@ -69,7 +72,7 @@ diff --git a/app/views/layouts/tylium/_navbar.html.erb b/app/views/layouts/tylium/_navbar.html.erb index 6b6e88c5f..9c4aceae9 100644 --- a/app/views/layouts/tylium/_navbar.html.erb +++ b/app/views/layouts/tylium/_navbar.html.erb @@ -1,68 +1,77 @@
- <%= link_to 'Manage your Dradis email preferences', edit_user_preferences_notifications_url, style: 'color: #298DCC;' %> -
+ <%= link_to 'Manage your Dradis email preferences', edit_user_preferences_notifications_url, style: "color: #{css_color};" %> +
- Privacy | <%= link_to 'Login to Dradis', login_url, style: 'color: #378137;' %> <%# color: #298DCC in pro %> + Privacy | <%= link_to 'Login to Dradis', login_url, style: "color: #{css_color};" %>
- © 2012-<%= Time.now.year %> Dradis Framework — Security Roots Ltd
+ © 2010-<%= Time.now.year %> Dradis Framework — Security Roots Ltd
10 Portfleet Place, De Beauvoir Road
London, N1 5SZ