Merge pull request #184 from Retrospring/mobile-layout
Adjust site layout to be nicer to use on smaller screens
This commit is contained in:
commit
89ce3e6e53
|
@ -39,6 +39,7 @@
|
|||
|
||||
@import
|
||||
"overrides/alerts",
|
||||
"overrides/badges",
|
||||
"overrides/bootstrap-datetimepicker",
|
||||
"overrides/buttons",
|
||||
"overrides/colors",
|
||||
|
@ -81,6 +82,7 @@
|
|||
"components/inbox-entry",
|
||||
"components/jumbotron",
|
||||
"components/locales",
|
||||
"components/mobile-nav",
|
||||
"components/notifications",
|
||||
"components/profile",
|
||||
"components/question",
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
bottom: unquote('calc(#{$navbar-height} + env(safe-area-inset-bottom))');
|
||||
right: 0px;
|
||||
margin-right: 7px;
|
||||
margin-bottom: 7px;
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
.container--main {
|
||||
padding-top: map-get($spacers, 3);
|
||||
padding-bottom: map-get($spacers, 3);
|
||||
// Sass doesn't know about the safe-area-inset-* env vars and throws a syntax error
|
||||
// We can get around this by using unquote()
|
||||
// Sass also has its own built-in max() function which is not the same as the CSS one
|
||||
// In order to use the correct one we can write it as Max() instead
|
||||
padding-left: unquote('Max(15px, env(safe-area-inset-left))');
|
||||
padding-right: unquote('Max(15px, env(safe-area-inset-right))');
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
#rs-mobile-nav {
|
||||
.container {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
padding: 4px 0 unquote('calc(env(safe-area-inset-bottom) + 4px)') 0;
|
||||
|
||||
.navbar-icon-row {
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
|
||||
.nav-link {
|
||||
padding: 0;
|
||||
|
||||
.fa {
|
||||
padding-top: 8px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
transform: translateX(16px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#rs-mobile-nav-profile {
|
||||
position: fixed;
|
||||
bottom: unquote("calc(env(safe-area-inset-bottom) + #{$navbar-height + 2px})");
|
||||
right: unquote("calc(env(safe-area-inset-right) + 15px)");
|
||||
left: unset;
|
||||
top: unset;
|
||||
}
|
|
@ -3,5 +3,15 @@ body {
|
|||
word-wrap: break-word;
|
||||
color: RGB(var(--body-text));
|
||||
background-color: var(--background);
|
||||
padding-top: $navbar-height;
|
||||
@include media-breakpoint-up('lg') {
|
||||
padding-top: $navbar-height;
|
||||
}
|
||||
@include media-breakpoint-down('md') {
|
||||
padding-bottom: $navbar-height;
|
||||
}
|
||||
|
||||
&.not-logged-in {
|
||||
padding-top: $navbar-height;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
@each $color in $color-names {
|
||||
.badge-#{$color} {
|
||||
color: var(--#{$color});
|
||||
background-color: RGB(var(--#{$color}-text));
|
||||
}
|
||||
}
|
|
@ -16,14 +16,17 @@ module ApplicationHelper
|
|||
].compact.join(" ")
|
||||
|
||||
unless options[:icon].nil?
|
||||
body = "#{content_tag(:i, '', class: "mdi-#{options[:icon]}")} #{body}"
|
||||
if options[:icon_only]
|
||||
body = "#{content_tag(:i, '', class: "fa fa-#{options[:icon]}", title: body)}"
|
||||
else
|
||||
body = "#{content_tag(:i, '', class: "fa fa-#{options[:icon]}")} #{body}"
|
||||
end
|
||||
end
|
||||
unless options[:badge].nil?
|
||||
# TODO: make this prettier?
|
||||
body << " #{
|
||||
content_tag(:span, options[:badge], class: ("badge#{
|
||||
" badge-#{options[:badge_color]}" unless options[:badge_color].nil?
|
||||
}"))}"
|
||||
badge_class = "badge"
|
||||
badge_class << " badge-#{options[:badge_color]}" unless options[:badge_color].nil?
|
||||
badge_class << " badge-pill" if options[:badge_pill]
|
||||
body += " #{content_tag(:span, options[:badge], class: badge_class)}"
|
||||
end
|
||||
|
||||
content_tag(:li, link_to(body.html_safe, path, class: "nav-link"), class: classes)
|
||||
|
|
|
@ -50,12 +50,21 @@ module ThemeHelper
|
|||
def theme_color
|
||||
theme = get_active_theme
|
||||
if theme
|
||||
"##{get_hex_color_from_theme_value(theme.primary_color)}"
|
||||
theme.theme_color
|
||||
else
|
||||
'#5e35b1'
|
||||
end
|
||||
end
|
||||
|
||||
def mobile_theme_color
|
||||
theme = get_active_theme
|
||||
if theme
|
||||
theme.mobile_theme_color
|
||||
else
|
||||
'#f0edf4'
|
||||
end
|
||||
end
|
||||
|
||||
def get_active_theme
|
||||
if @user&.theme
|
||||
if user_signed_in?
|
||||
|
|
|
@ -20,4 +20,8 @@ class Theme < ApplicationRecord
|
|||
def theme_color
|
||||
('#' + ('0000000' + primary_color.to_s(16))[-6, 6])
|
||||
end
|
||||
|
||||
def mobile_theme_color
|
||||
('#' + ('0000000' + background_color.to_s(16))[-6, 6])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- provide(:title, generate_title('Edit announcement'))
|
||||
.container.container--main
|
||||
.container-lg.container--main
|
||||
.card
|
||||
.card-body
|
||||
= bootstrap_form_for(@announcement, url: { action: 'update' }, method: 'PATCH') do |f|
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- provide(:title, generate_title('Announcements'))
|
||||
.container.container--main
|
||||
.container-lg.container--main
|
||||
- @announcements.each do |announcement|
|
||||
.card
|
||||
.card-body
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- provide(:title, generate_title('Add new announcement'))
|
||||
.container.container--main
|
||||
.container-lg.container--main
|
||||
.card
|
||||
.card-body
|
||||
= bootstrap_form_for(@announcement, url: { action: 'create' }) do |f|
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
- provide(:title, answer_title(@answer))
|
||||
- provide(:og, answer_opengraph(@answer))
|
||||
.container.container--main
|
||||
.container-lg.container--main
|
||||
= render 'answerbox', a: @answer, display_all: @display_all
|
||||
|
|
|
@ -3,8 +3,12 @@
|
|||
%head
|
||||
%meta{ charset: 'utf-8' }
|
||||
%meta{ 'http-equiv': 'X-UA-Compatible', content: 'IE=edge' }
|
||||
%meta{ name: 'viewport', content: 'width=device-width, initial-scale=1, user-scalable=no' }
|
||||
%meta{ name: 'theme-color', content: theme_color }
|
||||
%meta{ name: 'viewport', content: 'width=device-width, initial-scale=1, user-scalable=no, viewport-fit=cover' }
|
||||
- if user_signed_in?
|
||||
%meta{ name: 'theme-color', content: theme_color, media: '(min-width: 993px)' }
|
||||
%meta{ name: 'theme-color', content: mobile_theme_color, media: '(max-width: 992px)' }
|
||||
- else
|
||||
%meta{ name: 'theme-color', content: theme_color }
|
||||
%link{ rel: 'apple-touch-icon', href: '/apple-touch-icon-precomposed.png' }
|
||||
%link{ rel: 'icon', href: '/images/favicon/favicon-16.png', sizes: '16x16' }
|
||||
%link{ rel: 'icon', href: '/icon-152.png', sizes: '152x152' }
|
||||
|
@ -19,7 +23,7 @@
|
|||
= csrf_meta_tags
|
||||
= yield(:og)
|
||||
= yield(:meta)
|
||||
%body
|
||||
%body{ class: user_signed_in? ? '' : 'not-logged-in' }
|
||||
- if user_signed_in?
|
||||
= render 'navigation/main'
|
||||
- else
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.container.container--main
|
||||
.container-lg.container--main
|
||||
.row
|
||||
.col-md-3.col-sm-4.d-none.d-sm-block
|
||||
= render 'shared/sidebar'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.container.container--main
|
||||
.container-lg.container--main
|
||||
.row
|
||||
.col-md-3.col-xs-12.col-sm-4.order-2.order-sm-1
|
||||
= render 'inbox/sidebar', delete_id: @delete_id, disabled: @disabled, inbox_count: @inbox_count
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
= render 'navigation/moderation'
|
||||
.container.container--main
|
||||
.container-lg.container--main
|
||||
.row
|
||||
.col-md-3.col-sm-4.col-xs-12
|
||||
= render 'tabs/moderation'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
= render 'navigation/notification'
|
||||
.container.container--main
|
||||
.container-lg.container--main
|
||||
.row
|
||||
.col-md-3.col-xs-12.col-sm-4
|
||||
= render 'tabs/notifications'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.container.container--main
|
||||
.container-lg.container--main
|
||||
.row
|
||||
.col-md-3.col-xs-12.col-sm-4
|
||||
= render 'tabs/settings'
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
%nav.navbar.navbar-themed.navbar-expand-lg.bg-primary.fixed-top.d-lg-block.d-none{ role: :navigation }
|
||||
.container{ class: ios_web_app? ? 'ios-web-app' : '' }
|
||||
%a.navbar-brand{ href: '/' }
|
||||
= APP_CONFIG['site_name']
|
||||
%ul.nav.navbar-nav.mr-auto
|
||||
= nav_entry t('views.navigation.timeline'), root_path, icon: 'home'
|
||||
= nav_entry t('views.navigation.inbox'), '/inbox', icon: 'inbox', badge: inbox_count
|
||||
- if APP_CONFIG.dig(:features, :discover, :enabled) || current_user.mod?
|
||||
= nav_entry t('views.navigation.discover'), discover_path, icon: 'compass'
|
||||
%ul.nav.navbar-nav
|
||||
- if @user.present? && @user != current_user
|
||||
%li.nav-item.d-none.d-sm-block{ data: { toggle: 'tooltip', placement: 'bottom' }, title: t('views.actions.list') }
|
||||
%a.nav-link{ href: '#', data: { target: '#modal-list-memberships', toggle: :modal } }
|
||||
%i.fa.fa-list.hidden-xs
|
||||
%span.d-none.d-sm-inline.d-md-none= t('views.actions.list')
|
||||
= render 'navigation/main/notifications'
|
||||
%li.nav-item.d-none.d-sm-block{ data: { toggle: 'tooltip', placement: 'bottom' }, title: t('views.actions.ask_question') }
|
||||
%a.nav-link{ href: '#', name: 'toggle-all-ask', data: { target: '#modal-ask-followers', toggle: :modal } }
|
||||
%i.fa.fa-pencil-square-o
|
||||
= render 'navigation/main/profile'
|
|
@ -1,26 +1,5 @@
|
|||
%nav.navbar.navbar-themed.navbar-expand-lg.bg-primary.fixed-top{ role: :navigation }
|
||||
.container{ class: ios_web_app? ? 'ios-web-app' : '' }
|
||||
%a.navbar-brand{ href: '/' }= APP_CONFIG['site_name']
|
||||
%button.navbar-toggler{ data: { target: '#j2-main-navbar-collapse', toggle: :collapse }, type: :button }
|
||||
%span.sr-only Toggle navigation
|
||||
%span.navbar-toggler-icon
|
||||
.collapse.navbar-collapse#j2-main-navbar-collapse
|
||||
%ul.nav.navbar-nav.mr-auto
|
||||
= nav_entry t('views.navigation.timeline'), root_path
|
||||
= nav_entry t('views.navigation.inbox'), '/inbox', badge: inbox_count
|
||||
- if APP_CONFIG.dig(:features, :discover, :enabled) || current_user.mod?
|
||||
= nav_entry t('views.navigation.discover'), discover_path
|
||||
%ul.nav.navbar-nav
|
||||
- if @user.present? && @user != current_user
|
||||
%li.nav-item.d-none.d-sm-block{ data: { toggle: 'tooltip', placement: 'bottom' }, title: t('views.actions.list') }
|
||||
%a.nav-link{ href: '#', data: { target: '#modal-list-memberships', toggle: :modal } }
|
||||
%i.fa.fa-list.hidden-xs
|
||||
%span.d-none.d-sm-inline.d-md-none= t('views.actions.list')
|
||||
= render 'navigation/main/notifications'
|
||||
%li.nav-item.d-none.d-sm-block{ data: { toggle: 'tooltip', placement: 'bottom' }, title: t('views.actions.ask_question') }
|
||||
%a.nav-link{ href: '#', name: 'toggle-all-ask', data: { target: '#modal-ask-followers', toggle: :modal } }
|
||||
%i.fa.fa-pencil-square-o
|
||||
= render 'navigation/main/profile'
|
||||
= render 'navigation/desktop'
|
||||
= render 'navigation/mobile'
|
||||
|
||||
= render 'modal/ask'
|
||||
%button.btn.btn-primary.btn-fab.d-block.d-sm-none{ data: { target: '#modal-ask-followers', toggle: :modal }, type: 'button' }
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
= render 'navigation/mobile/profile'
|
||||
- notifications_icon = notification_count.nil? ? 'bell-o' : 'bell'
|
||||
%nav.navbar.navbar-themed.bg-primary.fixed-bottom.d-lg-none.d-block#rs-mobile-nav{ role: :navigation }
|
||||
.container{ class: ios_web_app? ? 'ios-web-app' : '' }
|
||||
%ul.nav.navbar-nav.navbar-icon-row
|
||||
= nav_entry t('views.navigation.timeline'), root_path, icon: 'home', icon_only: true
|
||||
= nav_entry t('views.navigation.inbox'), '/inbox',
|
||||
badge: inbox_count, badge_color: 'primary', badge_pill: true,
|
||||
icon: 'inbox', icon_only: true
|
||||
- if APP_CONFIG.dig(:features, :discover, :enabled) || current_user.mod?
|
||||
= nav_entry t('views.navigation.discover'), discover_path, icon: 'compass', icon_only: true
|
||||
= nav_entry t('views.navigation.notifications'), '/notifications',
|
||||
badge: notification_count, badge_color: 'primary', badge_pill: true,
|
||||
icon: notifications_icon, icon_only: true
|
||||
%li.nav-item.profile--image-dropdown
|
||||
%a.nav-link{ href: '#', data: { toggle: 'dropdown', target: '#rs-mobile-nav-profile' }, aria: { controls: 'rs-mobile-nav-profile', expanded: 'false' } }
|
||||
%img.avatar-md.d-inline{ src: current_user.profile_picture.url(:small) }
|
|
@ -0,0 +1,31 @@
|
|||
.dropdown-menu#rs-mobile-nav-profile
|
||||
%h6.dropdown-header.d-none.d-sm-block= current_user.screen_name
|
||||
%a.dropdown-item{ href: show_user_profile_path(current_user.screen_name) }
|
||||
%i.fa.fa-fw.fa-user
|
||||
= t('views.navigation.show')
|
||||
%a.dropdown-item{ href: edit_user_registration_path }
|
||||
%i.fa.fa-fw.fa-cog
|
||||
= t('views.navigation.settings')
|
||||
.dropdown-divider
|
||||
- if current_user.has_role?(:administrator)
|
||||
%a.dropdown-item{ href: rails_admin_path }
|
||||
%i.fa.fa-fw.fa-cogs
|
||||
= t('views.navigation.admin')
|
||||
%a.dropdown-item{ href: sidekiq_web_path }
|
||||
%i.fa.fa-fw.fa-bar-chart
|
||||
= t('views.navigation.sidekiq')
|
||||
%a.dropdown-item{ href: pghero_path }
|
||||
%i.fa.fa-fw.fa-database
|
||||
Database Monitor
|
||||
%a.dropdown-item{ href: announcement_index_path }
|
||||
%i.fa.fa-fw.fa-info
|
||||
Announcements
|
||||
.dropdown-divider
|
||||
- if current_user.mod?
|
||||
%a.dropdown-item{ href: moderation_path }
|
||||
%i.fa.fa-fw.fa-gavel
|
||||
= t('views.navigation.moderation')
|
||||
.dropdown-divider
|
||||
%a.dropdown-item{ href: destroy_user_session_path, data: { method: :delete } }
|
||||
%i.fa.fa-fw.fa-sign-out
|
||||
= t 'views.sessions.destroy'
|
|
@ -1,4 +1,4 @@
|
|||
.container.container--main
|
||||
.container-lg.container--main
|
||||
.row.justify-content-center
|
||||
.col-md-5.totp-setup__recovery-container
|
||||
.card
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- provide(:title, generate_title('Privacy Policy'))
|
||||
.container.container--main
|
||||
.container-lg.container--main
|
||||
.card
|
||||
.card-body
|
||||
= raw_markdown_io 'service-docs/en/policy/privacy.md'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- provide(:title, generate_title('Terms of Service'))
|
||||
.container.container--main
|
||||
.container-lg.container--main
|
||||
.card
|
||||
.card-body
|
||||
= raw_markdown_io 'service-docs/en/policy/terms.md'
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :theme do
|
||||
primary_color { 9_342_168 }
|
||||
primary_text { 16_777_215 }
|
||||
danger_color { 14_257_035 }
|
||||
danger_text { 16_777_215 }
|
||||
success_color { 12_573_067 }
|
||||
success_text { 16_777_215 }
|
||||
warning_color { 14_261_899 }
|
||||
warning_text { 16_777_215 }
|
||||
info_color { 9_165_273 }
|
||||
info_text { 16_777_215 }
|
||||
dark_color { 6_710_886 }
|
||||
dark_text { 15_658_734 }
|
||||
raised_background { 16_777_215 }
|
||||
background_color { 13_026_795 }
|
||||
body_text { 3_355_443 }
|
||||
muted_text { 3_355_443 }
|
||||
input_color { 15_789_556 }
|
||||
input_text { 6_710_886 }
|
||||
raised_accent { 16_250_871 }
|
||||
light_color { 16_316_922 }
|
||||
light_text { 0 }
|
||||
end
|
||||
end
|
|
@ -3,6 +3,40 @@
|
|||
require "rails_helper"
|
||||
|
||||
describe ApplicationHelper, :type => :helper do
|
||||
describe '#nav_entry' do
|
||||
it 'should return a HTML navigation item which links to a given address' do
|
||||
allow(self).to receive(:current_page?).and_return(false)
|
||||
expect(nav_entry('Example', '/example')).to(
|
||||
eq('<li class="nav-item "><a class="nav-link" href="/example">Example</a></li>')
|
||||
)
|
||||
end
|
||||
|
||||
it 'should return with an active attribute if the link matches the current URL' do
|
||||
allow(self).to receive(:current_page?).and_return(true)
|
||||
expect(nav_entry('Example', '/example')).to(
|
||||
eq('<li class="nav-item active "><a class="nav-link" href="/example">Example</a></li>')
|
||||
)
|
||||
end
|
||||
|
||||
it 'should include an icon if given' do
|
||||
allow(self).to receive(:current_page?).and_return(false)
|
||||
expect(nav_entry('Example', '/example', icon: 'beaker')).to(
|
||||
eq('<li class="nav-item "><a class="nav-link" href="/example"><i class="fa fa-beaker"></i> Example</a></li>')
|
||||
)
|
||||
end
|
||||
|
||||
it 'should include a badge if given' do
|
||||
allow(self).to receive(:current_page?).and_return(false)
|
||||
expect(nav_entry('Example', '/example', badge: 3)).to(
|
||||
eq('<li class="nav-item "><a class="nav-link" href="/example">Example <span class="badge">3</span></a></li>')
|
||||
)
|
||||
|
||||
expect(nav_entry('Example', '/example', badge: 3, badge_color: 'primary', badge_pill: true)).to(
|
||||
eq('<li class="nav-item "><a class="nav-link" href="/example">Example <span class="badge badge-primary badge-pill">3</span></a></li>')
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#bootstrap_color" do
|
||||
it 'should map error and alert to danger' do
|
||||
expect(bootstrap_color("error")).to eq("danger")
|
||||
|
|
|
@ -141,4 +141,54 @@ describe ThemeHelper, :type => :helper do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#theme_color' do
|
||||
subject { helper.theme_color }
|
||||
|
||||
context 'when user is signed in' do
|
||||
let(:user) { FactoryBot.create(:user) }
|
||||
let(:theme) { FactoryBot.create(:theme, user: user) }
|
||||
|
||||
before(:each) do
|
||||
user.theme = theme
|
||||
user.save!
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'should return the user theme\'s primary color' do
|
||||
expect(subject).to eq('#8e8cd8')
|
||||
end
|
||||
end
|
||||
|
||||
context 'user is not signed in' do
|
||||
it 'should return the default primary color' do
|
||||
expect(subject).to eq('#5e35b1')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#mobile_theme_color' do
|
||||
subject { helper.mobile_theme_color }
|
||||
|
||||
context 'when user is signed in' do
|
||||
let(:user) { FactoryBot.create(:user) }
|
||||
let(:theme) { FactoryBot.create(:theme, user: user) }
|
||||
|
||||
before(:each) do
|
||||
user.theme = theme
|
||||
user.save!
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'should return the user theme\'s background color' do
|
||||
expect(subject).to eq('#c6c5eb')
|
||||
end
|
||||
end
|
||||
|
||||
context 'user is not signed in' do
|
||||
it 'should return the default background color' do
|
||||
expect(subject).to eq('#f0edf4')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe(Theme, type: :model) do
|
||||
context 'user-defined theme' do
|
||||
let(:user) { FactoryBot.create(:user) }
|
||||
let(:theme) { FactoryBot.create(:theme, user: user) }
|
||||
|
||||
describe '#theme_color' do
|
||||
subject { theme.theme_color }
|
||||
|
||||
it 'should return the theme\'s primary colour as a hex triplet' do
|
||||
expect(subject).to eq('#8e8cd8')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#mobile_theme_color' do
|
||||
subject { theme.mobile_theme_color }
|
||||
|
||||
it 'should return the theme\'s background colour as a hex triplet' do
|
||||
expect(subject).to eq('#c6c5eb')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue