first commit

This commit is contained in:
2025-04-24 13:11:28 +08:00
commit ff9c54d5e4
5960 changed files with 834111 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
mixin back-to-btns(settingsAnchor)
a.btn.btn-secondary.text-capitalize(href=`/user/settings${settingsAnchor ? '#' + settingsAnchor : '' }`) #{translate('back_to_account_settings')}
|
a.btn.btn-secondary.text-capitalize(href='/project') #{translate('back_to_your_projects')}

View File

@@ -0,0 +1,25 @@
mixin begin_now_card()
- var registerURL = '/register'
- var plansURL = '/user/subscription/plans'
- var isUserLoggedIn = !!getSessionUser()
.begin-now-card
div.card.card-pattern
.card-body
p.dm-mono
span.font-size-display-xs
span.text-purple-bright \begin
wbr
span.text-green-bright {
span now
span.text-green-bright }
p #{translate("discover_why_over_people_worldwide_trust_overleaf", {count: settings.userCountInMillions})}
p.card-links
if !isUserLoggedIn
a.btn.btn-primary.card-link(
href=registerURL
) #{translate("sign_up_for_free")}
a.btn.card-link(
class = isUserLoggedIn ? 'btn-primary' : 'btn-secondary'
href=plansURL
) #{translate("explore_all_plans")}

View File

@@ -0,0 +1,10 @@
mixin bookmarkable-tabset-header(id, title, active)
li(role="presentation")
a.nav-link(
href='#' + id
class=(active ? 'active' : '')
aria-controls=id
role="tab"
data-toggle="tab"
data-ol-bookmarkable-tab
) #{title}

View File

@@ -0,0 +1,3 @@
mixin bootstrap-js(bootstrapVersion)
each file in (entrypointScripts(bootstrapVersion === 5 ? 'bootstrap-5' : 'bootstrap-3'))
script(type="text/javascript", nonce=scriptNonce, src=file)

View File

@@ -0,0 +1,5 @@
mixin eyebrow(text)
span.eyebrow-text
span(aria-hidden="true") {
span #{text}
span(aria-hidden="true") }

View File

@@ -0,0 +1,30 @@
mixin faq_search-marketing(headerText, headerClass)
if (typeof(settings.algolia) != "undefined" && typeof(settings.algolia.indexes) != "undefined" && typeof(settings.algolia.indexes.wiki) != "undefined")
if headerText
div(class=headerClass) #{headerText}
.wiki
form.project-search.form-horizontal(role="search" data-ol-faq-search)
.form-group.has-feedback.has-feedback-left
.col-sm-12
input.form-control(type='text', placeholder="Search help library…")
i.fa.fa-search.form-control-feedback-left(aria-hidden="true")
i.fa.fa-times.form-control-feedback(
style="cursor: pointer;",
hidden
data-ol-clear-search
aria-hidden="true"
)
button.sr-only(
type="button"
hidden
data-ol-clear-search
aria-label=translate('clear_search')
)
.row(role="region" aria-label="search results")
.col-md-12()
div(data-ol-search-results-wrapper)
span.sr-only(aria-live="polite" data-ol-search-sr-help-message)
div(data-ol-search-results)
.row-spaced-small.search-result.card.card-thin(hidden data-ol-search-no-results)
p #{translate("no_search_results")}

View File

@@ -0,0 +1,6 @@
mixin foot-scripts()
each file in entrypointScripts(entrypoint)
script(type="text/javascript", nonce=scriptNonce, src=file, defer=deferScripts)
if (settings.devToolbar.enabled)
each file in entrypointScripts("devToolbar")
script(type="text/javascript", nonce=scriptNonce, src=file, defer=deferScripts)

View File

@@ -0,0 +1,91 @@
mixin formMessages()
div(
data-ol-form-messages='',
role="alert"
)
mixin formMessagesNewStyle()
div(
data-ol-form-messages-new-style='',
role="alert"
)
mixin customFormMessage(key, kind)
if kind === 'success'
div.alert.alert-success(
hidden,
data-ol-custom-form-message=key,
role="alert"
aria-live="polite"
)
block
else if kind === 'danger'
div.alert.alert-danger(
hidden,
data-ol-custom-form-message=key,
role="alert"
aria-live="assertive"
)
block
else
div.alert.alert-warning(
hidden,
data-ol-custom-form-message=key,
role="alert"
aria-live="polite"
)
block
mixin customFormMessageNewStyle(key, kind)
if kind === 'success'
div.notification.notification-type-success(
hidden,
data-ol-custom-form-message=key,
role="alert"
aria-live="polite"
)
div.notification-icon
span.material-symbols(aria-hidden="true") check_circle
div.notification-content.text-left
block
else if kind === 'danger'
div.notification.notification-type-error(
hidden,
data-ol-custom-form-message=key,
role="alert"
aria-live="polite"
)
div.notification-icon
span.material-symbols(aria-hidden="true") error
div.notification-content.text-left
block
else
div.notification.notification-type-warning(
hidden,
data-ol-custom-form-message=key,
role="alert"
aria-live="polite"
)
div.notification-icon
span.material-symbols(aria-hidden="true") warning
div.notification-content.text-left
block
mixin customValidationMessage(key)
div.invalid-feedback.mt-2(
hidden,
data-ol-custom-form-message=key
)
i.fa.fa-fw.fa-warning.me-1(aria-hidden="true")
div
block
mixin customValidationMessageNewStyle(key)
div.notification.notification-type-error(
hidden,
data-ol-custom-form-message=key
)
div.notification-icon
span.material-symbols(aria-hidden="true") error
div.notification-content.text-left.small
block

View File

@@ -0,0 +1,123 @@
mixin linkAdvisors(linkText, linkClass, track)
//- To Do: verify path
- var gaCategory = track && track.category ? track.category : 'All'
- var gaAction = track && track.action ? track.action : null
- var gaLabel = track && track.label ? track.label : null
- var mb = track && track.mb ? 'true' : null
- var mbSegmentation = track && track.segmentation ? track.segmentation : null
- var trigger = track && track.trigger ? track.trigger : null
a(href="/advisors"
class=linkClass ? linkClass : ''
event-tracking-ga=gaCategory
event-tracking=gaAction
event-tracking-label=gaLabel
event-tracking-trigger=trigger
event-tracking-mb=mb
event-segmentation=mbSegmentation
)
span #{linkText ? linkText : 'advisor programme'}
mixin linkBenefits(linkText, linkClass)
a(href=(settings.siteUrl ? settings.siteUrl : '') + "/for/authors" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'benefits'}
mixin linkBlog(linkText, linkClass, slug)
if slug
a(href=(settings.siteUrl ? settings.siteUrl : '') + "/blog/" + slug class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'blog'}
mixin linkContact(linkText, linkClass)
a(href=(settings.siteUrl ? settings.siteUrl : '') + "/contact" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'contact'}
mixin linkDash(linkText, linkClass)
a(href="/project" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'project dashboard'}
mixin linkEducation(linkText, linkClass)
a(href=(settings.siteUrl ? settings.siteUrl : '') + "/for/edu" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'teaching toolkit'}
mixin linkInvite(linkText, linkClass, track)
- var gaCategory = track && track.category ? track.category : 'All'
- var gaAction = track && track.action ? track.action : null
- var gaLabel = track && track.label ? track.label : null
- var mb = track && track.mb ? 'true' : null
- var mbSegmentation = track && track.segmentation ? track.segmentation : null
- var trigger = track && track.trigger ? track.trigger : null
a(href="/user/bonus"
class=linkClass ? linkClass : ''
event-tracking-ga=gaCategory
event-tracking=gaAction
event-tracking-label=gaLabel
event-tracking-trigger=trigger
event-tracking-mb=mb
event-segmentation=mbSegmentation
)
span #{linkText ? linkText : 'invite your friends'}
mixin linkPlansAndPricing(linkText, linkClass)
a(href="/user/subscription/plans" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'plans and pricing'}
mixin linkPrintNewTab(linkText, linkClass, icon, track)
- var gaCategory = track && track.category ? track.category : null
- var gaAction = track && track.action ? track.action : null
- var gaLabel = track && track.label ? track.label : null
- var mb = track && track.mb ? 'true' : null
- var mbSegmentation = track && track.segmentation ? track.segmentation : null
- var trigger = track && track.trigger ? track.trigger : null
a(href='?media=print'
class=linkClass ? linkClass : ''
event-tracking-ga=gaCategory
event-tracking=gaAction
event-tracking-label=gaLabel
event-tracking-trigger=trigger
event-tracking-mb=mb
event-segmentation=mbSegmentation
target="_BLANK",
rel="noopener noreferrer"
)
if icon
i(class="fa fa-print")
|  
span #{linkText ? linkText : 'print'}
mixin linkSignIn(linkText, linkClass, redirect)
a(href=`/login${redirect ? '?redir=' + redirect : ''}` class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'sign in'}
mixin linkSignUp(linkText, linkClass, redirect)
a(href=`/register${redirect ? '?redir=' + redirect : ''}` class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'sign up'}
mixin linkTweet(linkText, linkClass, tweetText, track)
//- twitter-share-button is required by twitter
- var gaCategory = track && track.category ? track.category : 'All'
- var gaAction = track && track.action ? track.action : null
- var gaLabel = track && track.label ? track.label : null
- var mb = track && track.mb ? 'true' : null
- var mbSegmentation = track && track.segmentation ? track.segmentation : null
- var trigger = track && track.trigger ? track.trigger : null
a(class="twitter-share-button " + linkClass
event-tracking-ga=gaCategory
event-tracking=gaAction
event-tracking-label=gaLabel
event-tracking-trigger=trigger
event-tracking-mb=mb
event-segmentation=mbSegmentation
href="https://twitter.com/intent/tweet?text=" + tweetText
target="_BLANK",
rel="noopener noreferrer"
) #{linkText ? linkText : 'tweet'}
mixin linkUniversities(linkText, linkClass)
a(href=(settings.siteUrl ? settings.siteUrl : '') + "/for/universities" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'universities'}
mixin linkWithArrow({text, href, eventTracking, eventSegmentation, eventTrackingTrigger})
a.link-with-arrow(href=href event-tracking=eventTracking event-segmentation=eventSegmentation, event-tracking-trigger=eventTrackingTrigger event-tracking-mb)
| #{text}
i.material-symbols(aria-hidden="true") arrow_right_alt

View File

@@ -0,0 +1,23 @@
mixin nav-item
li(role="none")&attributes(attributes)
block
mixin nav-link
a(role="menuitem").nav-link&attributes(attributes)
block
mixin dropdown-menu
ul(role="menu").dropdown-menu&attributes(attributes)
block
mixin dropdown-menu-item
li(role="none")
block
mixin dropdown-menu-link-item
+dropdown-menu-item
a(role="menuitem").dropdown-item&attributes(attributes)
block
mixin dropdown-menu-divider
li(role="separator").dropdown-divider.d-none.d-lg-block

View File

@@ -0,0 +1,42 @@
//- to be kept in sync with frontend/js/shared/components/notification.tsx
mixin notificationIcon(type)
if type === 'info'
span.material-symbols(aria-hidden="true") info
else if type === 'success'
span.material-symbols(aria-hidden="true") check_circle
else if type === 'error'
span.material-symbols(aria-hidden="true") error
else if type === 'warning'
span.material-symbols(aria-hidden="true") warning
mixin notification(options)
- var {ariaLive, id, type, title, content, disclaimer, className} = options
- var classNames = `notification notification-type-${type} ${className ? className : ''} ${isActionBelowContent ? 'notification-cta-below-content' : ''}`
div(
aria-live=ariaLive,
role="alert",
id=id,
class=classNames
)
.notification-icon
+notificationIcon(type)
.notification-content-and-cta
.notification-content
if title
p
b #{title}
| !{content}
//- TODO: handle action
//- if action
//- .notification-cta
if disclaimer
.notification-disclaimer #{disclaimer}
//- TODO: handle dismissible notifications
//- TODO: handle onDismiss
//- if isDismissible
//- .notification-close-btn
//- button(aria-label=translate('close'))
//- span.material-symbols(aria-hidden="true") close

View File

@@ -0,0 +1,86 @@
mixin pagination(pages, page_path, max_btns)
//- @param pages.current_page the current page viewed
//- @param pages.total_pages previously calculated,
//- based on total entries and entries per page
//- @param page_path the relative path, minus a trailing slash and page param
//- @param max_btns max number of buttons on either side of the current page
//- button and excludes first, prev, next, last
if pages && pages.current_page && pages.total_pages && pages.total_pages > 1
- var max_btns = max_btns || 4
- var prev_page = Math.max(parseInt(pages.current_page, 10) - max_btns, 1)
- var next_page = parseInt(pages.current_page, 10) + 1
- var next_index = 0;
- var full_page_path = page_path + "/page/"
nav(role="navigation" aria-label=(translate("pagination_navigation")))
ul.pagination
if pages.current_page > 1
li
a(
aria-label=translate("go_to_first_page")
href=page_path
)
span(aria-hidden="true") <<
|
| First
li
a(
aria-label=translate("go_to_previous_page")
href=full_page_path + (parseInt(pages.current_page, 10) - 1)
rel="prev"
)
span(aria-hidden="true") <
|
| Prev
if pages.current_page - max_btns > 1
li(aria-hidden="true")
span …
while prev_page < pages.current_page
li
a(
aria-label=translate("go_to_page_x", {page: prev_page})
href=full_page_path + prev_page
) #{prev_page}
- prev_page++
li(class="active")
span(
aria-label=translate("current_page_page", {page: pages.current_page})
aria-current="true"
) #{pages.current_page}
if pages.current_page < pages.total_pages
while next_page <= pages.total_pages && next_index < max_btns
li
a(
aria-label=translate("go_to_page_x", {page: next_page})
href=full_page_path + next_page
) #{next_page}
- next_page++
- next_index++
if next_page <= pages.total_pages
li.ellipses(aria-hidden="true")
span …
li
a(
aria-label=translate("go_to_next_page")
href=full_page_path + (parseInt(pages.current_page, 10) + 1)
rel="next"
)
| Next
|
span(aria-hidden="true") &gt;
li
a(
aria-label=translate("go_to_last_page")
href=full_page_path + pages.total_pages
)
| Last
|
span(aria-hidden="true") &gt;&gt;

View File

@@ -0,0 +1,4 @@
mixin previous-page-link(href, text)
a.previous-page-link(href=href)
i.material-symbols.material-symbols-rounded(aria-hidden="true") arrow_left_alt
| #{text}

View File

@@ -0,0 +1,54 @@
mixin quoteLargeTextCentered(quote, person, position, affiliation, link, pictureUrl, pictureAltAttr)
blockquote.quote-large-text-centered
.quote !{quote}
if pictureUrl
.quote-img
img(src=pictureUrl alt=pictureAltAttr)
footer
div.quote-person
strong #{person}
if person && position
div #{position}
if affiliation
div #{affiliation}
if link
.quote-link !{link}
mixin quoteLeftGreenBorder({quote, person, position, affiliation, link})
blockquote.quote-left-green-border
.quote !{quote}
footer
| #{person}
| #{position || affiliation ? ',' : ''}
| #{position}
| #{position && affiliation ? ' at ' : ''}
| #{affiliation}
if link
.quote-link !{link}
mixin collinsQuote1
.card.card-dark-green-bg
-var quote = 'Overleaf is indispensable for us. We use it in our research, thesis writing, project proposals, and manuscripts for publication. When it comes to writing, its our main tool.'
-var quotePerson = 'Christopher Collins'
-var quotePersonPosition = 'Associate Professor and Lab Director, Ontario Tech University'
-var quotePersonImg = buildImgPath("advocates/collins.jpg")
.card-body
+quoteLargeTextCentered(quote, quotePerson, quotePersonPosition, null, null, quotePersonImg, quotePerson)
mixin collinsQuote2
.card.card-dark-green-bg
-var quote = 'We are writing collaboratively right up until the last minute. We are faced with deadlines all the time, and Overleaf gives us the ability to polish right up until the last possible second.'
-var quotePerson = 'Christopher Collins'
-var quotePersonPosition = 'Associate Professor and Lab Director, Ontario Tech University'
-var quotePersonImg = buildImgPath("advocates/collins.jpg")
.card-body
+quoteLargeTextCentered(quote, quotePerson, quotePersonPosition, null, null, quotePersonImg, quotePerson)
mixin bennettQuote1
.card.card-dark-green-bg
-var quote = 'With Overleaf, we now have a process for developing technical documentation which has virtually eliminated the time required to properly format and layout documents.'
-var quotePerson = 'Andrew Bennett'
-var quotePersonPosition = 'Software Architect, Symplectic'
-var quotePersonImg = buildImgPath("advocates/bennett.jpg")
.card-body
+quoteLargeTextCentered(quote, quotePerson, quotePersonPosition, null, null, quotePersonImg, quotePerson)

View File

@@ -0,0 +1,2 @@
mixin recaptchaConditions()
.recaptcha-branding !{translate("recaptcha_conditions", {}, [{}, {name: 'a', attrs: {href: 'https://policies.google.com/privacy', rel: 'noopener noreferrer', target: '_blank'}}, {name: 'a', attrs: {href: 'https://policies.google.com/terms', rel: 'noopener noreferrer', target: '_blank'}}])}

View File

@@ -0,0 +1,61 @@
mixin reconfirmAffiliationNotification-marketing(userEmail, location)
form(
data-ol-async-form
action='/user/emails/send-reconfirmation'
)
input(name="_csrf" type="hidden" value=csrfToken)
input(name="email" type="hidden" value=userEmail.email)
+formMessages()
.reconfirm-notification
div(data-ol-not-sent style="width:100%;")
i.fa.fa-warning
- var ssoEnabled = userEmail.affiliation && userEmail.affiliation.institution && userEmail.affiliation.institution.ssoEnabled
if ssoEnabled
- var institutionId = userEmail.affiliation && userEmail.affiliation.institution && userEmail.affiliation.institution.id
a.btn-reconfirm.btn.btn-sm.btn-info(
data-ol-slow-link
href=`${settings.saml.ukamf.initPath}?university_id=${institutionId}&reconfirm=${location}`
)
span(data-ol-inflight="idle") #{translate("confirm_affiliation")}
span(hidden data-ol-inflight="pending") #{translate("pending")}…
else
button.btn-reconfirm.btn.btn-sm.btn-info(
type="submit"
data-ol-disabled-inflight
)
span(data-ol-inflight="idle") #{translate("confirm_affiliation")}
span(hidden data-ol-inflight="pending") #{translate("pending")}…
| !{translate("are_you_still_at", {institutionName: userEmail.affiliation.institution.name}, ['strong'])}&nbsp;
if location == '/user/settings'
| !{translate('please_reconfirm_institutional_email', {}, [{ name: 'span' }])}
if userEmail.default
span &nbsp;#{translate('need_to_add_new_primary_before_remove')}
else
| !{translate("please_reconfirm_institutional_email", {}, [{name: 'a', attrs: {href: '/user/settings?remove=' + userEmail.email}}])}
| &nbsp;
a(href="/learn/how-to/Institutional_Email_Reconfirmation" target="_blank") #{translate("learn_more")}
div(hidden data-ol-sent)
| !{translate("please_check_your_inbox_to_confirm", {institutionName: userEmail.affiliation.institution.name}, ['strong'])}
| &nbsp;
button.btn-inline-link(
type="submit"
data-ol-disabled-inflight
)
span(data-ol-inflight="idle") #{translate('resend_confirmation_email')}
span(hidden data-ol-inflight="pending") #{translate("pending")}…
mixin reconfirmedAffiliationNotification-marketing(userEmail)
.alert.alert-info
.reconfirm-notification
div(style="width:100%;")
//- extra div for flex styling
| !{translate("your_affiliation_is_confirmed", {institutionName: userEmail.affiliation.institution.name}, ['strong'])}
|
| #{translate('thank_you_exclamation')}

View File

@@ -0,0 +1,3 @@
mixin termsOfServiceAgreement
div.tos-agreement-notice
| !{translate("by_registering_you_agree_to_our_terms_of_service", {}, [{name: 'a', attrs: {href: '/legal#Terms', target: '_blank'}}, {name: 'a', attrs: {href: '/legal#Privacy', target: '_blank'}}])}