Просмотр исходного кода

Prettier for PUG templates (#26170)

* Setup prettier

* Ignore these pug templates by prettier

* Fix typo

* Fix prettier error

* Add prettier-ignore for quoting of event-segmentation attribute

* Manual tab indentation

* Interpolate

* Remove unbuffered if conditional

* Inline event-segmentation objects and remove prettier-ignore rule

* Fix spacing before interpolation

* Source format

* Source format

GitOrigin-RevId: c30e037f5caf8f91efc1bd9e75f81ae533b5a506
Rebeka Dekany 1 год назад
Родитель
Сommit
c40ab3234d
96 измененных файлов с 2143 добавлено и 1761 удалено
  1. 28 0
      package-lock.json
  2. 25 0
      services/web/.prettierignore
  3. 15 1
      services/web/.prettierrc
  4. 7 0
      services/web/Makefile
  5. 13 5
      services/web/app/views/_cookie_banner.pug
  6. 87 70
      services/web/app/views/_metadata.pug
  7. 3 1
      services/web/app/views/_mixins/back_to_btns.pug
  8. 4 6
      services/web/app/views/_mixins/begin_now_card.pug
  9. 4 4
      services/web/app/views/_mixins/bookmarkable_tabset.pug
  10. 2 2
      services/web/app/views/_mixins/bootstrap_js.pug
  11. 4 4
      services/web/app/views/_mixins/eyebrow.pug
  12. 18 11
      services/web/app/views/_mixins/faq_search-marketing.pug
  13. 10 5
      services/web/app/views/_mixins/foot_scripts.pug
  14. 49 61
      services/web/app/views/_mixins/formMessages.pug
  15. 53 22
      services/web/app/views/_mixins/links.pug
  16. 3 2
      services/web/app/views/_mixins/material_symbol.pug
  17. 7 7
      services/web/app/views/_mixins/navbar.pug
  18. 5 11
      services/web/app/views/_mixins/notification.pug
  19. 21 24
      services/web/app/views/_mixins/pagination.pug
  20. 1 1
      services/web/app/views/_mixins/previous_page_link.pug
  21. 14 14
      services/web/app/views/_mixins/quote.pug
  22. 1 1
      services/web/app/views/_mixins/recaptcha.pug
  23. 15 21
      services/web/app/views/_mixins/reconfirm_affiliation-marketing.pug
  24. 1 1
      services/web/app/views/_mixins/terms_of_service.pug
  25. 44 41
      services/web/app/views/admin/index.pug
  26. 22 27
      services/web/app/views/beta_program/opt_in.pug
  27. 7 7
      services/web/app/views/general/400.pug
  28. 2 2
      services/web/app/views/general/404.pug
  29. 5 5
      services/web/app/views/general/500.pug
  30. 1 1
      services/web/app/views/general/closed.pug
  31. 5 9
      services/web/app/views/general/post-gateway.pug
  32. 5 5
      services/web/app/views/general/unsupported-browser.pug
  33. 89 52
      services/web/app/views/layout-base.pug
  34. 5 5
      services/web/app/views/layout-marketing.pug
  35. 39 31
      services/web/app/views/layout-react.pug
  36. 6 6
      services/web/app/views/layout-website-redesign.pug
  37. 49 18
      services/web/app/views/layout/fat-footer-base.pug
  38. 41 38
      services/web/app/views/layout/fat-footer-website-redesign.pug
  39. 41 38
      services/web/app/views/layout/fat-footer.pug
  40. 19 11
      services/web/app/views/layout/language-picker-bootstrap-5.pug
  41. 15 12
      services/web/app/views/layout/language-picker.pug
  42. 8 6
      services/web/app/views/layout/layout-no-js.pug
  43. 108 96
      services/web/app/views/layout/navbar-marketing-bootstrap-5.pug
  44. 105 86
      services/web/app/views/layout/navbar-marketing.pug
  45. 99 83
      services/web/app/views/layout/navbar-website-redesign.pug
  46. 2 2
      services/web/app/views/layout/thin-footer-bootstrap-5.pug
  47. 2 2
      services/web/app/views/layout/thin-footer.pug
  48. 29 29
      services/web/app/views/project/editor/new_from_template.pug
  49. 1 1
      services/web/app/views/project/ide-react-detached.pug
  50. 7 2
      services/web/app/views/project/ide-react.pug
  51. 3 3
      services/web/app/views/project/invite/not-valid.pug
  52. 8 8
      services/web/app/views/project/invite/show.pug
  53. 77 29
      services/web/app/views/project/list-react.pug
  54. 5 5
      services/web/app/views/project/token/access-react.pug
  55. 4 4
      services/web/app/views/project/token/sharing-updates.pug
  56. 11 7
      services/web/app/views/subscriptions/add-seats.pug
  57. 2 2
      services/web/app/views/subscriptions/canceled-subscription-react.pug
  58. 82 27
      services/web/app/views/subscriptions/dashboard-react.pug
  59. 3 3
      services/web/app/views/subscriptions/manually-collected-subscription.pug
  60. 3 3
      services/web/app/views/subscriptions/missing-billing-information.pug
  61. 29 41
      services/web/app/views/subscriptions/plans/_faq_new.pug
  62. 217 221
      services/web/app/views/subscriptions/plans/_plans_faq_tabs.pug
  63. 8 4
      services/web/app/views/subscriptions/preview-change.pug
  64. 3 3
      services/web/app/views/subscriptions/subtotal-limit-exceeded.pug
  65. 4 4
      services/web/app/views/subscriptions/successful-subscription-react.pug
  66. 3 3
      services/web/app/views/subscriptions/team/group-invites.pug
  67. 15 11
      services/web/app/views/subscriptions/team/invite-managed.pug
  68. 17 9
      services/web/app/views/subscriptions/team/invite.pug
  69. 9 5
      services/web/app/views/subscriptions/team/invite_logged_out.pug
  70. 9 5
      services/web/app/views/subscriptions/upgrade-group-subscription-react.pug
  71. 1 1
      services/web/app/views/user/accountSuspended.pug
  72. 1 1
      services/web/app/views/user/compromised_password.pug
  73. 1 1
      services/web/app/views/user/confirmSecondaryEmail.pug
  74. 24 30
      services/web/app/views/user/confirm_email.pug
  75. 17 17
      services/web/app/views/user/email-preferences.pug
  76. 18 22
      services/web/app/views/user/login.pug
  77. 2 2
      services/web/app/views/user/one_time_login.pug
  78. 30 26
      services/web/app/views/user/passwordReset-bs5.pug
  79. 37 33
      services/web/app/views/user/passwordReset.pug
  80. 19 19
      services/web/app/views/user/primaryEmailCheck-bs5.pug
  81. 27 26
      services/web/app/views/user/reconfirm-bs5.pug
  82. 29 28
      services/web/app/views/user/reconfirm.pug
  83. 3 3
      services/web/app/views/user/register.pug
  84. 14 18
      services/web/app/views/user/sessions.pug
  85. 23 27
      services/web/app/views/user/setPassword-bs5.pug
  86. 34 32
      services/web/app/views/user/setPassword.pug
  87. 54 27
      services/web/app/views/user/settings.pug
  88. 5 5
      services/web/app/views/user_membership/group-managers-react.pug
  89. 28 12
      services/web/app/views/user_membership/group-members-react.pug
  90. 5 5
      services/web/app/views/user_membership/institution-managers-react.pug
  91. 6 13
      services/web/app/views/user_membership/new.pug
  92. 5 5
      services/web/app/views/user_membership/publisher-managers-react.pug
  93. 70 89
      services/web/modules/launchpad/app/views/launchpad.pug
  94. 27 31
      services/web/modules/user-activate/app/views/user/activate.pug
  95. 2 2
      services/web/modules/user-activate/app/views/user/register.pug
  96. 3 0
      services/web/package.json

+ 28 - 0
package-lock.json

@@ -8911,6 +8911,33 @@
         "url": "https://opencollective.com/popperjs"
         "url": "https://opencollective.com/popperjs"
       }
       }
     },
     },
+    "node_modules/@prettier/plugin-pug": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/@prettier/plugin-pug/-/plugin-pug-3.4.0.tgz",
+      "integrity": "sha512-Jzd5rE/ellJz3vqfxyVewPsCHXw1dmIzJ3AXhAnqVBKQOj2u73ZS2oUacji8CbQSsYyCy7GXFjXWDlDTMG1x2g==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/Shinigami92"
+        },
+        {
+          "type": "paypal",
+          "url": "https://www.paypal.com/donate/?hosted_button_id=L7GY729FBKTZY"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "pug-lexer": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=9.0.0"
+      },
+      "peerDependencies": {
+        "prettier": "^3.0.0"
+      }
+    },
     "node_modules/@protobufjs/aspromise": {
     "node_modules/@protobufjs/aspromise": {
       "version": "1.1.2",
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
       "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@@ -45272,6 +45299,7 @@
         "@pollyjs/adapter-node-http": "^6.0.6",
         "@pollyjs/adapter-node-http": "^6.0.6",
         "@pollyjs/core": "^6.0.6",
         "@pollyjs/core": "^6.0.6",
         "@pollyjs/persister-fs": "^6.0.6",
         "@pollyjs/persister-fs": "^6.0.6",
+        "@prettier/plugin-pug": "^3.4.0",
         "@replit/codemirror-emacs": "overleaf/codemirror-emacs#4394c03858f27053f8768258e9493866e06e938e",
         "@replit/codemirror-emacs": "overleaf/codemirror-emacs#4394c03858f27053f8768258e9493866e06e938e",
         "@replit/codemirror-indentation-markers": "overleaf/codemirror-indentation-markers#78264032eb286bc47871569ae87bff5ca1c6c161",
         "@replit/codemirror-indentation-markers": "overleaf/codemirror-indentation-markers#78264032eb286bc47871569ae87bff5ca1c6c161",
         "@replit/codemirror-vim": "overleaf/codemirror-vim#1bef138382d948018f3f9b8a4d7a70ab61774e4b",
         "@replit/codemirror-vim": "overleaf/codemirror-vim#1bef138382d948018f3f9b8a4d7a70ab61774e4b",

+ 25 - 0
services/web/.prettierignore

@@ -13,3 +13,28 @@ frontend/js/features/source-editor/lezer-latex/latex.terms.mjs
 frontend/js/features/source-editor/lezer-bibtex/bibtex.mjs
 frontend/js/features/source-editor/lezer-bibtex/bibtex.mjs
 frontend/js/features/source-editor/lezer-bibtex/bibtex.terms.mjs
 frontend/js/features/source-editor/lezer-bibtex/bibtex.terms.mjs
 frontend/js/features/source-editor/hunspell/wasm/hunspell.mjs
 frontend/js/features/source-editor/hunspell/wasm/hunspell.mjs
+
+# complex pages
+app/views/project/editor.pug
+app/views/project/editor/**
+modules/open-in-overleaf/app/views/documentation.pug
+modules/references-search/app/views/project/editor/**
+modules/rich-text/app/views/toolbar.pug
+
+# loops
+app/views/referal/bonus.pug
+modules/templates/app/views/tag.pug
+
+# expressions that could not be formatted correctly
+app/views/_mixins/faq_search.pug
+app/views/external/home/v2.pug
+app/views/project/token/access.pug
+app/views/user/primaryEmailCheck.pug
+app/views/user/restricted.pug
+modules/admin-panel/app/views/project/show.pug
+modules/templates/app/views/project/editor/_left-menu.pug
+modules/two-factor-authentication/app/views/_mixins.pug
+
+# minified files
+app/views/_google_analytics.pug
+app/views/_customer_io.pug

+ 15 - 1
services/web/.prettierrc

@@ -1,9 +1,23 @@
 {
 {
   "arrowParens": "avoid",
   "arrowParens": "avoid",
   "jsxSingleQuote": false,
   "jsxSingleQuote": false,
+  "pugAttributeSeparator": "as-needed",
+  "pugBracketSpacing": false,
+  "pugClassNotation": "as-is",
+  "pugIdNotation": "as-is",
+  "pugSortAttributesBeginning": ["name", "data-type"],
+  "plugins": ["@prettier/plugin-pug"],
   "semi": false,
   "semi": false,
   "singleQuote": true,
   "singleQuote": true,
   "trailingComma": "es5",
   "trailingComma": "es5",
   "tabWidth": 2,
   "tabWidth": 2,
-  "useTabs": false
+  "useTabs": false,
+  "overrides": [
+    {
+      "files": "*.pug",
+      "options": {
+        "useTabs": true
+      }
+    }
+  ]
 }
 }

+ 7 - 0
services/web/Makefile

@@ -455,12 +455,19 @@ format: format_styles
 format_styles:
 format_styles:
 	npm run --silent format:styles
 	npm run --silent format:styles
 
 
+format: format_pug
+format_pug:
+	npm run --silent format:pug
+
 format_fix:
 format_fix:
 	npm run --silent format:fix
 	npm run --silent format:fix
 
 
 format_styles_fix:
 format_styles_fix:
 	npm run --silent format:styles:fix
 	npm run --silent format:styles:fix
 
 
+format_pug_fix:
+	npm run --silent format:pug:fix
+
 format_in_docker:
 format_in_docker:
 	$(RUN_LINT_FORMAT) make format -j2 --output-sync
 	$(RUN_LINT_FORMAT) make format -j2 --output-sync
 
 

+ 13 - 5
services/web/app/views/_cookie_banner.pug

@@ -1,5 +1,13 @@
-section.cookie-banner.hidden-print.hidden(aria-label="Cookie banner")
-    .cookie-banner-content We only use cookies for essential purposes and to improve your experience on our site. You can find out more in our <a href="/legal#Cookies">cookie policy</a>.
-    .cookie-banner-actions
-        button(type="button" class="btn btn-link btn-sm" data-ol-cookie-banner-set-consent="essential") Essential cookies only
-        button(type="button" class="btn btn-primary btn-sm" data-ol-cookie-banner-set-consent="all") Accept all cookies
+section.cookie-banner.hidden-print.hidden(aria-label='Cookie banner')
+	.cookie-banner-content We only use cookies for essential purposes and to improve your experience on our site. You can find out more in our <a href="/legal#Cookies">cookie policy</a>.
+	.cookie-banner-actions
+		button(
+			type='button'
+			class='btn btn-link btn-sm'
+			data-ol-cookie-banner-set-consent='essential'
+		) Essential cookies only
+		button(
+			type='button'
+			class='btn btn-primary btn-sm'
+			data-ol-cookie-banner-set-consent='all'
+		) Accept all cookies

+ 87 - 70
services/web/app/views/_metadata.pug

@@ -1,123 +1,140 @@
-
 //- Title
 //- Title
-if (metadata && metadata.title)
-	title= metadata.title + ' - ' + settings.appName + ', ' + translate("online_latex_editor")
-	meta(name="twitter:title", content=metadata.title)
-	meta(name="og:title", content=metadata.title)
-else if (typeof(title) == "undefined")
-	title= settings.appName + ', '+ translate("online_latex_editor")
-	meta(name="twitter:title", content=settings.appName + ', '+ translate("online_latex_editor"))
-	meta(name="og:title", content=settings.appName + ', '+ translate("online_latex_editor"))
+if metadata && metadata.title
+	title= metadata.title + ' - ' + settings.appName + ', ' + translate('online_latex_editor')
+	meta(name='twitter:title' content=metadata.title)
+	meta(name='og:title' content=metadata.title)
+else if typeof title == 'undefined'
+	title= settings.appName + ', ' + translate('online_latex_editor')
+	meta(
+		name='twitter:title'
+		content=settings.appName + ', ' + translate('online_latex_editor')
+	)
+	meta(
+		name='og:title'
+		content=settings.appName + ', ' + translate('online_latex_editor')
+	)
 else
 else
-	title= translate(title) + ' - ' + settings.appName + ', ' + translate("online_latex_editor")
+	title= translate(title) + ' - ' + settings.appName + ', ' + translate('online_latex_editor')
 	//- to do - not translate?
 	//- to do - not translate?
-	meta(name="twitter:title", content=translate(title))
-	meta(name="og:title", content=translate(title))
+	meta(name='twitter:title' content=translate(title))
+	meta(name='og:title' content=translate(title))
 
 
 //- Description
 //- Description
-if (metadata && metadata.description)
-	meta(name="description" , content=metadata.description)
-	meta(itemprop="description" , content=metadata.description)
+if metadata && metadata.description
+	meta(name='description' content=metadata.description)
+	meta(itemprop='description' content=metadata.description)
 	//-twitter and og descriptions handeled in their sections below
 	//-twitter and og descriptions handeled in their sections below
 else
 else
-	meta(name="description", content=translate("site_description"))
-	meta(itemprop="description", content=translate("site_description"))
+	meta(name='description' content=translate('site_description'))
+	meta(itemprop='description' content=translate('site_description'))
 
 
 //- Image
 //- Image
-if (metadata && metadata.image && metadata.image.fields)
+if metadata && metadata.image && metadata.image.fields
 	//- from the CMS
 	//- from the CMS
-	meta(itemprop="image", content=metadata.image.fields.file.url)
-	meta(name="image", content=metadata.image.fields.file.url)
-else if (metadata && metadata.image_src)
+	meta(itemprop='image' content=metadata.image.fields.file.url)
+	meta(name='image' content=metadata.image.fields.file.url)
+else if metadata && metadata.image_src
 	//- pages with custom metadata images, metadata.image_src is the full image URL
 	//- pages with custom metadata images, metadata.image_src is the full image URL
-	meta(itemprop="image", content=metadata.image_src)
-	meta(name="image", content=metadata.image_src)
-else if (settings.overleaf)
+	meta(itemprop='image' content=metadata.image_src)
+	meta(name='image' content=metadata.image_src)
+else if settings.overleaf
 	//- the default image for Overleaf
 	//- the default image for Overleaf
-	meta(itemprop="image", content=buildImgPath('ol-brand/overleaf_og_logo.png'))
-	meta(name="image", content=buildImgPath('ol-brand/overleaf_og_logo.png'))
+	meta(itemprop='image' content=buildImgPath('ol-brand/overleaf_og_logo.png'))
+	meta(name='image' content=buildImgPath('ol-brand/overleaf_og_logo.png'))
 else
 else
 	//- the default image for Overleaf Community Edition/Server Pro
 	//- the default image for Overleaf Community Edition/Server Pro
-	meta(itemprop="image", content='/apple-touch-icon.png')
-	meta(name="image", content='/apple-touch-icon.png')
+	meta(itemprop='image' content='/apple-touch-icon.png')
+	meta(name='image' content='/apple-touch-icon.png')
 
 
 //- Keywords
 //- Keywords
-if (metadata && metadata.keywords)
-	meta(name="keywords" content=metadata.keywords)
+if metadata && metadata.keywords
+	meta(name='keywords' content=metadata.keywords)
 
 
 //- Misc
 //- Misc
-meta(itemprop="name", content=settings.appName + ", the Online LaTeX Editor")
+meta(itemprop='name' content=settings.appName + ', the Online LaTeX Editor')
 
 
-if (metadata && metadata.robotsNoindexNofollow)
-	meta(name="robots" content="noindex, nofollow")
+if metadata && metadata.robotsNoindexNofollow
+	meta(name='robots' content='noindex, nofollow')
 
 
 //- Twitter
 //- Twitter
-meta(name="twitter:card", content=metadata && metadata.twitterCardType ? metadata.twitterCardType : 'summary')
-if (settings.social && settings.social.twitter && settings.social.twitter.handle)
-	meta(name="twitter:site", content="@" + settings.social.twitter.handle)
-if (metadata && metadata.twitterDescription)
-	meta(name="twitter:description", content=metadata.twitterDescription)
+meta(
+	name='twitter:card'
+	content=metadata && metadata.twitterCardType ? metadata.twitterCardType : 'summary'
+)
+if settings.social && settings.social.twitter && settings.social.twitter.handle
+	meta(name='twitter:site' content='@' + settings.social.twitter.handle)
+if metadata && metadata.twitterDescription
+	meta(name='twitter:description' content=metadata.twitterDescription)
 else
 else
-	meta(name="twitter:description", content=translate("site_description"))
-if (metadata && metadata.twitterImage && metadata.twitterImage.fields)
+	meta(name='twitter:description' content=translate('site_description'))
+if metadata && metadata.twitterImage && metadata.twitterImage.fields
 	//- from the CMS
 	//- from the CMS
-	meta(name="twitter:image", content=metadata.twitterImage.fields.file.url)
-	meta(name="twitter:image:alt", content=metadata.twitterImage.fields.title)
-else if (settings.overleaf)
+	meta(name='twitter:image' content=metadata.twitterImage.fields.file.url)
+	meta(name='twitter:image:alt' content=metadata.twitterImage.fields.title)
+else if settings.overleaf
 	//- the default image for Overleaf
 	//- the default image for Overleaf
-	meta(name="twitter:image", content=buildImgPath('ol-brand/overleaf_og_logo.png'))
+	meta(
+		name='twitter:image'
+		content=buildImgPath('ol-brand/overleaf_og_logo.png')
+	)
 else
 else
 	//- the default image for Overleaf Community Edition/Server Pro
 	//- the default image for Overleaf Community Edition/Server Pro
-	meta(name="twitter:image", content='/apple-touch-icon.png')
+	meta(name='twitter:image' content='/apple-touch-icon.png')
 
 
 //- Open Graph
 //- Open Graph
 //- to do - add og:url
 //- to do - add og:url
-if (settings.social && settings.social.facebook && settings.social.facebook.appId)
-	meta(property="fb:app_id", content=settings.social.facebook.appId)
+if settings.social && settings.social.facebook && settings.social.facebook.appId
+	meta(property='fb:app_id' content=settings.social.facebook.appId)
 
 
-if (metadata && metadata.openGraphDescription)
-	meta(property="og:description", content=metadata.openGraphDescription)
+if metadata && metadata.openGraphDescription
+	meta(property='og:description' content=metadata.openGraphDescription)
 else
 else
-	meta(property="og:description", content=translate("site_description"))
+	meta(property='og:description' content=translate('site_description'))
 
 
-if (metadata && metadata.openGraphImage && metadata.openGraphImage.fields)
+if metadata && metadata.openGraphImage && metadata.openGraphImage.fields
 	//- from the CMS
 	//- from the CMS
-	meta(property="og:image", content=metadata.openGraphImage.fields.file.url)
-else if (settings.overleaf)
+	meta(property='og:image' content=metadata.openGraphImage.fields.file.url)
+else if settings.overleaf
 	//- the default image for Overleaf
 	//- the default image for Overleaf
-	meta(property="og:image", content=buildImgPath('ol-brand/overleaf_og_logo.png'))
+	meta(
+		property='og:image'
+		content=buildImgPath('ol-brand/overleaf_og_logo.png')
+	)
 else
 else
 	//- the default image for Overleaf Community Edition/Server Pro
 	//- the default image for Overleaf Community Edition/Server Pro
-	meta(property="og:image", content='/apple-touch-icon.png')
+	meta(property='og:image' content='/apple-touch-icon.png')
 
 
-if (metadata && metadata.openGraphType)
-	meta(property="og:type", metadata.openGraphType)
+if metadata && metadata.openGraphType
+	meta(property='og:type' metadata.openGraphType)
 else
 else
-	meta(property="og:type", content="website")
+	meta(property='og:type' content='website')
 
 
-if (metadata && metadata.openGraphVideo)
+if metadata && metadata.openGraphVideo
 	//- from the CMS
 	//- from the CMS
-	meta(property="og:video", content=metadata.openGraphVideo)
+	meta(property='og:video' content=metadata.openGraphVideo)
 
 
 //- Viewport
 //- Viewport
 if !metadata || metadata.viewport !== false
 if !metadata || metadata.viewport !== false
-	meta(name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes")
+	meta(
+		name='viewport'
+		content='width=device-width, initial-scale=1.0, user-scalable=yes'
+	)
 
 
 //- Noindex
 //- Noindex
 if settings.robotsNoindex
 if settings.robotsNoindex
-	meta(name="robots" content="noindex")
+	meta(name='robots' content='noindex')
 
 
 //- Icons
 //- Icons
-link(rel="icon", sizes="32x32", href="/favicon-32x32.png")
-link(rel="icon", sizes="16x16", href="/favicon-16x16.png")
-link(rel="icon", href="/favicon.svg" type="image/svg+xml")
-link(rel="apple-touch-icon", href="/apple-touch-icon.png")
-link(rel="mask-icon", href="/mask-favicon.svg", color="#046530")
+link(rel='icon' sizes='32x32' href='/favicon-32x32.png')
+link(rel='icon' sizes='16x16' href='/favicon-16x16.png')
+link(rel='icon' href='/favicon.svg' type='image/svg+xml')
+link(rel='apple-touch-icon' href='/apple-touch-icon.png')
+link(rel='mask-icon' href='/mask-favicon.svg' color='#046530')
 
 
 //- Canonical Tag for SEO
 //- Canonical Tag for SEO
-if (metadata && metadata.canonicalURL)
-	link(rel="canonical" href=metadata.canonicalURL)
+if metadata && metadata.canonicalURL
+	link(rel='canonical' href=metadata.canonicalURL)
 
 
 //- Manifest
 //- Manifest
 //- Does not currently contain a start_url to prevent browser installation prompts
 //- Does not currently contain a start_url to prevent browser installation prompts
-link(rel="manifest" href="/web.sitemanifest")
+link(rel='manifest' href='/web.sitemanifest')

+ 3 - 1
services/web/app/views/_mixins/back_to_btns.pug

@@ -1,4 +1,6 @@
 mixin back-to-btns(settingsAnchor)
 mixin back-to-btns(settingsAnchor)
 	.d-flex.flex-column.flex-sm-row.gap-3
 	.d-flex.flex-column.flex-sm-row.gap-3
-		a.btn.btn-secondary(href=`/user/settings${settingsAnchor ? '#' + settingsAnchor : '' }`) #{translate('back_to_account_settings')}
+		a.btn.btn-secondary(
+			href=`/user/settings${settingsAnchor ? '#' + settingsAnchor : '' }`
+		) #{translate('back_to_account_settings')}
 		a.btn.btn-secondary(href='/project') #{translate('back_to_your_projects')}
 		a.btn.btn-secondary(href='/project') #{translate('back_to_your_projects')}

+ 4 - 6
services/web/app/views/_mixins/begin_now_card.pug

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

+ 4 - 4
services/web/app/views/_mixins/bookmarkable_tabset.pug

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

+ 2 - 2
services/web/app/views/_mixins/bootstrap_js.pug

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

+ 4 - 4
services/web/app/views/_mixins/eyebrow.pug

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

+ 18 - 11
services/web/app/views/_mixins/faq_search-marketing.pug

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

+ 10 - 5
services/web/app/views/_mixins/foot_scripts.pug

@@ -1,6 +1,11 @@
-mixin foot-scripts()
+mixin foot-scripts
 	each file in entrypointScripts(entrypoint)
 	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)
+		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
+			)

+ 49 - 61
services/web/app/views/_mixins/formMessages.pug

@@ -1,41 +1,35 @@
 include ./material_symbol
 include ./material_symbol
 
 
-mixin formMessages()
-	div(
-		data-ol-form-messages='',
-		role="alert"
-	)
+mixin formMessages
+	div(data-ol-form-messages='' role='alert')
 
 
 mixin formMessagesNewStyle(extraClass = 'form-messages-bottom-margin')
 mixin formMessagesNewStyle(extraClass = 'form-messages-bottom-margin')
-	- const attrs = extraClass ? { 'class': extraClass }  : {}
-	div(
-		data-ol-form-messages-new-style='',
-		role="alert"
-	)&attributes(attrs)
+	- const attrs = extraClass ? {class: extraClass} : {}
+	div(data-ol-form-messages-new-style='' role='alert')&attributes(attrs)
 
 
 mixin customFormMessage(key, kind)
 mixin customFormMessage(key, kind)
 	if kind === 'success'
 	if kind === 'success'
-		div.alert.alert-success(
-			hidden,
-			data-ol-custom-form-message=key,
-			role="alert"
-			aria-live="polite"
+		.alert.alert-success(
+			hidden
+			data-ol-custom-form-message=key
+			role='alert'
+			aria-live='polite'
 		)
 		)
 			block
 			block
 	else if kind === 'danger'
 	else if kind === 'danger'
-		div.alert.alert-danger(
-			hidden,
-			data-ol-custom-form-message=key,
-			role="alert"
-			aria-live="assertive"
+		.alert.alert-danger(
+			hidden
+			data-ol-custom-form-message=key
+			role='alert'
+			aria-live='assertive'
 		)
 		)
 			block
 			block
 	else
 	else
-		div.alert.alert-warning(
-			hidden,
-			data-ol-custom-form-message=key,
-			role="alert"
-			aria-live="polite"
+		.alert.alert-warning(
+			hidden
+			data-ol-custom-form-message=key
+			role='alert'
+			aria-live='polite'
 		)
 		)
 			block
 			block
 
 
@@ -43,56 +37,50 @@ mixin customFormMessageNewStyle(key, kind, extraClass = 'mb-3')
 	- extraClass = extraClass ? ' ' + extraClass : ''
 	- extraClass = extraClass ? ' ' + extraClass : ''
 	if kind === 'success'
 	if kind === 'success'
 		div(
 		div(
-			class="notification notification-type-success" + extraClass,
-			hidden,
-			data-ol-custom-form-message=key,
-			role="alert"
-			aria-live="polite"
+			class='notification notification-type-success' + extraClass
+			hidden
+			data-ol-custom-form-message=key
+			role='alert'
+			aria-live='polite'
 		)
 		)
-			div.notification-icon
-				+material-symbol("check_circle")
-			div.notification-content.text-left
+			.notification-icon
+				+material-symbol('check_circle')
+			.notification-content.text-left
 				block
 				block
 	else if kind === 'danger'
 	else if kind === 'danger'
 		div(
 		div(
-			class="notification notification-type-error" + extraClass,
-			hidden,
-			data-ol-custom-form-message=key,
-			role="alert"
-			aria-live="polite"
+			class='notification notification-type-error' + extraClass
+			hidden
+			data-ol-custom-form-message=key
+			role='alert'
+			aria-live='polite'
 		)
 		)
-			div.notification-icon
-				+material-symbol("error")
-			div.notification-content.text-left
+			.notification-icon
+				+material-symbol('error')
+			.notification-content.text-left
 				block
 				block
 	else
 	else
 		div(
 		div(
-			class="notification notification-type-warning" + extraClass,
-			hidden,
-			data-ol-custom-form-message=key,
-			role="alert"
-			aria-live="polite"
+			class='notification notification-type-warning' + extraClass
+			hidden
+			data-ol-custom-form-message=key
+			role='alert'
+			aria-live='polite'
 		)
 		)
-			div.notification-icon
-				+material-symbol("warning")
-			div.notification-content.text-left
+			.notification-icon
+				+material-symbol('warning')
+			.notification-content.text-left
 				block
 				block
 
 
 mixin customValidationMessage(key)
 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")
+	.invalid-feedback.mt-2(hidden data-ol-custom-form-message=key)
+		i.fa.fa-fw.fa-warning.me-1(aria-hidden='true')
 		div
 		div
 			block
 			block
 
 
 mixin customValidationMessageNewStyle(key)
 mixin customValidationMessageNewStyle(key)
-	div.notification.notification-type-error(
-			hidden,
-			data-ol-custom-form-message=key
-		)
-		div.notification-icon
-			+material-symbol("error")
-		div.notification-content.text-left.small
+	.notification.notification-type-error(hidden data-ol-custom-form-message=key)
+		.notification-icon
+			+material-symbol('error')
+		.notification-content.text-left.small
 			block
 			block

+ 53 - 22
services/web/app/views/_mixins/links.pug

@@ -8,7 +8,8 @@ mixin linkAdvisors(linkText, linkClass, track)
 	- var mb = track && track.mb ? 'true' : null
 	- var mb = track && track.mb ? 'true' : null
 	- var mbSegmentation = track && track.segmentation ? track.segmentation : null
 	- var mbSegmentation = track && track.segmentation ? track.segmentation : null
 	- var trigger = track && track.trigger ? track.trigger : null
 	- var trigger = track && track.trigger ? track.trigger : null
-	a(href="/advisors" 
+	a(
+		href='/advisors'
 		class=linkClass ? linkClass : ''
 		class=linkClass ? linkClass : ''
 		event-tracking-ga=gaCategory
 		event-tracking-ga=gaCategory
 		event-tracking=gaAction
 		event-tracking=gaAction
@@ -20,24 +21,36 @@ mixin linkAdvisors(linkText, linkClass, track)
 		span #{linkText ? linkText : 'advisor programme'}
 		span #{linkText ? linkText : 'advisor programme'}
 
 
 mixin linkBenefits(linkText, linkClass)
 mixin linkBenefits(linkText, linkClass)
-	a(href=(settings.siteUrl ? settings.siteUrl : '') + "/for/authors" class=linkClass ? linkClass : '')
+	a(
+		href=(settings.siteUrl ? settings.siteUrl : '') + '/for/authors'
+		class=linkClass ? linkClass : ''
+	)
 		| #{linkText ? linkText : 'benefits'}
 		| #{linkText ? linkText : 'benefits'}
-		
+
 mixin linkBlog(linkText, linkClass, slug)
 mixin linkBlog(linkText, linkClass, slug)
 	if slug
 	if slug
-		a(href=(settings.siteUrl ? settings.siteUrl : '') + "/blog/" + slug class=linkClass ? linkClass : '')
+		a(
+			href=(settings.siteUrl ? settings.siteUrl : '') + '/blog/' + slug
+			class=linkClass ? linkClass : ''
+		)
 			| #{linkText ? linkText : 'blog'}
 			| #{linkText ? linkText : 'blog'}
 
 
 mixin linkContact(linkText, linkClass)
 mixin linkContact(linkText, linkClass)
-	a(href=(settings.siteUrl ? settings.siteUrl : '') + "/contact" class=linkClass ? linkClass : '')
+	a(
+		href=(settings.siteUrl ? settings.siteUrl : '') + '/contact'
+		class=linkClass ? linkClass : ''
+	)
 		| #{linkText ? linkText : 'contact'}
 		| #{linkText ? linkText : 'contact'}
 
 
 mixin linkDash(linkText, linkClass)
 mixin linkDash(linkText, linkClass)
-	a(href="/project" class=linkClass ? linkClass : '')
+	a(href='/project' class=linkClass ? linkClass : '')
 		| #{linkText ? linkText : 'project dashboard'}
 		| #{linkText ? linkText : 'project dashboard'}
 
 
 mixin linkEducation(linkText, linkClass)
 mixin linkEducation(linkText, linkClass)
-	a(href=(settings.siteUrl ? settings.siteUrl : '') + "/for/edu" class=linkClass ? linkClass : '')
+	a(
+		href=(settings.siteUrl ? settings.siteUrl : '') + '/for/edu'
+		class=linkClass ? linkClass : ''
+	)
 		| #{linkText ? linkText : 'teaching toolkit'}
 		| #{linkText ? linkText : 'teaching toolkit'}
 
 
 mixin linkInvite(linkText, linkClass, track)
 mixin linkInvite(linkText, linkClass, track)
@@ -48,7 +61,8 @@ mixin linkInvite(linkText, linkClass, track)
 	- var mbSegmentation = track && track.segmentation ? track.segmentation : null
 	- var mbSegmentation = track && track.segmentation ? track.segmentation : null
 	- var trigger = track && track.trigger ? track.trigger : null
 	- var trigger = track && track.trigger ? track.trigger : null
 
 
-	a(href="/user/bonus"
+	a(
+		href='/user/bonus'
 		class=linkClass ? linkClass : ''
 		class=linkClass ? linkClass : ''
 		event-tracking-ga=gaCategory
 		event-tracking-ga=gaCategory
 		event-tracking=gaAction
 		event-tracking=gaAction
@@ -60,7 +74,7 @@ mixin linkInvite(linkText, linkClass, track)
 		span #{linkText ? linkText : 'invite your friends'}
 		span #{linkText ? linkText : 'invite your friends'}
 
 
 mixin linkPlansAndPricing(linkText, linkClass)
 mixin linkPlansAndPricing(linkText, linkClass)
-	a(href="/user/subscription/plans" class=linkClass ? linkClass : '')
+	a(href='/user/subscription/plans' class=linkClass ? linkClass : '')
 		| #{linkText ? linkText : 'plans and pricing'}
 		| #{linkText ? linkText : 'plans and pricing'}
 
 
 mixin linkPrintNewTab(linkText, linkClass, icon, track)
 mixin linkPrintNewTab(linkText, linkClass, icon, track)
@@ -71,7 +85,8 @@ mixin linkPrintNewTab(linkText, linkClass, icon, track)
 	- var mbSegmentation = track && track.segmentation ? track.segmentation : null
 	- var mbSegmentation = track && track.segmentation ? track.segmentation : null
 	- var trigger = track && track.trigger ? track.trigger : null
 	- var trigger = track && track.trigger ? track.trigger : null
 
 
-	a(href='?media=print'
+	a(
+		href='?media=print'
 		class=linkClass ? linkClass : ''
 		class=linkClass ? linkClass : ''
 		event-tracking-ga=gaCategory
 		event-tracking-ga=gaCategory
 		event-tracking=gaAction
 		event-tracking=gaAction
@@ -79,20 +94,26 @@ mixin linkPrintNewTab(linkText, linkClass, icon, track)
 		event-tracking-trigger=trigger
 		event-tracking-trigger=trigger
 		event-tracking-mb=mb
 		event-tracking-mb=mb
 		event-segmentation=mbSegmentation
 		event-segmentation=mbSegmentation
-		target="_BLANK",
-		rel="noopener noreferrer"
+		target='_BLANK'
+		rel='noopener noreferrer'
 	)
 	)
 		if icon
 		if icon
-			i(class="fa fa-print")
+			i(class='fa fa-print')
 			| &nbsp;
 			| &nbsp;
 		span #{linkText ? linkText : 'print'}
 		span #{linkText ? linkText : 'print'}
 
 
 mixin linkSignIn(linkText, linkClass, redirect)
 mixin linkSignIn(linkText, linkClass, redirect)
-	a(href=`/login${redirect ? '?redir=' + redirect : ''}` class=linkClass ? linkClass : '')
+	a(
+		href=`/login${redirect ? '?redir=' + redirect : ''}`
+		class=linkClass ? linkClass : ''
+	)
 		| #{linkText ? linkText : 'sign in'}
 		| #{linkText ? linkText : 'sign in'}
 
 
 mixin linkSignUp(linkText, linkClass, redirect)
 mixin linkSignUp(linkText, linkClass, redirect)
-	a(href=`/register${redirect ? '?redir=' + redirect : ''}` class=linkClass ? linkClass : '')
+	a(
+		href=`/register${redirect ? '?redir=' + redirect : ''}`
+		class=linkClass ? linkClass : ''
+	)
 		| #{linkText ? linkText : 'sign up'}
 		| #{linkText ? linkText : 'sign up'}
 
 
 mixin linkTweet(linkText, linkClass, tweetText, track)
 mixin linkTweet(linkText, linkClass, tweetText, track)
@@ -103,23 +124,33 @@ mixin linkTweet(linkText, linkClass, tweetText, track)
 	- var mb = track && track.mb ? 'true' : null
 	- var mb = track && track.mb ? 'true' : null
 	- var mbSegmentation = track && track.segmentation ? track.segmentation : null
 	- var mbSegmentation = track && track.segmentation ? track.segmentation : null
 	- var trigger = track && track.trigger ? track.trigger : null
 	- var trigger = track && track.trigger ? track.trigger : null
-	a(class="twitter-share-button " + linkClass
+	a(
+		class='twitter-share-button ' + linkClass
 		event-tracking-ga=gaCategory
 		event-tracking-ga=gaCategory
 		event-tracking=gaAction
 		event-tracking=gaAction
 		event-tracking-label=gaLabel
 		event-tracking-label=gaLabel
 		event-tracking-trigger=trigger
 		event-tracking-trigger=trigger
 		event-tracking-mb=mb
 		event-tracking-mb=mb
 		event-segmentation=mbSegmentation
 		event-segmentation=mbSegmentation
-		href="https://twitter.com/intent/tweet?text=" + tweetText
-		target="_BLANK",
-		rel="noopener noreferrer"
+		href='https://twitter.com/intent/tweet?text=' + tweetText
+		target='_BLANK'
+		rel='noopener noreferrer'
 	) #{linkText ? linkText : 'tweet'}
 	) #{linkText ? linkText : 'tweet'}
 
 
 mixin linkUniversities(linkText, linkClass)
 mixin linkUniversities(linkText, linkClass)
-	a(href=(settings.siteUrl ? settings.siteUrl : '') + "/for/universities" class=linkClass ? linkClass : '')
+	a(
+		href=(settings.siteUrl ? settings.siteUrl : '') + '/for/universities'
+		class=linkClass ? linkClass : ''
+	)
 		| #{linkText ? linkText : 'universities'}
 		| #{linkText ? linkText : 'universities'}
 
 
 mixin linkWithArrow({text, href, eventTracking, eventSegmentation, eventTrackingTrigger})
 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)
+	a.link-with-arrow(
+		href=href
+		event-tracking=eventTracking
+		event-segmentation=eventSegmentation
+		event-tracking-trigger=eventTrackingTrigger
+		event-tracking-mb
+	)
 		| #{text}
 		| #{text}
-		+material-symbol("arrow_right_alt")
+		+material-symbol('arrow_right_alt')

+ 3 - 2
services/web/app/views/_mixins/material_symbol.pug

@@ -1,7 +1,8 @@
 mixin material-symbol(icon, extraClass = null)
 mixin material-symbol(icon, extraClass = null)
 	- extraClass = extraClass ? ' ' + extraClass : ''
 	- extraClass = extraClass ? ' ' + extraClass : ''
-	span(aria-hidden="true", translate="no", class="material-symbols" + extraClass)&attributes(attributes) #{icon}
-	
+	span(aria-hidden='true' translate='no' class='material-symbols' + extraClass)&attributes(attributes)
+		| #{icon}
+
 mixin material-symbol-outlined(icon, extraClass = null)
 mixin material-symbol-outlined(icon, extraClass = null)
 	- extraClass = extraClass ? ' ' + extraClass : ''
 	- extraClass = extraClass ? ' ' + extraClass : ''
 	+material-symbol(icon, 'material-symbols-outlined' + extraClass)&attributes(attributes)
 	+material-symbol(icon, 'material-symbols-outlined' + extraClass)&attributes(attributes)

+ 7 - 7
services/web/app/views/_mixins/navbar.pug

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

+ 5 - 11
services/web/app/views/_mixins/notification.pug

@@ -3,25 +3,19 @@ include ./material_symbol
 
 
 mixin notificationIcon(type)
 mixin notificationIcon(type)
 	if type === 'info'
 	if type === 'info'
-		+material-symbol("info")
+		+material-symbol('info')
 	else if type === 'success'
 	else if type === 'success'
-		+material-symbol("check_circle")
+		+material-symbol('check_circle')
 	else if type === 'error'
 	else if type === 'error'
-		+material-symbol("error")
+		+material-symbol('error')
 	else if type === 'warning'
 	else if type === 'warning'
-		+material-symbol("warning")
-
+		+material-symbol('warning')
 
 
 mixin notification(options)
 mixin notification(options)
 	- var {ariaLive, id, type, title, content, disclaimer, className} = options
 	- var {ariaLive, id, type, title, content, disclaimer, className} = options
 	- var classNames = `notification notification-type-${type} ${className ? className : ''} ${isActionBelowContent ? 'notification-cta-below-content' : ''}`
 	- var classNames = `notification notification-type-${type} ${className ? className : ''} ${isActionBelowContent ? 'notification-cta-below-content' : ''}`
 
 
-	div(
-		aria-live=ariaLive,
-		role="alert",
-		id=id,
-		class=classNames
-	)
+	div(aria-live=ariaLive role='alert' id=id class=classNames)
 		.notification-icon
 		.notification-icon
 			+notificationIcon(type)
 			+notificationIcon(type)
 		.notification-content-and-cta
 		.notification-content-and-cta

+ 21 - 24
services/web/app/views/_mixins/pagination.pug

@@ -10,77 +10,74 @@ mixin pagination(pages, page_path, max_btns)
 		- var max_btns = max_btns || 4
 		- var max_btns = max_btns || 4
 		- var prev_page = Math.max(parseInt(pages.current_page, 10) - max_btns, 1)
 		- var prev_page = Math.max(parseInt(pages.current_page, 10) - max_btns, 1)
 		- var next_page = parseInt(pages.current_page, 10) + 1
 		- var next_page = parseInt(pages.current_page, 10) + 1
-		- var next_index = 0;
-		- var full_page_path = page_path + "/page/"
+		- var next_index = 0
+		- var full_page_path = page_path + '/page/'
 
 
-		nav(role="navigation" aria-label=(translate("pagination_navigation")))
+		nav(role='navigation' aria-label=translate('pagination_navigation'))
 			ul.pagination
 			ul.pagination
 				if pages.current_page > 1
 				if pages.current_page > 1
 					li
 					li
-						a(
-							aria-label=translate("go_to_first_page")
-							href=page_path
-						)
-							span(aria-hidden="true") &lt;&lt;
+						a(aria-label=translate('go_to_first_page') href=page_path)
+							span(aria-hidden='true') &lt;&lt;
 							|
 							|
 							| First
 							| First
 					li
 					li
 						a(
 						a(
-							aria-label=translate("go_to_previous_page")
+							aria-label=translate('go_to_previous_page')
 							href=full_page_path + (parseInt(pages.current_page, 10) - 1)
 							href=full_page_path + (parseInt(pages.current_page, 10) - 1)
-							rel="prev"
+							rel='prev'
 						)
 						)
-							span(aria-hidden="true") &lt;
+							span(aria-hidden='true') &lt;
 							|
 							|
 							| Prev
 							| Prev
 
 
 				if pages.current_page - max_btns > 1
 				if pages.current_page - max_btns > 1
-					li(aria-hidden="true")
+					li(aria-hidden='true')
 						span …
 						span …
 
 
 				while prev_page < pages.current_page
 				while prev_page < pages.current_page
 					li
 					li
 						a(
 						a(
-							aria-label=translate("go_to_page_x", {page: prev_page})
+							aria-label=translate('go_to_page_x', {page: prev_page})
 							href=full_page_path + prev_page
 							href=full_page_path + prev_page
 						) #{prev_page}
 						) #{prev_page}
 						- prev_page++
 						- prev_page++
 
 
-				li(class="active")
+				li(class='active')
 					span(
 					span(
-						aria-label=translate("current_page_page", {page: pages.current_page})
-						aria-current="true"
+						aria-label=translate('current_page_page', {page: pages.current_page})
+						aria-current='true'
 					) #{pages.current_page}
 					) #{pages.current_page}
 
 
 				if pages.current_page < pages.total_pages
 				if pages.current_page < pages.total_pages
 					while next_page <= pages.total_pages && next_index < max_btns
 					while next_page <= pages.total_pages && next_index < max_btns
 						li
 						li
 							a(
 							a(
-								aria-label=translate("go_to_page_x", {page: next_page})
+								aria-label=translate('go_to_page_x', {page: next_page})
 								href=full_page_path + next_page
 								href=full_page_path + next_page
 							) #{next_page}
 							) #{next_page}
 						- next_page++
 						- next_page++
 						- next_index++
 						- next_index++
 
 
-					if next_page <= pages.total_pages 
-						li.ellipses(aria-hidden="true")
+					if next_page <= pages.total_pages
+						li.ellipses(aria-hidden='true')
 							span …
 							span …
 
 
 					li
 					li
 						a(
 						a(
-							aria-label=translate("go_to_next_page")
+							aria-label=translate('go_to_next_page')
 							href=full_page_path + (parseInt(pages.current_page, 10) + 1)
 							href=full_page_path + (parseInt(pages.current_page, 10) + 1)
-							rel="next"
+							rel='next'
 						)
 						)
 							| Next
 							| Next
 							|
 							|
-							span(aria-hidden="true") &gt;
+							span(aria-hidden='true') &gt;
 
 
 					li
 					li
 						a(
 						a(
-							aria-label=translate("go_to_last_page")
+							aria-label=translate('go_to_last_page')
 							href=full_page_path + pages.total_pages
 							href=full_page_path + pages.total_pages
 						)
 						)
 							| Last
 							| Last
 							|
 							|
-							span(aria-hidden="true") &gt;&gt;
+							span(aria-hidden='true') &gt;&gt;

+ 1 - 1
services/web/app/views/_mixins/previous_page_link.pug

@@ -2,5 +2,5 @@ include ./material_symbol
 
 
 mixin previous-page-link(href, text)
 mixin previous-page-link(href, text)
 	a.previous-page-link(href=href)
 	a.previous-page-link(href=href)
-		+material-symbol-rounded("arrow_left_alt")
+		+material-symbol-rounded('arrow_left_alt')
 		| #{text}
 		| #{text}

+ 14 - 14
services/web/app/views/_mixins/quote.pug

@@ -3,10 +3,10 @@ mixin quoteLargeTextCentered(quote, person, position, affiliation, link, picture
 		.quote !{quote}
 		.quote !{quote}
 		if pictureUrl
 		if pictureUrl
 			.quote-img
 			.quote-img
-				-var pictureAlt=`Photo of ${person}`
+				- var pictureAlt = `Photo of ${person}`
 				img(src=pictureUrl alt=pictureAlt)
 				img(src=pictureUrl alt=pictureAlt)
 		footer
 		footer
-			div.quote-person
+			.quote-person
 				strong #{person}
 				strong #{person}
 			if person && position
 			if person && position
 				div #{position}
 				div #{position}
@@ -29,27 +29,27 @@ mixin quoteLeftGreenBorder({quote, person, position, affiliation, link})
 
 
 mixin collinsQuote1
 mixin collinsQuote1
 	.card.card-dark-green-bg
 	.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, it’s our main tool.'
-		-var quotePerson = 'Christopher Collins'
-		-var quotePersonPosition = 'Associate Professor and Lab Director, Ontario Tech University'
-		-var quotePersonImg = buildImgPath("advocates/collins.jpg")
+		- 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, it’s 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
 		.card-body
 			+quoteLargeTextCentered(quote, quotePerson, quotePersonPosition, null, null, quotePersonImg)
 			+quoteLargeTextCentered(quote, quotePerson, quotePersonPosition, null, null, quotePersonImg)
 
 
 mixin collinsQuote2
 mixin collinsQuote2
 	.card.card-dark-green-bg
 	.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")
+		- 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
 		.card-body
 			+quoteLargeTextCentered(quote, quotePerson, quotePersonPosition, null, null, quotePersonImg)
 			+quoteLargeTextCentered(quote, quotePerson, quotePersonPosition, null, null, quotePersonImg)
 
 
 mixin bennettQuote1
 mixin bennettQuote1
 	.card.card-dark-green-bg
 	.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")
+		- 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
 		.card-body
 			+quoteLargeTextCentered(quote, quotePerson, quotePersonPosition, null, null, quotePersonImg)
 			+quoteLargeTextCentered(quote, quotePerson, quotePersonPosition, null, null, quotePersonImg)

+ 1 - 1
services/web/app/views/_mixins/recaptcha.pug

@@ -1,2 +1,2 @@
-mixin recaptchaConditions()
+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'}}])}
 	.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'}}])}

+ 15 - 21
services/web/app/views/_mixins/reconfirm_affiliation-marketing.pug

@@ -1,14 +1,11 @@
 mixin reconfirmAffiliationNotification-marketing(userEmail, location)
 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()
+	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
 		.reconfirm-notification
-			div(data-ol-not-sent style="width:100%;")
+			div(data-ol-not-sent style='width: 100%')
 				i.fa.fa-warning
 				i.fa.fa-warning
 
 
 				- var ssoEnabled = userEmail.affiliation && userEmail.affiliation.institution && userEmail.affiliation.institution.ssoEnabled
 				- var ssoEnabled = userEmail.affiliation && userEmail.affiliation.institution && userEmail.affiliation.institution.ssoEnabled
@@ -18,16 +15,16 @@ mixin reconfirmAffiliationNotification-marketing(userEmail, location)
 						data-ol-slow-link
 						data-ol-slow-link
 						href=`${settings.saml.ukamf.initPath}?university_id=${institutionId}&reconfirm=${location}`
 						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")}…
+						span(data-ol-inflight='idle') #{translate("confirm_affiliation")}
+						span(hidden data-ol-inflight='pending') #{translate("pending")}…
 
 
 				else
 				else
 					button.btn-reconfirm.btn.btn-sm.btn-info(
 					button.btn-reconfirm.btn.btn-sm.btn-info(
-						type="submit"
+						type='submit'
 						data-ol-disabled-inflight
 						data-ol-disabled-inflight
 					)
 					)
-						span(data-ol-inflight="idle") #{translate("confirm_affiliation")}
-						span(hidden data-ol-inflight="pending") #{translate("pending")}…
+						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;
 				| !{translate("are_you_still_at", {institutionName: userEmail.affiliation.institution.name}, ['strong'])}&nbsp;
 
 
@@ -39,22 +36,19 @@ mixin reconfirmAffiliationNotification-marketing(userEmail, location)
 					| !{translate("please_reconfirm_institutional_email", {}, [{name: 'a', attrs: {href: '/user/settings?remove=' + userEmail.email}}])}
 					| !{translate("please_reconfirm_institutional_email", {}, [{name: 'a', attrs: {href: '/user/settings?remove=' + userEmail.email}}])}
 
 
 				| &nbsp;
 				| &nbsp;
-				a(href="/learn/how-to/Institutional_Email_Reconfirmation" target="_blank") #{translate("learn_more")}
+				a(href='/learn/how-to/Institutional_Email_Reconfirmation' target='_blank') #{translate("learn_more")}
 
 
 			div(hidden data-ol-sent)
 			div(hidden data-ol-sent)
 				| !{translate("please_check_your_inbox_to_confirm", {institutionName: userEmail.affiliation.institution.name}, ['strong'])}
 				| !{translate("please_check_your_inbox_to_confirm", {institutionName: userEmail.affiliation.institution.name}, ['strong'])}
 				| &nbsp;
 				| &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")}…
+				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)
 mixin reconfirmedAffiliationNotification-marketing(userEmail)
 	.alert.alert-info
 	.alert.alert-info
 		.reconfirm-notification
 		.reconfirm-notification
-			div(style="width:100%;")
+			div(style='width: 100%')
 				//- extra div for flex styling
 				//- extra div for flex styling
 				| !{translate("your_affiliation_is_confirmed", {institutionName: userEmail.affiliation.institution.name}, ['strong'])}
 				| !{translate("your_affiliation_is_confirmed", {institutionName: userEmail.affiliation.institution.name}, ['strong'])}
 				|
 				|

+ 1 - 1
services/web/app/views/_mixins/terms_of_service.pug

@@ -1,3 +1,3 @@
 mixin termsOfServiceAgreement
 mixin termsOfServiceAgreement
-	div.tos-agreement-notice
+	.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'}}])}
 		| !{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'}}])}

+ 44 - 41
services/web/app/views/admin/index.pug

@@ -2,7 +2,7 @@ extends ../layout-marketing
 include ../_mixins/bookmarkable_tabset
 include ../_mixins/bookmarkable_tabset
 
 
 block content
 block content
-	.content.content-alt#main-content
+	#main-content.content.content-alt
 		.container
 		.container
 			.row
 			.row
 				.col-sm-12
 				.col-sm-12
@@ -12,7 +12,7 @@ block content
 								h1 Admin Panel
 								h1 Admin Panel
 							.ol-tabs(data-ol-bookmarkable-tabset)
 							.ol-tabs(data-ol-bookmarkable-tabset)
 								.nav-tabs-container
 								.nav-tabs-container
-									ul.nav.nav-tabs.align-left(role="tablist")
+									ul.nav.nav-tabs.align-left(role='tablist')
 										+bookmarkable-tabset-header('system-messages', 'System Messages', true)
 										+bookmarkable-tabset-header('system-messages', 'System Messages', true)
 										+bookmarkable-tabset-header('open-sockets', 'Open Sockets')
 										+bookmarkable-tabset-header('open-sockets', 'Open Sockets')
 										+bookmarkable-tabset-header('open-close-editor', 'Open/Close Editor')
 										+bookmarkable-tabset-header('open-close-editor', 'Open/Close Editor')
@@ -20,29 +20,28 @@ block content
 											+bookmarkable-tabset-header('tpds', 'TPDS/Dropbox Management')
 											+bookmarkable-tabset-header('tpds', 'TPDS/Dropbox Management')
 
 
 								.tab-content
 								.tab-content
-									.tab-pane.active(
-										role="tabpanel"
-										id='system-messages'
-									)
+									.tab-pane.active(role='tabpanel' id='system-messages')
 										each message in systemMessages
 										each message in systemMessages
 											ul.system-messages
 											ul.system-messages
 												li.system-message.row-spaced #{message.content}
 												li.system-message.row-spaced #{message.content}
 										hr
 										hr
-										form(method='post', action='/admin/messages')
-											input(name="_csrf", type="hidden", value=csrfToken)
+										form(method='post' action='/admin/messages')
+											input(name='_csrf' type='hidden' value=csrfToken)
 											.form-group
 											.form-group
-												label.form-label(for="content")
-												input.form-control(name="content", type="text", placeholder="Message…", required)
-											button.btn.btn-primary(type="submit") Post Message
+												label.form-label(for='content')
+												input.form-control(
+													name='content'
+													type='text'
+													placeholder='Message…'
+													required
+												)
+											button.btn.btn-primary(type='submit') Post Message
 										hr
 										hr
-										form(method='post', action='/admin/messages/clear')
-											input(name="_csrf", type="hidden", value=csrfToken)
-											button.btn.btn-danger(type="submit") Clear all messages
+										form(method='post' action='/admin/messages/clear')
+											input(name='_csrf' type='hidden' value=csrfToken)
+											button.btn.btn-danger(type='submit') Clear all messages
 
 
-									.tab-pane(
-										role="tabpanel"
-										id='open-sockets'
-									)
+									.tab-pane(role='tabpanel' id='open-sockets')
 										.row-spaced
 										.row-spaced
 											ul
 											ul
 												each agents, url in openSockets
 												each agents, url in openSockets
@@ -51,52 +50,56 @@ block content
 															each agent in agents
 															each agent in agents
 																li #{agent}
 																li #{agent}
 
 
-									.tab-pane(
-										role="tabpanel"
-										id='open-close-editor'
-									)
+									.tab-pane(role='tabpanel' id='open-close-editor')
 										if hasFeature('saas')
 										if hasFeature('saas')
 											| The "Open/Close Editor" feature is not available in SAAS.
 											| The "Open/Close Editor" feature is not available in SAAS.
 										else
 										else
 											.row-spaced
 											.row-spaced
-												form(method='post',action='/admin/closeEditor')
-													input(name="_csrf", type="hidden", value=csrfToken)
-													button.btn.btn-danger(type="submit") Close Editor
+												form(method='post' action='/admin/closeEditor')
+													input(name='_csrf' type='hidden' value=csrfToken)
+													button.btn.btn-danger(type='submit') Close Editor
 												p.small Will stop anyone opening the editor. Will NOT disconnect already connected users.
 												p.small Will stop anyone opening the editor. Will NOT disconnect already connected users.
 
 
 											.row-spaced
 											.row-spaced
-												form(method='post',action='/admin/disconnectAllUsers')
-													input(name="_csrf", type="hidden", value=csrfToken)
-													button.btn.btn-danger(type="submit") Disconnect all users
+												form(method='post' action='/admin/disconnectAllUsers')
+													input(name='_csrf' type='hidden' value=csrfToken)
+													button.btn.btn-danger(type='submit') Disconnect all users
 												p.small Will force disconnect all users with the editor open. Make sure to close the editor first to avoid them reconnecting.
 												p.small Will force disconnect all users with the editor open. Make sure to close the editor first to avoid them reconnecting.
 
 
 											.row-spaced
 											.row-spaced
-												form(method='post',action='/admin/openEditor')
-													input(name="_csrf", type="hidden", value=csrfToken)
-													button.btn.btn-danger(type="submit") Reopen Editor
+												form(method='post' action='/admin/openEditor')
+													input(name='_csrf' type='hidden' value=csrfToken)
+													button.btn.btn-danger(type='submit') Reopen Editor
 												p.small Will reopen the editor after closing.
 												p.small Will reopen the editor after closing.
 
 
 									if hasFeature('saas')
 									if hasFeature('saas')
-										.tab-pane(
-											role="tabpanel"
-											id='tpds'
-										)
+										.tab-pane(role='tabpanel' id='tpds')
 											h3 Flush project to TPDS
 											h3 Flush project to TPDS
 											.row
 											.row
-												form.col-xs-6(method='post',action='/admin/flushProjectToTpds')
-													input(name="_csrf", type="hidden", value=csrfToken)
+												form.col-xs-6(method='post' action='/admin/flushProjectToTpds')
+													input(name='_csrf' type='hidden' value=csrfToken)
 													.form-group
 													.form-group
 														label.form-label(for='project_id') project_id
 														label.form-label(for='project_id') project_id
-														input.form-control(type='text',  name='project_id', placeholder='project_id', required)
+														input.form-control(
+															name='project_id'
+															type='text'
+															placeholder='project_id'
+															required
+														)
 													.form-group
 													.form-group
 														button.btn-primary.btn(type='submit') Flush
 														button.btn-primary.btn(type='submit') Flush
 											hr
 											hr
 											h3 Poll Dropbox for user
 											h3 Poll Dropbox for user
 											.row
 											.row
-												form.col-xs-6(method='post',action='/admin/pollDropboxForUser')
-													input(name="_csrf", type="hidden", value=csrfToken)
+												form.col-xs-6(method='post' action='/admin/pollDropboxForUser')
+													input(name='_csrf' type='hidden' value=csrfToken)
 													.form-group
 													.form-group
 														label.form-label(for='user_id') user_id
 														label.form-label(for='user_id') user_id
-														input.form-control(type='text',  name='user_id', placeholder='user_id', required)
+														input.form-control(
+															name='user_id'
+															type='text'
+															placeholder='user_id'
+															required
+														)
 													.form-group
 													.form-group
 														button.btn-primary.btn(type='submit') Poll
 														button.btn-primary.btn(type='submit') Poll

+ 22 - 27
services/web/app/views/beta_program/opt_in.pug

@@ -2,14 +2,14 @@ extends ../layout-marketing
 include ../_mixins/back_to_btns
 include ../_mixins/back_to_btns
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		.container.beta-opt-in-wrapper
 		.container.beta-opt-in-wrapper
 			.row
 			.row
 				.col-lg-10.offset-lg-1.col-xl-8.offset-xl-2
 				.col-lg-10.offset-lg-1.col-xl-8.offset-xl-2
 					.card
 					.card
 						.card-body
 						.card-body
 							.page-header
 							.page-header
-								h1 
+								h1
 									| #{translate("sharelatex_beta_program")}
 									| #{translate("sharelatex_beta_program")}
 							.beta-opt-in
 							.beta-opt-in
 								.container-fluid
 								.container-fluid
@@ -28,7 +28,9 @@ block content
 											ul
 											ul
 												li
 												li
 													| #{translate("beta_program_badge_description")}&nbsp;
 													| #{translate("beta_program_badge_description")}&nbsp;
-													span.badge.bg-warning-light-bg.text-warning(aria-label=translate("beta_feature_badge"))
+													span.badge.bg-warning-light-bg.text-warning(
+														aria-label=translate('beta_feature_badge')
+													)
 														span.badge-content β
 														span.badge-content β
 												li !{translate("you_will_be_able_to_contact_us_any_time_to_share_your_feedback", {}, ['strong'])}.
 												li !{translate("you_will_be_able_to_contact_us_any_time_to_share_your_feedback", {}, ['strong'])}.
 												li !{translate("we_may_also_contact_you_from_time_to_time_by_email_with_a_survey", {}, ['strong'])}.
 												li !{translate("we_may_also_contact_you_from_time_to_time_by_email_with_a_survey", {}, ['strong'])}.
@@ -40,37 +42,30 @@ block content
 											if user.betaProgram
 											if user.betaProgram
 												form(
 												form(
 													data-ol-regular-form
 													data-ol-regular-form
-													method="post"
-													action="/beta/opt-out"
+													method='post'
+													action='/beta/opt-out'
 													novalidate
 													novalidate
 												)
 												)
-													input(type="hidden", name="_csrf", value=csrfToken)
+													input(name='_csrf' type='hidden' value=csrfToken)
 													.form-group
 													.form-group
-														a(
-															href="https://forms.gle/CFEsmvZQTAwHCd3X9"
-															target="_blank"
-															rel="noopener noreferrer"
-														).btn.btn-primary.btn-lg #{translate("give_feedback")}
+														a.btn.btn-primary.btn-lg(
+															href='https://forms.gle/CFEsmvZQTAwHCd3X9'
+															target='_blank'
+															rel='noopener noreferrer'
+														) #{translate("give_feedback")}
 													.form-group
 													.form-group
 														button.btn.btn-secondary-info.btn-secondary.btn-sm(
 														button.btn.btn-secondary-info.btn-secondary.btn-sm(
-															type="submit"
+															type='submit'
 															data-ol-disabled-inflight
 															data-ol-disabled-inflight
 														)
 														)
-															span(data-ol-inflight="idle") #{translate("beta_program_opt_out_action")}
-															span(hidden data-ol-inflight="pending") #{translate("processing")}…
+															span(data-ol-inflight='idle') #{translate("beta_program_opt_out_action")}
+															span(hidden data-ol-inflight='pending') #{translate("processing")}…
 											else
 											else
-												form(
-													data-ol-regular-form
-													method="post",
-													action="/beta/opt-in"
-												)
-													input(type="hidden", name="_csrf", value=csrfToken)
+												form(data-ol-regular-form method='post' action='/beta/opt-in')
+													input(name='_csrf' type='hidden' value=csrfToken)
 													.form-group
 													.form-group
-														button.btn.btn-primary(
-															type="submit"
-															data-ol-disabled-inflight
-														)
-															span(data-ol-inflight="idle") #{translate("beta_program_opt_in_action")}
-															span(hidden data-ol-inflight="pending") #{translate("joining")}…
+														button.btn.btn-primary(type='submit' data-ol-disabled-inflight)
+															span(data-ol-inflight='idle') #{translate("beta_program_opt_in_action")}
+															span(hidden data-ol-inflight='pending') #{translate("joining")}…
 								.page-separator
 								.page-separator
-								+back-to-btns()
+								+back-to-btns

+ 7 - 7
services/web/app/views/general/400.pug

@@ -1,29 +1,29 @@
 extends ../layout/layout-no-js
 extends ../layout/layout-no-js
 
 
 block vars
 block vars
-	- metadata = { title: 'Something went wrong' }
+	- metadata = {title: 'Something went wrong'}
 
 
 block body
 block body
 	body.full-height
 	body.full-height
-		main.content.content-alt.full-height#main-content
+		main#main-content.content.content-alt.full-height
 			.container.full-height
 			.container.full-height
 				.error-container.full-height
 				.error-container.full-height
 					.error-details
 					.error-details
 						p.error-status Something went wrong, sorry.
 						p.error-status Something went wrong, sorry.
 						p.error-description
 						p.error-description
 							| There was a problem with your request.
 							| There was a problem with your request.
-							if(message)
+							if message
 								|
 								|
 								| The error is:
 								| The error is:
-						if(message)
+						if message
 							p.error-box
 							p.error-box
 								| #{message}
 								| #{message}
 						p.error-description
 						p.error-description
 							| Please go back and try again.
 							| Please go back and try again.
 							| If the problem persists, please contact us at
 							| If the problem persists, please contact us at
 							|
 							|
-							a(href="mailto:" + settings.adminEmail) #{settings.adminEmail}
+							a(href='mailto:' + settings.adminEmail) #{settings.adminEmail}
 							| .
 							| .
 						p.error-actions
 						p.error-actions
-							a.error-btn(href="javascript:history.back()") Back
-							a.btn.btn-secondary(href="/") Home
+							a.error-btn(href='javascript:history.back()') Back
+							a.btn.btn-secondary(href='/') Home

+ 2 - 2
services/web/app/views/general/404.pug

@@ -1,11 +1,11 @@
 extends ../layout-marketing
 extends ../layout-marketing
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		.container
 		.container
 			.error-container
 			.error-container
 				.error-details
 				.error-details
 					p.error-status Not found
 					p.error-status Not found
 					p.error-description #{translate("cant_find_page")}
 					p.error-description #{translate("cant_find_page")}
 					p.error-actions
 					p.error-actions
-						a.error-btn(href="/") Home
+						a.error-btn(href='/') Home

+ 5 - 5
services/web/app/views/general/500.pug

@@ -1,11 +1,11 @@
 extends ../layout/layout-no-js
 extends ../layout/layout-no-js
 
 
 block vars
 block vars
-	- metadata = { title: 'Something went wrong' }
+	- metadata = {title: 'Something went wrong'}
 
 
 block body
 block body
 	body.full-height
 	body.full-height
-		main.content.content-alt.full-height#main-content
+		main#main-content.content.content-alt.full-height
 			.container.full-height
 			.container.full-height
 				.error-container.full-height
 				.error-container.full-height
 					.error-details
 					.error-details
@@ -13,11 +13,11 @@ block body
 						p.error-description Our staff are probably looking into this, but if it continues, please check our status page at
 						p.error-description Our staff are probably looking into this, but if it continues, please check our status page at
 							|
 							|
 							|
 							|
-							a(href="http://" + settings.statusPageUrl) #{settings.statusPageUrl}
+							a(href='http://' + settings.statusPageUrl) #{settings.statusPageUrl}
 							|
 							|
 							| or contact us at
 							| or contact us at
 							|
 							|
-							a(href="mailto:" + settings.adminEmail) #{settings.adminEmail}
+							a(href='mailto:' + settings.adminEmail) #{settings.adminEmail}
 							| .
 							| .
 						p.error-actions
 						p.error-actions
-							a.error-btn(href="/") Home
+							a.error-btn(href='/') Home

+ 1 - 1
services/web/app/views/general/closed.pug

@@ -1,7 +1,7 @@
 extends ../layout-marketing
 extends ../layout-marketing
 
 
 block content
 block content
-	main.content#main-content
+	main#main-content.content
 		.container
 		.container
 			.row
 			.row
 				.col-lg-8.offset-lg-2.text-center
 				.col-lg-8.offset-lg-2.text-center

+ 5 - 9
services/web/app/views/general/post-gateway.pug

@@ -15,12 +15,8 @@ block content
 						.card-body
 						.card-body
 							p.text-center #{translate('processing_your_request')}
 							p.text-center #{translate('processing_your_request')}
 
 
-	form(
-		data-ol-regular-form
-		data-ol-auto-submit
-		method="POST"
-	)
-		input(name="_csrf" type="hidden" value=csrfToken)
-		input(hidden name="viaGateway" type="submit" value="true")
-		for name in Object.keys(form_data)
-			input(name=name type="hidden" value=form_data[name])
+	form(data-ol-regular-form data-ol-auto-submit method='POST')
+		input(name='_csrf' type='hidden' value=csrfToken)
+		input(name='viaGateway' hidden type='submit' value='true')
+		each name in Object.keys(form_data)
+			input(name=name type='hidden' value=form_data[name])

+ 5 - 5
services/web/app/views/general/unsupported-browser.pug

@@ -1,11 +1,11 @@
 extends ../layout/layout-no-js
 extends ../layout/layout-no-js
 
 
 block vars
 block vars
-	- metadata = { title: 'Unsupported browser' }
+	- metadata = {title: 'Unsupported browser'}
 
 
 block body
 block body
 	body.full-height
 	body.full-height
-		main.content.content-alt.full-height#main-content
+		main#main-content.content.content-alt.full-height
 			.container.full-height
 			.container.full-height
 				.error-container.full-height
 				.error-container.full-height
 					.error-details
 					.error-details
@@ -15,7 +15,7 @@ block body
 							br
 							br
 							| If you think you're seeing this message in error,
 							| If you think you're seeing this message in error,
 							|
 							|
-							a(href="mailto:" + settings.adminEmail) please let us know
+							a(href='mailto:' + settings.adminEmail) please let us know
 							| .
 							| .
 
 
 						if fromURL
 						if fromURL
@@ -33,7 +33,7 @@ block body
 						p
 						p
 							| Support for beta or developer-preview browser versions cannot be guaranteed. Please
 							| Support for beta or developer-preview browser versions cannot be guaranteed. Please
 							|
 							|
-							a(href="mailto:" + settings.adminEmail) get in touch
+							a(href='mailto:' + settings.adminEmail) get in touch
 							|
 							|
 							| if you encounter any issues while using the service with beta or developer-preview releases of supported browsers.
 							| if you encounter any issues while using the service with beta or developer-preview releases of supported browsers.
 						p
 						p
@@ -41,5 +41,5 @@ block body
 						p
 						p
 							| If you cannot upgrade to one of the supported browsers,
 							| If you cannot upgrade to one of the supported browsers,
 							|
 							|
-							a(href="mailto:" + settings.adminEmail) please let us know
+							a(href='mailto:' + settings.adminEmail) please let us know
 							| .
 							| .

+ 89 - 52
services/web/app/views/layout-base.pug

@@ -2,8 +2,8 @@ include ./_mixins/foot_scripts
 
 
 doctype html
 doctype html
 html(
 html(
-	lang=(currentLngCode || 'en')
-	class=(fixedSizeDocument ? 'fixed-size-document' : undefined)
+	lang=currentLngCode || 'en'
+	class=fixedSizeDocument ? 'fixed-size-document' : undefined
 )
 )
 	- metadata = metadata || {}
 	- metadata = metadata || {}
 	- let bootstrap5PageStatus = 'enabled' // One of 'disabled' and 'enabled'
 	- let bootstrap5PageStatus = 'enabled' // One of 'disabled' and 'enabled'
@@ -22,91 +22,128 @@ html(
 		include ./_metadata.pug
 		include ./_metadata.pug
 
 
 		- const bootstrapVersion = bootstrap5PageStatus !== 'disabled' && (bootstrap5Override || bootstrap5PageSplitTest === '' || splitTestVariants[bootstrap5PageSplitTest] === 'enabled') ? 5 : 3
 		- const bootstrapVersion = bootstrap5PageStatus !== 'disabled' && (bootstrap5Override || bootstrap5PageSplitTest === '' || splitTestVariants[bootstrap5PageSplitTest] === 'enabled') ? 5 : 3
-	
+
 		//- Stylesheet
 		//- Stylesheet
-		link(rel='stylesheet', href=buildCssPath(getCssThemeModifier(userSettings, brandVariation, enableIeeeBranding), bootstrapVersion), id="main-stylesheet")
+		link(
+			rel='stylesheet'
+			href=buildCssPath(getCssThemeModifier(userSettings, brandVariation, enableIeeeBranding), bootstrapVersion)
+			id='main-stylesheet'
+		)
 		block css
 		block css
 			each file in entrypointStyles(entrypoint)
 			each file in entrypointStyles(entrypoint)
-				link(rel='stylesheet', href=file)
+				link(rel='stylesheet' href=file)
 
 
 		block _headLinks
 		block _headLinks
 
 
-		if (typeof suppressRelAlternateLinks == "undefined")
+		if typeof suppressRelAlternateLinks == 'undefined'
 			if settings.i18n.subdomainLang
 			if settings.i18n.subdomainLang
 				each subdomainDetails in settings.i18n.subdomainLang
 				each subdomainDetails in settings.i18n.subdomainLang
 					if !subdomainDetails.hide
 					if !subdomainDetails.hide
-						link(rel="alternate", href=subdomainDetails.url + currentUrl, hreflang=subdomainDetails.lngCode)
-
-		if (entrypoint !== 'marketing')
-			link(rel="preload", href=buildJsPath(currentLngCode + "-json.js"), as="script", nonce=scriptNonce)
+						link(
+							rel='alternate'
+							href=subdomainDetails.url + currentUrl
+							hreflang=subdomainDetails.lngCode
+						)
+
+		if entrypoint !== 'marketing'
+			link(
+				rel='preload'
+				href=buildJsPath(currentLngCode + '-json.js')
+				as='script'
+				nonce=scriptNonce
+			)
 
 
 		//- Scripts
 		//- Scripts
-		if (typeof suppressGoogleAnalytics == "undefined")
+		if typeof suppressGoogleAnalytics == 'undefined'
 			include _google_analytics
 			include _google_analytics
 
 
 		block meta
 		block meta
-			meta(name="ol-csrfToken" content=csrfToken)
+			meta(name='ol-csrfToken' content=csrfToken)
 			//- Configure dynamically loaded assets (via webpack) to be downloaded from CDN
 			//- Configure dynamically loaded assets (via webpack) to be downloaded from CDN
 			//- See: https://webpack.js.org/guides/public-path/#on-the-fly
 			//- See: https://webpack.js.org/guides/public-path/#on-the-fly
-			meta(name="ol-baseAssetPath" content=buildBaseAssetPath())
-			meta(name="ol-mathJaxPath" content=mathJaxPath)
-			meta(name="ol-dictionariesRoot" content=dictionariesRoot)
+			meta(name='ol-baseAssetPath' content=buildBaseAssetPath())
+			meta(name='ol-mathJaxPath' content=mathJaxPath)
+			meta(name='ol-dictionariesRoot' content=dictionariesRoot)
 
 
-			meta(name="ol-usersEmail" content=getUserEmail())
-			meta(name="ol-ab" data-type="json" content={})
-			meta(name="ol-user_id" content=getLoggedInUserId())
+			meta(name='ol-usersEmail' content=getUserEmail())
+			meta(name='ol-ab' data-type='json' content={})
+			meta(name='ol-user_id' content=getLoggedInUserId())
 			//- Internationalisation settings
 			//- Internationalisation settings
-			meta(name="ol-i18n" data-type="json" content={
-				currentLangCode: currentLngCode
-			})
+			meta(
+				name='ol-i18n'
+				data-type='json'
+				content={
+					currentLangCode: currentLngCode,
+				}
+			)
 			//- Expose some settings globally to the frontend
 			//- Expose some settings globally to the frontend
-			meta(name="ol-ExposedSettings" data-type="json" content=ExposedSettings)
-			meta(name="ol-splitTestVariants" data-type="json" content=splitTestVariants || {})
-			meta(name="ol-splitTestInfo" data-type="json" content=splitTestInfo || {})
-
-			if (typeof settings.algolia != "undefined")
-				meta(name="ol-algolia" data-type="json" content={
-					appId: settings.algolia.app_id,
-					apiKey: settings.algolia.read_only_api_key,
-					indexes: settings.algolia.indexes
-				})
-
-			meta(name="ol-isManagedAccount" data-type="boolean" content=isManagedAccount)
+			meta(name='ol-ExposedSettings' data-type='json' content=ExposedSettings)
+			meta(
+				name='ol-splitTestVariants'
+				data-type='json'
+				content=splitTestVariants || {}
+			)
+			meta(name='ol-splitTestInfo' data-type='json' content=splitTestInfo || {})
+
+			if typeof settings.algolia != 'undefined'
+				meta(
+					name='ol-algolia'
+					data-type='json'
+					content={
+						appId: settings.algolia.app_id,
+						apiKey: settings.algolia.read_only_api_key,
+						indexes: settings.algolia.indexes,
+					}
+				)
+
+			meta(
+				name='ol-isManagedAccount'
+				data-type='boolean'
+				content=isManagedAccount
+			)
 			each restriction in userRestrictions || []
 			each restriction in userRestrictions || []
-				meta(name='ol-cannot-' + restriction data-type="boolean" content=true)
-			meta(name="ol-bootstrapVersion" data-type="json" content=bootstrapVersion)
+				meta(name='ol-cannot-' + restriction data-type='boolean' content)
+			meta(name='ol-bootstrapVersion' data-type='json' content=bootstrapVersion)
 
 
 		block head-scripts
 		block head-scripts
 
 
-	body(class={
-		'thin-footer': showThinFooter,
-		'website-redesign': isWebsiteRedesign === true || websiteRedesignOverride,
-		'application-page': isApplicationPage
-	}, data-theme="default")
-		if(settings.recaptcha && settings.recaptcha.siteKeyV3)
-			script(type="text/javascript", nonce=scriptNonce, src="https://www.recaptcha.net/recaptcha/api.js?render=" + settings.recaptcha.siteKeyV3, defer=deferScripts)
-
-		if (typeof suppressSkipToContent == "undefined")
-			a(class="skip-to-content" href="#main-content") #{translate('skip_to_content')}
+	body(
+		class={
+			'thin-footer': showThinFooter,
+			'website-redesign': isWebsiteRedesign === true || websiteRedesignOverride,
+			'application-page': isApplicationPage,
+		}
+		data-theme='default'
+	)
+		if settings.recaptcha && settings.recaptcha.siteKeyV3
+			script(
+				type='text/javascript'
+				nonce=scriptNonce
+				src='https://www.recaptcha.net/recaptcha/api.js?render=' + settings.recaptcha.siteKeyV3
+				defer=deferScripts
+			)
+
+		if typeof suppressSkipToContent == 'undefined'
+			a(class='skip-to-content' href='#main-content') #{translate('skip_to_content')}
 
 
 		block body
 		block body
 
 
-		if (settings.devToolbar.enabled)
-			div#dev-toolbar
+		if settings.devToolbar.enabled
+			#dev-toolbar
 
 
 	block foot-scripts
 	block foot-scripts
 		+foot-scripts
 		+foot-scripts
 
 
 	include _customer_io
 	include _customer_io
 
 
-	script(type="text/javascript", nonce=scriptNonce).
-		window.addEventListener('DOMContentLoaded', function() {
+	script(type='text/javascript' nonce=scriptNonce).
+		window.addEventListener('DOMContentLoaded', function () {
 			//- Look for bundle
 			//- Look for bundle
 			var cdnBlocked = typeof Frontend === 'undefined'
 			var cdnBlocked = typeof Frontend === 'undefined'
 			//- Prevent loops
 			//- Prevent loops
-			var noCdnAlreadyInUrl = window.location.href.indexOf("nocdn=true") != -1
-			if (cdnBlocked && !noCdnAlreadyInUrl && navigator.userAgent.indexOf("Googlebot") == -1) {
+			var noCdnAlreadyInUrl = window.location.href.indexOf('nocdn=true') != -1
+			if (cdnBlocked && !noCdnAlreadyInUrl && navigator.userAgent.indexOf('Googlebot') == -1) {
 				//- Set query param, server will not set CDN url
 				//- Set query param, server will not set CDN url
-				window.location.search += "&nocdn=true";
+				window.location.search += '&nocdn=true'
 			}
 			}
 		})
 		})

+ 5 - 5
services/web/app/views/layout-marketing.pug

@@ -7,7 +7,7 @@ block entrypointVar
 	- entrypoint = 'marketing'
 	- entrypoint = 'marketing'
 
 
 block body
 block body
-	if (typeof suppressNavbar === "undefined")
+	if typeof suppressNavbar === 'undefined'
 		if bootstrapVersion === 5
 		if bootstrapVersion === 5
 			include layout/navbar-marketing-bootstrap-5
 			include layout/navbar-marketing-bootstrap-5
 		else
 		else
@@ -15,7 +15,7 @@ block body
 
 
 	block content
 	block content
 
 
-	if (typeof suppressFooter === "undefined")
+	if typeof suppressFooter === 'undefined'
 		if showThinFooter
 		if showThinFooter
 			if bootstrapVersion === 5
 			if bootstrapVersion === 5
 				include layout/thin-footer-bootstrap-5
 				include layout/thin-footer-bootstrap-5
@@ -24,13 +24,13 @@ block body
 		else
 		else
 			include layout/fat-footer
 			include layout/fat-footer
 
 
-	if (typeof(suppressCookieBanner) == 'undefined')
+	if typeof suppressCookieBanner == 'undefined'
 		include _cookie_banner
 		include _cookie_banner
 
 
 	if bootstrapVersion === 5
 	if bootstrapVersion === 5
-		!= moduleIncludes("contactModal-marketing-bootstrap-5", locals)
+		!= moduleIncludes('contactModal-marketing-bootstrap-5', locals)
 	else
 	else
-		!= moduleIncludes("contactModal-marketing", locals)
+		!= moduleIncludes('contactModal-marketing', locals)
 
 
 block prepend foot-scripts
 block prepend foot-scripts
 	+bootstrap-js(bootstrapVersion)
 	+bootstrap-js(bootstrapVersion)

+ 39 - 31
services/web/app/views/layout-react.pug

@@ -7,10 +7,10 @@ include ./_mixins/bootstrap_js
 
 
 block entrypointVar
 block entrypointVar
 	- entrypoint = 'marketing'
 	- entrypoint = 'marketing'
-		
+
 block isApplicationPageVar
 block isApplicationPageVar
 	- isApplicationPage = true
 	- isApplicationPage = true
-		
+
 block append meta
 block append meta
 	- const canDisplayAdminMenu = hasAdminAccess()
 	- const canDisplayAdminMenu = hasAdminAccess()
 	- const canDisplayAdminRedirect = canRedirectToAdminDomain()
 	- const canDisplayAdminRedirect = canRedirectToAdminDomain()
@@ -22,44 +22,52 @@ block append meta
 	- const enableUpgradeButton = projectDashboardReact && usersBestSubscription && (usersBestSubscription.type === 'free' || usersBestSubscription.type === 'standalone-ai-add-on')
 	- const enableUpgradeButton = projectDashboardReact && usersBestSubscription && (usersBestSubscription.type === 'free' || usersBestSubscription.type === 'standalone-ai-add-on')
 	- const showSignUpLink = hasFeature('registration-page')
 	- const showSignUpLink = hasFeature('registration-page')
 
 
-	meta(name="ol-navbar" data-type="json" content={
-		customLogo: settings.nav.custom_logo,
-		title: nav.title,
-		canDisplayAdminMenu,
-		canDisplayAdminRedirect,
-		canDisplaySplitTestMenu,
-		canDisplaySurveyMenu,
-		canDisplayScriptLogMenu,
-		enableUpgradeButton,
-		suppressNavbarRight: !!suppressNavbarRight,
-		suppressNavContentLinks: !!suppressNavContentLinks,
-		showSubscriptionLink: nav.showSubscriptionLink,
-		showSignUpLink: showSignUpLink,
-		currentUrl: currentUrl,
-		sessionUser: sessionUser ? { email: sessionUser.email} : undefined,
-		adminUrl: settings.adminUrl,
-		items: cloneAndTranslateText(nav.header_extras)
-	})
-	meta(name="ol-footer" data-type="json" content={
-		showThinFooter: showThinFooter,
-		showPoweredBy: !hasFeature('saas') && !settings.nav.hide_powered_by,
-		subdomainLang: settings.i18n.subdomainLang,
-		translatedLanguages: settings.translatedLanguages,
-		leftItems: cloneAndTranslateText(settings.nav.left_footer),
-		rightItems: settings.nav.right_footer
-	})
+	meta(
+		name='ol-navbar'
+		data-type='json'
+		content={
+			customLogo: settings.nav.custom_logo,
+			title: nav.title,
+			canDisplayAdminMenu,
+			canDisplayAdminRedirect,
+			canDisplaySplitTestMenu,
+			canDisplaySurveyMenu,
+			canDisplayScriptLogMenu,
+			enableUpgradeButton,
+			suppressNavbarRight: !!suppressNavbarRight,
+			suppressNavContentLinks: !!suppressNavContentLinks,
+			showSubscriptionLink: nav.showSubscriptionLink,
+			showSignUpLink: showSignUpLink,
+			currentUrl: currentUrl,
+			sessionUser: sessionUser ? {email: sessionUser.email} : undefined,
+			adminUrl: settings.adminUrl,
+			items: cloneAndTranslateText(nav.header_extras),
+		}
+	)
+	meta(
+		name='ol-footer'
+		data-type='json'
+		content={
+			showThinFooter: showThinFooter,
+			showPoweredBy: !hasFeature('saas') && !settings.nav.hide_powered_by,
+			subdomainLang: settings.i18n.subdomainLang,
+			translatedLanguages: settings.translatedLanguages,
+			leftItems: cloneAndTranslateText(settings.nav.left_footer),
+			rightItems: settings.nav.right_footer,
+		}
+	)
 
 
 block body
 block body
-	if (typeof suppressNavbar === "undefined")
+	if typeof suppressNavbar === 'undefined'
 		include layout/navbar-marketing-react-bootstrap-5
 		include layout/navbar-marketing-react-bootstrap-5
 
 
 	block content
 	block content
 
 
-	if (typeof suppressFooter === "undefined")
+	if typeof suppressFooter === 'undefined'
 		if showThinFooter
 		if showThinFooter
 			include layout/thin-footer-bootstrap-5
 			include layout/thin-footer-bootstrap-5
 		else
 		else
 			include layout/fat-footer-react-bootstrap-5
 			include layout/fat-footer-react-bootstrap-5
 
 
-	if (typeof suppressCookieBanner === "undefined")
+	if typeof suppressCookieBanner === 'undefined'
 		include _cookie_banner
 		include _cookie_banner

+ 6 - 6
services/web/app/views/layout-website-redesign.pug

@@ -7,7 +7,7 @@ block entrypointVar
 	- entrypoint = 'marketing'
 	- entrypoint = 'marketing'
 
 
 block body
 block body
-	if (typeof(suppressNavbar) == "undefined")
+	if typeof suppressNavbar == 'undefined'
 		if bootstrapVersion === 5
 		if bootstrapVersion === 5
 			include layout/navbar-marketing-bootstrap-5
 			include layout/navbar-marketing-bootstrap-5
 		else
 		else
@@ -17,23 +17,23 @@ block body
 
 
 	//- bootstrapVersion needed here, because plans.pug uses both BS version
 	//- bootstrapVersion needed here, because plans.pug uses both BS version
 	//- If the `plans-page-bs5` split test has been completed, remove bootstrapVersion logic
 	//- If the `plans-page-bs5` split test has been completed, remove bootstrapVersion logic
-	if (typeof(suppressFooter) == "undefined")
+	if typeof suppressFooter == 'undefined'
 		if showThinFooter
 		if showThinFooter
 			if bootstrapVersion === 5
 			if bootstrapVersion === 5
 				include layout/thin-footer-bootstrap-5
 				include layout/thin-footer-bootstrap-5
-			else 
+			else
 				include layout/thin-footer
 				include layout/thin-footer
 		else
 		else
 			include layout/fat-footer-website-redesign
 			include layout/fat-footer-website-redesign
 
 
-	if (typeof(suppressCookieBanner) == 'undefined')
+	if typeof suppressCookieBanner == 'undefined'
 		include _cookie_banner
 		include _cookie_banner
 
 
 	block contactModal
 	block contactModal
 		if bootstrapVersion === 5
 		if bootstrapVersion === 5
-			!= moduleIncludes("contactModal-marketing-bootstrap-5", locals)
+			!= moduleIncludes('contactModal-marketing-bootstrap-5', locals)
 		else
 		else
-			!= moduleIncludes("contactModal-marketing", locals)
+			!= moduleIncludes('contactModal-marketing', locals)
 
 
 block prepend foot-scripts
 block prepend foot-scripts
 	+bootstrap-js(bootstrapVersion)
 	+bootstrap-js(bootstrapVersion)

+ 49 - 18
services/web/app/views/layout/fat-footer-base.pug

@@ -1,9 +1,9 @@
 .fat-footer-base
 .fat-footer-base
 	.fat-footer-base-section.fat-footer-base-meta
 	.fat-footer-base-section.fat-footer-base-meta
-		.fat-footer-base-item 
+		.fat-footer-base-item
 			.fat-footer-base-copyright © #{new Date().getFullYear()} Overleaf
 			.fat-footer-base-copyright © #{new Date().getFullYear()} Overleaf
-			a(href="/legal") #{translate('privacy_and_terms')}
-			a(href="https://www.digital-science.com/security-certifications/") #{translate('compliance')}
+			a(href='/legal') #{translate('privacy_and_terms')}
+			a(href='https://www.digital-science.com/security-certifications/') #{translate('compliance')}
 		ul.fat-footer-base-item.list-unstyled.fat-footer-base-language
 		ul.fat-footer-base-item.list-unstyled.fat-footer-base-language
 			if bootstrapVersion === 5
 			if bootstrapVersion === 5
 				include language-picker-bootstrap-5
 				include language-picker-bootstrap-5
@@ -11,22 +11,53 @@
 				include language-picker
 				include language-picker
 	.fat-footer-base-section.fat-footer-base-social
 	.fat-footer-base-section.fat-footer-base-social
 		.fat-footer-base-item
 		.fat-footer-base-item
-			a.fat-footer-social.x-logo(href="https://x.com/overleaf")
-				svg(xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1227" height="25")
-					path(d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z")
+			a.fat-footer-social.x-logo(href='https://x.com/overleaf')
+				svg(
+					xmlns='http://www.w3.org/2000/svg'
+					viewBox='0 0 1200 1227'
+					height='25'
+				)
+					path(
+						d='M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z'
+					)
 				span.visually-hidden #{translate("app_on_x", {social: "X"})}
 				span.visually-hidden #{translate("app_on_x", {social: "X"})}
-			a.fat-footer-social.facebook-logo(href="https://www.facebook.com/overleaf.editor")
-				svg(xmlns="http://www.w3.org/2000/svg" viewBox="0 0 666.66668 666.66717" height="25")
+			a.fat-footer-social.facebook-logo(
+				href='https://www.facebook.com/overleaf.editor'
+			)
+				svg(
+					xmlns='http://www.w3.org/2000/svg'
+					viewBox='0 0 666.66668 666.66717'
+					height='25'
+				)
 					defs
 					defs
-						clipPath(id="a" clipPathUnits="userSpaceOnUse")
-							path(d="M0 700h700V0H0Z")
-					g(clip-path="url(#a)" transform="matrix(1.33333 0 0 -1.33333 -133.333 800)")
-						path.background(d="M0 0c0 138.071-111.929 250-250 250S-500 138.071-500 0c0-117.245 80.715-215.622 189.606-242.638v166.242h-51.552V0h51.552v32.919c0 85.092 38.508 124.532 122.048 124.532 15.838 0 43.167-3.105 54.347-6.211V81.986c-5.901.621-16.149.932-28.882.932-40.993 0-56.832-15.528-56.832-55.9V0h81.659l-14.028-76.396h-67.631v-171.773C-95.927-233.218 0-127.818 0 0" fill="#0866ff" transform="translate(600 350)")
-						path.text(d="m0 0 14.029 76.396H-67.63v27.019c0 40.372 15.838 55.899 56.831 55.899 12.733 0 22.981-.31 28.882-.931v69.253c-11.18 3.106-38.509 6.212-54.347 6.212-83.539 0-122.048-39.441-122.048-124.533V76.396h-51.552V0h51.552v-166.242a250.559 250.559 0 0 1 60.394-7.362c10.254 0 20.358.632 30.288 1.831V0Z" fill="#fff" transform="translate(447.918 273.604)")
+						clipPath(id='a' clipPathUnits='userSpaceOnUse')
+							path(d='M0 700h700V0H0Z')
+					g(
+						clip-path='url(#a)'
+						transform='matrix(1.33333 0 0 -1.33333 -133.333 800)'
+					)
+						path.background(
+							d='M0 0c0 138.071-111.929 250-250 250S-500 138.071-500 0c0-117.245 80.715-215.622 189.606-242.638v166.242h-51.552V0h51.552v32.919c0 85.092 38.508 124.532 122.048 124.532 15.838 0 43.167-3.105 54.347-6.211V81.986c-5.901.621-16.149.932-28.882.932-40.993 0-56.832-15.528-56.832-55.9V0h81.659l-14.028-76.396h-67.631v-171.773C-95.927-233.218 0-127.818 0 0'
+							fill='#0866ff'
+							transform='translate(600 350)'
+						)
+						path.text(
+							d='m0 0 14.029 76.396H-67.63v27.019c0 40.372 15.838 55.899 56.831 55.899 12.733 0 22.981-.31 28.882-.931v69.253c-11.18 3.106-38.509 6.212-54.347 6.212-83.539 0-122.048-39.441-122.048-124.533V76.396h-51.552V0h51.552v-166.242a250.559 250.559 0 0 1 60.394-7.362c10.254 0 20.358.632 30.288 1.831V0Z'
+							fill='#fff'
+							transform='translate(447.918 273.604)'
+						)
 				span.visually-hidden #{translate("app_on_x", {social: "Facebook"})}
 				span.visually-hidden #{translate("app_on_x", {social: "Facebook"})}
-			a.fat-footer-social.linkedin-logo(href="https://www.linkedin.com/company/writelatex-limited")
-				svg(xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72" height="25")
-					g(fill="none" fill-rule="evenodd")
-						path.background(fill="#0B66C3" d="M8 72h56a8 8 0 0 0 8-8V8a8 8 0 0 0-8-8H8a8 8 0 0 0-8 8v56a8 8 0 0 0 8 8")
-						path.text(fill="#FFF" d="M62 62H51.316V43.802c0-4.99-1.896-7.777-5.845-7.777-4.296 0-6.54 2.901-6.54 7.777V62H28.632V27.333H38.93v4.67s3.096-5.729 10.453-5.729c7.353 0 12.617 4.49 12.617 13.777zM16.35 22.794c-3.508 0-6.35-2.864-6.35-6.397C10 12.864 12.842 10 16.35 10c3.507 0 6.347 2.864 6.347 6.397 0 3.533-2.84 6.397-6.348 6.397ZM11.032 62h10.736V27.333H11.033V62")
+			a.fat-footer-social.linkedin-logo(
+				href='https://www.linkedin.com/company/writelatex-limited'
+			)
+				svg(xmlns='http://www.w3.org/2000/svg' viewBox='0 0 72 72' height='25')
+					g(fill='none' fill-rule='evenodd')
+						path.background(
+							fill='#0B66C3'
+							d='M8 72h56a8 8 0 0 0 8-8V8a8 8 0 0 0-8-8H8a8 8 0 0 0-8 8v56a8 8 0 0 0 8 8'
+						)
+						path.text(
+							fill='#FFF'
+							d='M62 62H51.316V43.802c0-4.99-1.896-7.777-5.845-7.777-4.296 0-6.54 2.901-6.54 7.777V62H28.632V27.333H38.93v4.67s3.096-5.729 10.453-5.729c7.353 0 12.617 4.49 12.617 13.777zM16.35 22.794c-3.508 0-6.35-2.864-6.35-6.397C10 12.864 12.842 10 16.35 10c3.507 0 6.347 2.864 6.347 6.397 0 3.533-2.84 6.397-6.348 6.397ZM11.032 62h10.736V27.333H11.033V62'
+						)
 				span.visually-hidden #{translate("app_on_x", {social: "LinkedIn"})}
 				span.visually-hidden #{translate("app_on_x", {social: "LinkedIn"})}

+ 41 - 38
services/web/app/views/layout/fat-footer-website-redesign.pug

@@ -1,84 +1,87 @@
 footer.fat-footer.hidden-print.website-redesign-fat-footer
 footer.fat-footer.hidden-print.website-redesign-fat-footer
-	.fat-footer-container(role="navigation" aria-label=translate('footer_navigation'))
+	.fat-footer-container(
+		role='navigation'
+		aria-label=translate('footer_navigation')
+	)
 		.fat-footer-sections(class=hideFatFooter ? 'hidden' : undefined)
 		.fat-footer-sections(class=hideFatFooter ? 'hidden' : undefined)
-			.footer-section#footer-brand
-				a(href='/', aria-label=settings.appName).footer-brand
-					
+			#footer-brand.footer-section
+				a.footer-brand(href='/' aria-label=settings.appName)
+
 			.footer-section
 			.footer-section
 				h2.footer-section-heading #{translate('About')}
 				h2.footer-section-heading #{translate('About')}
-					
+
 				ul.list-unstyled
 				ul.list-unstyled
 					li
 					li
-						a(href="/about") #{translate('footer_about_us')}
+						a(href='/about') #{translate('footer_about_us')}
 					li
 					li
-						a(href="/about/values") #{translate('our_values')}
+						a(href='/about/values') #{translate('our_values')}
 					li
 					li
-						a(href="https://digitalscience.pinpointhq.com/") #{translate('careers')}
+						a(href='https://digitalscience.pinpointhq.com/') #{translate('careers')}
 					li
 					li
-						a(href="/for/press") !{translate('press_and_awards')}
+						a(href='/for/press') !{translate('press_and_awards')}
 					li
 					li
-						a(href="/blog") #{translate('blog')}
-						
+						a(href='/blog') #{translate('blog')}
+
 			.footer-section
 			.footer-section
 				h2.footer-section-heading #{translate('learn')}
 				h2.footer-section-heading #{translate('learn')}
-		
+
 				ul.list-unstyled
 				ul.list-unstyled
 					li
 					li
-						a(href="/learn/latex/Learn_LaTeX_in_30_minutes") #{translate('latex_in_thirty_minutes')}
+						a(href='/learn/latex/Learn_LaTeX_in_30_minutes') #{translate('latex_in_thirty_minutes')}
 					li
 					li
-						a(href="/latex/templates") #{translate('templates')}
+						a(href='/latex/templates') #{translate('templates')}
 					li
 					li
-						a(href="/events/webinars") #{translate('webinars')}
+						a(href='/events/webinars') #{translate('webinars')}
 					li
 					li
-						a(href="/learn/latex/Tutorials") #{translate('tutorials')}
+						a(href='/learn/latex/Tutorials') #{translate('tutorials')}
 					li
 					li
-						a(href="/learn/latex/Inserting_Images") #{translate('how_to_insert_images')}
+						a(href='/learn/latex/Inserting_Images') #{translate('how_to_insert_images')}
 					li
 					li
-						a(href="/learn/latex/Tables") #{translate('how_to_create_tables')}
-						
+						a(href='/learn/latex/Tables') #{translate('how_to_create_tables')}
+
 			.footer-section
 			.footer-section
 				h2.footer-section-heading !{translate('footer_plans_and_pricing')}
 				h2.footer-section-heading !{translate('footer_plans_and_pricing')}
-		
+
 				ul.list-unstyled
 				ul.list-unstyled
 					li
 					li
-						a(href="/learn/how-to/Overleaf_premium_features") #{translate('premium_features')}
+						a(href='/learn/how-to/Overleaf_premium_features') #{translate('premium_features')}
 					li
 					li
-						a(href="/user/subscription/plans?itm_referrer=footer-for-indv-groups") !{translate('for_individuals_and_groups')}
+						a(href='/user/subscription/plans?itm_referrer=footer-for-indv-groups') !{translate('for_individuals_and_groups')}
 					li
 					li
-						a(href="/for/enterprises") #{translate('for_business')}
+						a(href='/for/enterprises') #{translate('for_business')}
 					li
 					li
-						a(href="/for/universities") #{translate('for_universities')}
+						a(href='/for/universities') #{translate('for_universities')}
 					li
 					li
 						a(
 						a(
 							data-ol-for-students-link
 							data-ol-for-students-link
-							href="/user/subscription/plans?itm_referrer=footer-for-students#student-annual"
+							href='/user/subscription/plans?itm_referrer=footer-for-students#student-annual'
 						) #{translate('for_students')}
 						) #{translate('for_students')}
 					li
 					li
-						a(href="/for/government") #{translate('for_government')}
-						
+						a(href='/for/government') #{translate('for_government')}
+
 			.footer-section
 			.footer-section
 				h2.footer-section-heading #{translate('get_involved')}
 				h2.footer-section-heading #{translate('get_involved')}
-		
+
 				ul.list-unstyled
 				ul.list-unstyled
 					li
 					li
-						a(href="/for/community/advisors") #{translate('become_an_advisor')}
+						a(href='/for/community/advisors') #{translate('become_an_advisor')}
 					li
 					li
-						a(href="https://forms.gle/67PSpN1bLnjGCmPQ9") #{translate('let_us_know_what_you_think')}
+						a(href='https://forms.gle/67PSpN1bLnjGCmPQ9') #{translate('let_us_know_what_you_think')}
 					if user
 					if user
 						li
 						li
-							a(href="/beta/participate") #{translate('join_beta_program')}
-		
+							a(href='/beta/participate') #{translate('join_beta_program')}
+
 			.footer-section
 			.footer-section
 				h2.footer-section-heading #{translate('help')}
 				h2.footer-section-heading #{translate('help')}
-		
+
 				ul.list-unstyled
 				ul.list-unstyled
 					li
 					li
-						a(href="/about/why-latex") #{translate('why_latex')}
+						a(href='/about/why-latex') #{translate('why_latex')}
 					li
 					li
-						a(href="/learn") #{translate('Documentation')} 
+						a(href='/learn') #{translate('Documentation')} 
 					li
 					li
-						a(href="/contact") #{translate('footer_contact_us')}  
+						a(href='/contact') #{translate('footer_contact_us')} 
 					li
 					li
-						a(href="https://status.overleaf.com/") #{translate('website_status')}
-					
+						a(href='https://status.overleaf.com/') #{translate('website_status')}
+
 		include fat-footer-base
 		include fat-footer-base

+ 41 - 38
services/web/app/views/layout/fat-footer.pug

@@ -1,84 +1,87 @@
 footer.fat-footer.hidden-print
 footer.fat-footer.hidden-print
-	.fat-footer-container(role="navigation" aria-label=translate('footer_navigation'))
+	.fat-footer-container(
+		role='navigation'
+		aria-label=translate('footer_navigation')
+	)
 		.fat-footer-sections(class=hideFatFooter ? 'hidden' : undefined)
 		.fat-footer-sections(class=hideFatFooter ? 'hidden' : undefined)
-			.footer-section#footer-brand
-				a(href='/', aria-label=settings.appName).footer-brand
-					
+			#footer-brand.footer-section
+				a.footer-brand(href='/' aria-label=settings.appName)
+
 			.footer-section
 			.footer-section
 				h2.footer-section-heading #{translate('About')}
 				h2.footer-section-heading #{translate('About')}
-					
+
 				ul.list-unstyled
 				ul.list-unstyled
 					li
 					li
-						a(href="/about") #{translate('footer_about_us')}
+						a(href='/about') #{translate('footer_about_us')}
 					li
 					li
-						a(href="/about/values") #{translate('our_values')}
+						a(href='/about/values') #{translate('our_values')}
 					li
 					li
-						a(href="https://digitalscience.pinpointhq.com/") #{translate('careers')}
+						a(href='https://digitalscience.pinpointhq.com/') #{translate('careers')}
 					li
 					li
-						a(href="/for/press") !{translate('press_and_awards')}
+						a(href='/for/press') !{translate('press_and_awards')}
 					li
 					li
-						a(href="/blog") #{translate('blog')}
-						
+						a(href='/blog') #{translate('blog')}
+
 			.footer-section
 			.footer-section
 				h2.footer-section-heading #{translate('learn')}
 				h2.footer-section-heading #{translate('learn')}
-		
+
 				ul.list-unstyled
 				ul.list-unstyled
 					li
 					li
-						a(href="/learn/latex/Learn_LaTeX_in_30_minutes") #{translate('latex_in_thirty_minutes')}
+						a(href='/learn/latex/Learn_LaTeX_in_30_minutes') #{translate('latex_in_thirty_minutes')}
 					li
 					li
-						a(href="/latex/templates") #{translate('templates')}
+						a(href='/latex/templates') #{translate('templates')}
 					li
 					li
-						a(href="/events/webinars") #{translate('webinars')}
+						a(href='/events/webinars') #{translate('webinars')}
 					li
 					li
-						a(href="/learn/latex/Tutorials") #{translate('tutorials')}
+						a(href='/learn/latex/Tutorials') #{translate('tutorials')}
 					li
 					li
-						a(href="/learn/latex/Inserting_Images") #{translate('how_to_insert_images')}
+						a(href='/learn/latex/Inserting_Images') #{translate('how_to_insert_images')}
 					li
 					li
-						a(href="/learn/latex/Tables") #{translate('how_to_create_tables')}
-						
+						a(href='/learn/latex/Tables') #{translate('how_to_create_tables')}
+
 			.footer-section
 			.footer-section
 				h2.footer-section-heading !{translate('footer_plans_and_pricing')}
 				h2.footer-section-heading !{translate('footer_plans_and_pricing')}
-		
+
 				ul.list-unstyled
 				ul.list-unstyled
 					li
 					li
-						a(href="/learn/how-to/Overleaf_premium_features") #{translate('premium_features')}
+						a(href='/learn/how-to/Overleaf_premium_features') #{translate('premium_features')}
 					li
 					li
-						a(href="/user/subscription/plans?itm_referrer=footer-for-indv-groups") !{translate('for_individuals_and_groups')}
+						a(href='/user/subscription/plans?itm_referrer=footer-for-indv-groups') !{translate('for_individuals_and_groups')}
 					li
 					li
-						a(href="/for/enterprises") #{translate('for_enterprise')}
+						a(href='/for/enterprises') #{translate('for_enterprise')}
 					li
 					li
-						a(href="/for/universities") #{translate('for_universities')}
+						a(href='/for/universities') #{translate('for_universities')}
 					li
 					li
 						a(
 						a(
 							data-ol-for-students-link
 							data-ol-for-students-link
-							href="/user/subscription/plans?itm_referrer=footer-for-students#student-annual"
+							href='/user/subscription/plans?itm_referrer=footer-for-students#student-annual'
 						) #{translate('for_students')}
 						) #{translate('for_students')}
 					li
 					li
-						a(href="/for/government") #{translate('for_government')}
-						
+						a(href='/for/government') #{translate('for_government')}
+
 			.footer-section
 			.footer-section
 				h2.footer-section-heading #{translate('get_involved')}
 				h2.footer-section-heading #{translate('get_involved')}
-		
+
 				ul.list-unstyled
 				ul.list-unstyled
 					li
 					li
-						a(href="/for/community/advisors") #{translate('become_an_advisor')}
+						a(href='/for/community/advisors') #{translate('become_an_advisor')}
 					li
 					li
-						a(href="https://forms.gle/67PSpN1bLnjGCmPQ9") #{translate('let_us_know_what_you_think')}
+						a(href='https://forms.gle/67PSpN1bLnjGCmPQ9') #{translate('let_us_know_what_you_think')}
 					if user
 					if user
 						li
 						li
-							a(href="/beta/participate") #{translate('join_beta_program')}
-		
+							a(href='/beta/participate') #{translate('join_beta_program')}
+
 			.footer-section
 			.footer-section
 				h2.footer-section-heading #{translate('help')}
 				h2.footer-section-heading #{translate('help')}
-		
+
 				ul.list-unstyled
 				ul.list-unstyled
 					li
 					li
-						a(href="/about/why-latex") #{translate('why_latex')}
+						a(href='/about/why-latex') #{translate('why_latex')}
 					li
 					li
-						a(href="/learn") #{translate('Documentation')} 
+						a(href='/learn') #{translate('Documentation')} 
 					li
 					li
-						a(href="/contact") #{translate('footer_contact_us')}  
+						a(href='/contact') #{translate('footer_contact_us')} 
 					li
 					li
-						a(href="https://status.overleaf.com/") #{translate('website_status')}
-					
+						a(href='https://status.overleaf.com/') #{translate('website_status')}
+
 		include fat-footer-base
 		include fat-footer-base

+ 19 - 11
services/web/app/views/layout/language-picker-bootstrap-5.pug

@@ -1,27 +1,35 @@
 include ../_mixins/material_symbol
 include ../_mixins/material_symbol
 
 
-li.dropdown.dropup.subdued(dropdown).language-picker
+li.dropdown.dropup.subdued.language-picker(dropdown)
 	button#language-picker-toggle.btn.btn-link.btn-inline-link(
 	button#language-picker-toggle.btn.btn-link.btn-inline-link(
-		dropdown-toggle,
-		data-ol-lang-selector-tooltip,
-		data-bs-toggle="dropdown",
-		aria-haspopup="true",
-		aria-expanded="false",
-		aria-label="Select " + translate('language'),
+		dropdown-toggle
+		data-ol-lang-selector-tooltip
+		data-bs-toggle='dropdown'
+		aria-haspopup='true'
+		aria-expanded='false'
+		aria-label='Select ' + translate('language')
 		tooltip=translate('language')
 		tooltip=translate('language')
 		title=translate('language')
 		title=translate('language')
 	)
 	)
-		+material-symbol("translate")
+		+material-symbol('translate')
 		| &nbsp;
 		| &nbsp;
 		span.language-picker-text #{settings.translatedLanguages[currentLngCode]}
 		span.language-picker-text #{settings.translatedLanguages[currentLngCode]}
 
 
-	ul.dropdown-menu.dropdown-menu-sm-width(role="menu" aria-labelledby="language-picker-toggle")
+	ul.dropdown-menu.dropdown-menu-sm-width(
+		role='menu'
+		aria-labelledby='language-picker-toggle'
+	)
 		li.dropdown-header #{translate("language")}
 		li.dropdown-header #{translate("language")}
 		each subdomainDetails, subdomain in settings.i18n.subdomainLang
 		each subdomainDetails, subdomain in settings.i18n.subdomainLang
 			if !subdomainDetails.hide
 			if !subdomainDetails.hide
 				- let isActive = subdomainDetails.lngCode === currentLngCode
 				- let isActive = subdomainDetails.lngCode === currentLngCode
 				li.lng-option
 				li.lng-option
-					a.menu-indent(href=subdomainDetails.url+currentUrlWithQueryParams, role="menuitem", class=isActive ? 'dropdown-item active' : 'dropdown-item', aria-selected=isActive ? 'true' : 'false')
+					a.menu-indent(
+						href=subdomainDetails.url + currentUrlWithQueryParams
+						role='menuitem'
+						class=isActive ? 'dropdown-item active' : 'dropdown-item'
+						aria-selected=isActive ? 'true' : 'false'
+					)
 						| #{settings.translatedLanguages[subdomainDetails.lngCode]}
 						| #{settings.translatedLanguages[subdomainDetails.lngCode]}
 						if subdomainDetails.lngCode === currentLngCode
 						if subdomainDetails.lngCode === currentLngCode
-							+material-symbol("check", "dropdown-item-trailing-icon")
+							+material-symbol('check', 'dropdown-item-trailing-icon')

+ 15 - 12
services/web/app/views/layout/language-picker.pug

@@ -1,13 +1,13 @@
-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'),
+li.dropdown.dropup.subdued.language-picker(dropdown)
+	a#language-picker-toggle.dropdown-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')
 		tooltip=translate('language')
 		title=translate('language')
 		title=translate('language')
 	)
 	)
@@ -15,10 +15,13 @@ li.dropdown.dropup.subdued(dropdown).language-picker
 		|
 		|
 		| #{settings.translatedLanguages[currentLngCode]}
 		| #{settings.translatedLanguages[currentLngCode]}
 
 
-	ul.dropdown-menu(role="menu" aria-labelledby="language-picker-toggle")
+	ul.dropdown-menu(role='menu' aria-labelledby='language-picker-toggle')
 		li.dropdown-header #{translate("language")}
 		li.dropdown-header #{translate("language")}
 		each subdomainDetails, subdomain in settings.i18n.subdomainLang
 		each subdomainDetails, subdomain in settings.i18n.subdomainLang
 			if !subdomainDetails.hide
 			if !subdomainDetails.hide
 				li.lng-option
 				li.lng-option
-					a.menu-indent(href=subdomainDetails.url+currentUrlWithQueryParams role="menuitem")
+					a.menu-indent(
+						href=subdomainDetails.url + currentUrlWithQueryParams
+						role='menuitem'
+					)
 						| #{settings.translatedLanguages[subdomainDetails.lngCode]}
 						| #{settings.translatedLanguages[subdomainDetails.lngCode]}

+ 8 - 6
services/web/app/views/layout/layout-no-js.pug

@@ -1,18 +1,20 @@
 doctype html
 doctype html
-html(lang="en")
-
+html(lang='en')
 	- metadata = metadata || {}
 	- metadata = metadata || {}
 	block vars
 	block vars
 
 
 	head
 	head
-		if (metadata && metadata.title)
+		if metadata && metadata.title
 			title= metadata.title
 			title= metadata.title
 		if metadata && metadata.viewport
 		if metadata && metadata.viewport
-			meta(name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes")
+			meta(
+				name='viewport'
+				content='width=device-width, initial-scale=1.0, user-scalable=yes'
+			)
 
 
-		link(rel="icon", href="/favicon.ico")
+		link(rel='icon' href='/favicon.ico')
 
 
 		if buildCssPath
 		if buildCssPath
-			link(rel="stylesheet", href=buildCssPath('', 5))
+			link(rel='stylesheet' href=buildCssPath('', 5))
 
 
 block body
 block body

+ 108 - 96
services/web/app/views/layout/navbar-marketing-bootstrap-5.pug

@@ -1,26 +1,32 @@
 include ../_mixins/navbar
 include ../_mixins/navbar
 
 
-nav.navbar.navbar-default.navbar-main.navbar-expand-lg(class={
-	'website-redesign-navbar': isWebsiteRedesign
-})
+nav.navbar.navbar-default.navbar-main.navbar-expand-lg(
+	class={
+		'website-redesign-navbar': isWebsiteRedesign,
+	}
+)
 	.container-fluid.navbar-container
 	.container-fluid.navbar-container
 		.navbar-header
 		.navbar-header
 			if settings.nav.custom_logo
 			if settings.nav.custom_logo
-				a(href='/', aria-label=settings.appName, style='background-image:url("'+settings.nav.custom_logo+'")').navbar-brand
-			else if (nav.title)
-				a(href='/', aria-label=settings.appName).navbar-title #{nav.title}
+				a.navbar-brand(
+					href='/'
+					aria-label=settings.appName
+					style='background-image:url("' + settings.nav.custom_logo + '")'
+				)
+			else if nav.title
+				a.navbar-title(href='/' aria-label=settings.appName) #{nav.title}
 			else
 			else
-				a(href='/', aria-label=settings.appName).navbar-brand
+				a.navbar-brand(href='/' aria-label=settings.appName)
 
 
 			- var enableUpgradeButton = projectDashboardReact && usersBestSubscription && (usersBestSubscription.type === 'free' || usersBestSubscription.type === 'standalone-ai-add-on')
 			- var enableUpgradeButton = projectDashboardReact && usersBestSubscription && (usersBestSubscription.type === 'free' || usersBestSubscription.type === 'standalone-ai-add-on')
-			if (enableUpgradeButton)
+			if enableUpgradeButton
 				a.btn.btn-primary.me-2.d-md-none(
 				a.btn.btn-primary.me-2.d-md-none(
-					href="/user/subscription/plans"
-					event-tracking="upgrade-button-click"
-					event-tracking-mb="true"
-					event-tracking-label="upgrade"
-					event-tracking-trigger="click"
-					event-segmentation='{"source": "dashboard-top", "project-dashboard-react": "enabled", "is-dashboard-sidebar-hidden": "true", "is-screen-width-less-than-768px": "true"}'
+					href='/user/subscription/plans'
+					event-tracking='upgrade-button-click'
+					event-tracking-mb='true'
+					event-tracking-label='upgrade'
+					event-tracking-trigger='click'
+					event-segmentation={source: 'dashboard-top', projectDashboardReact: 'enabled', isDashboardSidebarHidden: 'true', isScreenWidthLessThan768px: 'true'}
 				) #{translate("upgrade")}
 				) #{translate("upgrade")}
 
 
 		- var canDisplayAdminMenu = hasAdminAccess()
 		- var canDisplayAdminMenu = hasAdminAccess()
@@ -29,45 +35,45 @@ nav.navbar.navbar-default.navbar-main.navbar-expand-lg(class={
 		- var canDisplaySurveyMenu = hasFeature('saas') && canDisplayAdminMenu
 		- var canDisplaySurveyMenu = hasFeature('saas') && canDisplayAdminMenu
 		- var canDisplayScriptLogMenu = hasFeature('saas') && canDisplayAdminMenu
 		- var canDisplayScriptLogMenu = hasFeature('saas') && canDisplayAdminMenu
 
 
-		if (typeof suppressNavbarRight === "undefined")
+		if typeof suppressNavbarRight === 'undefined'
 			button.navbar-toggler.collapsed(
 			button.navbar-toggler.collapsed(
-				type="button",
-				data-bs-toggle="collapse",
-				data-bs-target="#navbar-main-collapse"
-				aria-controls="navbar-main-collapse"
-				aria-expanded="false"
-				aria-label="Toggle " + translate('navigation')
+				type='button'
+				data-bs-toggle='collapse'
+				data-bs-target='#navbar-main-collapse'
+				aria-controls='navbar-main-collapse'
+				aria-expanded='false'
+				aria-label='Toggle ' + translate('navigation')
 			)
 			)
-				i.fa.fa-bars(aria-hidden="true")
+				i.fa.fa-bars(aria-hidden='true')
 
 
-			.navbar-collapse.collapse#navbar-main-collapse
-				ul.nav.navbar-nav.navbar-right.ms-auto(role="menubar")
-					if (canDisplayAdminMenu || canDisplayAdminRedirect || canDisplaySplitTestMenu)
+			#navbar-main-collapse.navbar-collapse.collapse
+				ul.nav.navbar-nav.navbar-right.ms-auto(role='menubar')
+					if canDisplayAdminMenu || canDisplayAdminRedirect || canDisplaySplitTestMenu
 						+nav-item.dropdown.subdued
 						+nav-item.dropdown.subdued
 							button.dropdown-toggle(
 							button.dropdown-toggle(
-								aria-haspopup="true",
-								aria-expanded="false",
-								data-bs-toggle="dropdown"
-								role="menuitem"
-								event-tracking="menu-expand"
-								event-tracking-mb="true"
-								event-tracking-trigger="click"
-								event-segmentation={"item": "admin", "location": "top-menu"}
+								aria-haspopup='true'
+								aria-expanded='false'
+								data-bs-toggle='dropdown'
+								role='menuitem'
+								event-tracking='menu-expand'
+								event-tracking-mb='true'
+								event-tracking-trigger='click'
+								event-segmentation={item: 'admin', location: 'top-menu'}
 							)
 							)
 								| Admin
 								| Admin
 							+dropdown-menu.dropdown-menu-end
 							+dropdown-menu.dropdown-menu-end
 								if canDisplayAdminMenu
 								if canDisplayAdminMenu
-									+dropdown-menu-link-item()(href="/admin") Manage Site
-									+dropdown-menu-link-item()(href="/admin/user") Manage Users
-									+dropdown-menu-link-item()(href="/admin/project") Project URL Lookup
+									+dropdown-menu-link-item(href='/admin') Manage Site
+									+dropdown-menu-link-item(href='/admin/user') Manage Users
+									+dropdown-menu-link-item(href='/admin/project') Project URL Lookup
 								if canDisplayAdminRedirect
 								if canDisplayAdminRedirect
-									+dropdown-menu-link-item()(href=settings.adminUrl) Switch to Admin
+									+dropdown-menu-link-item(href=settings.adminUrl) Switch to Admin
 								if canDisplaySplitTestMenu
 								if canDisplaySplitTestMenu
-									+dropdown-menu-link-item()(href="/admin/split-test") Manage Feature Flags
+									+dropdown-menu-link-item(href='/admin/split-test') Manage Feature Flags
 								if canDisplaySurveyMenu
 								if canDisplaySurveyMenu
-									+dropdown-menu-link-item()(href="/admin/survey") Manage Surveys
+									+dropdown-menu-link-item(href='/admin/survey') Manage Surveys
 								if canDisplayScriptLogMenu
 								if canDisplayScriptLogMenu
-									+dropdown-menu-link-item()(href="/admin/script-logs") View Script Logs
+									+dropdown-menu-link-item(href='/admin/script-logs') View Script Logs
 
 
 					// loop over header_extras
 					// loop over header_extras
 					each item in nav.header_extras
 					each item in nav.header_extras
@@ -86,14 +92,14 @@ nav.navbar.navbar-default.navbar-main.navbar-expand-lg(class={
 							if item.dropdown
 							if item.dropdown
 								+nav-item.dropdown(class=item.class)
 								+nav-item.dropdown(class=item.class)
 									button.dropdown-toggle(
 									button.dropdown-toggle(
-										aria-haspopup="true",
-										aria-expanded="false",
-										data-bs-toggle="dropdown"
-										role="menuitem"
-										event-tracking="menu-expand"
-										event-tracking-mb="true"
-										event-tracking-trigger="click"
-										event-segmentation={"item": item.trackingKey, "location": "top-menu"}
+										aria-haspopup='true'
+										aria-expanded='false'
+										data-bs-toggle='dropdown'
+										role='menuitem'
+										event-tracking='menu-expand'
+										event-tracking-mb='true'
+										event-tracking-trigger='click'
+										event-segmentation={item: item.trackingKey, location: 'top-menu'}
 									)
 									)
 										| !{translate(item.text)}
 										| !{translate(item.text)}
 									+dropdown-menu.dropdown-menu-end
 									+dropdown-menu.dropdown-menu-end
@@ -101,31 +107,41 @@ nav.navbar.navbar-default.navbar-main.navbar-expand-lg(class={
 											if child.divider
 											if child.divider
 												+dropdown-menu-divider
 												+dropdown-menu-divider
 											else if child.isContactUs
 											else if child.isContactUs
-												+dropdown-menu-link-item()(data-ol-open-contact-form-modal="contact-us" data-bs-target="#contactUsModal" href data-bs-toggle="modal" event-tracking="menu-click" event-tracking-mb="true" event-tracking-trigger="click" event-segmentation={"item": "contact", "location": "top-menu"})
+												+dropdown-menu-link-item(
+													data-ol-open-contact-form-modal='contact-us'
+													data-bs-target='#contactUsModal'
+													href
+													data-bs-toggle='modal'
+													event-tracking='menu-click'
+													event-tracking-mb='true'
+													event-tracking-trigger='click'
+													event-segmentation={item: 'contact', location: 'top-menu'}
+												)
 													span
 													span
 														| #{translate("contact_us")}
 														| #{translate("contact_us")}
 											else
 											else
 												if child.url
 												if child.url
-													+dropdown-menu-link-item()(
-														href=child.url,
-														class=child.class,
-														event-tracking="menu-click"
-														event-tracking-mb="true"
-														event-tracking-trigger="click"
-														event-segmentation={ item: child.trackingKey, location: 'top-menu' }
+													+dropdown-menu-link-item(
+														href=child.url
+														class=child.class
+														event-tracking='menu-click'
+														event-tracking-mb='true'
+														event-tracking-trigger='click'
+														event-segmentation={item: child.trackingKey, location: 'top-menu'}
 													) !{translate(child.text)}
 													) !{translate(child.text)}
 												else
 												else
-													+dropdown-menu-item !{translate(child.text)}
+													+dropdown-menu-item
+														| !{translate(child.text)}
 							else
 							else
 								+nav-item(class=item.class)
 								+nav-item(class=item.class)
 									if item.url
 									if item.url
 										+nav-link(
 										+nav-link(
-											href=item.url,
-											class=item.class,
-											event-tracking="menu-click"
-											event-tracking-mb="true"
-											event-tracking-trigger="click"
-											event-segmentation={ item: item.trackingKey, location: 'top-menu' }
+											href=item.url
+											class=item.class
+											event-tracking='menu-click'
+											event-tracking-mb='true'
+											event-tracking-trigger='click'
+											event-segmentation={item: item.trackingKey, location: 'top-menu'}
 										) !{translate(item.text)}
 										) !{translate(item.text)}
 									else
 									else
 										| !{translate(item.text)}
 										| !{translate(item.text)}
@@ -136,48 +152,48 @@ nav.navbar.navbar-default.navbar-main.navbar-expand-lg(class={
 						if hasFeature('registration-page')
 						if hasFeature('registration-page')
 							+nav-item.primary
 							+nav-item.primary
 								+nav-link(
 								+nav-link(
-									href="/register"
-									event-tracking="menu-click"
-									event-tracking-action="clicked"
-									event-tracking-trigger="click"
-									event-tracking-mb="true"
-									event-segmentation={ page: currentUrl, item: 'register', location: 'top-menu' }
+									href='/register'
+									event-tracking='menu-click'
+									event-tracking-action='clicked'
+									event-tracking-trigger='click'
+									event-tracking-mb='true'
+									event-segmentation={page: currentUrl, item: 'register', location: 'top-menu'}
 								) #{translate('sign_up')}
 								) #{translate('sign_up')}
 
 
 						// login link
 						// login link
 						+nav-item
 						+nav-item
 							+nav-link(
 							+nav-link(
-								href="/login"
-								event-tracking="menu-click"
-								event-tracking-action="clicked"
-								event-tracking-trigger="click"
-								event-tracking-mb="true"
-								event-segmentation={ page: currentUrl, item: 'login', location: 'top-menu' }
+								href='/login'
+								event-tracking='menu-click'
+								event-tracking-action='clicked'
+								event-tracking-trigger='click'
+								event-tracking-mb='true'
+								event-segmentation={page: currentUrl, item: 'login', location: 'top-menu'}
 							) #{translate('log_in')}
 							) #{translate('log_in')}
 
 
 					// projects link and account menu
 					// projects link and account menu
 					if getSessionUser()
 					if getSessionUser()
 						+nav-item
 						+nav-item
-							+nav-link(href="/project") #{translate('Projects')}
+							+nav-link(href='/project') #{translate('Projects')}
 						+nav-item.dropdown
 						+nav-item.dropdown
 							button.dropdown-toggle(
 							button.dropdown-toggle(
-								aria-haspopup="true",
-								aria-expanded="false",
-								data-bs-toggle="dropdown"
-								role="menuitem"
-								event-tracking="menu-expand"
-								event-tracking-mb="true"
-								event-tracking-trigger="click"
-								event-segmentation={"item": "account", "location": "top-menu"}
+								aria-haspopup='true'
+								aria-expanded='false'
+								data-bs-toggle='dropdown'
+								role='menuitem'
+								event-tracking='menu-expand'
+								event-tracking-mb='true'
+								event-tracking-trigger='click'
+								event-segmentation={item: 'account', location: 'top-menu'}
 							)
 							)
 								| #{translate('Account')}
 								| #{translate('Account')}
 							+dropdown-menu.dropdown-menu-end
 							+dropdown-menu.dropdown-menu-end
 								+dropdown-menu-item
 								+dropdown-menu-item
-									div.disabled.dropdown-item #{getSessionUser().email}
+									.disabled.dropdown-item #{getSessionUser().email}
 								+dropdown-menu-divider
 								+dropdown-menu-divider
-								+dropdown-menu-link-item()(href="/user/settings") #{translate('account_settings')}
+								+dropdown-menu-link-item(href='/user/settings') #{translate('account_settings')}
 								if nav.showSubscriptionLink
 								if nav.showSubscriptionLink
-									+dropdown-menu-link-item()(href="/user/subscription") #{translate('subscription')}
+									+dropdown-menu-link-item(href='/user/subscription') #{translate('subscription')}
 								+dropdown-menu-divider
 								+dropdown-menu-divider
 								+dropdown-menu-item
 								+dropdown-menu-item
 									//-
 									//-
@@ -185,14 +201,10 @@ nav.navbar.navbar-default.navbar-main.navbar-expand-lg(class={
 										this is that if the button is inside the form, screen readers will not count it in the total
 										this is that if the button is inside the form, screen readers will not count it in the total
 										number of menu items.
 										number of menu items.
 									button.btn-link.text-left.dropdown-menu-button.dropdown-item(
 									button.btn-link.text-left.dropdown-menu-button.dropdown-item(
-										role="menuitem",
-										tabindex="-1"
-										form="logOutForm"
+										role='menuitem'
+										tabindex='-1'
+										form='logOutForm'
 									)
 									)
 										| #{translate('log_out')}
 										| #{translate('log_out')}
-									form(
-										method="POST",
-										action="/logout",
-										id="logOutForm"
-									)
-										input(name='_csrf', type='hidden', value=csrfToken)
+									form(method='POST' action='/logout' id='logOutForm')
+										input(name='_csrf' type='hidden' value=csrfToken)

+ 105 - 86
services/web/app/views/layout/navbar-marketing.pug

@@ -1,32 +1,44 @@
-nav.navbar.navbar-default.navbar-main(class={
-	'website-redesign-navbar': isWebsiteRedesign
-})
+nav.navbar.navbar-default.navbar-main(
+	class={
+		'website-redesign-navbar': isWebsiteRedesign,
+	}
+)
 	.container-fluid
 	.container-fluid
 		.navbar-header
 		.navbar-header
-			if (typeof(suppressNavbarRight) == "undefined")
+			if typeof suppressNavbarRight == 'undefined'
 				button.navbar-toggle.collapsed(
 				button.navbar-toggle.collapsed(
-					type="button",
-					data-toggle="collapse",
-					data-target="#navbar-main-collapse"
-					aria-label="Toggle " + translate('navigation')
+					type='button'
+					data-toggle='collapse'
+					data-target='#navbar-main-collapse'
+					aria-label='Toggle ' + translate('navigation')
 				)
 				)
-					i.fa.fa-bars(aria-hidden="true")
+					i.fa.fa-bars(aria-hidden='true')
 			- var enableUpgradeButton = projectDashboardReact && usersBestSubscription && (usersBestSubscription.type === 'free' || usersBestSubscription.type === 'standalone-ai-add-on')
 			- var enableUpgradeButton = projectDashboardReact && usersBestSubscription && (usersBestSubscription.type === 'free' || usersBestSubscription.type === 'standalone-ai-add-on')
-			if (enableUpgradeButton)
+			if enableUpgradeButton
+				//- prettier-ignore
 				a.btn.btn-primary.pull-right.me-2.visible-xs(
 				a.btn.btn-primary.pull-right.me-2.visible-xs(
-					href="/user/subscription/plans"
-					event-tracking="upgrade-button-click"
-					event-tracking-mb="true"
-					event-tracking-label="upgrade"
-					event-tracking-trigger="click"
-					event-segmentation='{"source": "dashboard-top", "project-dashboard-react": "enabled", "is-dashboard-sidebar-hidden": "true", "is-screen-width-less-than-768px": "true"}'
+					href='/user/subscription/plans'
+					event-tracking='upgrade-button-click'
+					event-tracking-mb='true'
+					event-tracking-label='upgrade'
+					event-tracking-trigger='click'
+					event-segmentation={
+						source: "dashboard-top",
+						"project-dashboard-react": "enabled",
+						"is-dashboard-sidebar-hidden": "true",
+						"is-screen-width-less-than-768px": "true"
+					}
 				) #{translate("upgrade")}
 				) #{translate("upgrade")}
 			if settings.nav.custom_logo
 			if settings.nav.custom_logo
-				a(href='/', aria-label=settings.appName, style='background-image:url("'+settings.nav.custom_logo+'")').navbar-brand
-			else if (nav.title)
-				a(href='/', aria-label=settings.appName).navbar-title #{nav.title}
+				a.navbar-brand(
+					href='/'
+					aria-label=settings.appName
+					style='background-image:url("' + settings.nav.custom_logo + '")'
+				)
+			else if nav.title
+				a.navbar-title(href='/' aria-label=settings.appName) #{nav.title}
 			else
 			else
-				a(href='/', aria-label=settings.appName).navbar-brand
+				a.navbar-brand(href='/' aria-label=settings.appName)
 
 
 		- var canDisplayAdminMenu = hasAdminAccess()
 		- var canDisplayAdminMenu = hasAdminAccess()
 		- var canDisplayAdminRedirect = canRedirectToAdminDomain()
 		- var canDisplayAdminRedirect = canRedirectToAdminDomain()
@@ -34,44 +46,44 @@ nav.navbar.navbar-default.navbar-main(class={
 		- var canDisplaySurveyMenu = hasFeature('saas') && canDisplayAdminMenu
 		- var canDisplaySurveyMenu = hasFeature('saas') && canDisplayAdminMenu
 		- var canDisplayScriptLogMenu = hasFeature('saas') && canDisplayAdminMenu
 		- var canDisplayScriptLogMenu = hasFeature('saas') && canDisplayAdminMenu
 
 
-		if (typeof(suppressNavbarRight) == "undefined")
-			.navbar-collapse.collapse#navbar-main-collapse
+		if typeof suppressNavbarRight == 'undefined'
+			#navbar-main-collapse.navbar-collapse.collapse
 				ul.nav.navbar-nav.navbar-right
 				ul.nav.navbar-nav.navbar-right
-					if (canDisplayAdminMenu || canDisplayAdminRedirect || canDisplaySplitTestMenu)
+					if canDisplayAdminMenu || canDisplayAdminRedirect || canDisplaySplitTestMenu
 						li.dropdown.subdued
 						li.dropdown.subdued
 							a.dropdown-toggle(
 							a.dropdown-toggle(
-								href="#",
-								role="button",
-								aria-haspopup="true",
-								aria-expanded="false",
-								data-toggle="dropdown"
-								event-tracking="menu-expand"
-								event-tracking-mb="true"
-								event-tracking-trigger="click"
-								event-segmentation={"item": "admin", "location": "top-menu"}
+								href='#'
+								role='button'
+								aria-haspopup='true'
+								aria-expanded='false'
+								data-toggle='dropdown'
+								event-tracking='menu-expand'
+								event-tracking-mb='true'
+								event-tracking-trigger='click'
+								event-segmentation={item: 'admin', location: 'top-menu'}
 							)
 							)
 								| Admin
 								| Admin
 								span.caret
 								span.caret
 							ul.dropdown-menu
 							ul.dropdown-menu
 								if canDisplayAdminMenu
 								if canDisplayAdminMenu
 									li
 									li
-										a(href="/admin") Manage Site
+										a(href='/admin') Manage Site
 									li
 									li
-										a(href="/admin/user") Manage Users
+										a(href='/admin/user') Manage Users
 									li
 									li
-										a(href="/admin/project") Project URL Lookup
+										a(href='/admin/project') Project URL Lookup
 								if canDisplayAdminRedirect
 								if canDisplayAdminRedirect
 									li
 									li
 										a(href=settings.adminUrl) Switch to Admin
 										a(href=settings.adminUrl) Switch to Admin
 								if canDisplaySplitTestMenu
 								if canDisplaySplitTestMenu
 									li
 									li
-										a(href="/admin/split-test") Manage Feature Flags
+										a(href='/admin/split-test') Manage Feature Flags
 								if canDisplaySurveyMenu
 								if canDisplaySurveyMenu
 									li
 									li
-										a(href="/admin/survey") Manage Surveys
+										a(href='/admin/survey') Manage Surveys
 								if canDisplayScriptLogMenu
 								if canDisplayScriptLogMenu
 									li
 									li
-										a(href="/admin/script-logs") View Script Logs
+										a(href='/admin/script-logs') View Script Logs
 
 
 					// loop over header_extras
 					// loop over header_extras
 					each item in nav.header_extras
 					each item in nav.header_extras
@@ -90,15 +102,15 @@ nav.navbar.navbar-default.navbar-main(class={
 							if item.dropdown
 							if item.dropdown
 								li.dropdown(class=item.class)
 								li.dropdown(class=item.class)
 									a.dropdown-toggle(
 									a.dropdown-toggle(
-										href="#",
-										role="button",
-										aria-haspopup="true",
-										aria-expanded="false",
-										data-toggle="dropdown"
-										event-tracking="menu-expand"
-										event-tracking-mb="true"
-										event-tracking-trigger="click"
-										event-segmentation={"item": item.trackingKey, "location": "top-menu"}
+										href='#'
+										role='button'
+										aria-haspopup='true'
+										aria-expanded='false'
+										data-toggle='dropdown'
+										event-tracking='menu-expand'
+										event-tracking-mb='true'
+										event-tracking-trigger='click'
+										event-segmentation={item: item.trackingKey, location: 'top-menu'}
 									)
 									)
 										| !{translate(item.text)}
 										| !{translate(item.text)}
 										span.caret
 										span.caret
@@ -108,18 +120,25 @@ nav.navbar.navbar-default.navbar-main(class={
 												li.divider
 												li.divider
 											else if child.isContactUs
 											else if child.isContactUs
 												li
 												li
-													a(data-ol-open-contact-form-modal="contact-us" href event-tracking="menu-click" event-tracking-mb="true" event-tracking-trigger="click" event-segmentation={"item": "contact", "location": "top-menu"})
+													a(
+														data-ol-open-contact-form-modal='contact-us'
+														href
+														event-tracking='menu-click'
+														event-tracking-mb='true'
+														event-tracking-trigger='click'
+														event-segmentation={item: 'contact', location: 'top-menu'}
+													)
 														span
 														span
 															| #{translate("contact_us")}
 															| #{translate("contact_us")}
 											else
 											else
 												li
 												li
 													if child.url
 													if child.url
 														a(
 														a(
-															href=child.url,
-															class=child.class,
-															event-tracking="menu-click"
-															event-tracking-mb="true"
-															event-tracking-trigger="click"
+															href=child.url
+															class=child.class
+															event-tracking='menu-click'
+															event-tracking-mb='true'
+															event-tracking-trigger='click'
 															event-segmentation={item: item.trackingKey, location: 'top-menu'}
 															event-segmentation={item: item.trackingKey, location: 'top-menu'}
 														) !{translate(child.text)}
 														) !{translate(child.text)}
 													else
 													else
@@ -128,12 +147,12 @@ nav.navbar.navbar-default.navbar-main(class={
 								li(class=item.class)
 								li(class=item.class)
 									if item.url
 									if item.url
 										a(
 										a(
-											href=item.url,
-											class=item.class,
-											event-tracking="menu-click"
-											event-tracking-mb="true"
-											event-tracking-trigger="click"
-											event-segmentation={ item: item.trackingKey, location: 'top-menu' }
+											href=item.url
+											class=item.class
+											event-tracking='menu-click'
+											event-tracking-mb='true'
+											event-tracking-trigger='click'
+											event-segmentation={item: item.trackingKey, location: 'top-menu'}
 										) !{translate(item.text)}
 										) !{translate(item.text)}
 									else
 									else
 										| !{translate(item.text)}
 										| !{translate(item.text)}
@@ -144,54 +163,54 @@ nav.navbar.navbar-default.navbar-main(class={
 						if hasFeature('registration-page')
 						if hasFeature('registration-page')
 							li.primary
 							li.primary
 								a(
 								a(
-									href="/register"
-									event-tracking="menu-click"
-									event-tracking-action="clicked"
-									event-tracking-trigger="click"
-									event-tracking-mb="true"
-									event-segmentation={ page: currentUrl, item: 'register', location: 'top-menu' }
+									href='/register'
+									event-tracking='menu-click'
+									event-tracking-action='clicked'
+									event-tracking-trigger='click'
+									event-tracking-mb='true'
+									event-segmentation={page: currentUrl, item: 'register', location: 'top-menu'}
 								) #{translate('sign_up')}
 								) #{translate('sign_up')}
 
 
 						// login link
 						// login link
 						li
 						li
 							a(
 							a(
-								href="/login"
-								event-tracking="menu-click"
-								event-tracking-action="clicked"
-								event-tracking-trigger="click"
-								event-tracking-mb="true"
-								event-segmentation={ page: currentUrl, item: 'login', location: 'top-menu' }
+								href='/login'
+								event-tracking='menu-click'
+								event-tracking-action='clicked'
+								event-tracking-trigger='click'
+								event-tracking-mb='true'
+								event-segmentation={page: currentUrl, item: 'login', location: 'top-menu'}
 							) #{translate('log_in')}
 							) #{translate('log_in')}
 
 
 					// projects link and account menu
 					// projects link and account menu
 					if getSessionUser()
 					if getSessionUser()
 						li
 						li
-							a(href="/project") #{translate('Projects')}
+							a(href='/project') #{translate('Projects')}
 						li.dropdown
 						li.dropdown
 							a.dropdown-toggle(
 							a.dropdown-toggle(
-								href="#",
-								role="button",
-								aria-haspopup="true",
-								aria-expanded="false",
-								data-toggle="dropdown"
-								event-tracking="menu-expand"
-								event-tracking-mb="true"
-								event-tracking-trigger="click"
-								event-segmentation={"item": "account", "location": "top-menu"}
+								href='#'
+								role='button'
+								aria-haspopup='true'
+								aria-expanded='false'
+								data-toggle='dropdown'
+								event-tracking='menu-expand'
+								event-tracking-mb='true'
+								event-tracking-trigger='click'
+								event-segmentation={item: 'account', location: 'top-menu'}
 							)
 							)
 								| #{translate('Account')}
 								| #{translate('Account')}
 								span.caret
 								span.caret
 							ul.dropdown-menu
 							ul.dropdown-menu
 								li
 								li
-									div.subdued #{getSessionUser().email}
+									.subdued #{getSessionUser().email}
 								li.divider.hidden-xs.hidden-sm
 								li.divider.hidden-xs.hidden-sm
 								li
 								li
-									a(href="/user/settings") #{translate('account_settings')}
+									a(href='/user/settings') #{translate('account_settings')}
 								if nav.showSubscriptionLink
 								if nav.showSubscriptionLink
 									li
 									li
-										a(href="/user/subscription") #{translate('subscription')}
+										a(href='/user/subscription') #{translate('subscription')}
 								li.divider.hidden-xs.hidden-sm
 								li.divider.hidden-xs.hidden-sm
 								li
 								li
-									form(method="POST" action="/logout")
-										input(name='_csrf', type='hidden', value=csrfToken)
+									form(method='POST' action='/logout')
+										input(name='_csrf' type='hidden' value=csrfToken)
 										button.btn-link.text-left.dropdown-menu-button #{translate('log_out')}
 										button.btn-link.text-left.dropdown-menu-button #{translate('log_out')}

+ 99 - 83
services/web/app/views/layout/navbar-website-redesign.pug

@@ -1,30 +1,39 @@
 nav.navbar.navbar-default.navbar-main.website-redesign-navbar
 nav.navbar.navbar-default.navbar-main.website-redesign-navbar
 	.container-fluid
 	.container-fluid
 		.navbar-header
 		.navbar-header
-			if (typeof(suppressNavbarRight) == "undefined")
+			if typeof suppressNavbarRight == 'undefined'
 				button.navbar-toggle.collapsed(
 				button.navbar-toggle.collapsed(
-					type="button",
-					data-toggle="collapse",
-					data-target="#navbar-main-collapse"
-					aria-label="Toggle " + translate('navigation')
+					type='button'
+					data-toggle='collapse'
+					data-target='#navbar-main-collapse'
+					aria-label='Toggle ' + translate('navigation')
 				)
 				)
-					i.fa.fa-bars(aria-hidden="true")
+					i.fa.fa-bars(aria-hidden='true')
 			- var enableUpgradeButton = projectDashboardReact && usersBestSubscription && (usersBestSubscription.type === 'free' || usersBestSubscription.type === 'standalone-ai-add-on')
 			- var enableUpgradeButton = projectDashboardReact && usersBestSubscription && (usersBestSubscription.type === 'free' || usersBestSubscription.type === 'standalone-ai-add-on')
-			if (enableUpgradeButton)
+			if enableUpgradeButton
 				a.btn.btn-primary.pull-right.me-2.visible-xs(
 				a.btn.btn-primary.pull-right.me-2.visible-xs(
-					href="/user/subscription/plans"
-					event-tracking="upgrade-button-click"
-					event-tracking-mb="true"
-					event-tracking-label="upgrade"
-					event-tracking-trigger="click"
-					event-segmentation='{"source": "dashboard-top", "project-dashboard-react": "enabled", "is-dashboard-sidebar-hidden": "true", "is-screen-width-less-than-768px": "true"}'
+					href='/user/subscription/plans'
+					event-tracking='upgrade-button-click'
+					event-tracking-mb='true'
+					event-tracking-label='upgrade'
+					event-tracking-trigger='click'
+					event-segmentation={
+						source: 'dashboard-top',
+						projectDashboardReact: 'enabled',
+						isDashboardSidebarHidden: 'true',
+						isScreenWidthLessThan768px: 'true',
+					}
 				) #{translate("upgrade")}
 				) #{translate("upgrade")}
 			if settings.nav.custom_logo
 			if settings.nav.custom_logo
-				a(href='/', aria-label=settings.appName, style='background-image:url("'+settings.nav.custom_logo+'")').navbar-brand
-			else if (nav.title)
-				a(href='/', aria-label=settings.appName).navbar-title #{nav.title}
+				a.navbar-brand(
+					href='/'
+					aria-label=settings.appName
+					style='background-image:url("' + settings.nav.custom_logo + '")'
+				)
+			else if nav.title
+				a.navbar-title(href='/' aria-label=settings.appName) #{nav.title}
 			else
 			else
-				a(href='/', aria-label=settings.appName).navbar-brand
+				a.navbar-brand(href='/' aria-label=settings.appName)
 
 
 		- var canDisplayAdminMenu = hasAdminAccess()
 		- var canDisplayAdminMenu = hasAdminAccess()
 		- var canDisplayAdminRedirect = canRedirectToAdminDomain()
 		- var canDisplayAdminRedirect = canRedirectToAdminDomain()
@@ -32,44 +41,44 @@ nav.navbar.navbar-default.navbar-main.website-redesign-navbar
 		- var canDisplaySurveyMenu = hasFeature('saas') && canDisplayAdminMenu
 		- var canDisplaySurveyMenu = hasFeature('saas') && canDisplayAdminMenu
 		- var canDisplayScriptLogMenu = hasFeature('saas') && canDisplayAdminMenu
 		- var canDisplayScriptLogMenu = hasFeature('saas') && canDisplayAdminMenu
 
 
-		if (typeof(suppressNavbarRight) == "undefined")
-			.navbar-collapse.collapse#navbar-main-collapse
+		if typeof suppressNavbarRight == 'undefined'
+			#navbar-main-collapse.navbar-collapse.collapse
 				ul.nav.navbar-nav.navbar-right
 				ul.nav.navbar-nav.navbar-right
-					if (canDisplayAdminMenu || canDisplayAdminRedirect || canDisplaySplitTestMenu)
+					if canDisplayAdminMenu || canDisplayAdminRedirect || canDisplaySplitTestMenu
 						li.dropdown.subdued
 						li.dropdown.subdued
 							a.dropdown-toggle(
 							a.dropdown-toggle(
-								href="#",
-								role="button",
-								aria-haspopup="true",
-								aria-expanded="false",
-								data-toggle="dropdown"
-								event-tracking="menu-expand"
-								event-tracking-mb="true"
-								event-tracking-trigger="click"
-								event-segmentation={"item": "admin", "location": "top-menu"}
+								href='#'
+								role='button'
+								aria-haspopup='true'
+								aria-expanded='false'
+								data-toggle='dropdown'
+								event-tracking='menu-expand'
+								event-tracking-mb='true'
+								event-tracking-trigger='click'
+								event-segmentation={item: 'admin', location: 'top-menu'}
 							)
 							)
 								| Admin
 								| Admin
 								span.caret
 								span.caret
 							ul.dropdown-menu
 							ul.dropdown-menu
 								if canDisplayAdminMenu
 								if canDisplayAdminMenu
 									li
 									li
-										a(href="/admin") Manage Site
+										a(href='/admin') Manage Site
 									li
 									li
-										a(href="/admin/user") Manage Users
+										a(href='/admin/user') Manage Users
 									li
 									li
-										a(href="/admin/project") Project URL Lookup
+										a(href='/admin/project') Project URL Lookup
 								if canDisplayAdminRedirect
 								if canDisplayAdminRedirect
 									li
 									li
 										a(href=settings.adminUrl) Switch to Admin
 										a(href=settings.adminUrl) Switch to Admin
 								if canDisplaySplitTestMenu
 								if canDisplaySplitTestMenu
 									li
 									li
-										a(href="/admin/split-test") Manage Feature Flags
+										a(href='/admin/split-test') Manage Feature Flags
 								if canDisplaySurveyMenu
 								if canDisplaySurveyMenu
 									li
 									li
-										a(href="/admin/survey") Manage Surveys
+										a(href='/admin/survey') Manage Surveys
 								if canDisplayScriptLogMenu
 								if canDisplayScriptLogMenu
 									li
 									li
-										a(href="/admin/script-logs") View Script Logs
+										a(href='/admin/script-logs') View Script Logs
 
 
 					// loop over header_extras
 					// loop over header_extras
 					each item in nav.header_extras
 					each item in nav.header_extras
@@ -88,15 +97,15 @@ nav.navbar.navbar-default.navbar-main.website-redesign-navbar
 							if item.dropdown
 							if item.dropdown
 								li.dropdown(class=item.class)
 								li.dropdown(class=item.class)
 									a.dropdown-toggle(
 									a.dropdown-toggle(
-										href="#",
-										role="button",
-										aria-haspopup="true",
-										aria-expanded="false",
-										data-toggle="dropdown"
-										event-tracking="menu-expand"
-										event-tracking-mb="true"
-										event-tracking-trigger="click"
-										event-segmentation={"item": item.trackingKey, "location": "top-menu"}
+										href='#'
+										role='button'
+										aria-haspopup='true'
+										aria-expanded='false'
+										data-toggle='dropdown'
+										event-tracking='menu-expand'
+										event-tracking-mb='true'
+										event-tracking-trigger='click'
+										event-segmentation={item: item.trackingKey, location: 'top-menu'}
 									)
 									)
 										| !{translate(item.text)}
 										| !{translate(item.text)}
 										span.caret
 										span.caret
@@ -106,18 +115,25 @@ nav.navbar.navbar-default.navbar-main.website-redesign-navbar
 												li.divider
 												li.divider
 											else if child.isContactUs
 											else if child.isContactUs
 												li
 												li
-													a(data-ol-open-contact-form-modal="contact-us" href event-tracking="menu-click" event-tracking-mb="true" event-tracking-trigger="click" event-segmentation={"item": "contact", "location": "top-menu"})
+													a(
+														data-ol-open-contact-form-modal='contact-us'
+														href
+														event-tracking='menu-click'
+														event-tracking-mb='true'
+														event-tracking-trigger='click'
+														event-segmentation={item: 'contact', location: 'top-menu'}
+													)
 														span
 														span
 															| #{translate("contact_us")}
 															| #{translate("contact_us")}
 											else
 											else
 												li
 												li
 													if child.url
 													if child.url
 														a(
 														a(
-															href=child.url,
-															class=child.class,
-															event-tracking="menu-click"
-															event-tracking-mb="true"
-															event-tracking-trigger="click"
+															href=child.url
+															class=child.class
+															event-tracking='menu-click'
+															event-tracking-mb='true'
+															event-tracking-trigger='click'
 															event-segmentation={item: child.trackingKey, location: 'top-menu'}
 															event-segmentation={item: child.trackingKey, location: 'top-menu'}
 														) !{translate(child.text)}
 														) !{translate(child.text)}
 													else
 													else
@@ -126,12 +142,12 @@ nav.navbar.navbar-default.navbar-main.website-redesign-navbar
 								li(class=item.class)
 								li(class=item.class)
 									if item.url
 									if item.url
 										a(
 										a(
-											href=item.url,
-											class=item.class,
-											event-tracking="menu-click"
-											event-tracking-mb="true"
-											event-tracking-trigger="click"
-											event-segmentation={ item: item.trackingKey, location: 'top-menu' }
+											href=item.url
+											class=item.class
+											event-tracking='menu-click'
+											event-tracking-mb='true'
+											event-tracking-trigger='click'
+											event-segmentation={item: item.trackingKey, location: 'top-menu'}
 										) !{translate(item.text)}
 										) !{translate(item.text)}
 									else
 									else
 										| !{translate(item.text)}
 										| !{translate(item.text)}
@@ -142,54 +158,54 @@ nav.navbar.navbar-default.navbar-main.website-redesign-navbar
 						if hasFeature('registration-page')
 						if hasFeature('registration-page')
 							li.primary
 							li.primary
 								a(
 								a(
-									href="/register"
-									event-tracking="menu-click"
-									event-tracking-action="clicked"
-									event-tracking-trigger="click"
-									event-tracking-mb="true"
-									event-segmentation={ page: currentUrl, item: 'register', location: 'top-menu' }
+									href='/register'
+									event-tracking='menu-click'
+									event-tracking-action='clicked'
+									event-tracking-trigger='click'
+									event-tracking-mb='true'
+									event-segmentation={page: currentUrl, item: 'register', location: 'top-menu'}
 								) #{translate('sign_up')}
 								) #{translate('sign_up')}
 
 
 						// login link
 						// login link
 						li.secondary
 						li.secondary
 							a(
 							a(
-								href="/login"
-								event-tracking="menu-click"
-								event-tracking-action="clicked"
-								event-tracking-trigger="click"
-								event-tracking-mb="true"
-								event-segmentation={ page: currentUrl, item: 'login', location: 'top-menu' }
+								href='/login'
+								event-tracking='menu-click'
+								event-tracking-action='clicked'
+								event-tracking-trigger='click'
+								event-tracking-mb='true'
+								event-segmentation={page: currentUrl, item: 'login', location: 'top-menu'}
 							) #{translate('log_in')}
 							) #{translate('log_in')}
 
 
 					// projects link and account menu
 					// projects link and account menu
 					if getSessionUser()
 					if getSessionUser()
 						li.secondary
 						li.secondary
-							a(href="/project") #{translate('Projects')}
+							a(href='/project') #{translate('Projects')}
 						li.secondary.dropdown
 						li.secondary.dropdown
 							a.dropdown-toggle(
 							a.dropdown-toggle(
-								href="#",
-								role="button",
-								aria-haspopup="true",
-								aria-expanded="false",
-								data-toggle="dropdown"
-								event-tracking="menu-expand"
-								event-tracking-mb="true"
-								event-tracking-trigger="click"
-								event-segmentation={"item": "account", "location": "top-menu"}
+								href='#'
+								role='button'
+								aria-haspopup='true'
+								aria-expanded='false'
+								data-toggle='dropdown'
+								event-tracking='menu-expand'
+								event-tracking-mb='true'
+								event-tracking-trigger='click'
+								event-segmentation={item: 'account', location: 'top-menu'}
 							)
 							)
 								| #{translate('Account')}
 								| #{translate('Account')}
 								span.caret
 								span.caret
 							ul.dropdown-menu
 							ul.dropdown-menu
 								li
 								li
-									div.subdued #{getSessionUser().email}
+									.subdued #{getSessionUser().email}
 								li.divider.hidden-xs.hidden-sm
 								li.divider.hidden-xs.hidden-sm
 								li
 								li
-									a(href="/user/settings") #{translate('account_settings')}
+									a(href='/user/settings') #{translate('account_settings')}
 								if nav.showSubscriptionLink
 								if nav.showSubscriptionLink
 									li
 									li
-										a(href="/user/subscription") #{translate('subscription')}
+										a(href='/user/subscription') #{translate('subscription')}
 								li.divider.hidden-xs.hidden-sm
 								li.divider.hidden-xs.hidden-sm
 								li
 								li
-									form(method="POST" action="/logout")
-										input(name='_csrf', type='hidden', value=csrfToken)
+									form(method='POST' action='/logout')
+										input(name='_csrf' type='hidden' value=csrfToken)
 										button.btn-link.text-left.dropdown-menu-button #{translate('log_out')}
 										button.btn-link.text-left.dropdown-menu-button #{translate('log_out')}

+ 2 - 2
services/web/app/views/layout/thin-footer-bootstrap-5.pug

@@ -25,7 +25,7 @@ footer.site-footer
 				each item in nav.left_footer
 				each item in nav.left_footer
 					li
 					li
 						if item.url
 						if item.url
-							a(href=item.url, class=item.class) !{translate(item.text)}
+							a(href=item.url class=item.class) !{translate(item.text)}
 						else
 						else
 							| !{item.text}
 							| !{item.text}
 
 
@@ -33,6 +33,6 @@ footer.site-footer
 				each item in nav.right_footer
 				each item in nav.right_footer
 					li
 					li
 						if item.url
 						if item.url
-							a(href=item.url, class=item.class, aria-label=item.label) !{item.text}
+							a(href=item.url class=item.class aria-label=item.label) !{item.text}
 						else
 						else
 							| !{item.text}
 							| !{item.text}

+ 2 - 2
services/web/app/views/layout/thin-footer.pug

@@ -27,7 +27,7 @@ footer.site-footer
 				each item in nav.left_footer
 				each item in nav.left_footer
 					li
 					li
 						if item.url
 						if item.url
-							a(href=item.url, class=item.class) !{translate(item.text)}
+							a(href=item.url class=item.class) !{translate(item.text)}
 						else
 						else
 							| !{item.text}
 							| !{item.text}
 
 
@@ -35,6 +35,6 @@ footer.site-footer
 				each item in nav.right_footer
 				each item in nav.right_footer
 					li
 					li
 						if item.url
 						if item.url
-							a(href=item.url, class=item.class, aria-label=item.label) !{item.text}
+							a(href=item.url class=item.class aria-label=item.label) !{item.text}
 						else
 						else
 							| !{item.text}
 							| !{item.text}

+ 29 - 29
services/web/app/views/project/editor/new_from_template.pug

@@ -1,36 +1,36 @@
 extends ../../layout-marketing
 extends ../../layout-marketing
 
 
 block vars
 block vars
-  - var suppressFooter = true
-  - var suppressCookieBanner = true
-  - var suppressSkipToContent = true
+	- var suppressFooter = true
+	- var suppressCookieBanner = true
+	- var suppressSkipToContent = true
 
 
 block content
 block content
-  .editor.full-size
-    .loading-screen()
-      .loading-screen-brand-container
-        .loading-screen-brand(
-          style="height: 20%;"
-        )
+	.editor.full-size
+		.loading-screen()
+			.loading-screen-brand-container
+				.loading-screen-brand(
+					style="height: 20%;"
+				)
 
 
-      h3.loading-screen-label() #{translate("Opening template")}
-        span.loading-screen-ellip .
-        span.loading-screen-ellip .
-        span.loading-screen-ellip .
+			h3.loading-screen-label() #{translate("Opening template")}
+				span.loading-screen-ellip .
+				span.loading-screen-ellip .
+				span.loading-screen-ellip .
 
 
-  form(
-    data-ol-regular-form
-    data-ol-auto-submit
-    method='POST'
-    action='/project/new/template/'
-  )
-    input(type="hidden", name="_csrf", value=csrfToken)
-    input(type="hidden" name="templateId" value=templateId)
-    input(type="hidden" name="templateVersionId" value=templateVersionId)
-    input(type="hidden" name="templateName" value=name)
-    input(type="hidden" name="compiler" value=compiler)
-    input(type="hidden" name="imageName" value=imageName)
-    input(type="hidden" name="mainFile" value=mainFile)
-    if brandVariationId
-      input(type="hidden" name="brandVariationId" value=brandVariationId)
-    input(hidden type="submit")
+	form(
+		data-ol-regular-form
+		data-ol-auto-submit
+		method='POST'
+		action='/project/new/template/'
+	)
+		input(type="hidden", name="_csrf", value=csrfToken)
+		input(type="hidden" name="templateId" value=templateId)
+		input(type="hidden" name="templateVersionId" value=templateVersionId)
+		input(type="hidden" name="templateName" value=name)
+		input(type="hidden" name="compiler" value=compiler)
+		input(type="hidden" name="imageName" value=imageName)
+		input(type="hidden" name="mainFile" value=mainFile)
+		if brandVariationId
+			input(type="hidden" name="brandVariationId" value=brandVariationId)
+		input(hidden type="submit")

+ 1 - 1
services/web/app/views/project/ide-react-detached.pug

@@ -11,7 +11,7 @@ block vars
 	- metadata.robotsNoindexNofollow = true
 	- metadata.robotsNoindexNofollow = true
 
 
 block content
 block content
-	#pdf-preview-detached-root()
+	#pdf-preview-detached-root
 
 
 block append meta
 block append meta
 	include editor/_meta
 	include editor/_meta

+ 7 - 2
services/web/app/views/project/ide-react.pug

@@ -15,7 +15,7 @@ block content
 	main#ide-root
 	main#ide-root
 		.loading-screen
 		.loading-screen
 			.loading-screen-brand-container
 			.loading-screen-brand-container
-				.loading-screen-brand(style="height: 20%;")
+				.loading-screen-brand(style='height: 20%')
 			h3.loading-screen-label #{translate("loading")}
 			h3.loading-screen-label #{translate("loading")}
 				span.loading-screen-ellip .
 				span.loading-screen-ellip .
 				span.loading-screen-ellip .
 				span.loading-screen-ellip .
@@ -25,4 +25,9 @@ block append meta
 	include editor/_meta
 	include editor/_meta
 
 
 block prepend foot-scripts
 block prepend foot-scripts
-	script(type="text/javascript", nonce=scriptNonce, src=(wsUrl || '/socket.io') + '/socket.io.js', defer=deferScripts)
+	script(
+		type='text/javascript'
+		nonce=scriptNonce
+		src=(wsUrl || '/socket.io') + '/socket.io.js'
+		defer=deferScripts
+	)

+ 3 - 3
services/web/app/views/project/invite/not-valid.pug

@@ -1,18 +1,18 @@
 extends ../../layout-marketing
 extends ../../layout-marketing
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		.container
 		.container
 			.row
 			.row
 				.col-md-8.col-md-offset-2.offset-md-2
 				.col-md-8.col-md-offset-2.offset-md-2
 					.card.project-invite-invalid
 					.card.project-invite-invalid
 						.card-body
 						.card-body
 							.page-header.text-center
 							.page-header.text-center
-								h1  #{translate("invite_not_valid")}
+								h1 #{translate("invite_not_valid")}
 							.row.text-center
 							.row.text-center
 								.col-12.col-md-12
 								.col-12.col-md-12
 									p
 									p
 										| #{translate("invite_not_valid_description")}.
 										| #{translate("invite_not_valid_description")}.
 							.row.text-center.actions
 							.row.text-center.actions
 								.col-12.col-md-12
 								.col-12.col-md-12
-									a.btn.btn-secondary-info.btn-secondary(href="/project") #{translate("back_to_your_projects")}
+									a.btn.btn-secondary-info.btn-secondary(href='/project') #{translate("back_to_your_projects")}

+ 8 - 8
services/web/app/views/project/invite/show.pug

@@ -1,7 +1,7 @@
 extends ../../layout-marketing
 extends ../../layout-marketing
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		.container
 		.container
 			.row
 			.row
 				.col-12.col-md-8.col-md-offset-2.offset-md-2
 				.col-12.col-md-8.col-md-offset-2.offset-md-2
@@ -20,16 +20,16 @@ block content
 								.col-12.col-md-12
 								.col-12.col-md-12
 									form.form(
 									form.form(
 										data-ol-regular-form
 										data-ol-regular-form
-										method="POST",
-										action="/project/"+invite.projectId+"/invite/token/"+token+"/accept"
+										method='POST'
+										action='/project/' + invite.projectId + '/invite/token/' + token + '/accept'
 									)
 									)
-										input(name='_csrf', type='hidden', value=csrfToken)
-										input(name='token', type='hidden', value=token)
+										input(name='_csrf' type='hidden' value=csrfToken)
+										input(name='token' type='hidden' value=token)
 										.form-group.text-center
 										.form-group.text-center
 											button.btn.btn-lg.btn-primary(
 											button.btn.btn-lg.btn-primary(
-												type="submit"
+												type='submit'
 												data-ol-disabled-inflight
 												data-ol-disabled-inflight
 											)
 											)
-												span(data-ol-inflight="idle") #{translate("join_project")}
-												span(hidden data-ol-inflight="pending") #{translate("joining")}…
+												span(data-ol-inflight='idle') #{translate("join_project")}
+												span(hidden data-ol-inflight='pending') #{translate("joining")}…
 										.form-group.text-center
 										.form-group.text-center

+ 77 - 29
services/web/app/views/project/list-react.pug

@@ -9,35 +9,83 @@ block vars
 	- const suppressFooter = true
 	- const suppressFooter = true
 
 
 block append meta
 block append meta
-	meta(name="ol-usersBestSubscription" data-type="json" content=usersBestSubscription)
-	meta(name="ol-notifications" data-type="json" content=notifications)
-	meta(name="ol-notificationsInstitution" data-type="json" content=notificationsInstitution)
-	meta(name="ol-userEmails" data-type="json" content=userEmails)
-	meta(name="ol-allInReconfirmNotificationPeriods" data-type="json" content=allInReconfirmNotificationPeriods)
-	meta(name="ol-user" data-type="json" content=user)
-	meta(name="ol-userAffiliations" data-type="json" content=userAffiliations)
-	meta(name="ol-reconfirmedViaSAML" content=reconfirmedViaSAML)
-	meta(name="ol-survey" data-type="json" content=survey)
-	meta(name="ol-tags" data-type="json" content=tags)
-	meta(name="ol-portalTemplates" data-type="json" content=portalTemplates)
-	meta(name="ol-prefetchedProjectsBlob" data-type="json" content=prefetchedProjectsBlob)
-	if (suggestedLanguageSubdomainConfig)
-		meta(name="ol-suggestedLanguage" data-type="json" content=Object.assign(suggestedLanguageSubdomainConfig, {
-			lngName: translate(suggestedLanguageSubdomainConfig.lngCode),
-			imgUrl: buildImgPath("flags/24/" + suggestedLanguageSubdomainConfig.lngCode + ".png")
-		}))
-	meta(name="ol-currentUrl" data-type="string" content=currentUrl)
-	meta(name="ol-showGroupsAndEnterpriseBanner" data-type="boolean" content=showGroupsAndEnterpriseBanner)
-	meta(name="ol-groupsAndEnterpriseBannerVariant" data-type="string" content=groupsAndEnterpriseBannerVariant)
-	meta(name="ol-showInrGeoBanner" data-type="boolean" content=showInrGeoBanner)
-	meta(name="ol-showBrlGeoBanner" data-type="boolean" content=showBrlGeoBanner)
-	meta(name="ol-recommendedCurrency" data-type="string" content=recommendedCurrency)
-	meta(name="ol-showLATAMBanner" data-type="boolean" content=showLATAMBanner)
-	meta(name="ol-groupSubscriptionsPendingEnrollment" data-type="json" content=groupSubscriptionsPendingEnrollment)
-	meta(name="ol-hasIndividualPaidSubscription" data-type="boolean" content=hasIndividualPaidSubscription)
-	meta(name="ol-groupSsoSetupSuccess" data-type="boolean" content=groupSsoSetupSuccess)
-	meta(name="ol-showUSGovBanner" data-type="boolean" content=showUSGovBanner)
-	meta(name="ol-usGovBannerVariant" data-type="string" content=usGovBannerVariant)
+	meta(
+		name='ol-usersBestSubscription'
+		data-type='json'
+		content=usersBestSubscription
+	)
+	meta(name='ol-notifications' data-type='json' content=notifications)
+	meta(
+		name='ol-notificationsInstitution'
+		data-type='json'
+		content=notificationsInstitution
+	)
+	meta(name='ol-userEmails' data-type='json' content=userEmails)
+	meta(
+		name='ol-allInReconfirmNotificationPeriods'
+		data-type='json'
+		content=allInReconfirmNotificationPeriods
+	)
+	meta(name='ol-user' data-type='json' content=user)
+	meta(name='ol-userAffiliations' data-type='json' content=userAffiliations)
+	meta(name='ol-reconfirmedViaSAML' content=reconfirmedViaSAML)
+	meta(name='ol-survey' data-type='json' content=survey)
+	meta(name='ol-tags' data-type='json' content=tags)
+	meta(name='ol-portalTemplates' data-type='json' content=portalTemplates)
+	meta(
+		name='ol-prefetchedProjectsBlob'
+		data-type='json'
+		content=prefetchedProjectsBlob
+	)
+	if suggestedLanguageSubdomainConfig
+		meta(
+			name='ol-suggestedLanguage'
+			data-type='json'
+			content=Object.assign(suggestedLanguageSubdomainConfig, {
+				lngName: translate(suggestedLanguageSubdomainConfig.lngCode),
+				imgUrl: buildImgPath('flags/24/' + suggestedLanguageSubdomainConfig.lngCode + '.png'),
+			})
+		)
+	meta(name='ol-currentUrl' data-type='string' content=currentUrl)
+	meta(
+		name='ol-showGroupsAndEnterpriseBanner'
+		data-type='boolean'
+		content=showGroupsAndEnterpriseBanner
+	)
+	meta(
+		name='ol-groupsAndEnterpriseBannerVariant'
+		data-type='string'
+		content=groupsAndEnterpriseBannerVariant
+	)
+	meta(name='ol-showInrGeoBanner' data-type='boolean' content=showInrGeoBanner)
+	meta(name='ol-showBrlGeoBanner' data-type='boolean' content=showBrlGeoBanner)
+	meta(
+		name='ol-recommendedCurrency'
+		data-type='string'
+		content=recommendedCurrency
+	)
+	meta(name='ol-showLATAMBanner' data-type='boolean' content=showLATAMBanner)
+	meta(
+		name='ol-groupSubscriptionsPendingEnrollment'
+		data-type='json'
+		content=groupSubscriptionsPendingEnrollment
+	)
+	meta(
+		name='ol-hasIndividualPaidSubscription'
+		data-type='boolean'
+		content=hasIndividualPaidSubscription
+	)
+	meta(
+		name='ol-groupSsoSetupSuccess'
+		data-type='boolean'
+		content=groupSsoSetupSuccess
+	)
+	meta(name='ol-showUSGovBanner' data-type='boolean' content=showUSGovBanner)
+	meta(
+		name='ol-usGovBannerVariant'
+		data-type='string'
+		content=usGovBannerVariant
+	)
 
 
 block content
 block content
 	#project-list-root
 	#project-list-root

+ 5 - 5
services/web/app/views/project/token/access-react.pug

@@ -7,11 +7,11 @@ block vars
 	- var suppressFooter = true
 	- var suppressFooter = true
 	- var suppressCookieBanner = true
 	- var suppressCookieBanner = true
 	- var suppressSkipToContent = true
 	- var suppressSkipToContent = true
-		
+
 block append meta
 block append meta
-	meta(name="ol-postUrl" data-type="string" content=postUrl)
-	meta(name="ol-user" data-type="json" content=user)
+	meta(name='ol-postUrl' data-type='string' content=postUrl)
+	meta(name='ol-user' data-type='json' content=user)
 
 
 block content
 block content
-	.content.content-alt#main-content
-		div#token-access-page
+	#main-content.content.content-alt
+		#token-access-page

+ 4 - 4
services/web/app/views/project/token/sharing-updates.pug

@@ -9,9 +9,9 @@ block vars
 	- var suppressSkipToContent = true
 	- var suppressSkipToContent = true
 
 
 block append meta
 block append meta
-	meta(name="ol-user" data-type="json" content=user)
-	meta(name="ol-project_id" data-type="string" content=projectId)
+	meta(name='ol-user' data-type='json' content=user)
+	meta(name='ol-project_id' data-type='string' content=projectId)
 
 
 block content
 block content
-	.content.content-alt#main-content
-		div#sharing-updates-page
+	#main-content.content.content-alt
+		#sharing-updates-page

+ 11 - 7
services/web/app/views/subscriptions/add-seats.pug

@@ -4,13 +4,17 @@ block entrypointVar
 	- entrypoint = 'pages/user/subscription/group-management/add-seats'
 	- entrypoint = 'pages/user/subscription/group-management/add-seats'
 
 
 block append meta
 block append meta
-	meta(name="ol-user" data-type="json" content=user)
-	meta(name="ol-groupName", data-type="string", content=groupName)
-	meta(name="ol-subscriptionId", data-type="string", content=subscriptionId)
-	meta(name="ol-totalLicenses", data-type="number", content=totalLicenses)
-	meta(name="ol-isProfessional", data-type="boolean", content=isProfessional)
-	meta(name="ol-isCollectionMethodManual", data-type="boolean", content=isCollectionMethodManual)
+	meta(name='ol-user' data-type='json' content=user)
+	meta(name='ol-groupName' data-type='string' content=groupName)
+	meta(name='ol-subscriptionId' data-type='string' content=subscriptionId)
+	meta(name='ol-totalLicenses' data-type='number' content=totalLicenses)
+	meta(name='ol-isProfessional' data-type='boolean' content=isProfessional)
+	meta(
+		name='ol-isCollectionMethodManual'
+		data-type='boolean'
+		content=isCollectionMethodManual
+	)
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		#add-seats-root
 		#add-seats-root

+ 2 - 2
services/web/app/views/subscriptions/canceled-subscription-react.pug

@@ -4,7 +4,7 @@ block entrypointVar
 	- entrypoint = 'pages/user/subscription/canceled-subscription'
 	- entrypoint = 'pages/user/subscription/canceled-subscription'
 
 
 block append meta
 block append meta
-	meta(name="ol-user" data-type="json" content=user)
+	meta(name='ol-user' data-type='json' content=user)
 
 
 block content
 block content
-	main.content.content-alt#subscription-canceled-root
+	main#subscription-canceled-root.content.content-alt

+ 82 - 27
services/web/app/views/subscriptions/dashboard-react.pug

@@ -1,36 +1,91 @@
 extends ../layout-react
 extends ../layout-react
 
 
 block entrypointVar
 block entrypointVar
-  - entrypoint = 'pages/user/subscription/dashboard'
+	- entrypoint = 'pages/user/subscription/dashboard'
 
 
 block head-scripts
 block head-scripts
-  script(type="text/javascript", nonce=scriptNonce, src="https://js.recurly.com/v4/recurly.js")
+	script(
+		type='text/javascript'
+		nonce=scriptNonce
+		src='https://js.recurly.com/v4/recurly.js'
+	)
 
 
 block append meta
 block append meta
-  meta(name="ol-subscription" data-type="json" content=personalSubscription)
-  meta(name="ol-userCanExtendTrial" data-type="boolean" content=userCanExtendTrial)
-  meta(name="ol-managedGroupSubscriptions" data-type="json" content=managedGroupSubscriptions)
-  meta(name="ol-memberGroupSubscriptions" data-type="json" content=memberGroupSubscriptions)
-  meta(name="ol-managedInstitutions" data-type="json" content=managedInstitutions)
-  meta(name="ol-managedPublishers" data-type="json" content=managedPublishers)
-  meta(name="ol-planCodesChangingAtTermEnd" data-type="json", content=planCodesChangingAtTermEnd)
-  meta(name="ol-currentInstitutionsWithLicence" data-type="json" content=currentInstitutionsWithLicence)
-  meta(name="ol-hasSubscription" data-type="boolean" content=hasSubscription)
-  meta(name="ol-fromPlansPage" data-type="boolean" content=fromPlansPage)
-  meta(name="ol-plans" data-type="json" content=plans)
-  meta(name="ol-groupSettingsAdvertisedFor" data-type="json" content=groupSettingsAdvertisedFor)
-  meta(name="ol-canUseFlexibleLicensing" data-type="boolean", content=canUseFlexibleLicensing)
-  meta(name="ol-showGroupDiscount" data-type="boolean", content=showGroupDiscount)
-  meta(name="ol-groupSettingsEnabledFor" data-type="json" content=groupSettingsEnabledFor)
-  meta(name="ol-hasAiAssistViaWritefull" data-type="boolean", content=hasAiAssistViaWritefull)
-  meta(name="ol-aiAssistViaWritefullSource" data-type="string", content=aiAssistViaWritefullSource)
-  meta(name="ol-user" data-type="json" content=user)
-  if (personalSubscription && personalSubscription.payment)
-    meta(name="ol-recurlyApiKey" content=settings.apis.recurly.publicKey)
-    meta(name="ol-stripeUKApiKey" content=settings.apis.stripeUK.publishableKey)
-    meta(name="ol-recommendedCurrency" content=personalSubscription.payment.currency)
-    meta(name="ol-groupPlans" data-type="json" content=groupPlans)
+	meta(name='ol-subscription' data-type='json' content=personalSubscription)
+	meta(
+		name='ol-userCanExtendTrial'
+		data-type='boolean'
+		content=userCanExtendTrial
+	)
+	meta(
+		name='ol-managedGroupSubscriptions'
+		data-type='json'
+		content=managedGroupSubscriptions
+	)
+	meta(
+		name='ol-memberGroupSubscriptions'
+		data-type='json'
+		content=memberGroupSubscriptions
+	)
+	meta(
+		name='ol-managedInstitutions'
+		data-type='json'
+		content=managedInstitutions
+	)
+	meta(name='ol-managedPublishers' data-type='json' content=managedPublishers)
+	meta(
+		name='ol-planCodesChangingAtTermEnd'
+		data-type='json'
+		content=planCodesChangingAtTermEnd
+	)
+	meta(
+		name='ol-currentInstitutionsWithLicence'
+		data-type='json'
+		content=currentInstitutionsWithLicence
+	)
+	meta(name='ol-hasSubscription' data-type='boolean' content=hasSubscription)
+	meta(name='ol-fromPlansPage' data-type='boolean' content=fromPlansPage)
+	meta(name='ol-plans' data-type='json' content=plans)
+	meta(
+		name='ol-groupSettingsAdvertisedFor'
+		data-type='json'
+		content=groupSettingsAdvertisedFor
+	)
+	meta(
+		name='ol-canUseFlexibleLicensing'
+		data-type='boolean'
+		content=canUseFlexibleLicensing
+	)
+	meta(
+		name='ol-showGroupDiscount'
+		data-type='boolean'
+		content=showGroupDiscount
+	)
+	meta(
+		name='ol-groupSettingsEnabledFor'
+		data-type='json'
+		content=groupSettingsEnabledFor
+	)
+	meta(
+		name='ol-hasAiAssistViaWritefull'
+		data-type='boolean'
+		content=hasAiAssistViaWritefull
+	)
+	meta(
+		name='ol-aiAssistViaWritefullSource'
+		data-type='string'
+		content=aiAssistViaWritefullSource
+	)
+	meta(name='ol-user' data-type='json' content=user)
+	if personalSubscription && personalSubscription.payment
+		meta(name='ol-recurlyApiKey' content=settings.apis.recurly.publicKey)
+		meta(name='ol-stripeUKApiKey' content=settings.apis.stripeUK.publishableKey)
+		meta(
+			name='ol-recommendedCurrency'
+			content=personalSubscription.payment.currency
+		)
+		meta(name='ol-groupPlans' data-type='json' content=groupPlans)
 
 
 block content
 block content
-  main.content.content-alt#main-content
-    #subscription-dashboard-root
+	main#main-content.content.content-alt
+		#subscription-dashboard-root

+ 3 - 3
services/web/app/views/subscriptions/manually-collected-subscription.pug

@@ -4,8 +4,8 @@ block entrypointVar
 	- entrypoint = 'pages/user/subscription/group-management/manually-collected-subscription'
 	- entrypoint = 'pages/user/subscription/group-management/manually-collected-subscription'
 
 
 block append meta
 block append meta
-	meta(name="ol-user" data-type="json" content=user)
-	meta(name="ol-groupName", data-type="string", content=groupName)
+	meta(name='ol-user' data-type='json' content=user)
+	meta(name='ol-groupName' data-type='string' content=groupName)
 
 
 block content
 block content
-	main.content.content-alt#manually-collected-subscription-root
+	main#manually-collected-subscription-root.content.content-alt

+ 3 - 3
services/web/app/views/subscriptions/missing-billing-information.pug

@@ -4,8 +4,8 @@ block entrypointVar
 	- entrypoint = 'pages/user/subscription/group-management/missing-billing-information'
 	- entrypoint = 'pages/user/subscription/group-management/missing-billing-information'
 
 
 block append meta
 block append meta
-	meta(name="ol-user" data-type="json" content=user)
-	meta(name="ol-groupName", data-type="string", content=groupName)
+	meta(name='ol-user' data-type='json' content=user)
+	meta(name='ol-groupName' data-type='string' content=groupName)
 
 
 block content
 block content
-	main.content.content-alt#missing-billing-information-root
+	main#missing-billing-information-root.content.content-alt

+ 29 - 41
services/web/app/views/subscriptions/plans/_faq_new.pug

@@ -9,7 +9,7 @@ include ../../_mixins/material_symbol
 	.row.row-spaced-extra-large
 	.row.row-spaced-extra-large
 		.col-md-12.faq-heading-container
 		.col-md-12.faq-heading-container
 			h2
 			h2
-				+eyebrow(translate("frequently_asked_questions"))
+				+eyebrow(translate('frequently_asked_questions'))
 				| #{translate("your_questions_answered")}
 				| #{translate("your_questions_answered")}
 
 
 	.row
 	.row
@@ -18,74 +18,62 @@ include ../../_mixins/material_symbol
 				class={
 				class={
 					'plans-faq-tabs': bootstrapVersion === 5,
 					'plans-faq-tabs': bootstrapVersion === 5,
 					'ol-tabs': bootstrapVersion === 5,
 					'ol-tabs': bootstrapVersion === 5,
-					'ol-tabs-scrollable': bootstrapVersion === 3
+					'ol-tabs-scrollable': bootstrapVersion === 3,
 				}
 				}
 			)
 			)
 				.nav-tabs-container
 				.nav-tabs-container
-					ul.nav.nav-tabs(role="tablist")
+					ul.nav.nav-tabs(role='tablist')
 						//- In the bs5 version of plans page, the `active` class need to be added to the `a` tag instead of the parent `li` tag
 						//- In the bs5 version of plans page, the `active` class need to be added to the `a` tag instead of the parent `li` tag
 						//- If the `plans-page-bs5` split test has been completed, remove the `active` class from the `li` tag since we're not using it anymore
 						//- If the `plans-page-bs5` split test has been completed, remove the `active` class from the `li` tag since we're not using it anymore
 						//- If the `plans-page-bs5` split test has been completed, remove the `data-toggle` because it is not needed anymore (bs5 uses `data-bs-toggle`)
 						//- If the `plans-page-bs5` split test has been completed, remove the `data-toggle` because it is not needed anymore (bs5 uses `data-bs-toggle`)
-						li(
-							role="presentation"
-							class={
-								active: bootstrapVersion === 3
-							}
-						)
+						li(role='presentation' class={
+							active: bootstrapVersion === 3,
+						})
 							a(
 							a(
-								role="tab"
-								data-toggle="tab"
-								data-bs-toggle="tab"
+								role='tab'
+								data-toggle='tab'
+								data-bs-toggle='tab'
 								href='#' + managingYourSubscription
 								href='#' + managingYourSubscription
 								aria-controls=managingYourSubscription
 								aria-controls=managingYourSubscription
 								class={
 								class={
-									active: bootstrapVersion === 5
+									active: bootstrapVersion === 5,
 								}
 								}
 							)
 							)
 								| #{translate('managing_your_subscription')}
 								| #{translate('managing_your_subscription')}
-						li(role="presentation")
+						li(role='presentation')
 							a(
 							a(
-								role="tab"
-								data-toggle="tab"
-								data-bs-toggle="tab"
+								role='tab'
+								data-toggle='tab'
+								data-bs-toggle='tab'
 								href='#' + overleafIndividualPlans
 								href='#' + overleafIndividualPlans
 								aria-controls=overleafIndividualPlans
 								aria-controls=overleafIndividualPlans
 							)
 							)
 								| #{translate('overleaf_individual_plans')}
 								| #{translate('overleaf_individual_plans')}
-						li(role="presentation")
+						li(role='presentation')
 							a(
 							a(
-								role="tab"
-								data-toggle="tab"
-								data-bs-toggle="tab"
+								role='tab'
+								data-toggle='tab'
+								data-bs-toggle='tab'
 								href='#' + overleafGroupPlans
 								href='#' + overleafGroupPlans
 								aria-controls=overleafGroupPlans
 								aria-controls=overleafGroupPlans
 							)
 							)
 								| #{translate('overleaf_group_plans')}
 								| #{translate('overleaf_group_plans')}
 
 
 				.tab-content
 				.tab-content
-					.tab-pane.active(
-						role="tabpanel"
-						id=managingYourSubscription
-					)
-						+managingYourSubscription()
-					.tab-pane(
-						role="tabpanel"
-						id=overleafIndividualPlans
-					)
-						+overleafIndividualPlans()
-					.tab-pane(
-						role="tabpanel"
-						id=overleafGroupPlans
-					)
-						+overleafGroupPlans()
+					.tab-pane.active(role='tabpanel' id=managingYourSubscription)
+						+managingYourSubscription
+					.tab-pane(role='tabpanel' id=overleafIndividualPlans)
+						+overleafIndividualPlans
+					.tab-pane(role='tabpanel' id=overleafGroupPlans)
+						+overleafGroupPlans
 
 
 	.row
 	.row
 		.col-xs-12.plans-faq-support
 		.col-xs-12.plans-faq-support
 			span #{translate('still_have_questions')}
 			span #{translate('still_have_questions')}
 			button(
 			button(
-				data-ol-open-contact-form-modal="general"
-				data-bs-toggle=bootstrapVersion === 5 ? "modal" : undefined
-				data-bs-target=bootstrapVersion === 5 ? "#contactUsModal" : undefined
+				data-ol-open-contact-form-modal='general'
+				data-bs-toggle=bootstrapVersion === 5 ? 'modal' : undefined
+				data-bs-target=bootstrapVersion === 5 ? '#contactUsModal' : undefined
 			)
 			)
-				span(style="margin-right: 4px") #{translate('contact_support')}
-				+material-symbol-rounded("arrow_right_alt", "icon-md")
+				span(style='margin-right: 4px') #{translate('contact_support')}
+				+material-symbol-rounded('arrow_right_alt', 'icon-md')

+ 217 - 221
services/web/app/views/subscriptions/plans/_plans_faq_tabs.pug

@@ -1,357 +1,353 @@
 //- If the `plans-page-bs5` split test has been completed, remove the `data-toggle` and `data-target` because it is not needed anymore (bs5 uses `data-bs-toggle` and `data-bs-target`)
 //- If the `plans-page-bs5` split test has been completed, remove the `data-toggle` and `data-target` because it is not needed anymore (bs5 uses `data-bs-toggle` and `data-bs-target`)
 include ../../_mixins/material_symbol
 include ../../_mixins/material_symbol
-	
-mixin managingYourSubscription()
+
+mixin managingYourSubscription
 	.ol-accordions-container
 	.ol-accordions-container
 		.custom-accordion-item
 		.custom-accordion-item
 			button.custom-accordion-header.collapsed(
 			button.custom-accordion-header.collapsed(
-				type="button"
-				data-toggle="collapse"
-				data-target="#managingYourSubscriptionQ1"
-				data-bs-toggle="collapse"
-				data-bs-target="#managingYourSubscriptionQ1"
-				aria-expanded="false"
-				aria-controls="managingYourSubscriptionQ1"
+				type='button'
+				data-toggle='collapse'
+				data-target='#managingYourSubscriptionQ1'
+				data-bs-toggle='collapse'
+				data-bs-target='#managingYourSubscriptionQ1'
+				aria-expanded='false'
+				aria-controls='managingYourSubscriptionQ1'
 			)
 			)
 				| Can I change plans or cancel later?
 				| Can I change plans or cancel later?
 				span.custom-accordion-icon
 				span.custom-accordion-icon
-					+material-symbol-outlined("keyboard_arrow_down")
-			.collapse(id="managingYourSubscriptionQ1")
+					+material-symbol-outlined('keyboard_arrow_down')
+			.collapse(id='managingYourSubscriptionQ1')
 				.custom-accordion-body
 				.custom-accordion-body
-					span Yes, you can do this at any time by going to 
-					strong Account > Subscription 
+					span Yes, you can do this at any time by going to
+					strong Account > Subscription
 					span when logged in to Overleaf. You can change plans, switch between monthly and annual billing options, or cancel to downgrade to the free plan. When canceling, your subscription will continue until the end of the billing period.
 					span when logged in to Overleaf. You can change plans, switch between monthly and annual billing options, or cancel to downgrade to the free plan. When canceling, your subscription will continue until the end of the billing period.
 		.custom-accordion-item
 		.custom-accordion-item
 			button.custom-accordion-header.collapsed(
 			button.custom-accordion-header.collapsed(
-				type="button"
-				data-toggle="collapse"
-				data-target="#managingYourSubscriptionQ2"
-				data-bs-toggle="collapse"
-				data-bs-target="#managingYourSubscriptionQ2"
-				aria-expanded="false"
-				aria-controls="managingYourSubscriptionQ2"
+				type='button'
+				data-toggle='collapse'
+				data-target='#managingYourSubscriptionQ2'
+				data-bs-toggle='collapse'
+				data-bs-target='#managingYourSubscriptionQ2'
+				aria-expanded='false'
+				aria-controls='managingYourSubscriptionQ2'
 			)
 			)
 				| If I change or cancel my Overleaf plan, will I lose my projects?
 				| If I change or cancel my Overleaf plan, will I lose my projects?
 				span.custom-accordion-icon
 				span.custom-accordion-icon
-					+material-symbol-outlined("keyboard_arrow_down")
-			.collapse(id="managingYourSubscriptionQ2")
+					+material-symbol-outlined('keyboard_arrow_down')
+			.collapse(id='managingYourSubscriptionQ2')
 				.custom-accordion-body
 				.custom-accordion-body
 					| No. Changing or canceling your plan won’t affect your projects, the only change will be to the features available to you. You can see which features are available only on paid plans in the comparison table.
 					| No. Changing or canceling your plan won’t affect your projects, the only change will be to the features available to you. You can see which features are available only on paid plans in the comparison table.
 		.custom-accordion-item
 		.custom-accordion-item
 			button.custom-accordion-header.collapsed(
 			button.custom-accordion-header.collapsed(
-				type="button"
-				data-toggle="collapse"
-				data-target="#managingYourSubscriptionQ3"
-				data-bs-toggle="collapse"
-				data-bs-target="#managingYourSubscriptionQ3"
-				aria-expanded="false"
-				aria-controls="managingYourSubscriptionQ3"
+				type='button'
+				data-toggle='collapse'
+				data-target='#managingYourSubscriptionQ3'
+				data-bs-toggle='collapse'
+				data-bs-target='#managingYourSubscriptionQ3'
+				aria-expanded='false'
+				aria-controls='managingYourSubscriptionQ3'
 			)
 			)
 				| Can I pay by invoice or purchase order?
 				| Can I pay by invoice or purchase order?
 				span.custom-accordion-icon
 				span.custom-accordion-icon
-					+material-symbol-outlined("keyboard_arrow_down")
-			.collapse(id="managingYourSubscriptionQ3")
+					+material-symbol-outlined('keyboard_arrow_down')
+			.collapse(id='managingYourSubscriptionQ3')
 				.custom-accordion-body
 				.custom-accordion-body
 					| This is possible when you’re purchasing a group subscription for five or more people, or a site license. For individual subscriptions, we can only accept payment online via credit card, debit card, or PayPal.
 					| This is possible when you’re purchasing a group subscription for five or more people, or a site license. For individual subscriptions, we can only accept payment online via credit card, debit card, or PayPal.
 		.custom-accordion-item
 		.custom-accordion-item
 			button.custom-accordion-header.collapsed(
 			button.custom-accordion-header.collapsed(
-				type="button"
-				data-toggle="collapse"
-				data-target="#managingYourSubscriptionQ4"
-				data-bs-toggle="collapse"
-				data-bs-target="#managingYourSubscriptionQ4"
-				aria-expanded="false"
-				aria-controls="managingYourSubscriptionQ4"
+				type='button'
+				data-toggle='collapse'
+				data-target='#managingYourSubscriptionQ4'
+				data-bs-toggle='collapse'
+				data-bs-target='#managingYourSubscriptionQ4'
+				aria-expanded='false'
+				aria-controls='managingYourSubscriptionQ4'
 			)
 			)
 				| How do I view/update the credit card being charged for my subscription?
 				| How do I view/update the credit card being charged for my subscription?
 				span.custom-accordion-icon
 				span.custom-accordion-icon
-					+material-symbol-outlined("keyboard_arrow_down")
-			.collapse(id="managingYourSubscriptionQ4")
+					+material-symbol-outlined('keyboard_arrow_down')
+			.collapse(id='managingYourSubscriptionQ4')
 				.custom-accordion-body
 				.custom-accordion-body
-					| You can view and update the card on file by going to Account > 
+					| You can view and update the card on file by going to Account >
 					a.inline-green-link(
 					a.inline-green-link(
-						target="_blank"
-						href="/user/subscription"
-						event-tracking="plans-page-click"
-						event-tracking-mb="true"
-						event-tracking-trigger="click"
-						event-segmentation={ button: 'contact', location: 'faq' }
+						target='_blank'
+						href='/user/subscription'
+						event-tracking='plans-page-click'
+						event-tracking-mb='true'
+						event-tracking-trigger='click'
+						event-segmentation={button: 'contact', location: 'faq'}
 					)
 					)
 						span Subscription
 						span Subscription
 					| .
 					| .
 
 
-
-
-
-mixin overleafIndividualPlans()
+mixin overleafIndividualPlans
 	.ol-accordions-container
 	.ol-accordions-container
 		.custom-accordion-item
 		.custom-accordion-item
 			button.custom-accordion-header.collapsed(
 			button.custom-accordion-header.collapsed(
-				type="button"
-				data-toggle="collapse"
-				data-target="#overleafIndividualPlansQ1"
-				data-bs-toggle="collapse"
-				data-bs-target="#overleafIndividualPlansQ1"
-				aria-expanded="false"
-				aria-controls="overleafIndividualPlansQ1"
+				type='button'
+				data-toggle='collapse'
+				data-target='#overleafIndividualPlansQ1'
+				data-bs-toggle='collapse'
+				data-bs-target='#overleafIndividualPlansQ1'
+				aria-expanded='false'
+				aria-controls='overleafIndividualPlansQ1'
 			)
 			)
 				| How does the free trial work?
 				| How does the free trial work?
 				span.custom-accordion-icon
 				span.custom-accordion-icon
-					+material-symbol-outlined("keyboard_arrow_down")
-			.collapse(id="overleafIndividualPlansQ1")
+					+material-symbol-outlined('keyboard_arrow_down')
+			.collapse(id='overleafIndividualPlansQ1')
 				.custom-accordion-body
 				.custom-accordion-body
-					span You get full access to your chosen plan during your 7-day free trial, and there’s no obligation to continue beyond the trial. Your card will be charged at the end of your trial unless you cancel before then. To cancel, go to 
-					strong Account > 
+					span You get full access to your chosen plan during your 7-day free trial, and there’s no obligation to continue beyond the trial. Your card will be charged at the end of your trial unless you cancel before then. To cancel, go to
+					strong Account >
+						|
 						a.inline-green-link(
 						a.inline-green-link(
-							target="_blank"
-							href="/user/subscription"
-							event-tracking="plans-page-click"
-							event-tracking-mb="true"
-							event-tracking-trigger="click"
-							event-segmentation={ button: 'contact', location: 'faq' }
+							target='_blank'
+							href='/user/subscription'
+							event-tracking='plans-page-click'
+							event-tracking-mb='true'
+							event-tracking-trigger='click'
+							event-segmentation={button: 'contact', location: 'faq'}
 						)
 						)
 							span Subscription
 							span Subscription
-					span  when logged in to Overleaf (the trial will continue for the full 7 days).
+					span when logged in to Overleaf (the trial will continue for the full 7 days).
 
 
 		.custom-accordion-item
 		.custom-accordion-item
 			button.custom-accordion-header.collapsed(
 			button.custom-accordion-header.collapsed(
-				type="button"
-				data-toggle="collapse"
-				data-target="#overleafIndividualPlansQ2"
-				data-bs-toggle="collapse"
-				data-bs-target="#overleafIndividualPlansQ2"
-				aria-expanded="false"
-				aria-controls="overleafIndividualPlansQ2"
+				type='button'
+				data-toggle='collapse'
+				data-target='#overleafIndividualPlansQ2'
+				data-bs-toggle='collapse'
+				data-bs-target='#overleafIndividualPlansQ2'
+				aria-expanded='false'
+				aria-controls='overleafIndividualPlansQ2'
 			)
 			)
 				| What’s a collaborator on an Overleaf individual subscription?
 				| What’s a collaborator on an Overleaf individual subscription?
 				span.custom-accordion-icon
 				span.custom-accordion-icon
-					+material-symbol-outlined("keyboard_arrow_down")
-			.collapse(id="overleafIndividualPlansQ2")
+					+material-symbol-outlined('keyboard_arrow_down')
+			.collapse(id='overleafIndividualPlansQ2')
 				.custom-accordion-body
 				.custom-accordion-body
-					| A collaborator is someone you invite to work with you on a project. So, for example, on our Standard plan you can have up to 10 people collaborating with you on any given project. 
+					| A collaborator is someone you invite to work with you on a project. So, for example, on our Standard plan you can have up to 10 people collaborating with you on any given project.
 
 
 		.custom-accordion-item
 		.custom-accordion-item
 			button.custom-accordion-header.collapsed(
 			button.custom-accordion-header.collapsed(
-				type="button"
-				data-toggle="collapse"
-				data-target="#overleafIndividualPlansQ3"
-				data-bs-toggle="collapse"
-				data-bs-target="#overleafIndividualPlansQ3"
-				aria-expanded="false"
-				aria-controls="overleafIndividualPlansQ3"
+				type='button'
+				data-toggle='collapse'
+				data-target='#overleafIndividualPlansQ3'
+				data-bs-toggle='collapse'
+				data-bs-target='#overleafIndividualPlansQ3'
+				aria-expanded='false'
+				aria-controls='overleafIndividualPlansQ3'
 			)
 			)
 				| The individual Standard plan has 10 project collaborators, does it mean that 10 people will be upgraded?
 				| The individual Standard plan has 10 project collaborators, does it mean that 10 people will be upgraded?
 				span.custom-accordion-icon
 				span.custom-accordion-icon
-					+material-symbol-outlined("keyboard_arrow_down")
-			.collapse(id="overleafIndividualPlansQ3")
+					+material-symbol-outlined('keyboard_arrow_down')
+			.collapse(id='overleafIndividualPlansQ3')
 				.custom-accordion-body
 				.custom-accordion-body
-					span No. Only the subscriber’s account will be upgraded. An individual Standard subscription allows you to invite 10 people per project to edit the project with you. Your collaborators can access features such as the full document history and extended compile time, but 
-					strong only 
+					span No. Only the subscriber’s account will be upgraded. An individual Standard subscription allows you to invite 10 people per project to edit the project with you. Your collaborators can access features such as the full document history and extended compile time, but
+					strong only
 					span for the project(s) they’re working on with you. If your collaborators want access to those features on their own projects, they will need to purchase their own subscription. (If you work with the same people regularly, you might find a group subscription more cost effective.)
 					span for the project(s) they’re working on with you. If your collaborators want access to those features on their own projects, they will need to purchase their own subscription. (If you work with the same people regularly, you might find a group subscription more cost effective.)
 		.custom-accordion-item
 		.custom-accordion-item
 			button.custom-accordion-header.collapsed(
 			button.custom-accordion-header.collapsed(
-				type="button"
-				data-toggle="collapse"
-				data-target="#overleafIndividualPlansQ4"
-				data-bs-toggle="collapse"
-				data-bs-target="#overleafIndividualPlansQ4"
-				aria-expanded="false"
-				aria-controls="overleafIndividualPlansQ4"
+				type='button'
+				data-toggle='collapse'
+				data-target='#overleafIndividualPlansQ4'
+				data-bs-toggle='collapse'
+				data-bs-target='#overleafIndividualPlansQ4'
+				aria-expanded='false'
+				aria-controls='overleafIndividualPlansQ4'
 			)
 			)
 				| Do collaborators also have access to the editing and collaboration features I’ve paid for?
 				| Do collaborators also have access to the editing and collaboration features I’ve paid for?
 				span.custom-accordion-icon
 				span.custom-accordion-icon
-					+material-symbol-outlined("keyboard_arrow_down")
-			.collapse(id="overleafIndividualPlansQ4")
+					+material-symbol-outlined('keyboard_arrow_down')
+			.collapse(id='overleafIndividualPlansQ4')
 				.custom-accordion-body
 				.custom-accordion-body
-					span If you have an Overleaf subscription, then your project collaborators will have access to features like real-time track changes and document history, but 
-					strong only 
+					span If you have an Overleaf subscription, then your project collaborators will have access to features like real-time track changes and document history, but
+					strong only
 					span for the project(s) they’re working on with you. If your collaborators want access to those features on their own projects, they will need to purchase their own subscription. (If you work with the same people regularly, you might find a group subscription more cost effective.)
 					span for the project(s) they’re working on with you. If your collaborators want access to those features on their own projects, they will need to purchase their own subscription. (If you work with the same people regularly, you might find a group subscription more cost effective.)
 		.custom-accordion-item
 		.custom-accordion-item
 			button.custom-accordion-header.collapsed(
 			button.custom-accordion-header.collapsed(
-				type="button"
-				data-toggle="collapse"
-				data-target="#overleafIndividualPlansQ5"
-				data-bs-toggle="collapse"
-				data-bs-target="#overleafIndividualPlansQ5"
-				aria-expanded="false"
-				aria-controls="overleafIndividualPlansQ5"
+				type='button'
+				data-toggle='collapse'
+				data-target='#overleafIndividualPlansQ5'
+				data-bs-toggle='collapse'
+				data-bs-target='#overleafIndividualPlansQ5'
+				aria-expanded='false'
+				aria-controls='overleafIndividualPlansQ5'
 			)
 			)
 				| Can I purchase an individual plan on behalf of someone else?
 				| Can I purchase an individual plan on behalf of someone else?
 				span.custom-accordion-icon
 				span.custom-accordion-icon
-					+material-symbol-outlined("keyboard_arrow_down")
-			.collapse(id="overleafIndividualPlansQ5")
+					+material-symbol-outlined('keyboard_arrow_down')
+			.collapse(id='overleafIndividualPlansQ5')
 				.custom-accordion-body
 				.custom-accordion-body
-					| Individual subscriptions must be purchased by the account that will be the end user. If you want to purchase a plan for someone else, you’ll need to provide them with relevant payment details to enable them to make the purchase. 
+					| Individual subscriptions must be purchased by the account that will be the end user. If you want to purchase a plan for someone else, you’ll need to provide them with relevant payment details to enable them to make the purchase.
 		.custom-accordion-item
 		.custom-accordion-item
 			button.custom-accordion-header.collapsed(
 			button.custom-accordion-header.collapsed(
-				type="button"
-				data-toggle="collapse"
-				data-target="#overleafIndividualPlansQ6"
-				data-bs-toggle="collapse"
-				data-bs-target="#overleafIndividualPlansQ6"
-				aria-expanded="false"
-				aria-controls="overleafIndividualPlansQ6"
+				type='button'
+				data-toggle='collapse'
+				data-target='#overleafIndividualPlansQ6'
+				data-bs-toggle='collapse'
+				data-bs-target='#overleafIndividualPlansQ6'
+				aria-expanded='false'
+				aria-controls='overleafIndividualPlansQ6'
 			)
 			)
 				| Who is eligible for the Student plan?
 				| Who is eligible for the Student plan?
 				span.custom-accordion-icon
 				span.custom-accordion-icon
-					+material-symbol-outlined("keyboard_arrow_down")
-			.collapse(id="overleafIndividualPlansQ6")
+					+material-symbol-outlined('keyboard_arrow_down')
+			.collapse(id='overleafIndividualPlansQ6')
 				.custom-accordion-body
 				.custom-accordion-body
 					| As the name suggests, the Student plan is only for students at educational institutions. This includes graduate students.
 					| As the name suggests, the Student plan is only for students at educational institutions. This includes graduate students.
 		.custom-accordion-item
 		.custom-accordion-item
 			button.custom-accordion-header.collapsed(
 			button.custom-accordion-header.collapsed(
-				type="button"
-				data-toggle="collapse"
-				data-target="#overleafIndividualPlansQ7"
-				data-bs-toggle="collapse"
-				data-bs-target="#overleafIndividualPlansQ7"
-				aria-expanded="false"
-				aria-controls="overleafIndividualPlansQ7"
+				type='button'
+				data-toggle='collapse'
+				data-target='#overleafIndividualPlansQ7'
+				data-bs-toggle='collapse'
+				data-bs-target='#overleafIndividualPlansQ7'
+				aria-expanded='false'
+				aria-controls='overleafIndividualPlansQ7'
 			)
 			)
 				| Can I transfer an individual subscription to someone else?
 				| Can I transfer an individual subscription to someone else?
 				span.custom-accordion-icon
 				span.custom-accordion-icon
-					+material-symbol-outlined("keyboard_arrow_down")
-			.collapse(id="overleafIndividualPlansQ7")
+					+material-symbol-outlined('keyboard_arrow_down')
+			.collapse(id='overleafIndividualPlansQ7')
 				.custom-accordion-body
 				.custom-accordion-body
-					| No. Individual plans can’t be transferred. 
-
-
-
-
+					| No. Individual plans can’t be transferred.
 
 
-mixin overleafGroupPlans()
+mixin overleafGroupPlans
 	.ol-accordions-container
 	.ol-accordions-container
 		.custom-accordion-item
 		.custom-accordion-item
 			button.custom-accordion-header.collapsed(
 			button.custom-accordion-header.collapsed(
-				type="button"
-				data-toggle="collapse"
-				data-target="#overleafGroupPlansQ1"
-				data-bs-toggle="collapse"
-				data-bs-target="#overleafGroupPlansQ1"
-				aria-expanded="false"
-				aria-controls="overleafGroupPlansQ1"
+				type='button'
+				data-toggle='collapse'
+				data-target='#overleafGroupPlansQ1'
+				data-bs-toggle='collapse'
+				data-bs-target='#overleafGroupPlansQ1'
+				aria-expanded='false'
+				aria-controls='overleafGroupPlansQ1'
 			)
 			)
 				| What’s the difference between users and collaborators on an Overleaf group subscription?
 				| What’s the difference between users and collaborators on an Overleaf group subscription?
 				span.custom-accordion-icon
 				span.custom-accordion-icon
-					+material-symbol-outlined("keyboard_arrow_down")
-			.collapse(id="overleafGroupPlansQ1")
+					+material-symbol-outlined('keyboard_arrow_down')
+			.collapse(id='overleafGroupPlansQ1')
 				.custom-accordion-body
 				.custom-accordion-body
-					div On any of our group plans, the number of users refers to the number of people you can invite to join your group. All of these people will have access to the plan’s paid-for features across all their projects, such as real-time track changes and document history. 
-					div.mt-2 Collaborators are people that your group users may invite to work with them on their projects. So, for example, if you have the Group Standard plan, the users in your group can invite up to 10 people to work with them on a project. And if you have the Group Professional plan, your users can invite as many people to work with them as they want.
+					div On any of our group plans, the number of users refers to the number of people you can invite to join your group. All of these people will have access to the plan’s paid-for features across all their projects, such as real-time track changes and document history.
+					.mt-2 Collaborators are people that your group users may invite to work with them on their projects. So, for example, if you have the Group Standard plan, the users in your group can invite up to 10 people to work with them on a project. And if you have the Group Professional plan, your users can invite as many people to work with them as they want.
 		.custom-accordion-item
 		.custom-accordion-item
 			button.custom-accordion-header.collapsed(
 			button.custom-accordion-header.collapsed(
-				type="button"
-				data-toggle="collapse"
-				data-target="#overleafGroupPlansQ2"
-				data-bs-toggle="collapse"
-				data-bs-target="#overleafGroupPlansQ2"
-				aria-expanded="false"
-				aria-controls="overleafGroupPlansQ2"
+				type='button'
+				data-toggle='collapse'
+				data-target='#overleafGroupPlansQ2'
+				data-bs-toggle='collapse'
+				data-bs-target='#overleafGroupPlansQ2'
+				aria-expanded='false'
+				aria-controls='overleafGroupPlansQ2'
 			)
 			)
 				| What is the benefit of purchasing an Overleaf Group plan?
 				| What is the benefit of purchasing an Overleaf Group plan?
 				span.custom-accordion-icon
 				span.custom-accordion-icon
-					+material-symbol-outlined("keyboard_arrow_down")
-			.collapse(id="overleafGroupPlansQ2")
+					+material-symbol-outlined('keyboard_arrow_down')
+			.collapse(id='overleafGroupPlansQ2')
 				.custom-accordion-body
 				.custom-accordion-body
-					| Our Group subscriptions allow you to purchase access to our premium features for multiple people. They’re easy to manage, help save on paperwork, and allow groups of 5 or more to purchase via purchase order (PO). We also offer discounts on purchases of Group subscriptions for more than 20 users; just get in touch with our 
+					| Our Group subscriptions allow you to purchase access to our premium features for multiple people. They’re easy to manage, help save on paperwork, and allow groups of 5 or more to purchase via purchase order (PO). We also offer discounts on purchases of Group subscriptions for more than 20 users; just get in touch with our
 					a.inline-green-link(
 					a.inline-green-link(
-						target="_blank"
-						href="/for/contact-sales"
-						event-tracking="plans-page-click"
-						event-tracking-mb="true"
-						event-tracking-trigger="click"
-						event-segmentation={ button: 'contact', location: 'faq' }
+						target='_blank'
+						href='/for/contact-sales'
+						event-tracking='plans-page-click'
+						event-tracking-mb='true'
+						event-tracking-trigger='click'
+						event-segmentation={button: 'contact', location: 'faq'}
 					)
 					)
 						span Sales team
 						span Sales team
 					| .
 					| .
 		.custom-accordion-item
 		.custom-accordion-item
 			button.custom-accordion-header.collapsed(
 			button.custom-accordion-header.collapsed(
-				type="button"
-				data-toggle="collapse"
-				data-target="#overleafGroupPlansQ3"
-				data-bs-toggle="collapse"
-				data-bs-target="#overleafGroupPlansQ3"
-				aria-expanded="false"
-				aria-controls="overleafGroupPlansQ3"
+				type='button'
+				data-toggle='collapse'
+				data-target='#overleafGroupPlansQ3'
+				data-bs-toggle='collapse'
+				data-bs-target='#overleafGroupPlansQ3'
+				aria-expanded='false'
+				aria-controls='overleafGroupPlansQ3'
 			)
 			)
 				| Who is eligible for the educational discount?
 				| Who is eligible for the educational discount?
 				span.custom-accordion-icon
 				span.custom-accordion-icon
-					+material-symbol-outlined("keyboard_arrow_down")
-			.collapse(id="overleafGroupPlansQ3")
+					+material-symbol-outlined('keyboard_arrow_down')
+			.collapse(id='overleafGroupPlansQ3')
 				.custom-accordion-body
 				.custom-accordion-body
-					| The educational discount for group subscriptions is for students or faculty who are using Overleaf primarily for teaching. 
+					| The educational discount for group subscriptions is for students or faculty who are using Overleaf primarily for teaching.
 		.custom-accordion-item
 		.custom-accordion-item
 			button.custom-accordion-header.collapsed(
 			button.custom-accordion-header.collapsed(
-				type="button"
-				data-toggle="collapse"
-				data-target="#overleafGroupPlansQ4"
-				data-bs-toggle="collapse"
-				data-bs-target="#overleafGroupPlansQ4"
-				aria-expanded="false"
-				aria-controls="overleafGroupPlansQ4"
+				type='button'
+				data-toggle='collapse'
+				data-target='#overleafGroupPlansQ4'
+				data-bs-toggle='collapse'
+				data-bs-target='#overleafGroupPlansQ4'
+				aria-expanded='false'
+				aria-controls='overleafGroupPlansQ4'
 			)
 			)
 				| How do I add more licenses to my group subscription, and what will it cost?
 				| How do I add more licenses to my group subscription, and what will it cost?
 				span.custom-accordion-icon
 				span.custom-accordion-icon
-					+material-symbol-outlined("keyboard_arrow_down")
-			.collapse(id="overleafGroupPlansQ4")
+					+material-symbol-outlined('keyboard_arrow_down')
+			.collapse(id='overleafGroupPlansQ4')
 				.custom-accordion-body
 				.custom-accordion-body
-					div 
-						| You can add up to 20 licenses using the 
+					div
+						| You can add up to 20 licenses using the
 						a.inline-green-link(
 						a.inline-green-link(
-							target="_blank"
-							href="/user/subscription"
-							event-tracking="plans-page-click"
-							event-tracking-mb="true"
-							event-tracking-trigger="click"
-							event-segmentation={ button: 'contact', location: 'faq' }
+							target='_blank'
+							href='/user/subscription'
+							event-tracking='plans-page-click'
+							event-tracking-mb='true'
+							event-tracking-trigger='click'
+							event-segmentation={button: 'contact', location: 'faq'}
 						)
 						)
 							span subscription management page
 							span subscription management page
-						|  accessed by going to Account > 
+						|
+						| accessed by going to Account >
 						a.inline-green-link(
 						a.inline-green-link(
-							target="_blank"
-							href="/user/subscription"
-							event-tracking="plans-page-click"
-							event-tracking-mb="true"
-							event-tracking-trigger="click"
-							event-segmentation={ button: 'contact', location: 'faq' }
+							target='_blank'
+							href='/user/subscription'
+							event-tracking='plans-page-click'
+							event-tracking-mb='true'
+							event-tracking-trigger='click'
+							event-segmentation={button: 'contact', location: 'faq'}
 						)
 						)
 							span Subscription
 							span Subscription
-						|  when logged into Overleaf. The cost per license will be prorated at the current per license rate, and will end with your existing renewal date.
-					div.mt-2 
-						| If you need more than 20 licenses added to your subscription, please 
+						|
+						| when logged into Overleaf. The cost per license will be prorated at the current per license rate, and will end with your existing renewal date.
+					.mt-2
+						| If you need more than 20 licenses added to your subscription, please
 						a.inline-green-link(
 						a.inline-green-link(
-							target="_blank"
-							href="/for/contact-sales"
-							event-tracking="plans-page-click"
-							event-tracking-mb="true"
-							event-tracking-trigger="click"
-							event-segmentation={ button: 'contact', location: 'faq' }
+							target='_blank'
+							href='/for/contact-sales'
+							event-tracking='plans-page-click'
+							event-tracking-mb='true'
+							event-tracking-trigger='click'
+							event-segmentation={button: 'contact', location: 'faq'}
 						)
 						)
 							span contact the Sales team
 							span contact the Sales team
 						| .
 						| .
 		.custom-accordion-item
 		.custom-accordion-item
 			button.custom-accordion-header.collapsed(
 			button.custom-accordion-header.collapsed(
-				type="button"
-				data-toggle="collapse"
-				data-target="#overleafGroupPlansQ5"
-				data-bs-toggle="collapse"
-				data-bs-target="#overleafGroupPlansQ5"
-				aria-expanded="false"
-				aria-controls="overleafGroupPlansQ5"
+				type='button'
+				data-toggle='collapse'
+				data-target='#overleafGroupPlansQ5'
+				data-bs-toggle='collapse'
+				data-bs-target='#overleafGroupPlansQ5'
+				aria-expanded='false'
+				aria-controls='overleafGroupPlansQ5'
 			)
 			)
 				| How do I upgrade my plan from Group Standard to Group Professional?
 				| How do I upgrade my plan from Group Standard to Group Professional?
 				span.custom-accordion-icon
 				span.custom-accordion-icon
-					+material-symbol-outlined("keyboard_arrow_down")
-			.collapse(id="overleafGroupPlansQ5")
+					+material-symbol-outlined('keyboard_arrow_down')
+			.collapse(id='overleafGroupPlansQ5')
 				.custom-accordion-body
 				.custom-accordion-body
-					| You can upgrade your plan from Group Standard to Group Professional on the 
+					| You can upgrade your plan from Group Standard to Group Professional on the
 					a.inline-green-link(
 					a.inline-green-link(
-						target="_blank"
-						href="/user/subscription"
-						event-tracking="plans-page-click"
-						event-tracking-mb="true"
-						event-tracking-trigger="click"
-						event-segmentation={ button: 'contact', location: 'faq' }
+						target='_blank'
+						href='/user/subscription'
+						event-tracking='plans-page-click'
+						event-tracking-mb='true'
+						event-tracking-trigger='click'
+						event-segmentation={button: 'contact', location: 'faq'}
 					)
 					)
 						span subscription management page
 						span subscription management page
 					| .
 					| .

+ 8 - 4
services/web/app/views/subscriptions/preview-change.pug

@@ -4,10 +4,14 @@ block entrypointVar
 	- entrypoint = 'pages/user/subscription/preview-change'
 	- entrypoint = 'pages/user/subscription/preview-change'
 
 
 block append meta
 block append meta
-	meta(name="ol-user" data-type="json" content=user)
-	meta(name="ol-subscriptionChangePreview" data-type="json" content=changePreview)
-	meta(name="ol-purchaseReferrer" data-type="string" content=purchaseReferrer)
+	meta(name='ol-user' data-type='json' content=user)
+	meta(
+		name='ol-subscriptionChangePreview'
+		data-type='json'
+		content=changePreview
+	)
+	meta(name='ol-purchaseReferrer' data-type='string' content=purchaseReferrer)
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		#subscription-preview-change
 		#subscription-preview-change

+ 3 - 3
services/web/app/views/subscriptions/subtotal-limit-exceeded.pug

@@ -4,8 +4,8 @@ block entrypointVar
 	- entrypoint = 'pages/user/subscription/group-management/subtotal-limit-exceeded'
 	- entrypoint = 'pages/user/subscription/group-management/subtotal-limit-exceeded'
 
 
 block append meta
 block append meta
-	meta(name="ol-user" data-type="json" content=user)
-	meta(name="ol-groupName", data-type="string", content=groupName)
+	meta(name='ol-user' data-type='json' content=user)
+	meta(name='ol-groupName' data-type='string' content=groupName)
 
 
 block content
 block content
-	main.content.content-alt#subtotal-limit-exceeded-root
+	main#subtotal-limit-exceeded-root.content.content-alt

+ 4 - 4
services/web/app/views/subscriptions/successful-subscription-react.pug

@@ -4,9 +4,9 @@ block entrypointVar
 	- entrypoint = 'pages/user/subscription/successful-subscription'
 	- entrypoint = 'pages/user/subscription/successful-subscription'
 
 
 block append meta
 block append meta
-	meta(name="ol-subscription" data-type="json" content=personalSubscription)
-	meta(name="ol-postCheckoutRedirect" content=postCheckoutRedirect)
-	meta(name="ol-user" data-type="json" content=user)
+	meta(name='ol-subscription' data-type='json' content=personalSubscription)
+	meta(name='ol-postCheckoutRedirect' content=postCheckoutRedirect)
+	meta(name='ol-user' data-type='json' content=user)
 
 
 block content
 block content
-	main.content.content-alt#subscription-success-root
+	main#subscription-success-root.content.content-alt

+ 3 - 3
services/web/app/views/subscriptions/team/group-invites.pug

@@ -4,8 +4,8 @@ block entrypointVar
 	- entrypoint = 'pages/user/subscription/group-invites'
 	- entrypoint = 'pages/user/subscription/group-invites'
 
 
 block append meta
 block append meta
-	meta(name="ol-teamInvites" data-type="json" content=teamInvites)
-	meta(name="ol-user" data-type="json" content=user)
+	meta(name='ol-teamInvites' data-type='json' content=teamInvites)
+	meta(name='ol-user' data-type='json' content=user)
 
 
 block content
 block content
-	main.content.content-alt.team-invite#group-invites-root
+	main#group-invites-root.content.content-alt.team-invite

+ 15 - 11
services/web/app/views/subscriptions/team/invite-managed.pug

@@ -4,16 +4,20 @@ block entrypointVar
 	- entrypoint = 'pages/user/subscription/invite-managed'
 	- entrypoint = 'pages/user/subscription/invite-managed'
 
 
 block append meta
 block append meta
-	meta(name="ol-inviteToken" content=inviteToken)
-	meta(name="ol-inviterName" content=inviterName)
-	meta(name="ol-expired" data-type="boolean" content=expired)
-	meta(name="ol-alreadyEnrolled" data-type="boolean" content=alreadyEnrolled)
-	meta(name="ol-validationStatus" data-type="json" content=validationStatus)
-	meta(name="ol-currentManagedUserAdminEmail" data-type="string" content=currentManagedUserAdminEmail)
-	meta(name="ol-groupSSOActive" data-type="boolean" content=groupSSOActive)
-	meta(name="ol-subscriptionId" data-type="string" content=subscriptionId)
-	meta(name="ol-user" data-type="json" content=user)
-	meta(name="ol-usersSubscription" data-type="json" content=usersSubscription)
+	meta(name='ol-inviteToken' content=inviteToken)
+	meta(name='ol-inviterName' content=inviterName)
+	meta(name='ol-expired' data-type='boolean' content=expired)
+	meta(name='ol-alreadyEnrolled' data-type='boolean' content=alreadyEnrolled)
+	meta(name='ol-validationStatus' data-type='json' content=validationStatus)
+	meta(
+		name='ol-currentManagedUserAdminEmail'
+		data-type='string'
+		content=currentManagedUserAdminEmail
+	)
+	meta(name='ol-groupSSOActive' data-type='boolean' content=groupSSOActive)
+	meta(name='ol-subscriptionId' data-type='string' content=subscriptionId)
+	meta(name='ol-user' data-type='json' content=user)
+	meta(name='ol-usersSubscription' data-type='json' content=usersSubscription)
 
 
 block content
 block content
-	main.content.content-alt.team-invite#invite-managed-root
+	main#invite-managed-root.content.content-alt.team-invite

+ 17 - 9
services/web/app/views/subscriptions/team/invite.pug

@@ -4,14 +4,22 @@ block entrypointVar
 	- entrypoint = 'pages/user/subscription/invite'
 	- entrypoint = 'pages/user/subscription/invite'
 
 
 block append meta
 block append meta
-	meta(name="ol-hasIndividualPaidSubscription" data-type="boolean" content=hasIndividualPaidSubscription)
-	meta(name="ol-inviterName" data-type="string" content=inviterName)
-	meta(name="ol-inviteToken" data-type="string" content=inviteToken)
-	meta(name="ol-currentManagedUserAdminEmail" data-type="string" content=currentManagedUserAdminEmail)
-	meta(name="ol-expired" data-type="boolean" content=expired)
-	meta(name="ol-groupSSOActive" data-type="boolean" content=groupSSOActive)
-	meta(name="ol-subscriptionId" data-type="string" content=subscriptionId)
-	meta(name="ol-user" data-type="json" content=user)
+	meta(
+		name='ol-hasIndividualPaidSubscription'
+		data-type='boolean'
+		content=hasIndividualPaidSubscription
+	)
+	meta(name='ol-inviterName' data-type='string' content=inviterName)
+	meta(name='ol-inviteToken' data-type='string' content=inviteToken)
+	meta(
+		name='ol-currentManagedUserAdminEmail'
+		data-type='string'
+		content=currentManagedUserAdminEmail
+	)
+	meta(name='ol-expired' data-type='boolean' content=expired)
+	meta(name='ol-groupSSOActive' data-type='boolean' content=groupSSOActive)
+	meta(name='ol-subscriptionId' data-type='string' content=subscriptionId)
+	meta(name='ol-user' data-type='json' content=user)
 
 
 block content
 block content
-	main.content.content-alt#invite-root
+	main#invite-root.content.content-alt

+ 9 - 5
services/web/app/views/subscriptions/team/invite_logged_out.pug

@@ -1,12 +1,12 @@
 extends ../../layout-marketing
 extends ../../layout-marketing
 
 
 block append meta
 block append meta
-	meta(name="ol-user" data-type="json" content=user)
+	meta(name='ol-user' data-type='json' content=user)
 
 
 block content
 block content
 	- var colClass = bootstrapVersion === 5 ? 'col-lg-8 m-auto' : 'col-md-8 col-md-offset-2'
 	- var colClass = bootstrapVersion === 5 ? 'col-lg-8 m-auto' : 'col-md-8 col-md-offset-2'
 
 
-	main.content.content-alt.team-invite#main-content
+	main#main-content.content.content-alt.team-invite
 		.container
 		.container
 			.row
 			.row
 				div(class=colClass)
 				div(class=colClass)
@@ -16,15 +16,19 @@ block content
 								// TODO: Remove `team-invite-name` once we fully migrated to Bootstrap 5
 								// TODO: Remove `team-invite-name` once we fully migrated to Bootstrap 5
 								h1.text-center !{translate("invited_to_group", {inviterName: inviterName, appName: appName }, [{name: 'span', attrs: {class: 'team-invite-name'}}])}
 								h1.text-center !{translate("invited_to_group", {inviterName: inviterName, appName: appName }, [{name: 'span', attrs: {class: 'team-invite-name'}}])}
 
 
-							if (accountExists)
+							if accountExists
 								div
 								div
 									p #{translate("invited_to_group_login_benefits", {appName: appName})}
 									p #{translate("invited_to_group_login_benefits", {appName: appName})}
 									p #{translate("invited_to_group_login", {emailAddress: emailAddress})}
 									p #{translate("invited_to_group_login", {emailAddress: emailAddress})}
 									p
 									p
-										a.btn.btn-primary(href=`/login?redir=/subscription/invites/${inviteToken}${groupSSOActive ? "&hide_sso_login=true" : ""}`) #{translate("login_to_accept_invitation")}
+										a.btn.btn-primary(
+											href=`/login?redir=/subscription/invites/${inviteToken}${groupSSOActive ? "&hide_sso_login=true" : ""}`
+										) #{translate("login_to_accept_invitation")}
 							else
 							else
 								div
 								div
 									p #{translate("invited_to_group_register_benefits", {appName: appName})}
 									p #{translate("invited_to_group_register_benefits", {appName: appName})}
 									p #{translate("invited_to_group_register", {inviterName: inviterName})}
 									p #{translate("invited_to_group_register", {inviterName: inviterName})}
 									p
 									p
-										a.btn.btn-primary(href=`/register?redir=/subscription/invites/${inviteToken}${groupSSOActive ? "&hide_sso_login=true" : ""}`) #{translate("register_to_accept_invitation")}
+										a.btn.btn-primary(
+											href=`/register?redir=/subscription/invites/${inviteToken}${groupSSOActive ? "&hide_sso_login=true" : ""}`
+										) #{translate("register_to_accept_invitation")}

+ 9 - 5
services/web/app/views/subscriptions/upgrade-group-subscription-react.pug

@@ -4,10 +4,14 @@ block entrypointVar
 	- entrypoint = 'pages/user/subscription/group-management/upgrade-group-subscription'
 	- entrypoint = 'pages/user/subscription/group-management/upgrade-group-subscription'
 
 
 block append meta
 block append meta
-	meta(name="ol-user" data-type="json" content=user)
-	meta(name="ol-subscriptionChangePreview" data-type="json" content=changePreview)
-	meta(name="ol-totalLicenses", data-type="number", content=totalLicenses)
-	meta(name="ol-groupName", data-type="string", content=groupName)
+	meta(name='ol-user' data-type='json' content=user)
+	meta(
+		name='ol-subscriptionChangePreview'
+		data-type='json'
+		content=changePreview
+	)
+	meta(name='ol-totalLicenses' data-type='number' content=totalLicenses)
+	meta(name='ol-groupName' data-type='string' content=groupName)
 
 
 block content
 block content
-	main.content.content-alt#upgrade-group-subscription-root
+	main#upgrade-group-subscription-root.content.content-alt

+ 1 - 1
services/web/app/views/user/accountSuspended.pug

@@ -6,7 +6,7 @@ block vars
 	- metadata.robotsNoindexNofollow = true
 	- metadata.robotsNoindexNofollow = true
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		.container-custom-sm.mx-auto
 		.container-custom-sm.mx-auto
 			.card
 			.card
 				.card-body
 				.card-body

+ 1 - 1
services/web/app/views/user/compromised_password.pug

@@ -9,5 +9,5 @@ block entrypointVar
 	- entrypoint = 'pages/compromised-password'
 	- entrypoint = 'pages/compromised-password'
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		#compromised-password
 		#compromised-password

+ 1 - 1
services/web/app/views/user/confirmSecondaryEmail.pug

@@ -8,7 +8,7 @@ block entrypointVar
 	- entrypoint = 'pages/user/confirm-secondary-email'
 	- entrypoint = 'pages/user/confirm-secondary-email'
 
 
 block append meta
 block append meta
-	meta(name="ol-email" content=email)
+	meta(name='ol-email' content=email)
 
 
 block content
 block content
 	main.content.content-alt
 	main.content.content-alt

+ 24 - 30
services/web/app/views/user/confirm_email.pug

@@ -2,56 +2,50 @@ extends ../layout-marketing
 include ../_mixins/notification
 include ../_mixins/notification
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		.container
 		.container
 			.row
 			.row
 				.col-lg-8.offset-lg-2.col-xl-6.offset-xl-3
 				.col-lg-8.offset-lg-2.col-xl-6.offset-xl-3
 					.card
 					.card
 						.card-body
 						.card-body
-							.page-header(data-ol-hide-on-error-message="confirm-email-wrong-user")
+							.page-header(data-ol-hide-on-error-message='confirm-email-wrong-user')
 								h1 #{translate("confirm_email")}
 								h1 #{translate("confirm_email")}
+							form(method='POST' action='/logout' id='logoutForm')
+								input(name='_csrf' type='hidden' value=csrfToken)
+								input(name='redirect' type='hidden' value=currentUrlWithQueryParams)
 							form(
 							form(
-								method="POST"
-								action="/logout"
-								id="logoutForm"
+								name='confirmEmailForm'
+								data-ol-async-form
+								data-ol-auto-submit
+								action='/user/emails/confirm'
+								method='POST'
+								id='confirmEmailForm'
 							)
 							)
-								input(type="hidden", name="_csrf", value=csrfToken)
-								input(type="hidden", name="redirect", value=currentUrlWithQueryParams)
-							form(
-								data-ol-async-form,
-								data-ol-auto-submit,
-								name="confirmEmailForm"
-								action="/user/emails/confirm",
-								method="POST",
-								id="confirmEmailForm",
-							)
-								input(type="hidden", name="_csrf", value=csrfToken)
-								input(type="hidden", name="token", value=token)
+								input(name='_csrf' type='hidden' value=csrfToken)
+								input(name='token' type='hidden' value=token)
 
 
 								div(data-ol-not-sent)
 								div(data-ol-not-sent)
-									+formMessages()
-									div(data-ol-custom-form-message="confirm-email-wrong-user" hidden)
+									+formMessages
+									div(data-ol-custom-form-message='confirm-email-wrong-user' hidden)
 										h1.h3 #{translate("we_cant_confirm_this_email")}
 										h1.h3 #{translate("we_cant_confirm_this_email")}
 										p !{translate("to_confirm_email_address_you_must_be_logged_in_with_the_requesting_account")}
 										p !{translate("to_confirm_email_address_you_must_be_logged_in_with_the_requesting_account")}
 										p !{translate("you_are_currently_logged_in_as", {email: getUserEmail()})}
 										p !{translate("you_are_currently_logged_in_as", {email: getUserEmail()})}
 										.actions
 										.actions
-											button.btn-primary.btn.w-100(
-												form="logoutForm"
-											) #{translate('log_in_with_a_different_account')}
+											button.btn-primary.btn.w-100(form='logoutForm') #{translate('log_in_with_a_different_account')}
 
 
 									.actions
 									.actions
 										button.btn-primary.btn.w-100(
 										button.btn-primary.btn.w-100(
-											type='submit',
+											type='submit'
 											data-ol-disabled-inflight
 											data-ol-disabled-inflight
-											data-ol-hide-on-error-message="confirm-email-wrong-user"
+											data-ol-hide-on-error-message='confirm-email-wrong-user'
 										)
 										)
-											span(data-ol-inflight="idle")
+											span(data-ol-inflight='idle')
 												| #{translate('confirm')}
 												| #{translate('confirm')}
-											span(hidden data-ol-inflight="pending")
-												span(role='status').spinner-border.spinner-border-sm.mx-2
+											span(hidden data-ol-inflight='pending')
+												span.spinner-border.spinner-border-sm.mx-2(role='status')
 
 
 								div(hidden data-ol-sent)
 								div(hidden data-ol-sent)
-									+notification({ariaLive: 'polite', type: 'success', className: 'mb-3', content: translate("thank_you_email_confirmed")})
-									div.text-center
-										a.btn.btn-primary(href="/user/settings")
+									+notification({ariaLive: 'polite', type: 'success', className: 'mb-3', content: translate('thank_you_email_confirmed')})
+									.text-center
+										a.btn.btn-primary(href='/user/settings')
 											| #{translate('go_to_account_settings')}
 											| #{translate('go_to_account_settings')}

+ 17 - 17
services/web/app/views/user/email-preferences.pug

@@ -2,7 +2,7 @@ extends ../layout-marketing
 include ../_mixins/back_to_btns
 include ../_mixins/back_to_btns
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		.container
 		.container
 			.row
 			.row
 				.col-lg-10.offset-lg-1.col-xl-8.offset-xl-2
 				.col-lg-10.offset-lg-1.col-xl-8.offset-xl-2
@@ -10,9 +10,9 @@ block content
 						.card-body
 						.card-body
 							.page-header
 							.page-header
 								h1 #{translate("newsletter_info_title")}
 								h1 #{translate("newsletter_info_title")}
-	
+
 							p #{translate("newsletter_info_summary")}
 							p #{translate("newsletter_info_summary")}
-	
+
 							- var submitAction
 							- var submitAction
 							if subscribed
 							if subscribed
 								- submitAction = '/user/newsletter/unsubscribe'
 								- submitAction = '/user/newsletter/unsubscribe'
@@ -20,28 +20,28 @@ block content
 							else
 							else
 								- submitAction = '/user/newsletter/subscribe'
 								- submitAction = '/user/newsletter/subscribe'
 								p !{translate("newsletter_info_unsubscribed", {}, ['strong'])}
 								p !{translate("newsletter_info_unsubscribed", {}, ['strong'])}
-	
+
 							form(
 							form(
+								name='newsletterForm'
 								data-ol-async-form
 								data-ol-async-form
 								data-ol-reload-on-success
 								data-ol-reload-on-success
-								name="newsletterForm"
 								action=submitAction
 								action=submitAction
-								method="POST"
+								method='POST'
 							)
 							)
-								input(name='_csrf', type='hidden', value=csrfToken)
-								+formMessages()
+								input(name='_csrf' type='hidden' value=csrfToken)
+								+formMessages
 								p.actions.text-center
 								p.actions.text-center
 									if subscribed
 									if subscribed
-										button.btn-danger.btn(type='submit', data-ol-disabled-inflight)
-											span(data-ol-inflight="idle") #{translate("unsubscribe")}
-											span(hidden data-ol-inflight="pending") #{translate("saving")}…
+										button.btn-danger.btn(type='submit' data-ol-disabled-inflight)
+											span(data-ol-inflight='idle') #{translate("unsubscribe")}
+											span(hidden data-ol-inflight='pending') #{translate("saving")}…
 									else
 									else
-										button.btn-primary.btn(type='submit', data-ol-disabled-inflight)
-											span(data-ol-inflight="idle") #{translate("subscribe")}
-											span(hidden data-ol-inflight="pending") #{translate("saving")}…
-	
+										button.btn-primary.btn(type='submit' data-ol-disabled-inflight)
+											span(data-ol-inflight='idle') #{translate("subscribe")}
+											span(hidden data-ol-inflight='pending') #{translate("saving")}…
+
 							if subscribed
 							if subscribed
 								p #{translate("newsletter_info_note")}
 								p #{translate("newsletter_info_note")}
-	
+
 							.page-separator
 							.page-separator
-							+back-to-btns()
+							+back-to-btns

+ 18 - 22
services/web/app/views/user/login.pug

@@ -1,7 +1,7 @@
 extends ../layout-marketing
 extends ../layout-marketing
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		.container
 		.container
 			.row
 			.row
 				.col-lg-6.offset-lg-3.col-xl-4.offset-xl-4
 				.col-lg-6.offset-lg-3.col-xl-4.offset-xl-4
@@ -11,10 +11,10 @@ block content
 								if login_support_title
 								if login_support_title
 									h1 !{login_support_title}
 									h1 !{login_support_title}
 								else
 								else
-									h1 #{translate("log_in")}					
-							form(data-ol-async-form, name="loginForm", action='/login', method="POST")
-								input(name='_csrf', type='hidden', value=csrfToken)
-								+formMessagesNewStyle()
+									h1 #{translate("log_in")} 
+							form(name='loginForm' data-ol-async-form action='/login' method='POST')
+								input(name='_csrf' type='hidden' value=csrfToken)
+								+formMessagesNewStyle
 								+customFormMessageNewStyle('invalid-password-retry-or-reset', 'danger')
 								+customFormMessageNewStyle('invalid-password-retry-or-reset', 'danger')
 									| !{translate('email_or_password_wrong_try_again_or_reset', {}, [{ name: 'a', attrs: { href: '/user/password/reset', 'aria-describedby': 'resetPasswordDescription' } }])}
 									| !{translate('email_or_password_wrong_try_again_or_reset', {}, [{ name: 'a', attrs: { href: '/user/password/reset', 'aria-describedby': 'resetPasswordDescription' } }])}
 									span.visually-hidden(id='resetPasswordDescription')
 									span.visually-hidden(id='resetPasswordDescription')
@@ -23,28 +23,24 @@ block content
 									| !{translate('password_compromised_try_again_or_use_known_device_or_reset', {}, [{name: 'a', attrs: {href: 'https://haveibeenpwned.com/passwords', rel: 'noopener noreferrer', target: '_blank'}}, {name: 'a', attrs: {href: '/user/password/reset', target: '_blank'}}])}.
 									| !{translate('password_compromised_try_again_or_use_known_device_or_reset', {}, [{name: 'a', attrs: {href: 'https://haveibeenpwned.com/passwords', rel: 'noopener noreferrer', target: '_blank'}}, {name: 'a', attrs: {href: '/user/password/reset', target: '_blank'}}])}.
 								.form-group
 								.form-group
 									input.form-control(
 									input.form-control(
-										type='email',
-										name='email',
-										required,
-										placeholder='email@example.com',
-										autofocus="true"
+										name='email'
+										type='email'
+										required
+										placeholder='email@example.com'
+										autofocus='true'
 									)
 									)
 								.form-group
 								.form-group
 									input.form-control(
 									input.form-control(
-										type='password',
-										name='password',
-										required,
-										placeholder='********',
+										name='password'
+										type='password'
+										required
+										placeholder='********'
 									)
 									)
 								.actions
 								.actions
-									button.btn-primary.btn(
-										type='submit',
-										data-ol-disabled-inflight
-									)
-										span(data-ol-inflight="idle") #{translate("login")}
-										span(hidden data-ol-inflight="pending") #{translate("logging_in")}…
+									button.btn-primary.btn(type='submit' data-ol-disabled-inflight)
+										span(data-ol-inflight='idle') #{translate("login")}
+										span(hidden data-ol-inflight='pending') #{translate("logging_in")}…
 									a.float-end(href='/user/password/reset') #{translate("forgot_your_password")}?
 									a.float-end(href='/user/password/reset') #{translate("forgot_your_password")}?
 								if login_support_text
 								if login_support_text
 									hr
 									hr
-									p.text-center !{login_support_text}	
-		
+									p.text-center !{login_support_text}

+ 2 - 2
services/web/app/views/user/one_time_login.pug

@@ -1,7 +1,7 @@
 extends ../layout-marketing
 extends ../layout-marketing
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		.container
 		.container
 			.row
 			.row
 				.col-lg-6.offset-lg-3.col-xl-4.offset-xl-4
 				.col-lg-6.offset-lg-3.col-xl-4.offset-xl-4
@@ -13,6 +13,6 @@ block content
 							p
 							p
 								| Please
 								| Please
 								|
 								|
-								a(href="/login") log in
+								a(href='/login') log in
 								|
 								|
 								| to continue working on your projects.
 								| to continue working on your projects.

+ 30 - 26
services/web/app/views/user/passwordReset-bs5.pug

@@ -11,51 +11,55 @@ block content
 	- var showCaptcha = settings.recaptcha && settings.recaptcha.siteKey && !(settings.recaptcha.disabled && settings.recaptcha.disabled.passwordReset)
 	- var showCaptcha = settings.recaptcha && settings.recaptcha.siteKey && !(settings.recaptcha.disabled && settings.recaptcha.disabled.passwordReset)
 
 
 	if showCaptcha
 	if showCaptcha
-		script(type="text/javascript", nonce=scriptNonce, src="https://www.recaptcha.net/recaptcha/api.js?render=explicit")
+		script(
+			type='text/javascript'
+			nonce=scriptNonce
+			src='https://www.recaptcha.net/recaptcha/api.js?render=explicit'
+		)
 		div(
 		div(
-			id="recaptcha"
-			class="g-recaptcha"
+			id='recaptcha'
+			class='g-recaptcha'
 			data-sitekey=settings.recaptcha.siteKey
 			data-sitekey=settings.recaptcha.siteKey
-			data-size="invisible"
-			data-badge="inline"
+			data-size='invisible'
+			data-badge='inline'
 		)
 		)
 
 
-	main#main-content(data-ol-captcha-retry-trigger-area="")
-		a.auth-aux-logo(href="/")
-			img(src=buildImgPath("ol-brand/overleaf-o-dark.svg") alt=settings.appName)
+	main#main-content(data-ol-captcha-retry-trigger-area='')
+		a.auth-aux-logo(href='/')
+			img(src=buildImgPath('ol-brand/overleaf-o-dark.svg') alt=settings.appName)
 		.auth-aux-container
 		.auth-aux-container
 			form(
 			form(
+				name='passwordResetForm'
+				captcha-action-name=showCaptcha ? 'passwordReset' : false
 				data-ol-async-form
 				data-ol-async-form
-				name="passwordResetForm"
-				action="/user/password/reset"
-				method="POST"
-				captcha=(showCaptcha ? '' : false)
-				captcha-action-name=(showCaptcha ? "passwordReset" : false)
+				action='/user/password/reset'
+				method='POST'
+				captcha=showCaptcha ? '' : false
 			)
 			)
 				if error === 'password_reset_token_expired'
 				if error === 'password_reset_token_expired'
 					h1.h3.mb-3.mt-0 #{translate("sorry_your_token_expired")}
 					h1.h3.mb-3.mt-0 #{translate("sorry_your_token_expired")}
 					p #{translate('please_request_a_new_password_reset_email_and_follow_the_link')}.
 					p #{translate('please_request_a_new_password_reset_email_and_follow_the_link')}.
 				else
 				else
 					h1.h3.mb-3.mt-0(data-ol-not-sent) #{translate("password_reset_sentence_case")}
 					h1.h3.mb-3.mt-0(data-ol-not-sent) #{translate("password_reset_sentence_case")}
-					h1.h3.mb-3.mt-0(hidden data-ol-sent) #{translate("check_your_email")}   
+					h1.h3.mb-3.mt-0(hidden data-ol-sent) #{translate("check_your_email")} 
 					p.mb-3.pb-3(data-ol-not-sent) #{translate("enter_your_email_address_below_and_we_will_send_you_a_link_to_reset_your_password")}.
 					p.mb-3.pb-3(data-ol-not-sent) #{translate("enter_your_email_address_below_and_we_will_send_you_a_link_to_reset_your_password")}.
 
 
 				div(data-ol-not-sent)
 				div(data-ol-not-sent)
-					+formMessagesNewStyle()
+					+formMessagesNewStyle
 					if error && error !== 'password_reset_token_expired'
 					if error && error !== 'password_reset_token_expired'
 						+notification({ariaLive: 'assertive', type: 'error', className: 'mb-3', content: translate(error)})
 						+notification({ariaLive: 'assertive', type: 'error', className: 'mb-3', content: translate(error)})
 
 
-					div(data-ol-custom-form-message="no-password-allowed-due-to-sso" hidden)
-						+notification({ariaLive: 'polite', type: 'error', className: 'mb-3', content: translate("you_cant_reset_password_due_to_sso", {}, [{name: 'a', attrs: {href: '/sso-login'}}])})
-					input(type="hidden" name="_csrf" value=csrfToken)
+					div(data-ol-custom-form-message='no-password-allowed-due-to-sso' hidden)
+						+notification({ariaLive: 'polite', type: 'error', className: 'mb-3', content: translate('you_cant_reset_password_due_to_sso', {}, [{name: 'a', attrs: {href: '/sso-login'}}])})
+					input(name='_csrf' type='hidden' value=csrfToken)
 					.form-group.mb-3
 					.form-group.mb-3
 						label.form-label(for='email') #{translate("email")}
 						label.form-label(for='email') #{translate("email")}
-						input.form-control#email(
-							aria-label="email"
-							type='email'
+						input#email.form-control(
 							name='email'
 							name='email'
+							aria-label='email'
+							type='email'
 							required
 							required
-							autocomplete="username"
+							autocomplete='username'
 							autofocus
 							autofocus
 						)
 						)
 					.actions
 					.actions
@@ -64,14 +68,14 @@ block content
 							data-ol-disabled-inflight
 							data-ol-disabled-inflight
 							aria-label=translate('reset_password_sentence_case')
 							aria-label=translate('reset_password_sentence_case')
 						)
 						)
-							span(data-ol-inflight="idle")
+							span(data-ol-inflight='idle')
 								| #{translate("reset_password_sentence_case")}
 								| #{translate("reset_password_sentence_case")}
-							span(hidden data-ol-inflight="pending")
+							span(hidden data-ol-inflight='pending')
 								| #{translate("requesting_password_reset")}…
 								| #{translate("requesting_password_reset")}…
-						a.btn.btn-ghost.w-100.mb-3(href="/login") #{translate("back_to_log_in")}
+						a.btn.btn-ghost.w-100.mb-3(href='/login') #{translate("back_to_log_in")}
 				div(hidden data-ol-sent)
 				div(hidden data-ol-sent)
 					p.mb-4 #{translate('password_reset_email_sent')}
 					p.mb-4 #{translate('password_reset_email_sent')}
-					a.btn.btn-primary.w-100.mb-3(href="/login") #{translate('back_to_log_in')}
+					a.btn.btn-primary.w-100.mb-3(href='/login') #{translate('back_to_log_in')}
 
 
 			if showCaptcha
 			if showCaptcha
 				+recaptchaConditions
 				+recaptchaConditions

+ 37 - 33
services/web/app/views/user/passwordReset.pug

@@ -9,77 +9,81 @@ block content
 	- var showCaptcha = settings.recaptcha && settings.recaptcha.siteKey && !(settings.recaptcha.disabled && settings.recaptcha.disabled.passwordReset)
 	- var showCaptcha = settings.recaptcha && settings.recaptcha.siteKey && !(settings.recaptcha.disabled && settings.recaptcha.disabled.passwordReset)
 
 
 	if showCaptcha
 	if showCaptcha
-		script(type="text/javascript", nonce=scriptNonce, src="https://www.recaptcha.net/recaptcha/api.js?render=explicit")
+		script(
+			type='text/javascript'
+			nonce=scriptNonce
+			src='https://www.recaptcha.net/recaptcha/api.js?render=explicit'
+		)
 		div(
 		div(
-			id="recaptcha"
-			class="g-recaptcha"
+			id='recaptcha'
+			class='g-recaptcha'
 			data-sitekey=settings.recaptcha.siteKey
 			data-sitekey=settings.recaptcha.siteKey
-			data-size="invisible"
-			data-badge="inline"
+			data-size='invisible'
+			data-badge='inline'
 		)
 		)
 
 
-	main.content.content-alt#main-content(data-ol-captcha-retry-trigger-area="")
+	main#main-content.content.content-alt(data-ol-captcha-retry-trigger-area='')
 		.container-custom-sm.mx-auto
 		.container-custom-sm.mx-auto
 			.card
 			.card
 				form(
 				form(
+					name='passwordResetForm'
+					captcha-action-name=showCaptcha ? 'passwordReset' : false
 					data-ol-async-form
 					data-ol-async-form
-					name="passwordResetForm"
-					action="/user/password/reset",
-					method="POST",
-					captcha=(showCaptcha ? '' : false),
-					captcha-action-name=(showCaptcha ? "passwordReset" : false),
+					action='/user/password/reset'
+					method='POST'
+					captcha=showCaptcha ? '' : false
 				)
 				)
 					if error === 'password_reset_token_expired'
 					if error === 'password_reset_token_expired'
 						h3.mt-0.mb-2 #{translate("sorry_your_token_expired")}
 						h3.mt-0.mb-2 #{translate("sorry_your_token_expired")}
 						p #{translate('please_request_a_new_password_reset_email_and_follow_the_link')}.
 						p #{translate('please_request_a_new_password_reset_email_and_follow_the_link')}.
 					else
 					else
 						h3.mt-0.mb-2(data-ol-not-sent) #{translate("password_reset")}
 						h3.mt-0.mb-2(data-ol-not-sent) #{translate("password_reset")}
-						h3.mt-0.mb-2(hidden data-ol-sent) #{translate("check_your_email")}	
+						h3.mt-0.mb-2(hidden data-ol-sent) #{translate("check_your_email")}
 						p(data-ol-not-sent) #{translate("enter_your_email_address_below_and_we_will_send_you_a_link_to_reset_your_password")}.
 						p(data-ol-not-sent) #{translate("enter_your_email_address_below_and_we_will_send_you_a_link_to_reset_your_password")}.
 
 
 					div(data-ol-not-sent)
 					div(data-ol-not-sent)
-						+formMessages()
+						+formMessages
 						if error && error !== 'password_reset_token_expired'
 						if error && error !== 'password_reset_token_expired'
-							div.alert.alert-danger.mb-2(
-								role="alert"
-								aria-live="assertive"
-							)
+							.alert.alert-danger.mb-2(role='alert' aria-live='assertive')
 								| #{translate(error)}
 								| #{translate(error)}
 
 
-						div(data-ol-custom-form-message="no-password-allowed-due-to-sso" hidden)
-							.notification.notification-type-error(aria-live="polite" style="margin-bottom: 10px;")
+						div(data-ol-custom-form-message='no-password-allowed-due-to-sso' hidden)
+							.notification.notification-type-error(
+								aria-live='polite'
+								style='margin-bottom: 10px'
+							)
 								.notification-icon
 								.notification-icon
-									+material-symbol-rounded("error")
+									+material-symbol-rounded('error')
 								.notification-content-and-cta
 								.notification-content-and-cta
 									.notification-content
 									.notification-content
 										p
 										p
 											| !{translate("you_cant_reset_password_due_to_sso", {}, [{name: 'a', attrs: {href: '/sso-login'}}])}
 											| !{translate("you_cant_reset_password_due_to_sso", {}, [{name: 'a', attrs: {href: '/sso-login'}}])}
 
 
-						input(type="hidden", name="_csrf", value=csrfToken)
+						input(name='_csrf' type='hidden' value=csrfToken)
 						.form-group.mb-3
 						.form-group.mb-3
 							label(for='email') #{translate("email")}
 							label(for='email') #{translate("email")}
-							input.form-control#email(
-								aria-label="email"
-								type='email',
-								name='email',
-								placeholder=translate("enter_your_email_address"),
-								required,
-								autocomplete="username",
+							input#email.form-control(
+								name='email'
+								aria-label='email'
+								type='email'
+								placeholder=translate('enter_your_email_address')
+								required
+								autocomplete='username'
 								autofocus
 								autofocus
 							)
 							)
 						.actions
 						.actions
 							button.btn.btn-primary.w-100(
 							button.btn.btn-primary.w-100(
-								type='submit',
-								data-ol-disabled-inflight,
+								type='submit'
+								data-ol-disabled-inflight
 								aria-label=translate('request_password_reset_to_reconfirm')
 								aria-label=translate('request_password_reset_to_reconfirm')
 							)
 							)
-								span(data-ol-inflight="idle")
+								span(data-ol-inflight='idle')
 									| #{translate("request_password_reset")}
 									| #{translate("request_password_reset")}
-								span(hidden data-ol-inflight="pending")
+								span(hidden data-ol-inflight='pending')
 									| #{translate("requesting_password_reset")}…
 									| #{translate("requesting_password_reset")}…
 					div(hidden data-ol-sent)
 					div(hidden data-ol-sent)
 						p.mb-4 #{translate('password_reset_email_sent')}
 						p.mb-4 #{translate('password_reset_email_sent')}
-						a(href="/login") #{translate('back_to_log_in')}
+						a(href='/login') #{translate('back_to_log_in')}
 
 
 			if showCaptcha
 			if showCaptcha
 				+recaptchaConditions
 				+recaptchaConditions

+ 19 - 19
services/web/app/views/user/primaryEmailCheck-bs5.pug

@@ -7,36 +7,36 @@ block vars
 block content
 block content
 	main#main-content
 	main#main-content
 		.auth-aux-container
 		.auth-aux-container
-			img.w-50.d-block(src=buildImgPath("ol-brand/overleaf.svg") alt=settings.appName)
+			img.w-50.d-block(
+				src=buildImgPath('ol-brand/overleaf.svg')
+				alt=settings.appName
+			)
 			h1.h3.mb-3 #{translate("keep_your_account_safe")}
 			h1.h3.mb-3 #{translate("keep_your_account_safe")}
 			div(data-ol-multi-submit)
 			div(data-ol-multi-submit)
 				p.small.mb-4
 				p.small.mb-4
 					| !{translate("primary_email_check_question", { email: getUserEmail() }, ["strong"])}
 					| !{translate("primary_email_check_question", { email: getUserEmail() }, ["strong"])}
 				form(
 				form(
 					data-ol-async-form
 					data-ol-async-form
-					action="/user/emails/primary-email-check"
-					method="POST"
+					action='/user/emails/primary-email-check'
+					method='POST'
 				)
 				)
-					input(name='_csrf', type='hidden', value=csrfToken)
-					+formMessagesNewStyle()
+					input(name='_csrf' type='hidden' value=csrfToken)
+					+formMessagesNewStyle
 
 
-					button.btn.btn-primary.w-100.mb-3(
-						type='submit'
-						data-ol-disabled-inflight
-					)
-						span(data-ol-inflight="idle") #{translate("yes_that_is_correct")}
-						span(hidden data-ol-inflight="pending") #{translate("confirming")}…
+					button.btn.btn-primary.w-100.mb-3(type='submit' data-ol-disabled-inflight)
+						span(data-ol-inflight='idle') #{translate("yes_that_is_correct")}
+						span(hidden data-ol-inflight='pending') #{translate("confirming")}…
 
 
 				a.btn.btn-secondary.w-100.mb-4(
 				a.btn.btn-secondary.w-100.mb-4(
-					href="/user/settings#add-email"
+					href='/user/settings#add-email'
 					data-ol-slow-link
 					data-ol-slow-link
-					event-tracking="primary-email-check-change-email"
-					event-tracking-mb="true"
-					event-tracking-trigger="click"
+					event-tracking='primary-email-check-change-email'
+					event-tracking-mb='true'
+					event-tracking-trigger='click'
 				)
 				)
-					span(data-ol-inflight="idle") #{translate("no_update_email")}
-					span(hidden data-ol-inflight="pending") #{translate("redirecting")}…  
+					span(data-ol-inflight='idle') #{translate("no_update_email")}
+					span(hidden data-ol-inflight='pending') #{translate("redirecting")}… 
 				p.small.mb-2
 				p.small.mb-2
-					|  #{translate("keep_your_email_updated")}
+					| #{translate("keep_your_email_updated")}
 				p.small
 				p.small
-					|  !{translate("learn_more_about_emails", {}, [{name: 'a', attrs: {href: '/learn/how-to/Keeping_your_account_secure', 'event-tracking': 'primary-email-check-learn-more', 'event-tracking-mb': 'true', 'event-tracking-trigger': 'click' }}])}
+					| !{translate("learn_more_about_emails", {}, [{name: 'a', attrs: {href: '/learn/how-to/Keeping_your_account_secure', 'event-tracking': 'primary-email-check-learn-more', 'event-tracking-mb': 'true', 'event-tracking-trigger': 'click' }}])}

+ 27 - 26
services/web/app/views/user/reconfirm-bs5.pug

@@ -5,46 +5,50 @@ block vars
 	- isWebsiteRedesign = true
 	- isWebsiteRedesign = true
 
 
 block content
 block content
-	- var email = reconfirm_email ? reconfirm_email : ""
+	- var email = reconfirm_email ? reconfirm_email : ''
 	- var showCaptcha = settings.recaptcha && settings.recaptcha.siteKey && !(settings.recaptcha.disabled && settings.recaptcha.disabled.passwordReset)
 	- var showCaptcha = settings.recaptcha && settings.recaptcha.siteKey && !(settings.recaptcha.disabled && settings.recaptcha.disabled.passwordReset)
 
 
 	if showCaptcha
 	if showCaptcha
-		script(type="text/javascript", nonce=scriptNonce, src="https://www.recaptcha.net/recaptcha/api.js?render=explicit")
+		script(
+			type='text/javascript'
+			nonce=scriptNonce
+			src='https://www.recaptcha.net/recaptcha/api.js?render=explicit'
+		)
 		div(
 		div(
-			id="recaptcha"
-			class="g-recaptcha"
+			id='recaptcha'
+			class='g-recaptcha'
 			data-sitekey=settings.recaptcha.siteKey
 			data-sitekey=settings.recaptcha.siteKey
-			data-size="invisible"
-			data-badge="inline"
+			data-size='invisible'
+			data-badge='inline'
 		)
 		)
 
 
-	main#main-content(data-ol-captcha-retry-trigger-area="")
-		.container.auth-aux-container(style="max-width: 420px;")
+	main#main-content(data-ol-captcha-retry-trigger-area='')
+		.container.auth-aux-container(style='max-width: 420px')
 			form(
 			form(
+				name='reconfirmAccountForm'
+				captcha-action-name=showCaptcha ? 'passwordReset' : false
 				data-ol-async-form
 				data-ol-async-form
-				name="reconfirmAccountForm"
-				action="/user/reconfirm"
-				method="POST"
+				action='/user/reconfirm'
+				method='POST'
 				aria-label=translate('request_reconfirmation_email')
 				aria-label=translate('request_reconfirmation_email')
-				captcha=(showCaptcha ? '' : false)
-				captcha-action-name=(showCaptcha ? "passwordReset" : false)
+				captcha=showCaptcha ? '' : false
 			)
 			)
 				h1.h5.mb-3 #{translate("reconfirm_account")}
 				h1.h5.mb-3 #{translate("reconfirm_account")}
 				p #{translate('reconfirm_explained')}
 				p #{translate('reconfirm_explained')}
-					| 
+					|
 					a(href=`mailto:${settings.adminEmail}`) #{settings.adminEmail}
 					a(href=`mailto:${settings.adminEmail}`) #{settings.adminEmail}
 					| .
 					| .
-				
+
 				div(data-ol-not-sent)
 				div(data-ol-not-sent)
-					+formMessagesNewStyle()
+					+formMessagesNewStyle
 
 
-					input(type="hidden" name="_csrf" value=csrfToken)
+					input(name='_csrf' type='hidden' value=csrfToken)
 					.form-group.mb-3
 					.form-group.mb-3
 						label.form-label(for='email') #{translate("please_enter_email")}
 						label.form-label(for='email') #{translate("please_enter_email")}
 						input.form-control(
 						input.form-control(
-							aria-label="email"
-							type='email'
 							name='email'
 							name='email'
+							aria-label='email'
+							type='email'
 							placeholder='email@example.com'
 							placeholder='email@example.com'
 							required
 							required
 							autofocus
 							autofocus
@@ -52,20 +56,17 @@ block content
 						)
 						)
 					.actions
 					.actions
 						button.btn.btn-primary.w-100(
 						button.btn.btn-primary.w-100(
-							style="white-space: normal;"
+							style='white-space: normal'
 							type='submit'
 							type='submit'
 							data-ol-disabled-inflight
 							data-ol-disabled-inflight
 							aria-label=translate('request_password_reset_to_reconfirm')
 							aria-label=translate('request_password_reset_to_reconfirm')
 						)
 						)
-							span(data-ol-inflight="idle")
+							span(data-ol-inflight='idle')
 								| #{translate('request_password_reset_to_reconfirm')}
 								| #{translate('request_password_reset_to_reconfirm')}
-							span(hidden data-ol-inflight="pending")
+							span(hidden data-ol-inflight='pending')
 								| #{translate('request_password_reset_to_reconfirm')}… 
 								| #{translate('request_password_reset_to_reconfirm')}… 
 				div(hidden data-ol-sent)
 				div(hidden data-ol-sent)
-					div.alert.alert-success(
-						role="alert"
-						aria-live="polite"
-					)
+					.alert.alert-success(role='alert' aria-live='polite')
 						span #{translate('password_reset_email_sent')}
 						span #{translate('password_reset_email_sent')}
 
 
 			if showCaptcha
 			if showCaptcha

+ 29 - 28
services/web/app/views/user/reconfirm.pug

@@ -5,20 +5,24 @@ block vars
 	- bootstrap5PageStatus = 'disabled'
 	- bootstrap5PageStatus = 'disabled'
 
 
 block content
 block content
-	- var email = reconfirm_email ? reconfirm_email : ""
+	- var email = reconfirm_email ? reconfirm_email : ''
 	- var showCaptcha = settings.recaptcha && settings.recaptcha.siteKey && !(settings.recaptcha.disabled && settings.recaptcha.disabled.passwordReset)
 	- var showCaptcha = settings.recaptcha && settings.recaptcha.siteKey && !(settings.recaptcha.disabled && settings.recaptcha.disabled.passwordReset)
 
 
 	if showCaptcha
 	if showCaptcha
-		script(type="text/javascript", nonce=scriptNonce, src="https://www.recaptcha.net/recaptcha/api.js?render=explicit")
+		script(
+			type='text/javascript'
+			nonce=scriptNonce
+			src='https://www.recaptcha.net/recaptcha/api.js?render=explicit'
+		)
 		div(
 		div(
-			id="recaptcha"
-			class="g-recaptcha"
+			id='recaptcha'
+			class='g-recaptcha'
 			data-sitekey=settings.recaptcha.siteKey
 			data-sitekey=settings.recaptcha.siteKey
-			data-size="invisible"
-			data-badge="inline"
+			data-size='invisible'
+			data-badge='inline'
 		)
 		)
 
 
-	main.content.content-alt#main-content(data-ol-captcha-retry-trigger-area="")
+	main#main-content.content.content-alt(data-ol-captcha-retry-trigger-area='')
 		.container
 		.container
 			.row
 			.row
 				.col-sm-12.col-md-6.col-md-offset-3
 				.col-sm-12.col-md-6.col-md-offset-3
@@ -28,44 +32,41 @@ block content
 							a(href=`mailto:${settings.adminEmail}`) #{settings.adminEmail}
 							a(href=`mailto:${settings.adminEmail}`) #{settings.adminEmail}
 							| .
 							| .
 						form(
 						form(
+							name='reconfirmAccountForm'
+							captcha-action-name=showCaptcha ? 'passwordReset' : false
 							data-ol-async-form
 							data-ol-async-form
-							name="reconfirmAccountForm"
-							action="/user/reconfirm",
-							method="POST",
+							action='/user/reconfirm'
+							method='POST'
 							aria-label=translate('request_reconfirmation_email')
 							aria-label=translate('request_reconfirmation_email')
-							captcha=(showCaptcha ? '' : false),
-							captcha-action-name=(showCaptcha ? "passwordReset" : false)
+							captcha=showCaptcha ? '' : false
 						)
 						)
 							div(data-ol-not-sent)
 							div(data-ol-not-sent)
-								+formMessages()
-	
-								input(type="hidden", name="_csrf", value=csrfToken)
+								+formMessages
+
+								input(name='_csrf' type='hidden' value=csrfToken)
 								.form-group
 								.form-group
 									label(for='email') #{translate("please_enter_email")}
 									label(for='email') #{translate("please_enter_email")}
 									input.form-control(
 									input.form-control(
-										aria-label="email"
-										type='email',
-										name='email',
-										placeholder='email@example.com',
-										required,
+										name='email'
+										aria-label='email'
+										type='email'
+										placeholder='email@example.com'
+										required
 										autofocus
 										autofocus
 										value=email
 										value=email
 									)
 									)
 								.actions
 								.actions
 									button.btn.btn-primary(
 									button.btn.btn-primary(
-										type='submit',
-										data-ol-disabled-inflight,
+										type='submit'
+										data-ol-disabled-inflight
 										aria-label=translate('request_password_reset_to_reconfirm')
 										aria-label=translate('request_password_reset_to_reconfirm')
 									)
 									)
-										span(data-ol-inflight="idle")
+										span(data-ol-inflight='idle')
 											| #{translate('request_password_reset_to_reconfirm')}
 											| #{translate('request_password_reset_to_reconfirm')}
-										span(hidden data-ol-inflight="pending")
+										span(hidden data-ol-inflight='pending')
 											| #{translate('request_password_reset_to_reconfirm')}… 
 											| #{translate('request_password_reset_to_reconfirm')}… 
 							div(hidden data-ol-sent)
 							div(hidden data-ol-sent)
-								div.alert.alert-success(
-									role="alert"
-									aria-live="polite"
-								)
+								.alert.alert-success(role='alert' aria-live='polite')
 									span #{translate('password_reset_email_sent')}
 									span #{translate('password_reset_email_sent')}
 			.row
 			.row
 				.col-sm-12.col-md-6.col-md-offset-3
 				.col-sm-12.col-md-6.col-md-offset-3

+ 3 - 3
services/web/app/views/user/register.pug

@@ -4,7 +4,7 @@ block vars
 	- bootstrap5PageStatus = 'disabled'
 	- bootstrap5PageStatus = 'disabled'
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		.container
 		.container
 			.row
 			.row
 				.registration_message
 				.registration_message
@@ -15,12 +15,12 @@ block content
 							| #{translate("join_sl_to_view_project")}.
 							| #{translate("join_sl_to_view_project")}.
 						div
 						div
 							| #{translate("already_have_sl_account")}
 							| #{translate("already_have_sl_account")}
-							a(href="/login")  #{translate("login_here")}
+							a(href='/login') #{translate("login_here")}
 					else if newTemplateData.templateName !== undefined
 					else if newTemplateData.templateName !== undefined
 						h1 #{translate("register_to_edit_template", {templateName:newTemplateData.templateName})}
 						h1 #{translate("register_to_edit_template", {templateName:newTemplateData.templateName})}
 
 
 						div #{translate("already_have_sl_account")}
 						div #{translate("already_have_sl_account")}
-							a(href="/login")  #{translate("login_here")}
+							a(href='/login') #{translate("login_here")}
 
 
 			.row
 			.row
 				.col-md-8.col-md-offset-2.col-lg-6.col-lg-offset-3
 				.col-md-8.col-md-offset-2.col-lg-6.col-lg-offset-3

+ 14 - 18
services/web/app/views/user/sessions.pug

@@ -1,15 +1,15 @@
 extends ../layout-marketing
 extends ../layout-marketing
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		.container
 		.container
 			.row
 			.row
 				.col-lg-10.offset-lg-1.col-xl-8.offset-xl-2
 				.col-lg-10.offset-lg-1.col-xl-8.offset-xl-2
 					.card.clear-user-sessions
 					.card.clear-user-sessions
 						.card-body
 						.card-body
 							.page-header
 							.page-header
-								h1  #{translate("your_sessions")}
-	
+								h1 #{translate("your_sessions")}
+
 							if currentSession.ip_address && currentSession.session_created
 							if currentSession.ip_address && currentSession.session_created
 								h3 #{translate("current_session")}
 								h3 #{translate("current_session")}
 								div
 								div
@@ -21,47 +21,43 @@ block content
 											tr
 											tr
 												td #{currentSession.ip_address}
 												td #{currentSession.ip_address}
 												td #{moment(currentSession.session_created).utc().format('Do MMM YYYY, h:mm a')} UTC
 												td #{moment(currentSession.session_created).utc().format('Do MMM YYYY, h:mm a')} UTC
-	
+
 							h3 #{translate("other_sessions")}
 							h3 #{translate("other_sessions")}
 							div
 							div
 								p.small
 								p.small
 									| !{translate("clear_sessions_description")}
 									| !{translate("clear_sessions_description")}
-	
-							form(
-								data-ol-async-form
-								action='/user/sessions/clear'
-								method='POST'
-							)
+
+							form(data-ol-async-form action='/user/sessions/clear' method='POST')
 								input(name='_csrf' type='hidden' value=csrfToken)
 								input(name='_csrf' type='hidden' value=csrfToken)
 								div(data-ol-not-sent)
 								div(data-ol-not-sent)
 									if sessions.length == 0
 									if sessions.length == 0
 										p.text-center
 										p.text-center
 											| #{translate("no_other_sessions")}
 											| #{translate("no_other_sessions")}
-	
+
 									if sessions.length > 0
 									if sessions.length > 0
 										table.table.table-striped
 										table.table.table-striped
 											thead
 											thead
 												tr
 												tr
 													th #{translate("ip_address")}
 													th #{translate("ip_address")}
 													th #{translate("session_created_at")}
 													th #{translate("session_created_at")}
-											for session in sessions
+											each session in sessions
 												tr
 												tr
 													td #{session.ip_address}
 													td #{session.ip_address}
 													td #{moment(session.session_created).utc().format('Do MMM YYYY, h:mm a')} UTC
 													td #{moment(session.session_created).utc().format('Do MMM YYYY, h:mm a')} UTC
-	
+
 										p.actions
 										p.actions
 											.text-center
 											.text-center
 												button.btn.btn-lg.btn-primary(
 												button.btn.btn-lg.btn-primary(
-													type="submit"
+													type='submit'
 													data-ol-disable-inflight
 													data-ol-disable-inflight
 												)
 												)
-													span(data-ol-inflight="idle") #{translate('clear_sessions')}
-													span(hidden data-ol-inflight="pending") #{translate("processing")}…
-	
+													span(data-ol-inflight='idle') #{translate('clear_sessions')}
+													span(hidden data-ol-inflight='pending') #{translate("processing")}…
+
 								div(hidden data-ol-sent)
 								div(hidden data-ol-sent)
 									p.text-center
 									p.text-center
 										| #{translate("no_other_sessions")}
 										| #{translate("no_other_sessions")}
-	
+
 									p.text-success.text-center
 									p.text-success.text-center
 										| #{translate('clear_sessions_success')}
 										| #{translate('clear_sessions_success')}
 							.page-separator
 							.page-separator

+ 23 - 27
services/web/app/views/user/setPassword-bs5.pug

@@ -7,28 +7,25 @@ block vars
 
 
 block content
 block content
 	main#main-content
 	main#main-content
-		a.auth-aux-logo(href="/")
-			img(src=buildImgPath("ol-brand/overleaf-o-dark.svg") alt=settings.appName)
+		a.auth-aux-logo(href='/')
+			img(src=buildImgPath('ol-brand/overleaf-o-dark.svg') alt=settings.appName)
 		.auth-aux-container
 		.auth-aux-container
 			form(
 			form(
+				name='passwordResetForm'
 				data-ol-async-form
 				data-ol-async-form
-				name="passwordResetForm"
-				action="/user/password/set"
-				method="POST"
-				data-ol-hide-on-error="token-expired"
+				action='/user/password/set'
+				method='POST'
+				data-ol-hide-on-error='token-expired'
 			)
 			)
-				div(
-					hidden
-					data-ol-sent
-				)
+				div(hidden data-ol-sent)
 					h1.h3.mb-3.mt-0 #{translate("password_updated")}
 					h1.h3.mb-3.mt-0 #{translate("password_updated")}
 					p.mb-4 #{translate("your_password_has_been_successfully_changed")}.
 					p.mb-4 #{translate("your_password_has_been_successfully_changed")}.
 					a.btn.btn-primary.w-100(href='/login') #{translate("log_in_now")}
 					a.btn.btn-primary.w-100(href='/login') #{translate("log_in_now")}
 
 
 				div(data-ol-not-sent)
 				div(data-ol-not-sent)
 					h1.h3.mb-3.mt-0 #{translate("reset_your_password")}
 					h1.h3.mb-3.mt-0 #{translate("reset_your_password")}
-					p(data-ol-hide-on-error-message="token-expired") #{translate("create_a_new_password_for_your_account")}.
-					+formMessagesNewStyle()
+					p(data-ol-hide-on-error-message='token-expired') #{translate("create_a_new_password_for_your_account")}.
+					+formMessagesNewStyle
 
 
 					+customFormMessageNewStyle('password-contains-email', 'danger')
 					+customFormMessageNewStyle('password-contains-email', 'danger')
 						| #{translate('invalid_password_contains_email')}.
 						| #{translate('invalid_password_contains_email')}.
@@ -41,18 +38,21 @@ block content
 					+customFormMessageNewStyle('token-expired', 'danger')
 					+customFormMessageNewStyle('token-expired', 'danger')
 						| #{translate('password_reset_token_expired')}
 						| #{translate('password_reset_token_expired')}
 						br
 						br
-						a(href="/user/password/reset")
+						a(href='/user/password/reset')
 							| #{translate('request_new_password_reset_email')}
 							| #{translate('request_new_password_reset_email')}
 
 
-					input(type="hidden" name="_csrf" value=csrfToken)
-					input(type="text" hidden name="email" autocomplete="username" value=email)
+					input(name='_csrf' type='hidden' value=csrfToken)
+					input(name='email' type='text' hidden autocomplete='username' value=email)
 
 
 					.form-group.mb-3
 					.form-group.mb-3
-						label.form-label(for='passwordField', data-ol-hide-on-error-message="token-expired") #{translate("new_password")}
-						input.form-control.auth-aux-new-password#passwordField(
-							type='password'
+						label.form-label(
+							for='passwordField'
+							data-ol-hide-on-error-message='token-expired'
+						) #{translate("new_password")}
+						input#passwordField.form-control.auth-aux-new-password(
 							name='password'
 							name='password'
-							autocomplete="new-password"
+							type='password'
+							autocomplete='new-password'
 							autofocus
 							autofocus
 							required
 							required
 							minlength=settings.passwordStrengthOptions.length.min
 							minlength=settings.passwordStrengthOptions.length.min
@@ -68,12 +68,8 @@ block content
 							| !{translate('password_was_detected_on_a_public_list_of_known_compromised_passwords', {}, [{name: 'a', attrs: {href: 'https://haveibeenpwned.com/passwords', rel: 'noopener noreferrer', target: '_blank'}}])}.
 							| !{translate('password_was_detected_on_a_public_list_of_known_compromised_passwords', {}, [{name: 'a', attrs: {href: 'https://haveibeenpwned.com/passwords', rel: 'noopener noreferrer', target: '_blank'}}])}.
 							| #{translate('use_a_different_password')}.
 							| #{translate('use_a_different_password')}.
 
 
-						input(
-							type="hidden"
-							name="passwordResetToken"
-							value=passwordResetToken
-						)
-					div(data-ol-hide-on-error-message="token-expired")
+						input(name='passwordResetToken' type='hidden' value=passwordResetToken)
+					div(data-ol-hide-on-error-message='token-expired')
 						div #{translate('in_order_to_have_a_secure_account_make_sure_your_password')}
 						div #{translate('in_order_to_have_a_secure_account_make_sure_your_password')}
 						ul.mb-3.ps-4
 						ul.mb-3.ps-4
 							li #{translate('is_longer_than_n_characters', {n: settings.passwordStrengthOptions.length.min})}
 							li #{translate('is_longer_than_n_characters', {n: settings.passwordStrengthOptions.length.min})}
@@ -85,7 +81,7 @@ block content
 							data-ol-disabled-inflight
 							data-ol-disabled-inflight
 							aria-label=translate('set_new_password')
 							aria-label=translate('set_new_password')
 						) 
 						) 
-							span(data-ol-inflight="idle")
+							span(data-ol-inflight='idle')
 								| #{translate('set_new_password')}
 								| #{translate('set_new_password')}
-							span(hidden data-ol-inflight="pending")
+							span(hidden data-ol-inflight='pending')
 								| #{translate('set_new_password')}…
 								| #{translate('set_new_password')}…

+ 34 - 32
services/web/app/views/user/setPassword.pug

@@ -4,28 +4,25 @@ block vars
 	- bootstrap5PageStatus = 'disabled'
 	- bootstrap5PageStatus = 'disabled'
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		.container-custom-sm.mx-auto
 		.container-custom-sm.mx-auto
 			.card
 			.card
 				form(
 				form(
-					data-ol-async-form,
-					name="passwordResetForm",
-					action="/user/password/set",
-					method="POST",
-					data-ol-hide-on-error="token-expired"
+					name='passwordResetForm'
+					data-ol-async-form
+					action='/user/password/set'
+					method='POST'
+					data-ol-hide-on-error='token-expired'
 				)
 				)
-					div(
-						hidden
-						data-ol-sent
-					)
+					div(hidden data-ol-sent)
 						h3.mt-0.mb-2 #{translate("password_updated")}
 						h3.mt-0.mb-2 #{translate("password_updated")}
 						p.mb-4 #{translate("your_password_has_been_successfully_changed")}.
 						p.mb-4 #{translate("your_password_has_been_successfully_changed")}.
 						a(href='/login') #{translate("log_in_now")}
 						a(href='/login') #{translate("log_in_now")}
 
 
 					div(data-ol-not-sent)
 					div(data-ol-not-sent)
 						h3.mt-0.mb-2 #{translate("reset_your_password")}
 						h3.mt-0.mb-2 #{translate("reset_your_password")}
-						p(data-ol-hide-on-error-message="token-expired") #{translate("create_a_new_password_for_your_account")}.
-						+formMessages()
+						p(data-ol-hide-on-error-message='token-expired') #{translate("create_a_new_password_for_your_account")}.
+						+formMessages
 
 
 						+customFormMessage('password-contains-email', 'danger')
 						+customFormMessage('password-contains-email', 'danger')
 							| #{translate('invalid_password_contains_email')}.
 							| #{translate('invalid_password_contains_email')}.
@@ -38,21 +35,30 @@ block content
 						+customFormMessage('token-expired', 'danger')
 						+customFormMessage('token-expired', 'danger')
 							| #{translate('password_reset_token_expired')}
 							| #{translate('password_reset_token_expired')}
 							br
 							br
-							a(href="/user/password/reset")
+							a(href='/user/password/reset')
 								| #{translate('request_new_password_reset_email')}
 								| #{translate('request_new_password_reset_email')}
 
 
-						input(type="hidden", name="_csrf", value=csrfToken)
-						input(type="text" hidden name="email" autocomplete="username" value=email)
+						input(name='_csrf' type='hidden' value=csrfToken)
+						input(
+							name='email'
+							type='text'
+							hidden
+							autocomplete='username'
+							value=email
+						)
 
 
 						.form-group
 						.form-group
-							label(for='passwordField', data-ol-hide-on-error-message="token-expired") #{translate("new_password")}
-							input.form-control#passwordField(
-								type='password',
-								name='password',
-								placeholder=translate("enter_your_new_password"),
-								autocomplete="new-password",
-								autofocus,
-								required,
+							label(
+								for='passwordField'
+								data-ol-hide-on-error-message='token-expired'
+							) #{translate("new_password")}
+							input#passwordField.form-control(
+								name='password'
+								type='password'
+								placeholder=translate('enter_your_new_password')
+								autocomplete='new-password'
+								autofocus
+								required
 								minlength=settings.passwordStrengthOptions.length.min
 								minlength=settings.passwordStrengthOptions.length.min
 							)
 							)
 
 
@@ -66,12 +72,8 @@ block content
 								| !{translate('password_was_detected_on_a_public_list_of_known_compromised_passwords', {}, [{name: 'a', attrs: {href: 'https://haveibeenpwned.com/passwords', rel: 'noopener noreferrer', target: '_blank'}}])}.
 								| !{translate('password_was_detected_on_a_public_list_of_known_compromised_passwords', {}, [{name: 'a', attrs: {href: 'https://haveibeenpwned.com/passwords', rel: 'noopener noreferrer', target: '_blank'}}])}.
 								| #{translate('use_a_different_password')}.
 								| #{translate('use_a_different_password')}.
 
 
-							input(
-								type="hidden",
-								name="passwordResetToken",
-								value=passwordResetToken
-							)
-						div(data-ol-hide-on-error-message="token-expired")
+							input(name='passwordResetToken' type='hidden' value=passwordResetToken)
+						div(data-ol-hide-on-error-message='token-expired')
 							div #{translate('in_order_to_have_a_secure_account_make_sure_your_password')}
 							div #{translate('in_order_to_have_a_secure_account_make_sure_your_password')}
 							ul.mb-4.ps-4
 							ul.mb-4.ps-4
 								li #{translate('is_longer_than_n_characters', {n: settings.passwordStrengthOptions.length.min})}
 								li #{translate('is_longer_than_n_characters', {n: settings.passwordStrengthOptions.length.min})}
@@ -79,11 +81,11 @@ block content
 								li #{translate('is_not_used_on_any_other_website')}
 								li #{translate('is_not_used_on_any_other_website')}
 						.actions
 						.actions
 							button.btn.btn-primary.w-100(
 							button.btn.btn-primary.w-100(
-								type='submit',
+								type='submit'
 								data-ol-disabled-inflight
 								data-ol-disabled-inflight
 								aria-label=translate('set_new_password')
 								aria-label=translate('set_new_password')
 							) 
 							) 
-								span(data-ol-inflight="idle")
+								span(data-ol-inflight='idle')
 									| #{translate('set_new_password')}
 									| #{translate('set_new_password')}
-								span(hidden data-ol-inflight="pending")
+								span(hidden data-ol-inflight='pending')
 									| #{translate('set_new_password')}… 
 									| #{translate('set_new_password')}… 

+ 54 - 27
services/web/app/views/user/settings.pug

@@ -2,38 +2,65 @@ extends ../layout-react
 
 
 block entrypointVar
 block entrypointVar
 	- entrypoint = 'pages/user/settings'
 	- entrypoint = 'pages/user/settings'
-		
+
 block vars
 block vars
 	- isWebsiteRedesign = true
 	- isWebsiteRedesign = true
 
 
 block append meta
 block append meta
-	meta(name="ol-hasPassword" data-type="boolean" content=hasPassword)
-	meta(name="ol-shouldAllowEditingDetails" data-type="boolean" content=shouldAllowEditingDetails)
-	meta(name="ol-oauthProviders", data-type="json", content=oauthProviders)
-	meta(name="ol-institutionLinked", data-type="json", content=institutionLinked)
-	meta(name="ol-samlError", data-type="json", content=samlError)
-	meta(name="ol-institutionEmailNonCanonical", content=institutionEmailNonCanonical)
+	meta(name='ol-hasPassword' data-type='boolean' content=hasPassword)
+	meta(
+		name='ol-shouldAllowEditingDetails'
+		data-type='boolean'
+		content=shouldAllowEditingDetails
+	)
+	meta(name='ol-oauthProviders' data-type='json' content=oauthProviders)
+	meta(name='ol-institutionLinked' data-type='json' content=institutionLinked)
+	meta(name='ol-samlError' data-type='json' content=samlError)
+	meta(
+		name='ol-institutionEmailNonCanonical'
+		content=institutionEmailNonCanonical
+	)
 
 
-	meta(name="ol-reconfirmedViaSAML", content=reconfirmedViaSAML)
-	meta(name="ol-reconfirmationRemoveEmail", content=reconfirmationRemoveEmail)
-	meta(name="ol-samlBeta", content=samlBeta)
-	meta(name="ol-ssoErrorMessage", content=ssoErrorMessage)
-	meta(name="ol-thirdPartyIds", data-type="json", content=thirdPartyIds || {})
-	meta(name="ol-passwordStrengthOptions", data-type="json", content=settings.passwordStrengthOptions || {})
-	meta(name="ol-isExternalAuthenticationSystemUsed" data-type="boolean" content=externalAuthenticationSystemUsed())
-	meta(name="ol-user" data-type="json" content=user)
-	meta(name="ol-labsExperiments" data-type="json" content=labsExperiments)
-	meta(name="ol-dropbox" data-type="json" content=dropbox)
-	meta(name="ol-github" data-type="json" content=github)
-	meta(name="ol-projectSyncSuccessMessage", content=projectSyncSuccessMessage)
-	meta(name="ol-personalAccessTokens", data-type="json" content=personalAccessTokens)
-	meta(name="ol-emailAddressLimit", data-type="json", content=emailAddressLimit)
-	meta(name="ol-currentManagedUserAdminEmail" data-type="string" content=currentManagedUserAdminEmail)
-	meta(name="ol-gitBridgeEnabled" data-type="boolean" content=gitBridgeEnabled)
-	meta(name="ol-isSaas" data-type="boolean" content=isSaas)
-	meta(name="ol-memberOfSSOEnabledGroups" data-type="json" content=memberOfSSOEnabledGroups)
-	meta(name="ol-capabilities" data-type="json" content=capabilities)
+	meta(name='ol-reconfirmedViaSAML' content=reconfirmedViaSAML)
+	meta(name='ol-reconfirmationRemoveEmail' content=reconfirmationRemoveEmail)
+	meta(name='ol-samlBeta' content=samlBeta)
+	meta(name='ol-ssoErrorMessage' content=ssoErrorMessage)
+	meta(name='ol-thirdPartyIds' data-type='json' content=thirdPartyIds || {})
+	meta(
+		name='ol-passwordStrengthOptions'
+		data-type='json'
+		content=settings.passwordStrengthOptions || {}
+	)
+	meta(
+		name='ol-isExternalAuthenticationSystemUsed'
+		data-type='boolean'
+		content=externalAuthenticationSystemUsed()
+	)
+	meta(name='ol-user' data-type='json' content=user)
+	meta(name='ol-labsExperiments' data-type='json' content=labsExperiments)
+	meta(name='ol-dropbox' data-type='json' content=dropbox)
+	meta(name='ol-github' data-type='json' content=github)
+	meta(name='ol-projectSyncSuccessMessage' content=projectSyncSuccessMessage)
+	meta(
+		name='ol-personalAccessTokens'
+		data-type='json'
+		content=personalAccessTokens
+	)
+	meta(name='ol-emailAddressLimit' data-type='json' content=emailAddressLimit)
+	meta(
+		name='ol-currentManagedUserAdminEmail'
+		data-type='string'
+		content=currentManagedUserAdminEmail
+	)
+	meta(name='ol-gitBridgeEnabled' data-type='boolean' content=gitBridgeEnabled)
+	meta(name='ol-isSaas' data-type='boolean' content=isSaas)
+	meta(
+		name='ol-memberOfSSOEnabledGroups'
+		data-type='json'
+		content=memberOfSSOEnabledGroups
+	)
+	meta(name='ol-capabilities' data-type='json' content=capabilities)
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		#settings-page-root
 		#settings-page-root

+ 5 - 5
services/web/app/views/user_membership/group-managers-react.pug

@@ -4,10 +4,10 @@ block entrypointVar
 	- entrypoint = 'pages/user/subscription/group-management/group-managers'
 	- entrypoint = 'pages/user/subscription/group-management/group-managers'
 
 
 block append meta
 block append meta
-	meta(name="ol-user", data-type="json", content=user)
-	meta(name="ol-users", data-type="json", content=users)
-	meta(name="ol-groupId", data-type="string", content=groupId)
-	meta(name="ol-groupName", data-type="string", content=name)
+	meta(name='ol-user' data-type='json' content=user)
+	meta(name='ol-users' data-type='json' content=users)
+	meta(name='ol-groupId' data-type='string' content=groupId)
+	meta(name='ol-groupName' data-type='string' content=name)
 
 
 block content
 block content
-	main.content.content-alt#subscription-manage-group-root
+	main#subscription-manage-group-root.content.content-alt

+ 28 - 12
services/web/app/views/user_membership/group-members-react.pug

@@ -2,18 +2,34 @@ extends ../layout-react
 
 
 block entrypointVar
 block entrypointVar
 	- entrypoint = 'pages/user/subscription/group-management/group-members'
 	- entrypoint = 'pages/user/subscription/group-management/group-members'
-		
+
 block append meta
 block append meta
-	meta(name="ol-user", data-type="json", content=user)
-	meta(name="ol-users", data-type="json", content=users)
-	meta(name="ol-groupId", data-type="string", content=groupId)
-	meta(name="ol-groupName", data-type="string", content=name)
-	meta(name="ol-groupSize", data-type="json", content=groupSize)
-	meta(name="ol-managedUsersActive", data-type="boolean", content=managedUsersActive)
-	meta(name="ol-isUserGroupManager", data-type="boolean", content=isUserGroupManager)
-	meta(name="ol-groupSSOActive", data-type="boolean", content=groupSSOActive)
-	meta(name="ol-canUseFlexibleLicensing", data-type="boolean", content=canUseFlexibleLicensing)
-	meta(name="ol-canUseAddSeatsFeature", data-type="boolean", content=canUseAddSeatsFeature)
+	meta(name='ol-user' data-type='json' content=user)
+	meta(name='ol-users' data-type='json' content=users)
+	meta(name='ol-groupId' data-type='string' content=groupId)
+	meta(name='ol-groupName' data-type='string' content=name)
+	meta(name='ol-groupSize' data-type='json' content=groupSize)
+	meta(
+		name='ol-managedUsersActive'
+		data-type='boolean'
+		content=managedUsersActive
+	)
+	meta(
+		name='ol-isUserGroupManager'
+		data-type='boolean'
+		content=isUserGroupManager
+	)
+	meta(name='ol-groupSSOActive' data-type='boolean' content=groupSSOActive)
+	meta(
+		name='ol-canUseFlexibleLicensing'
+		data-type='boolean'
+		content=canUseFlexibleLicensing
+	)
+	meta(
+		name='ol-canUseAddSeatsFeature'
+		data-type='boolean'
+		content=canUseAddSeatsFeature
+	)
 
 
 block content
 block content
-	main.content.content-alt#subscription-manage-group-root
+	main#subscription-manage-group-root.content.content-alt

+ 5 - 5
services/web/app/views/user_membership/institution-managers-react.pug

@@ -4,10 +4,10 @@ block entrypointVar
 	- entrypoint = 'pages/user/subscription/group-management/institution-managers'
 	- entrypoint = 'pages/user/subscription/group-management/institution-managers'
 
 
 block append meta
 block append meta
-	meta(name="ol-user" data-type="json" content=user)
-	meta(name="ol-users", data-type="json", content=users)
-	meta(name="ol-groupId", data-type="string", content=groupId)
-	meta(name="ol-groupName", data-type="string", content=name)
+	meta(name='ol-user' data-type='json' content=user)
+	meta(name='ol-users' data-type='json' content=users)
+	meta(name='ol-groupId' data-type='string' content=groupId)
+	meta(name='ol-groupName' data-type='string' content=name)
 
 
 block content
 block content
-	main.content.content-alt#subscription-manage-group-root
+	main#subscription-manage-group-root.content.content-alt

+ 6 - 13
services/web/app/views/user_membership/new.pug

@@ -4,20 +4,13 @@ block vars
 	- bootstrap5PageStatus = 'disabled'
 	- bootstrap5PageStatus = 'disabled'
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		.container
 		.container
 			.row
 			.row
 				.col-md-10.col-md-offset-1
 				.col-md-10.col-md-offset-1
 					h3 #{entityName} "#{entityId}" does not exists in v2
 					h3 #{entityName} "#{entityId}" does not exists in v2
-					form(
-						data-ol-regular-form
-						method='post',
-						action=''
-					)
-						input(name="_csrf", type="hidden", value=csrfToken)
-						button.btn.btn-primary(
-							type="submit",
-							data-ol-disabled-inflight
-						)
-							span(data-ol-inflight="idle") Create #{entityName} in v2
-							span(hidden data-ol-inflight="pending") #{translate("creating")}…
+					form(data-ol-regular-form method='post' action='')
+						input(name='_csrf' type='hidden' value=csrfToken)
+						button.btn.btn-primary(type='submit' data-ol-disabled-inflight)
+							span(data-ol-inflight='idle') Create #{entityName} in v2
+							span(hidden data-ol-inflight='pending') #{translate("creating")}…

+ 5 - 5
services/web/app/views/user_membership/publisher-managers-react.pug

@@ -4,10 +4,10 @@ block entrypointVar
 	- entrypoint = 'pages/user/subscription/group-management/publisher-managers'
 	- entrypoint = 'pages/user/subscription/group-management/publisher-managers'
 
 
 block append meta
 block append meta
-	meta(name="ol-user" data-type="json" content=user)
-	meta(name="ol-users", data-type="json", content=users)
-	meta(name="ol-groupId", data-type="string", content=groupId)
-	meta(name="ol-groupName", data-type="string", content=name)
+	meta(name='ol-user' data-type='json' content=user)
+	meta(name='ol-users' data-type='json' content=users)
+	meta(name='ol-groupId' data-type='string' content=groupId)
+	meta(name='ol-groupName' data-type='string' content=name)
 
 
 block content
 block content
-	main.content.content-alt#subscription-manage-group-root
+	main#subscription-manage-group-root.content.content-alt

+ 70 - 89
services/web/modules/launchpad/app/views/launchpad.pug

@@ -2,39 +2,43 @@ extends ../../../../app/views/layout-marketing
 
 
 mixin launchpad-check(section)
 mixin launchpad-check(section)
 	div(data-ol-launchpad-check=section)
 	div(data-ol-launchpad-check=section)
-		span(data-ol-inflight="pending")
+		span(data-ol-inflight='pending')
 			i.fa.fa-fw.fa-spinner.fa-spin
 			i.fa.fa-fw.fa-spinner.fa-spin
 			span &nbsp;#{translate('checking')}
 			span &nbsp;#{translate('checking')}
-	
-		span(hidden data-ol-inflight="idle")
-			div(data-ol-result="success")
+
+		span(hidden data-ol-inflight='idle')
+			div(data-ol-result='success')
 				i.fa.fa-check
 				i.fa.fa-check
 				span &nbsp;#{translate('ok')}
 				span &nbsp;#{translate('ok')}
 				button.btn.btn-inline-link
 				button.btn.btn-inline-link
 					span.text-danger &nbsp;#{translate('retry')}
 					span.text-danger &nbsp;#{translate('retry')}
-			div(hidden data-ol-result="error")
+			div(hidden data-ol-result='error')
 				i.fa.fa-exclamation
 				i.fa.fa-exclamation
 				span &nbsp;#{translate('error')}
 				span &nbsp;#{translate('error')}
 				button.btn.btn-inline-link
 				button.btn.btn-inline-link
 					span.text-danger &nbsp;#{translate('retry')}
 					span.text-danger &nbsp;#{translate('retry')}
-				div.alert.alert-danger
+				.alert.alert-danger
 					span(data-ol-error)
 					span(data-ol-error)
 
 
 block entrypointVar
 block entrypointVar
 	- entrypoint = 'modules/launchpad/pages/launchpad'
 	- entrypoint = 'modules/launchpad/pages/launchpad'
-					
+
 block vars
 block vars
 	- metadata = metadata || {}
 	- metadata = metadata || {}
 	- bootstrap5PageStatus = 'disabled'
 	- bootstrap5PageStatus = 'disabled'
 
 
 block append meta
 block append meta
-	meta(name="ol-adminUserExists" data-type="boolean" content=adminUserExists)
-	meta(name="ol-ideJsPath" content=buildJsPath('ide.js'))
+	meta(name='ol-adminUserExists' data-type='boolean' content=adminUserExists)
+	meta(name='ol-ideJsPath' content=buildJsPath('ide.js'))
 
 
 block content
 block content
-	script(type="text/javascript", nonce=scriptNonce, src=(wsUrl || '/socket.io') + '/socket.io.js')
+	script(
+		type='text/javascript'
+		nonce=scriptNonce
+		src=(wsUrl || '/socket.io') + '/socket.io.js'
+	)
 
 
-	.content.content-alt#main-content
+	#main-content.content.content-alt
 		.container
 		.container
 			.row
 			.row
 				.col-md-8.col-md-offset-2
 				.col-md-8.col-md-offset-2
@@ -49,8 +53,6 @@ block content
 						<!-- wrapper -->
 						<!-- wrapper -->
 						.row
 						.row
 							.col-md-8.col-md-offset-2
 							.col-md-8.col-md-offset-2
-
-
 								<!-- create first admin form -->
 								<!-- create first admin form -->
 								if !adminUserExists
 								if !adminUserExists
 									.row(data-ol-not-sent)
 									.row(data-ol-not-sent)
@@ -62,37 +64,34 @@ block content
 												form(
 												form(
 													data-ol-async-form
 													data-ol-async-form
 													data-ol-register-admin
 													data-ol-register-admin
-													action="/launchpad/register_admin"
-													method="POST"
+													action='/launchpad/register_admin'
+													method='POST'
 												)
 												)
-													input(name='_csrf', type='hidden', value=csrfToken)
-													+formMessages()
+													input(name='_csrf' type='hidden' value=csrfToken)
+													+formMessages
 													.form-group
 													.form-group
 														label(for='email') #{translate("email")}
 														label(for='email') #{translate("email")}
 														input.form-control(
 														input.form-control(
-															type='email',
-															name='email',
-															placeholder="email@example.com"
-															autocomplete="username"
-															required,
-															autofocus="true"
+															name='email'
+															type='email'
+															placeholder='email@example.com'
+															autocomplete='username'
+															required
+															autofocus='true'
 														)
 														)
 													.form-group
 													.form-group
 														label(for='password') #{translate("password")}
 														label(for='password') #{translate("password")}
-														input.form-control#passwordField(
-															type='password',
-															name='password',
-															placeholder="********",
-															autocomplete="new-password"
-															required,
+														input#passwordField.form-control(
+															name='password'
+															type='password'
+															placeholder='********'
+															autocomplete='new-password'
+															required
 														)
 														)
 													.actions
 													.actions
-														button.btn-primary.btn(
-															type='submit'
-															data-ol-disabled-inflight
-														)
-															span(data-ol-inflight="idle") #{translate("register")}
-															span(hidden data-ol-inflight="pending") #{translate("registering")}…
+														button.btn-primary.btn(type='submit' data-ol-disabled-inflight)
+															span(data-ol-inflight='idle') #{translate("register")}
+															span(hidden data-ol-inflight='pending') #{translate("registering")}…
 
 
 											// Ldap Form
 											// Ldap Form
 											if authMethod === 'ldap'
 											if authMethod === 'ldap'
@@ -103,28 +102,25 @@ block content
 												form(
 												form(
 													data-ol-async-form
 													data-ol-async-form
 													data-ol-register-admin
 													data-ol-register-admin
-													action="/launchpad/register_ldap_admin"
-													method="POST"
+													action='/launchpad/register_ldap_admin'
+													method='POST'
 												)
 												)
-													input(name='_csrf', type='hidden', value=csrfToken)
-													+formMessages()
+													input(name='_csrf' type='hidden' value=csrfToken)
+													+formMessages
 													.form-group
 													.form-group
 														label(for='email') #{translate("email")}
 														label(for='email') #{translate("email")}
 														input.form-control(
 														input.form-control(
-															type='email',
-															name='email',
-															placeholder="email@example.com"
-															autocomplete="username"
-															required,
-															autofocus="true"
+															name='email'
+															type='email'
+															placeholder='email@example.com'
+															autocomplete='username'
+															required
+															autofocus='true'
 														)
 														)
 													.actions
 													.actions
-														button.btn-primary.btn(
-															type='submit'
-															data-ol-disabled-inflight
-														)
-															span(data-ol-inflight="idle") #{translate("register")}
-															span(hidden data-ol-inflight="pending") #{translate("registering")}…
+														button.btn-primary.btn(type='submit' data-ol-disabled-inflight)
+															span(data-ol-inflight='idle') #{translate("register")}
+															span(hidden data-ol-inflight='pending') #{translate("registering")}…
 
 
 											// Saml Form
 											// Saml Form
 											if authMethod === 'saml'
 											if authMethod === 'saml'
@@ -135,28 +131,25 @@ block content
 												form(
 												form(
 													data-ol-async-form
 													data-ol-async-form
 													data-ol-register-admin
 													data-ol-register-admin
-													action="/launchpad/register_saml_admin"
-													method="POST"
+													action='/launchpad/register_saml_admin'
+													method='POST'
 												)
 												)
-													input(name='_csrf', type='hidden', value=csrfToken)
-													+formMessages()
+													input(name='_csrf' type='hidden' value=csrfToken)
+													+formMessages
 													.form-group
 													.form-group
 														label(for='email') #{translate("email")}
 														label(for='email') #{translate("email")}
 														input.form-control(
 														input.form-control(
-															type='email',
-															name='email',
-															placeholder="email@example.com"
-															autocomplete="username"
-															required,
-															autofocus="true"
+															name='email'
+															type='email'
+															placeholder='email@example.com'
+															autocomplete='username'
+															required
+															autofocus='true'
 														)
 														)
 													.actions
 													.actions
-														button.btn-primary.btn(
-															type='submit'
-															data-ol-disabled-inflight
-														)
-															span(data-ol-inflight="idle") #{translate("register")}
-															span(hidden data-ol-inflight="pending") #{translate("registering")}…
+														button.btn-primary.btn(type='submit' data-ol-disabled-inflight)
+															span(data-ol-inflight='idle') #{translate("register")}
+															span(hidden data-ol-inflight='pending') #{translate("registering")}…
 
 
 									br
 									br
 
 
@@ -164,7 +157,6 @@ block content
 								if adminUserExists
 								if adminUserExists
 									.row
 									.row
 										.col-md-12.status-indicators
 										.col-md-12.status-indicators
-
 											h2 #{translate('status_checks')}
 											h2 #{translate('status_checks')}
 
 
 											<!-- websocket -->
 											<!-- websocket -->
@@ -185,42 +177,31 @@ block content
 											h3 #{translate('send_test_email')}
 											h3 #{translate('send_test_email')}
 											form.form(
 											form.form(
 												data-ol-async-form
 												data-ol-async-form
-												action="/launchpad/send_test_email"
-												method="POST"
+												action='/launchpad/send_test_email'
+												method='POST'
 											)
 											)
 												.form-group
 												.form-group
-													label(for="email") Email
-													input.form-control(
-														type="text"
-														id="email"
-														name="email"
-														required
-													)
-												button.btn-primary.btn(
-													type='submit'
-													data-ol-disabled-inflight
-												)
-													span(data-ol-inflight="idle") #{translate("send")}
-													span(hidden data-ol-inflight="pending") #{translate("sending")}…
+													label(for='email') Email
+													input.form-control(name='email' type='text' id='email' required)
+												button.btn-primary.btn(type='submit' data-ol-disabled-inflight)
+													span(data-ol-inflight='idle') #{translate("send")}
+													span(hidden data-ol-inflight='pending') #{translate("sending")}…
 
 
 												p
 												p
-													+formMessages()
-
-
+													+formMessages
 
 
 									<!-- break -->
 									<!-- break -->
 									hr.thin
 									hr.thin
 
 
-
 									<!-- Go to app -->
 									<!-- Go to app -->
 									.row
 									.row
 										.col-md-12
 										.col-md-12
 											.text-center
 											.text-center
 												br
 												br
 												p
 												p
-													a(href="/admin").btn.btn-info
+													a.btn.btn-info(href='/admin')
 														| Go To Admin Panel
 														| Go To Admin Panel
 													| &nbsp;
 													| &nbsp;
-													a(href="/project").btn.btn-primary
+													a.btn.btn-primary(href='/project')
 														| Start Using #{settings.appName}
 														| Start Using #{settings.appName}
 												br
 												br

+ 27 - 31
services/web/modules/user-activate/app/views/user/activate.pug

@@ -6,14 +6,14 @@ block vars
 include ../../../../../app/views/_mixins/material_symbol
 include ../../../../../app/views/_mixins/material_symbol
 
 
 block content
 block content
-	main.content.content-alt#main-content
+	main#main-content.content.content-alt
 		.container
 		.container
-			div.col-lg-6.col-xl-4.m-auto
+			.col-lg-6.col-xl-4.m-auto
 				.notification-list
 				.notification-list
-					.notification.notification-type-success(aria-live="off" role="alert")
+					.notification.notification-type-success(aria-live='off' role='alert')
 						.notification-content-and-cta
 						.notification-content-and-cta
 							.notification-icon
 							.notification-icon
-								+material-symbol("check_circle")
+								+material-symbol('check_circle')
 							.notification-content
 							.notification-content
 								p
 								p
 									| #{translate("nearly_activated")}
 									| #{translate("nearly_activated")}
@@ -21,12 +21,12 @@ block content
 				h1.h3 #{translate("please_set_a_password")}
 				h1.h3 #{translate("please_set_a_password")}
 
 
 				form(
 				form(
+					name='activationForm'
 					data-ol-async-form
 					data-ol-async-form
-					name="activationForm",
-					action="/user/password/set",
-					method="POST",
+					action='/user/password/set'
+					method='POST'
 				)
 				)
-					+formMessages()
+					+formMessages
 
 
 					+customFormMessage('token-expired', 'danger')
 					+customFormMessage('token-expired', 'danger')
 						| #{translate("activation_token_expired")}
 						| #{translate("activation_token_expired")}
@@ -34,43 +34,39 @@ block content
 					+customFormMessage('invalid-password', 'danger')
 					+customFormMessage('invalid-password', 'danger')
 						| #{translate('invalid_password')}
 						| #{translate('invalid_password')}
 
 
-					input(name='_csrf', type='hidden', value=csrfToken)
-					input(
-						type="hidden",
-						name="passwordResetToken",
-						value=token
-					)
+					input(name='_csrf' type='hidden' value=csrfToken)
+					input(name='passwordResetToken' type='hidden' value=token)
 
 
 					.form-group
 					.form-group
 						label(for='emailField') #{translate("email")}
 						label(for='emailField') #{translate("email")}
-						input.form-control#emailField(
-							aria-label="email",
-							type='email',
-							name='email',
-							placeholder="email@example.com",
-							autocomplete="username"
+						input#emailField.form-control(
+							name='email'
+							aria-label='email'
+							type='email'
+							placeholder='email@example.com'
+							autocomplete='username'
 							value=email
 							value=email
-							required,
+							required
 							disabled
 							disabled
 						)
 						)
 					.form-group
 					.form-group
 						label(for='passwordField') #{translate("password")}
 						label(for='passwordField') #{translate("password")}
-						input.form-control#passwordField(
-							type='password',
-							name='password',
-							placeholder="********",
-							autocomplete="new-password",
-							autofocus,
-							required,
+						input#passwordField.form-control(
+							name='password'
+							type='password'
+							placeholder='********'
+							autocomplete='new-password'
+							autofocus
+							required
 							minlength=settings.passwordStrengthOptions.length.min
 							minlength=settings.passwordStrengthOptions.length.min
 						)
 						)
 					.actions
 					.actions
 						button.btn.btn-primary(
 						button.btn.btn-primary(
-							type='submit',
+							type='submit'
 							data-ol-disabled-inflight
 							data-ol-disabled-inflight
 							aria-label=translate('activate')
 							aria-label=translate('activate')
 						)
 						)
-							span(data-ol-inflight="idle")
+							span(data-ol-inflight='idle')
 								| #{translate('activate')}
 								| #{translate('activate')}
-							span(hidden data-ol-inflight="pending")
+							span(hidden data-ol-inflight='pending')
 								| #{translate('activating')}…
 								| #{translate('activating')}…

+ 2 - 2
services/web/modules/user-activate/app/views/user/register.pug

@@ -4,9 +4,9 @@ block entrypointVar
 	- entrypoint = 'modules/user-activate/pages/user-activate-page'
 	- entrypoint = 'modules/user-activate/pages/user-activate-page'
 
 
 block append meta
 block append meta
-	meta(name="ol-user" data-type="json" content=user)
+	meta(name='ol-user' data-type='json' content=user)
 
 
 block content
 block content
-	.content.content-alt#main-content
+	#main-content.content.content-alt
 		.container
 		.container
 			#user-activate-register-container
 			#user-activate-register-container

+ 3 - 0
services/web/package.json

@@ -28,6 +28,8 @@
     "format:fix": "prettier --write $PWD/'**/*.{js,jsx,mjs,ts,tsx,json}'",
     "format:fix": "prettier --write $PWD/'**/*.{js,jsx,mjs,ts,tsx,json}'",
     "format:styles": "prettier --list-different $PWD/'**/*.{css,less,scss}'",
     "format:styles": "prettier --list-different $PWD/'**/*.{css,less,scss}'",
     "format:styles:fix": "prettier --write $PWD/'**/*.{css,less,scss}'",
     "format:styles:fix": "prettier --write $PWD/'**/*.{css,less,scss}'",
+    "format:pug": "prettier --list-different $PWD/'**/*.pug'",
+    "format:pug:fix": "prettier --write $PWD/'**/*.pug'",
     "lint": "eslint --max-warnings 0 --format unix --ext .js,.jsx,.mjs,.ts,.tsx .",
     "lint": "eslint --max-warnings 0 --format unix --ext .js,.jsx,.mjs,.ts,.tsx .",
     "lint:fix": "eslint --fix --ext .js,.jsx,.mjs,.ts,.tsx .",
     "lint:fix": "eslint --fix --ext .js,.jsx,.mjs,.ts,.tsx .",
     "lint:styles": "stylelint '**/*.scss'",
     "lint:styles": "stylelint '**/*.scss'",
@@ -209,6 +211,7 @@
     "@pollyjs/adapter-node-http": "^6.0.6",
     "@pollyjs/adapter-node-http": "^6.0.6",
     "@pollyjs/core": "^6.0.6",
     "@pollyjs/core": "^6.0.6",
     "@pollyjs/persister-fs": "^6.0.6",
     "@pollyjs/persister-fs": "^6.0.6",
+    "@prettier/plugin-pug": "^3.4.0",
     "@replit/codemirror-emacs": "overleaf/codemirror-emacs#4394c03858f27053f8768258e9493866e06e938e",
     "@replit/codemirror-emacs": "overleaf/codemirror-emacs#4394c03858f27053f8768258e9493866e06e938e",
     "@replit/codemirror-indentation-markers": "overleaf/codemirror-indentation-markers#78264032eb286bc47871569ae87bff5ca1c6c161",
     "@replit/codemirror-indentation-markers": "overleaf/codemirror-indentation-markers#78264032eb286bc47871569ae87bff5ca1c6c161",
     "@replit/codemirror-vim": "overleaf/codemirror-vim#1bef138382d948018f3f9b8a4d7a70ab61774e4b",
     "@replit/codemirror-vim": "overleaf/codemirror-vim#1bef138382d948018f3f9b8a4d7a70ab61774e4b",