From 4610734f07d4da1b2af1ba959400f70fbd6c195f Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Tue, 1 Mar 2022 14:28:01 +0000 Subject: [PATCH] Add new fat footer (#6260) GitOrigin-RevId: 64c50caac2ec8d56b3f49d6f97c8a1c4d4b3a496 --- .../src/Features/Project/ProjectController.js | 1 + .../app/src/infrastructure/ExpressLocals.js | 5 + services/web/app/views/layout-marketing.pug | 7 +- services/web/app/views/layout.pug | 7 +- .../web/app/views/layout/fat-footer-base.pug | 18 ++ services/web/app/views/layout/fat-footer.pug | 79 +++++++ .../web/app/views/layout/footer-marketing.pug | 23 +- services/web/app/views/layout/footer.pug | 27 +-- .../web/app/views/layout/language-picker.pug | 23 ++ .../js/pages/user/subscription/plans.js | 50 +++-- .../stylesheets/components/footer.less | 212 ++++++++++++++++++ .../stylesheets/core/scaffolding.less | 5 +- .../frontend/stylesheets/core/variables.less | 1 + services/web/locales/en.json | 23 ++ 14 files changed, 417 insertions(+), 64 deletions(-) create mode 100644 services/web/app/views/layout/fat-footer-base.pug create mode 100644 services/web/app/views/layout/fat-footer.pug create mode 100644 services/web/app/views/layout/language-picker.pug diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index eeede1f84b..35b5e8cf6c 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -613,6 +613,7 @@ const ProjectController = { zipFileSizeLimit: Settings.maxUploadSize, isOverleaf: !!Settings.overleaf, metadata: { viewport: false }, + showThinFooter: true, // don't show the fat footer on the projects dashboard, as there's a fixed space available } const paidUser = diff --git a/services/web/app/src/infrastructure/ExpressLocals.js b/services/web/app/src/infrastructure/ExpressLocals.js index 412d14f84d..989d57f0ed 100644 --- a/services/web/app/src/infrastructure/ExpressLocals.js +++ b/services/web/app/src/infrastructure/ExpressLocals.js @@ -355,6 +355,11 @@ module.exports = function (webRouter, privateApiRouter, publicApiRouter) { next() }) + webRouter.use(function (req, res, next) { + res.locals.showThinFooter = !Features.hasFeature('saas') + next() + }) + webRouter.use(function (req, res, next) { res.locals.ExposedSettings = { isOverleaf: Settings.overleaf != null, diff --git a/services/web/app/views/layout-marketing.pug b/services/web/app/views/layout-marketing.pug index 6f16868f8f..db879cf986 100644 --- a/services/web/app/views/layout-marketing.pug +++ b/services/web/app/views/layout-marketing.pug @@ -99,7 +99,7 @@ html( block head-scripts - body + body(class=(showThinFooter ? 'thin-footer' : undefined)) if(settings.recaptcha && settings.recaptcha.siteKeyV3) script(type="text/javascript", nonce=scriptNonce, src="https://www.recaptcha.net/recaptcha/api.js?render="+settings.recaptcha.siteKeyV3) @@ -112,7 +112,10 @@ html( block content if (typeof(suppressFooter) == "undefined") - include layout/footer-marketing + if showThinFooter + include layout/footer-marketing + else + include layout/fat-footer != moduleIncludes("contactModal-marketing", locals) diff --git a/services/web/app/views/layout.pug b/services/web/app/views/layout.pug index d5483e1979..82fbd1a646 100644 --- a/services/web/app/views/layout.pug +++ b/services/web/app/views/layout.pug @@ -100,7 +100,7 @@ html( block head-scripts - body(ng-csp=(cspEnabled ? "no-unsafe-eval" : false)) + body(ng-csp=(cspEnabled ? "no-unsafe-eval" : false) class=(showThinFooter ? 'thin-footer' : undefined)) if(settings.recaptcha && settings.recaptcha.siteKeyV3) script(type="text/javascript", nonce=scriptNonce, src="https://www.recaptcha.net/recaptcha/api.js?render="+settings.recaptcha.siteKeyV3) @@ -113,7 +113,10 @@ html( block content if (typeof(suppressFooter) == "undefined") - include layout/footer + if showThinFooter + include layout/footer + else + include layout/fat-footer != moduleIncludes("contactModal", locals) diff --git a/services/web/app/views/layout/fat-footer-base.pug b/services/web/app/views/layout/fat-footer-base.pug new file mode 100644 index 0000000000..7fdb2ea5dc --- /dev/null +++ b/services/web/app/views/layout/fat-footer-base.pug @@ -0,0 +1,18 @@ +.fat-footer-base + .fat-footer-base-section.fat-footer-base-meta + .fat-footer-base-item + .fat-footer-base-copyright © #{new Date().getFullYear()} Overleaf + a(href="/legal") #{translate('privacy_and_terms')} + ul.fat-footer-base-item.list-unstyled.fat-footer-base-language + include language-picker + .fat-footer-base-section.fat-footer-base-social + .fat-footer-base-item + a.fat-footer-social(href="https://twitter.com/overleaf") + i.fa.fa-twitter-square(aria-hidden="true") + .sr-only Overleaf on Twitter + a.fat-footer-social(href="https://www.facebook.com/overleaf.editor") + i.fa.fa-facebook-square(aria-hidden="true") + .sr-only Overleaf on Facebook + a.fat-footer-social(href="https://www.linkedin.com/company/writelatex-limited") + i.fa.fa-linkedin-square(aria-hidden="true") + .sr-only Overleaf on LinkedIn diff --git a/services/web/app/views/layout/fat-footer.pug b/services/web/app/views/layout/fat-footer.pug new file mode 100644 index 0000000000..ced9865e8f --- /dev/null +++ b/services/web/app/views/layout/fat-footer.pug @@ -0,0 +1,79 @@ +footer.fat-footer.hidden-print + .fat-footer-container + .fat-footer-sections(class=hideFatFooter ? 'hidden' : undefined) + .footer-section#footer-brand + a(href='/', aria-label=settings.appName).footer-brand + + .footer-section + .footer-section-heading #{translate('About')} + + ul.list-unstyled + li + a(href="/about") #{translate('footer_about_us')} + li + a(href="/about/values") #{translate('our_values')} + li + a(href="https://apply.workable.com/overleaf/") #{translate('careers')} + li + a(href="/for/press") #{translate('press_and_awards')} + li + a(href="/blog") #{translate('blog')} + + .footer-section + .footer-section-heading #{translate('learn')} + + ul.list-unstyled + li + a(href="/learn/latex/Learn_LaTeX_in_30_minutes") #{translate('latex_in_thirty_minutes')} + li + a(href="/latex/templates") #{translate('templates')} + li + a(href="/events/webinars") #{translate('webinars')} + li + a(href="/learn/latex/Tutorials") #{translate('tutorials')} + li + a(href="/learn/latex/Inserting_Images") #{translate('how_to_insert_images')} + li + a(href="/learn/latex/Tables") #{translate('how_to_create_tables')} + + .footer-section + .footer-section-heading #{translate('footer_plans_and_pricing')} + + ul.list-unstyled + li + a(href="/learn/how-to/Overleaf_premium_features") #{translate('premium_features')} + li + a(href="/user/subscription/plans") #{translate('for_individuals_and_groups')} + li + a(href="/for/enterprises") #{translate('for_enterprise')} + li + a(href="/for/universities") #{translate('for_universities')} + li + a(href="/user/subscription/plans#view=student") #{translate('for_students')} + + .footer-section + .footer-section-heading #{translate('get_involved')} + + ul.list-unstyled + li + a(href="/for/community/advisors") #{translate('become_an_advisor')} + li + a(href="https://calendly.com/overleaf/user-research") #{translate('participate_in_user_research')} + if user + li + a(href="/beta/participate") #{translate('join_beta_program')} + li + a(href="/user/bonus") #{translate('refer_a_friend')} + + .footer-section + .footer-section-heading #{translate('help')} + + ul.list-unstyled + li + a(href="/learn") #{translate('Documentation')} + li + a(href="/contact") #{translate('footer_contact_us')} + li + a(href="https://status.overleaf.com/") #{translate('website_status')} + + include fat-footer-base diff --git a/services/web/app/views/layout/footer-marketing.pug b/services/web/app/views/layout/footer-marketing.pug index 568a5bfd5f..69b215519a 100644 --- a/services/web/app/views/layout/footer-marketing.pug +++ b/services/web/app/views/layout/footer-marketing.pug @@ -17,27 +17,7 @@ footer.site-footer strong.text-muted | if showLanguagePicker - li.dropdown.dropup.subdued - a.dropdown-toggle( - href="#", - data-toggle="dropdown", - aria-haspopup="true", - aria-expanded="false", - aria-label="Select " + translate('language'), - data-ol-lang-selector-tooltip, - title=translate('language') - ) - i.fa.fa-fw.fa-language - | - | #{settings.translatedLanguages[currentLngCode]} - - ul.dropdown-menu(role="menu") - li.dropdown-header #{translate("language")} - each subdomainDetails, subdomain in settings.i18n.subdomainLang - if !subdomainDetails.hide - li.lngOption - a.menu-indent(href=subdomainDetails.url+currentUrlWithQueryParams) - | #{settings.translatedLanguages[subdomainDetails.lngCode]} + include language-picker if showLanguagePicker && hasCustomLeftNav li @@ -51,7 +31,6 @@ footer.site-footer | !{item.text} ul.col-md-3.text-right - each item in nav.right_footer li(ng-non-bindable) if item.url diff --git a/services/web/app/views/layout/footer.pug b/services/web/app/views/layout/footer.pug index 993280a512..2ca02659a3 100644 --- a/services/web/app/views/layout/footer.pug +++ b/services/web/app/views/layout/footer.pug @@ -6,10 +6,9 @@ footer.site-footer .site-footer-content.hidden-print .row ul.col-md-9 - if hasFeature('saas') - li © #{new Date().getFullYear()} Overleaf - else if !settings.nav.hide_powered_by + if !settings.nav.hide_powered_by li + //- year of Server Pro release, static | © 2021 | a(href='https://www.overleaf.com/for/enterprises') Powered by Overleaf @@ -19,27 +18,7 @@ footer.site-footer strong.text-muted | if showLanguagePicker - li.dropdown.dropup.subdued(dropdown) - a.dropdown-toggle( - href="#", - dropdown-toggle, - data-toggle="dropdown", - aria-haspopup="true", - aria-expanded="false", - aria-label="Select " + translate('language') - tooltip=translate('language') - ) - i.fa.fa-fw.fa-language - | - | #{settings.translatedLanguages[currentLngCode]} - - ul.dropdown-menu - li.dropdown-header #{translate("language")} - each subdomainDetails, subdomain in settings.i18n.subdomainLang - if !subdomainDetails.hide - li.lngOption - a.menu-indent(href=subdomainDetails.url+currentUrlWithQueryParams) - | #{settings.translatedLanguages[subdomainDetails.lngCode]} + include language-picker if showLanguagePicker && hasCustomLeftNav li diff --git a/services/web/app/views/layout/language-picker.pug b/services/web/app/views/layout/language-picker.pug new file mode 100644 index 0000000000..205938831d --- /dev/null +++ b/services/web/app/views/layout/language-picker.pug @@ -0,0 +1,23 @@ +li.dropdown.dropup.subdued(dropdown).language-picker + a.dropdown-toggle#language-picker-toggle( + href="#", + dropdown-toggle, + data-ol-lang-selector-tooltip, + data-toggle="dropdown", + role="button" + aria-haspopup="true", + aria-expanded="false", + aria-label="Select " + translate('language'), + tooltip=translate('language') + ) + i.fa.fa-fw.fa-language + | + | #{settings.translatedLanguages[currentLngCode]} + + ul.dropdown-menu(role="menu" aria-labelledby="language-picker-toggle") + li.dropdown-header #{translate("language")} + each subdomainDetails, subdomain in settings.i18n.subdomainLang + if !subdomainDetails.hide + li.lngOption + a.menu-indent(href=subdomainDetails.url+currentUrlWithQueryParams role="menuitem") + | #{settings.translatedLanguages[subdomainDetails.lngCode]} diff --git a/services/web/frontend/js/pages/user/subscription/plans.js b/services/web/frontend/js/pages/user/subscription/plans.js index f643fae272..2a2c7b6091 100644 --- a/services/web/frontend/js/pages/user/subscription/plans.js +++ b/services/web/frontend/js/pages/user/subscription/plans.js @@ -7,24 +7,28 @@ import getMeta from '../../../utils/meta' let currentView = 'monthly' let currentCurrencyCode = getMeta('ol-recommendedCurrency') +function selectView(view) { + document.querySelectorAll('[data-ol-view-tab]').forEach(el => { + if (el.getAttribute('data-ol-view-tab') === view) { + el.classList.add('active') + } else { + el.classList.remove('active') + } + }) + document.querySelectorAll('[data-ol-view]').forEach(el => { + el.hidden = el.getAttribute('data-ol-view') !== view + }) + currentView = view + updateLinkTargets() +} + function setUpViewSwitching(liEl) { const view = liEl.getAttribute('data-ol-view-tab') liEl.querySelector('a').addEventListener('click', function (e) { e.preventDefault() - eventTracking.send('subscription-funnel', 'plans-page', `${view}-prices`) // deprecated by plans-page-toggle + eventTracking.send('subscription-funnel', 'plans-page', `${view}-prices`) eventTracking.sendMB('plans-page-toggle', { button: view }) - document.querySelectorAll('[data-ol-view-tab]').forEach(el => { - if (el.getAttribute('data-ol-view-tab') === view) { - el.classList.add('active') - } else { - el.classList.remove('active') - } - }) - document.querySelectorAll('[data-ol-view]').forEach(el => { - el.hidden = el.getAttribute('data-ol-view') !== view - }) - currentView = view - updateLinkTargets() + selectView(view) }) } @@ -77,6 +81,23 @@ function updateLinkTargets() { }) } +function selectViewFromHash() { + try { + const params = new URLSearchParams(window.location.hash.substring(1)) + const view = params.get('view') + if (view) { + // make sure the selected view is valid + if (document.querySelector(`[data-ol-view-tab="${view}"]`)) { + selectView(view) + // clear the hash so it doesn't persist when switching plans + window.location.hash = '' + } + } + } catch { + // do nothing + } +} + document.querySelectorAll('[data-ol-view-tab]').forEach(setUpViewSwitching) document .querySelectorAll('[data-ol-currencyCode-switch]') @@ -85,3 +106,6 @@ document .querySelectorAll('[data-ol-start-new-subscription]') .forEach(setUpSubscriptionTracking) updateLinkTargets() + +selectViewFromHash() +window.addEventListener('hashchange', selectViewFromHash) diff --git a/services/web/frontend/stylesheets/components/footer.less b/services/web/frontend/stylesheets/components/footer.less index 25f57544e4..1128dd3bd9 100644 --- a/services/web/frontend/stylesheets/components/footer.less +++ b/services/web/frontend/stylesheets/components/footer.less @@ -50,3 +50,215 @@ footer.site-footer { display: inline-block; vertical-align: middle; } + +.fat-footer { + background: @ol-blue-gray-6; + color: @ol-blue-gray-1; + display: flex; + flex-direction: column; + + .fat-footer-container { + margin: @margin-xxl auto; + } + + a { + color: @ol-blue-gray-1; + } + + .footer-brand-container { + flex: 1; + } + + .fat-footer-sections { + display: grid; + column-gap: @margin-md; + padding: 0 @padding-md @padding-md; + } + + .footer-brand { + display: block; + height: 37px; /* TODO: configurable? */ + width: @navbar-brand-width; + background-image: @navbar-brand-image-url; + background-size: contain; + background-repeat: no-repeat; + background-position: left center; + } + + .footer-section-heading { + font-family: @font-family-serif; + font-size: @font-size-h3; + line-height: @headings-line-height; + margin-bottom: @margin-md; + margin-top: @margin-md; + } + + .footer-section ul { + font-family: @font-family-sans-serif; + font-size: @font-size-base; + line-height: @line-height-small; + } + + .footer-section li { + padding-bottom: @padding-sm; + } + + #footer-brand { + grid-column: ~'1/-1'; + margin-top: @margin-md; + } + + .fat-footer-base { + color: @ol-blue-gray-2; + font-size: @font-size-small; + line-height: @line-height-base; + + .fat-footer-base-meta a:not(.dropdown-toggle) { + color: inherit; + } + + .language-picker .dropdown-menu { + .dropdown-header { + display: none; /* hiding rather than removing as still needed in the thin footer */ + } + + a { + color: @gray-dark; + + &:hover { + color: white; + } + } + } + } + + .fat-footer-base-item { + display: flex; + white-space: nowrap; + } + + @media (max-width: @screen-xs-max) { + .fat-footer-sections { + grid-template-columns: repeat(2, 1fr); + grid-template-rows: repeat(4, auto); + } + + .fat-footer-base { + display: flex; + flex-direction: column; + align-items: center; + margin: @margin-md auto 0; + + .fat-footer-base-section { + display: flex; + align-items: center; + } + + .fat-footer-base-social { + order: 1; + } + + .fat-footer-base-meta { + display: flex; + flex-direction: column-reverse; + align-items: center; + order: 2; + } + + .fat-footer-base-item { + padding: @padding-xs; + margin: @margin-lg / 2; + } + + .fat-footer-base-meta .fat-footer-base-item { + gap: @margin-lg; + } + + .fat-footer-base-social .fat-footer-base-item { + gap: @margin-md; + } + + .fat-footer-social { + font-size: @fat-footer-social-font-size; + } + + .fat-footer-base-copyright { + order: 2; + } + } + } + + @media (min-width: @screen-sm-min) { + .fat-footer-container { + width: (@screen-sm - (@padding-sm * 2)); + } + + #footer-brand { + grid-column: auto; + grid-row: ~'1/-1'; + } + + .fat-footer-sections { + grid-template-columns: repeat(4, 1fr); + grid-template-rows: repeat(2, auto); + } + + .footer-section:last-of-type { + grid-column: 4; + } + + .footer-section ul { + line-height: 20px; + } + + .fat-footer-base { + display: flex; + justify-content: space-between; + margin: @margin-md auto; + + .fat-footer-base-section { + display: flex; + align-items: center; + } + + .fat-footer-base-item { + padding: @padding-xs; + margin: @margin-xs; + } + + .fat-footer-base-meta .fat-footer-base-item { + gap: @margin-md; + } + + .fat-footer-social { + margin: 0 @margin-xs; + font-size: @fat-footer-social-font-size; + } + } + } + + @media (min-width: @screen-md-min) { + .fat-footer-container { + width: (@screen-md - @padding-md); + } + + .fat-footer-sections { + grid-template-columns: repeat(4, 1fr); + } + } + + @media (min-width: @screen-lg-min) { + .fat-footer-container { + width: (@screen-lg - @padding-lg); + } + + .fat-footer-sections { + grid-template-columns: repeat(6, 1fr); + grid-template-rows: auto; + } + + .footer-section:last-of-type { + grid-column: 6; + } + } +} diff --git a/services/web/frontend/stylesheets/core/scaffolding.less b/services/web/frontend/stylesheets/core/scaffolding.less index ffbe13318f..d3409045d4 100755 --- a/services/web/frontend/stylesheets/core/scaffolding.less +++ b/services/web/frontend/stylesheets/core/scaffolding.less @@ -31,11 +31,14 @@ body { background-color: @body-bg; min-height: 100%; position: relative; - padding-bottom: @footer-height; & > .content { min-height: calc(~'100vh -' @footer-height); padding-top: @header-height + @content-margin-vertical; } + + .thin-footer { + padding-bottom: @footer-height; + } } // Reset fonts for relevant elements diff --git a/services/web/frontend/stylesheets/core/variables.less b/services/web/frontend/stylesheets/core/variables.less index 953e31ee4b..8f6398a600 100644 --- a/services/web/frontend/stylesheets/core/variables.less +++ b/services/web/frontend/stylesheets/core/variables.less @@ -969,6 +969,7 @@ @footer-link-hover-color: @link-hover-color-alt; @footer-bg-color: #fff; @footer-padding: 2em 0; +@fat-footer-social-font-size: 30px; // Editor header @ide-body-top-offset: 40px; diff --git a/services/web/locales/en.json b/services/web/locales/en.json index e7177f6553..8d175e60e3 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -832,6 +832,29 @@ "Terms": "Terms", "Security": "Security", "About": "About", + "learn": "Learn", + "latex_in_thirty_minutes": "LaTeX in 30 minutes", + "how_to_insert_images": "How to insert images", + "how_to_create_tables": "How to create tables", + "for_individuals_and_groups": "For individuals & groups", + "for_enterprise": "For enterprise", + "for_universities": "For universities", + "for_students": "For students", + "get_involved": "Get involved", + "become_an_advisor": "Become an __appName__ advisor", + "participate_in_user_research": "Participate in user research", + "join_beta_program": "Join beta program", + "refer_a_friend": "Refer a friend", + "webinars": "Webinars", + "tutorials": "Tutorials", + "footer_plans_and_pricing": "Plans & pricing", + "footer_contact_us": "Contact us", + "website_status": "Website status", + "privacy_and_terms": "Privacy and Terms", + "footer_about_us": "About us", + "our_values": "Our values", + "careers": "Careers", + "press_and_awards": "Press & awards", "editor_disconected_click_to_reconnect": "Editor disconnected, click anywhere to reconnect.", "word_count": "Word Count", "please_compile_pdf_before_word_count": "Please compile your project before performing a word count",