Преглед на файлове

Merge pull request #18898 from overleaf/jpa-no-window

[web] migrate from window attributes to getMeta

GitOrigin-RevId: 3dcf1ab6b01155e5e4abeb3e78d0fa9053e055bc
Jakob Ackermann преди 2 години
родител
ревизия
aa480a2663
променени са 100 файла, в които са добавени 348 реда и са изтрити 380 реда
  1. 0 3
      services/web/.eslintrc.js
  2. 80 8
      services/web/.storybook/preview.tsx
  3. 8 0
      services/web/app/src/infrastructure/ExpressLocals.js
  4. 0 3
      services/web/app/views/project/editor/meta.pug
  5. 2 10
      services/web/cypress/support/component.ts
  6. 16 10
      services/web/cypress/support/ct/window.ts
  7. 7 2
      services/web/frontend/js/features/cookie-banner/index.js
  8. 2 2
      services/web/frontend/js/features/editor-left-menu/components/actions-menu.tsx
  9. 1 1
      services/web/frontend/js/features/editor-left-menu/components/help-menu.tsx
  10. 2 2
      services/web/frontend/js/features/editor-left-menu/components/settings-menu.tsx
  11. 2 4
      services/web/frontend/js/features/editor-left-menu/components/settings/settings-editor-theme.tsx
  12. 7 7
      services/web/frontend/js/features/editor-left-menu/components/settings/settings-image-name.tsx
  13. 2 3
      services/web/frontend/js/features/editor-left-menu/components/settings/settings-overall-theme.tsx
  14. 1 2
      services/web/frontend/js/features/editor-left-menu/components/settings/settings-spell-check-language.tsx
  15. 3 3
      services/web/frontend/js/features/editor-left-menu/components/sync-menu.tsx
  16. 2 1
      services/web/frontend/js/features/editor-left-menu/hooks/use-set-overall-theme.tsx
  17. 2 1
      services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.jsx
  18. 9 3
      services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-body.jsx
  19. 2 1
      services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-import-from-project.tsx
  20. 3 2
      services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-upload-doc.tsx
  21. 1 1
      services/web/frontend/js/features/file-tree/contexts/file-tree-path.tsx
  22. 3 1
      services/web/frontend/js/features/file-tree/util/count-in-tree.js
  23. 2 1
      services/web/frontend/js/features/file-tree/util/is-acceptable-file.ts
  24. 2 1
      services/web/frontend/js/features/file-view/components/file-view.jsx
  25. 2 2
      services/web/frontend/js/features/group-management/components/group-managers.tsx
  26. 3 3
      services/web/frontend/js/features/group-management/components/group-members.tsx
  27. 2 2
      services/web/frontend/js/features/group-management/components/institution-managers.tsx
  28. 1 1
      services/web/frontend/js/features/group-management/components/managers-table.tsx
  29. 1 1
      services/web/frontend/js/features/group-management/components/members-table/member-row.tsx
  30. 1 1
      services/web/frontend/js/features/group-management/components/members-table/members-list.tsx
  31. 2 2
      services/web/frontend/js/features/group-management/components/publisher-managers.tsx
  32. 2 2
      services/web/frontend/js/features/group-management/context/group-members-context.tsx
  33. 2 4
      services/web/frontend/js/features/ide-react/components/loading.tsx
  34. 10 5
      services/web/frontend/js/features/ide-react/context/ide-react-context.tsx
  35. 1 1
      services/web/frontend/js/features/ide-react/context/permissions-context.tsx
  36. 2 1
      services/web/frontend/js/features/ide-react/context/review-panel/hooks/use-review-panel-state.ts
  37. 2 1
      services/web/frontend/js/features/ide-react/editor/document-container.ts
  38. 2 2
      services/web/frontend/js/features/ide-react/editor/share-js-doc.ts
  39. 2 1
      services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt.tsx
  40. 3 2
      services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt-new.tsx
  41. 1 1
      services/web/frontend/js/features/pdf-preview/util/pdf-js-versions.js
  42. 1 1
      services/web/frontend/js/features/plans/utils/group-plan-pricing.js
  43. 2 4
      services/web/frontend/js/features/project-list/components/add-affiliation.tsx
  44. 1 4
      services/web/frontend/js/features/project-list/components/current-plan-widget/current-plan-widget.tsx
  45. 1 4
      services/web/frontend/js/features/project-list/components/modals/projects-action-modal.tsx
  46. 1 4
      services/web/frontend/js/features/project-list/components/modals/rename-project-modal.tsx
  47. 3 5
      services/web/frontend/js/features/project-list/components/new-project-button.tsx
  48. 1 5
      services/web/frontend/js/features/project-list/components/new-project-button/modal-content-new-project-form.tsx
  49. 2 5
      services/web/frontend/js/features/project-list/components/new-project-button/upload-project-modal.tsx
  50. 3 3
      services/web/frontend/js/features/project-list/components/notifications/geo-banners.tsx
  51. 11 14
      services/web/frontend/js/features/project-list/components/notifications/groups-and-enterprise-banner.tsx
  52. 2 6
      services/web/frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirm-affiliation.tsx
  53. 4 7
      services/web/frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirmation-info.tsx
  54. 4 9
      services/web/frontend/js/features/project-list/components/notifications/groups/common.tsx
  55. 4 12
      services/web/frontend/js/features/project-list/components/notifications/groups/confirm-email.tsx
  56. 1 4
      services/web/frontend/js/features/project-list/components/notifications/groups/group-invitation/group-invitation-cancel-subscription.tsx
  57. 1 4
      services/web/frontend/js/features/project-list/components/notifications/groups/group-invitation/group-invitation-join.tsx
  58. 1 4
      services/web/frontend/js/features/project-list/components/notifications/groups/group-invitation/group-invitation-successful.tsx
  59. 1 1
      services/web/frontend/js/features/project-list/components/notifications/groups/group-invitation/hooks/use-group-invitation-notification.tsx
  60. 3 13
      services/web/frontend/js/features/project-list/components/notifications/groups/institution.tsx
  61. 1 2
      services/web/frontend/js/features/project-list/components/notifications/notification.tsx
  62. 3 13
      services/web/frontend/js/features/project-list/components/notifications/user-notifications.tsx
  63. 1 2
      services/web/frontend/js/features/project-list/components/survey-widget.tsx
  64. 2 1
      services/web/frontend/js/features/project-list/components/table/cells/action-buttons/delete-project-button.tsx
  65. 2 1
      services/web/frontend/js/features/project-list/components/table/cells/action-buttons/leave-project-button.tsx
  66. 3 7
      services/web/frontend/js/features/project-list/components/welcome-message-new/welcome-message-create-new-project-dropdown.tsx
  67. 1 4
      services/web/frontend/js/features/project-list/components/welcome-message.tsx
  68. 1 1
      services/web/frontend/js/features/project-list/context/project-list-context.tsx
  69. 2 1
      services/web/frontend/js/features/project-list/util/user.ts
  70. 3 8
      services/web/frontend/js/features/settings/components/account-info-section.tsx
  71. 2 7
      services/web/frontend/js/features/settings/components/emails-section.tsx
  72. 1 1
      services/web/frontend/js/features/settings/components/emails/actions/remove.tsx
  73. 1 1
      services/web/frontend/js/features/settings/components/emails/add-email.tsx
  74. 1 2
      services/web/frontend/js/features/settings/components/emails/add-email/sso-linking-info.tsx
  75. 3 6
      services/web/frontend/js/features/settings/components/emails/reconfirmation-info.tsx
  76. 1 2
      services/web/frontend/js/features/settings/components/emails/row.tsx
  77. 3 16
      services/web/frontend/js/features/settings/components/emails/sso-alert.tsx
  78. 2 3
      services/web/frontend/js/features/settings/components/leave/modal-content.tsx
  79. 1 2
      services/web/frontend/js/features/settings/components/leave/modal-form-error.tsx
  80. 1 1
      services/web/frontend/js/features/settings/components/leave/modal-form.tsx
  81. 25 21
      services/web/frontend/js/features/settings/components/linking-section.tsx
  82. 3 4
      services/web/frontend/js/features/settings/components/managed-account-alert.tsx
  83. 5 9
      services/web/frontend/js/features/settings/components/password-section.tsx
  84. 1 4
      services/web/frontend/js/features/settings/components/root.tsx
  85. 2 5
      services/web/frontend/js/features/settings/components/security-section.tsx
  86. 3 7
      services/web/frontend/js/features/settings/context/sso-context.tsx
  87. 2 7
      services/web/frontend/js/features/settings/utils/sso.ts
  88. 2 1
      services/web/frontend/js/features/share-project-modal/components/link-sharing.jsx
  89. 2 1
      services/web/frontend/js/features/share-project-modal/components/share-modal-body.tsx
  90. 2 1
      services/web/frontend/js/features/share-project-modal/utils/api.js
  91. 2 1
      services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal-source-picker.tsx
  92. 2 1
      services/web/frontend/js/features/source-editor/components/figure-modal/file-sources/figure-modal-other-project-source.tsx
  93. 3 4
      services/web/frontend/js/features/source-editor/components/figure-modal/file-sources/figure-modal-upload-source.tsx
  94. 1 2
      services/web/frontend/js/features/source-editor/components/grammarly-advert.tsx
  95. 2 1
      services/web/frontend/js/features/source-editor/components/toolbar/insert-figure-dropdown.tsx
  96. 4 2
      services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts
  97. 1 5
      services/web/frontend/js/features/subscription/components/dashboard/group-settings-button.tsx
  98. 1 3
      services/web/frontend/js/features/subscription/components/dashboard/group-subscription-membership.tsx
  99. 2 5
      services/web/frontend/js/features/subscription/components/dashboard/managed-group-subscriptions.tsx
  100. 3 2
      services/web/frontend/js/features/subscription/components/dashboard/managed-institution.tsx

+ 0 - 3
services/web/.eslintrc.js

@@ -195,9 +195,6 @@ module.exports = {
         $: true,
         angular: true,
         ga: true,
-        // Injected in layout.pug
-        user_id: true,
-        ExposedSettings: true,
       },
       rules: {
         // TODO: remove once https://github.com/standard/eslint-config-standard-react/issues/68 (support eslint@8) is fixed.

+ 80 - 8
services/web/.storybook/preview.tsx

@@ -9,11 +9,82 @@ import i18n from 'i18next'
 import { initReactI18next } from 'react-i18next'
 // @ts-ignore
 import en from '../../../services/web/locales/en.json'
-import { ExposedSettings } from '../types/exposed-settings'
-import { User } from '../types/user'
 
-window.i18n = window.i18n || {}
-window.i18n.currentLangCode = window.i18n.currentLangCode || 'en'
+function resetMeta() {
+  window.metaAttributesCache = new Map()
+  window.metaAttributesCache.set('ol-i18n', { currentLangCode: 'en' })
+  window.metaAttributesCache.set('ol-ExposedSettings', {
+    adminEmail: 'placeholder@example.com',
+    appName: 'Overleaf',
+    cookieDomain: '.overleaf.stories',
+    dropboxAppName: 'Overleaf-Stories',
+    emailConfirmationDisabled: false,
+    enableSubscriptions: true,
+    hasAffiliationsFeature: false,
+    hasLinkUrlFeature: true,
+    hasLinkedProjectFileFeature: true,
+    hasLinkedProjectOutputFileFeature: true,
+    hasSamlFeature: true,
+    ieeeBrandId: 15,
+    isOverleaf: true,
+    labsEnabled: true,
+    maxEntitiesPerProject: 10,
+    maxUploadSize: 5 * 1024 * 1024,
+    recaptchaDisabled: {
+      invite: true,
+      login: true,
+      passwordReset: true,
+      register: true,
+      addEmail: true,
+    },
+    sentryAllowedOriginRegex: '',
+    siteUrl: 'http://localhost',
+    templateLinks: [],
+    textExtensions: [
+      'tex',
+      'latex',
+      'sty',
+      'cls',
+      'bst',
+      'bib',
+      'bibtex',
+      'txt',
+      'tikz',
+      'mtx',
+      'rtex',
+      'md',
+      'asy',
+      'lbx',
+      'bbx',
+      'cbx',
+      'm',
+      'lco',
+      'dtx',
+      'ins',
+      'ist',
+      'def',
+      'clo',
+      'ldf',
+      'rmd',
+      'lua',
+      'gv',
+      'mf',
+      'lhs',
+      'mk',
+      'xmpdata',
+      'cfg',
+      'rnw',
+      'ltx',
+      'inc',
+    ],
+    editableFilenames: ['latexmkrc', '.latexmkrc', 'makefile', 'gnumakefile'],
+    validRootDocExtensions: ['tex', 'Rtex', 'ltx', 'Rnw'],
+    fileIgnorePattern:
+      '**/{{__MACOSX,.git,.texpadtmp,.R}{,/**},.!(latexmkrc),*.{dvi,aux,log,toc,out,pdfsync,synctex,synctex(busy),fdb_latexmk,fls,nlo,ind,glo,gls,glg,bbl,blg,doc,docx,gz,swp}}',
+    projectUploadTimeout: 12000,
+  })
+}
+
 i18n.use(initReactI18next).init({
   lng: 'en',
 
@@ -41,10 +112,6 @@ i18n.use(initReactI18next).init({
   },
 })
 
-// avoid some errors by creating these objects in advance
-window.user = {} as User
-window.ExposedSettings = {} as ExposedSettings
-
 const preview: Preview = {
   parameters: {
     // Automatically mark prop-types like onClick, onToggle, etc as Storybook
@@ -98,6 +165,8 @@ const preview: Preview = {
         ? bootstrap5Style
         : bootstrap3Style
 
+      resetMeta()
+
       return (
         <>
           {activeStyle && <style>{activeStyle.default}</style>}
@@ -109,3 +178,6 @@ const preview: Preview = {
 }
 
 export default preview
+
+// Populate meta for top-level access in modules on import
+resetMeta()

+ 8 - 0
services/web/app/src/infrastructure/ExpressLocals.js

@@ -18,6 +18,10 @@ const {
 const {
   addOptionalCleanupHandlerAfterDrainingConnections,
 } = require('./GracefulShutdown')
+const { expressify } = require('@overleaf/promise-utils')
+const {
+  loadAssignmentsInLocals,
+} = require('../Features/SplitTests/SplitTestMiddleware')
 
 const IEEE_BRAND_ID = Settings.ieeeBrandId
 
@@ -76,6 +80,10 @@ function getWebpackAssets(entrypoint, section) {
 }
 
 module.exports = function (webRouter, privateApiRouter, publicApiRouter) {
+  webRouter.use(
+    expressify(loadAssignmentsInLocals(['remove-window-attributes']))
+  )
+
   if (process.env.NODE_ENV === 'development') {
     // In the dev-env, delay requests until we fetched the manifest once.
     webRouter.use(function (req, res, next) {

+ 0 - 3
services/web/app/views/project/editor/meta.pug

@@ -39,9 +39,6 @@ meta(name="ol-projectTags" data-type="json" content=projectTags)
 meta(name="ol-loadingText", data-type="string" content=translate("loading"))
 meta(name="ol-translationLoadErrorMessage", data-type="string" content=translate("could_not_load_translations"))
 
-- var fileActionI18n = ['edited', 'renamed', 'created', 'deleted'].reduce((acc, i) => {acc[i] = translate('file_action_' + i); return acc}, {})
-meta(name="ol-fileActionI18n" data-type="json" content=fileActionI18n)
-
 if (settings.overleaf != null)
 	meta(name="ol-overallThemes" data-type="json" content=overallThemes)
 

+ 2 - 10
services/web/cypress/support/component.ts

@@ -1,19 +1,11 @@
 import 'cypress-plugin-tab'
 import '../../frontend/stylesheets/main-style.less'
-import './ct/window' // needs to be before i18n
+import { resetMeta } from './ct/window' // needs to be before i18n
 import '../../frontend/js/i18n'
 import './shared/commands'
 import './shared/exceptions'
 import './ct/commands'
 
 beforeEach(function () {
-  cy.window().then(win => {
-    win.metaAttributesCache = new Map()
-  })
-})
-
-afterEach(function () {
-  cy.window().then(win => {
-    win.metaAttributesCache?.clear()
-  })
+  resetMeta()
 })

+ 16 - 10
services/web/cypress/support/ct/window.ts

@@ -1,10 +1,16 @@
-window.i18n = { currentLangCode: 'en' }
-window.ExposedSettings = {
-  appName: 'Overleaf',
-  validRootDocExtensions: ['tex', 'Rtex', 'ltx', 'Rnw'],
-  fileIgnorePattern:
-    '**/{{__MACOSX,.git,.texpadtmp,.R}{,/**},.!(latexmkrc),*.{dvi,aux,log,toc,out,pdfsync,synctex,synctex(busy),fdb_latexmk,fls,nlo,ind,glo,gls,glg,bbl,blg,doc,docx,gz,swp}}',
-  hasLinkedProjectFileFeature: true,
-  hasLinkedProjectOutputFileFeature: true,
-  hasLinkUrlFeature: true,
-} as typeof window.ExposedSettings
+export function resetMeta() {
+  window.metaAttributesCache = new Map()
+  window.metaAttributesCache.set('ol-i18n', { currentLangCode: 'en' })
+  window.metaAttributesCache.set('ol-ExposedSettings', {
+    appName: 'Overleaf',
+    validRootDocExtensions: ['tex', 'Rtex', 'ltx', 'Rnw'],
+    fileIgnorePattern:
+      '**/{{__MACOSX,.git,.texpadtmp,.R}{,/**},.!(latexmkrc),*.{dvi,aux,log,toc,out,pdfsync,synctex,synctex(busy),fdb_latexmk,fls,nlo,ind,glo,gls,glg,bbl,blg,doc,docx,gz,swp}}',
+    hasLinkedProjectFileFeature: true,
+    hasLinkedProjectOutputFileFeature: true,
+    hasLinkUrlFeature: true,
+  })
+}
+
+// Populate meta for top-level access in modules on import
+resetMeta()

+ 7 - 2
services/web/frontend/js/features/cookie-banner/index.js

@@ -1,3 +1,5 @@
+import getMeta from '@/utils/meta'
+
 function loadGA() {
   if (window.olLoadGA) {
     window.olLoadGA()
@@ -6,7 +8,7 @@ function loadGA() {
 
 function setConsent(value) {
   document.querySelector('.cookie-banner').classList.add('hidden')
-  const cookieDomain = window.ExposedSettings.cookieDomain
+  const cookieDomain = getMeta('ol-ExposedSettings').cookieDomain
   const oneYearInSeconds = 60 * 60 * 24 * 365
   const cookieAttributes =
     '; path=/' +
@@ -23,7 +25,10 @@ function setConsent(value) {
   }
 }
 
-if (window.ExposedSettings.gaToken || window.ExposedSettings.gaTokenV4) {
+if (
+  getMeta('ol-ExposedSettings').gaToken ||
+  getMeta('ol-ExposedSettings').gaTokenV4
+) {
   document
     .querySelectorAll('[data-ol-cookie-banner-set-consent]')
     .forEach(el => {

+ 2 - 2
services/web/frontend/js/features/editor-left-menu/components/actions-menu.tsx

@@ -12,9 +12,9 @@ const components = importOverleafModules('editorLeftMenuManageTemplate') as {
 
 export default function ActionsMenu() {
   const { t } = useTranslation()
-  const anonymous = getMeta('ol-anonymous') as boolean | undefined
+  const anonymous = getMeta('ol-anonymous')
 
-  if (anonymous === true || anonymous === undefined) {
+  if (anonymous) {
     return null
   }
 

+ 1 - 1
services/web/frontend/js/features/editor-left-menu/components/help-menu.tsx

@@ -6,7 +6,7 @@ import HelpShowHotkeys from './help-show-hotkeys'
 
 export default function HelpMenu() {
   const { t } = useTranslation()
-  const showSupport = getMeta('ol-showSupport') as boolean | undefined
+  const showSupport = getMeta('ol-showSupport')
 
   return (
     <>

+ 2 - 2
services/web/frontend/js/features/editor-left-menu/components/settings-menu.tsx

@@ -19,9 +19,9 @@ import SettingsSyntaxValidation from './settings/settings-syntax-validation'
 
 export default function SettingsMenu() {
   const { t } = useTranslation()
-  const anonymous = getMeta('ol-anonymous') as boolean | undefined
+  const anonymous = getMeta('ol-anonymous')
 
-  if (anonymous === true || anonymous === undefined) {
+  if (anonymous) {
     return null
   }
 

+ 2 - 4
services/web/frontend/js/features/editor-left-menu/components/settings/settings-editor-theme.tsx

@@ -7,10 +7,8 @@ import type { Option } from './settings-menu-select'
 
 export default function SettingsEditorTheme() {
   const { t } = useTranslation()
-  const editorThemes = getMeta('ol-editorThemes') as string[] | undefined
-  const legacyEditorThemes = getMeta('ol-legacyEditorThemes') as
-    | string[]
-    | undefined
+  const editorThemes = getMeta('ol-editorThemes')
+  const legacyEditorThemes = getMeta('ol-legacyEditorThemes')
   const { editorTheme, setEditorTheme } = useProjectSettingsContext()
 
   const options = useMemo(() => {

+ 7 - 7
services/web/frontend/js/features/editor-left-menu/components/settings/settings-image-name.tsx

@@ -4,26 +4,26 @@ import getMeta from '../../../../utils/meta'
 import SettingsMenuSelect from './settings-menu-select'
 import type { Option } from './settings-menu-select'
 import { useProjectSettingsContext } from '../../context/project-settings-context'
-import type { AllowedImageName } from '../../../../../../types/project-settings'
 
 export default function SettingsImageName() {
   const { t } = useTranslation()
   const { imageName, setImageName } = useProjectSettingsContext()
 
-  const allowedImageNames = getMeta('ol-allowedImageNames') as
-    | AllowedImageName[]
-    | undefined
+  const allowedImageNames = useMemo(
+    () => getMeta('ol-allowedImageNames') || [],
+    []
+  )
 
   const options: Array<Option> = useMemo(
     () =>
-      allowedImageNames?.map(({ imageName, imageDesc }) => ({
+      allowedImageNames.map(({ imageName, imageDesc }) => ({
         value: imageName,
         label: imageDesc,
-      })) ?? [],
+      })),
     [allowedImageNames]
   )
 
-  if ((allowedImageNames?.length ?? 0) === 0) {
+  if (allowedImageNames.length === 0) {
     return null
   }
 

+ 2 - 3
services/web/frontend/js/features/editor-left-menu/components/settings/settings-overall-theme.tsx

@@ -6,7 +6,6 @@ import SettingsMenuSelect, { Option } from './settings-menu-select'
 import { useProjectSettingsContext } from '../../context/project-settings-context'
 import type { OverallThemeMeta } from '../../../../../../types/project-settings'
 import type { OverallTheme } from '../../../source-editor/extensions/theme'
-import { ExposedSettings } from '../../../../../../types/exposed-settings'
 
 export default function SettingsOverallTheme() {
   const { t } = useTranslation()
@@ -25,8 +24,8 @@ export default function SettingsOverallTheme() {
     [overallThemes]
   )
 
-  const brandVariation = getMeta('ol-brandVariation') as any
-  const { ieeeBrandId } = getMeta('ol-ExposedSettings') as ExposedSettings
+  const brandVariation = getMeta('ol-brandVariation')
+  const { ieeeBrandId } = getMeta('ol-ExposedSettings')
   const isIEEEBranded = brandVariation?.brand_id === ieeeBrandId
 
   if (!overallThemes || isIEEEBranded) {

+ 1 - 2
services/web/frontend/js/features/editor-left-menu/components/settings/settings-spell-check-language.tsx

@@ -4,11 +4,10 @@ import getMeta from '../../../../utils/meta'
 import { useProjectSettingsContext } from '../../context/project-settings-context'
 import SettingsMenuSelect from './settings-menu-select'
 import type { Optgroup } from './settings-menu-select'
-import type { SpellCheckLanguage } from '../../../../../../types/project-settings'
 
 export default function SettingsSpellCheckLanguage() {
   const { t } = useTranslation()
-  const languages = getMeta('ol-languages') as SpellCheckLanguage[] | undefined
+  const languages = getMeta('ol-languages')
 
   const { spellCheckLanguage, setSpellCheckLanguage } =
     useProjectSettingsContext()

+ 3 - 3
services/web/frontend/js/features/editor-left-menu/components/sync-menu.tsx

@@ -10,10 +10,10 @@ const components = importOverleafModules('editorLeftMenuSync') as {
 
 export default function SyncMenu() {
   const { t } = useTranslation()
-  const anonymous = getMeta('ol-anonymous') as boolean | undefined
-  const gitBridgeEnabled = getMeta('ol-gitBridgeEnabled', false) as boolean
+  const anonymous = getMeta('ol-anonymous')
+  const gitBridgeEnabled = getMeta('ol-gitBridgeEnabled')
 
-  if (anonymous === true || anonymous === undefined) {
+  if (anonymous) {
     return null
   }
 

+ 2 - 1
services/web/frontend/js/features/editor-left-menu/hooks/use-set-overall-theme.tsx

@@ -5,6 +5,7 @@ import type { OverallThemeMeta } from '../../../../../types/project-settings'
 import { saveUserSettings } from '../utils/api'
 import { UserSettings } from '../../../../../types/user-settings'
 import { useUserSettingsContext } from '@/shared/context/user-settings-context'
+import getMeta from '@/utils/meta'
 
 export default function useSetOverallTheme() {
   const [chosenTheme, setChosenTheme] = useState<OverallThemeMeta | null>(null)
@@ -56,7 +57,7 @@ export default function useSetOverallTheme() {
     (newOverallTheme: UserSettings['overallTheme']) => {
       if (overallTheme !== newOverallTheme) {
         const chosenTheme = _.find(
-          window.overallThemes,
+          getMeta('ol-overallThemes'),
           theme => theme.val === newOverallTheme
         )
 

+ 2 - 1
services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.jsx

@@ -14,6 +14,7 @@ import HistoryToggleButton from './history-toggle-button'
 import ShareProjectButton from './share-project-button'
 import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
 import BackToEditorButton from './back-to-editor-button'
+import getMeta from '@/utils/meta'
 
 const [publishModalModules] = importOverleafModules('publishModal')
 const PublishButton = publishModalModules?.import.default
@@ -54,7 +55,7 @@ const ToolbarHeader = React.memo(function ToolbarHeader({
         )}
         <BackToProjectsButton />
       </div>
-      {window.showUpgradePrompt && <UpgradePrompt />}
+      {getMeta('ol-showUpgradePrompt') && <UpgradePrompt />}
       <ProjectNameEditableLabel
         className="toolbar-center"
         projectName={projectName}

+ 9 - 3
services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-body.jsx

@@ -10,6 +10,7 @@ import { useFileTreeData } from '../../../../shared/context/file-tree-data-conte
 import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
 import { lazy, Suspense } from 'react'
 import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
+import getMeta from '@/utils/meta'
 
 const createFileModeModules = importOverleafModules('createFileModes')
 
@@ -20,6 +21,11 @@ export default function FileTreeModalCreateFileBody() {
 
   const { newFileCreateMode } = useFileTreeActionable()
   const { fileCount } = useFileTreeData()
+  const {
+    hasLinkedProjectFileFeature,
+    hasLinkedProjectOutputFileFeature,
+    hasLinkUrlFeature,
+  } = getMeta('ol-ExposedSettings')
 
   if (!fileCount || fileCount.status === 'error') {
     return null
@@ -43,8 +49,8 @@ export default function FileTreeModalCreateFileBody() {
                 label={t('upload')}
               />
 
-              {(window.ExposedSettings.hasLinkedProjectFileFeature ||
-                window.ExposedSettings.hasLinkedProjectOutputFileFeature) && (
+              {(hasLinkedProjectFileFeature ||
+                hasLinkedProjectOutputFileFeature) && (
                 <FileTreeModalCreateFileMode
                   mode="project"
                   icon="folder-open"
@@ -52,7 +58,7 @@ export default function FileTreeModalCreateFileBody() {
                 />
               )}
 
-              {window.ExposedSettings.hasLinkUrlFeature && (
+              {hasLinkUrlFeature && (
                 <FileTreeModalCreateFileMode
                   mode="url"
                   icon="globe"

+ 2 - 1
services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-import-from-project.tsx

@@ -20,12 +20,13 @@ import ErrorMessage from '../error-message'
 import * as eventTracking from '../../../../../infrastructure/event-tracking'
 import { File } from '@/features/source-editor/utils/file'
 import { Project } from '../../../../../../../types/project'
+import getMeta from '@/utils/meta'
 
 export default function FileTreeImportFromProject() {
   const { t } = useTranslation()
 
   const { hasLinkedProjectFileFeature, hasLinkedProjectOutputFileFeature } =
-    window.ExposedSettings
+    getMeta('ol-ExposedSettings')
   const canSwitchOutputFilesMode =
     hasLinkedProjectFileFeature && hasLinkedProjectOutputFileFeature
 

+ 3 - 2
services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-upload-doc.tsx

@@ -16,6 +16,7 @@ import { findByNameInFolder } from '@/features/file-tree/util/is-name-unique-in-
 import { useFileTreeData } from '@/shared/context/file-tree-data-context'
 import { FileTreeEntity } from '../../../../../../../types/file-tree-entity'
 import { UploadConflicts } from '@/features/file-tree/components/file-tree-create/file-tree-upload-conflicts'
+import getMeta from '@/utils/meta'
 
 export default function FileTreeUploadDoc() {
   const { parentFolderId, cancel, droppedFiles, setDroppedFiles } =
@@ -30,7 +31,7 @@ export default function FileTreeUploadDoc() {
   const [overwrite, setOverwrite] = useState(false)
 
   const maxNumberOfFiles = 180
-  const maxFileSize = window.ExposedSettings.maxUploadSize
+  const maxFileSize = getMeta('ol-ExposedSettings').maxUploadSize
 
   // calculate conflicts
   const buildConflicts = (files: Record<string, any>) => {
@@ -130,7 +131,7 @@ export default function FileTreeUploadDoc() {
         .use(XHRUpload, {
           endpoint,
           headers: {
-            'X-CSRF-TOKEN': window.csrfToken,
+            'X-CSRF-TOKEN': getMeta('ol-csrfToken'),
           },
           // limit: maxConnections || 1,
           limit: 1,

+ 1 - 1
services/web/frontend/js/features/file-tree/contexts/file-tree-path.tsx

@@ -24,7 +24,7 @@ export const FileTreePathContext = createContext<
 
 export const FileTreePathProvider: FC = ({ children }) => {
   const { fileTreeData }: { fileTreeData: Folder } = useFileTreeData()
-  const projectId = getMeta('ol-project_id') as string
+  const projectId = getMeta('ol-project_id')
 
   const pathInFileTree = useCallback(
     (id: string) => pathInFolder(fileTreeData, id),

+ 3 - 1
services/web/frontend/js/features/file-tree/util/count-in-tree.js

@@ -1,3 +1,5 @@
+import getMeta from '@/utils/meta'
+
 export function countFiles(fileTreeData) {
   if (!fileTreeData) {
     return 0
@@ -5,7 +7,7 @@ export function countFiles(fileTreeData) {
 
   const value = _countElements(fileTreeData)
 
-  const limit = window.ExposedSettings.maxEntitiesPerProject
+  const limit = getMeta('ol-ExposedSettings').maxEntitiesPerProject
   const status = fileCountStatus(value, limit, Math.ceil(limit / 20))
 
   return { value, status, limit }

+ 2 - 1
services/web/frontend/js/features/file-tree/util/is-acceptable-file.ts

@@ -1,11 +1,12 @@
 import { Minimatch } from 'minimatch'
+import getMeta from '@/utils/meta'
 
 let fileIgnoreMatcher: Minimatch
 
 export const isAcceptableFile = (name?: string, relativePath?: string) => {
   if (!fileIgnoreMatcher) {
     fileIgnoreMatcher = new Minimatch(
-      window.ExposedSettings.fileIgnorePattern,
+      getMeta('ol-ExposedSettings').fileIgnorePattern,
       { nocase: true, dot: true }
     )
   }

+ 2 - 1
services/web/frontend/js/features/file-view/components/file-view.jsx

@@ -7,6 +7,7 @@ import FileViewImage from './file-view-image'
 import FileViewPdf from './file-view-pdf'
 import FileViewText from './file-view-text'
 import Icon from '../../../shared/components/icon'
+import getMeta from '@/utils/meta'
 
 const imageExtensions = ['png', 'jpg', 'jpeg', 'gif']
 
@@ -16,7 +17,7 @@ export default function FileView({ file }) {
 
   const { t } = useTranslation()
 
-  const { textExtensions, editableFilenames } = window.ExposedSettings
+  const { textExtensions, editableFilenames } = getMeta('ol-ExposedSettings')
 
   const extension = file.name.split('.').pop().toLowerCase()
 

+ 2 - 2
services/web/frontend/js/features/group-management/components/group-managers.tsx

@@ -8,8 +8,8 @@ export default function GroupManagers() {
   const { isReady } = useWaitForI18n()
   const { t } = useTranslation()
 
-  const groupId: string = getMeta('ol-groupId')
-  const groupName: string = getMeta('ol-groupName')
+  const groupId = getMeta('ol-groupId')
+  const groupName = getMeta('ol-groupName')
 
   const paths = useMemo(
     () => ({

+ 3 - 3
services/web/frontend/js/features/group-management/components/group-members.tsx

@@ -24,9 +24,9 @@ export default function GroupMembers() {
   } = useGroupMembersContext()
   const [emailString, setEmailString] = useState<string>('')
 
-  const groupId: string = getMeta('ol-groupId')
-  const groupName: string = getMeta('ol-groupName')
-  const groupSize: number = getMeta('ol-groupSize')
+  const groupId = getMeta('ol-groupId')
+  const groupName = getMeta('ol-groupName')
+  const groupSize = getMeta('ol-groupSize')
 
   const handleEmailsChange = useCallback(
     e => {

+ 2 - 2
services/web/frontend/js/features/group-management/components/institution-managers.tsx

@@ -8,8 +8,8 @@ export default function InstitutionManagers() {
   const { isReady } = useWaitForI18n()
   const { t } = useTranslation()
 
-  const groupId: string = getMeta('ol-groupId')
-  const groupName: string = getMeta('ol-groupName')
+  const groupId = getMeta('ol-groupId')
+  const groupName = getMeta('ol-groupName')
 
   const paths = useMemo(
     () => ({

+ 1 - 1
services/web/frontend/js/features/group-management/components/managers-table.tsx

@@ -42,7 +42,7 @@ export function ManagersTable({
     unselectAllUsers,
     selectUser,
     unselectUser,
-  } = useUserSelection(getMeta('ol-users', []))
+  } = useUserSelection(getMeta('ol-users') || [])
 
   const [emailString, setEmailString] = useState<string>('')
   const [inviteUserInflightCount, setInviteUserInflightCount] = useState(0)

+ 1 - 1
services/web/frontend/js/features/group-management/components/members-table/member-row.tsx

@@ -27,7 +27,7 @@ export default function MemberRow({
   groupId,
 }: ManagedUserRowProps) {
   const { t } = useTranslation()
-  const managedUsersActive: any = getMeta('ol-managedUsersActive')
+  const managedUsersActive = getMeta('ol-managedUsersActive')
   const groupSSOActive = getMeta('ol-groupSSOActive')
 
   return (

+ 1 - 1
services/web/frontend/js/features/group-management/components/members-table/members-list.tsx

@@ -26,7 +26,7 @@ export default function MembersList({ groupId }: ManagedUsersListProps) {
     useState<GroupUserAlert>(undefined)
   const [userToUnlink, setUserToUnlink] = useState<User | undefined>(undefined)
   const { users } = useGroupMembersContext()
-  const managedUsersActive: any = getMeta('ol-managedUsersActive')
+  const managedUsersActive = getMeta('ol-managedUsersActive')
   const groupSSOActive = getMeta('ol-groupSSOActive')
 
   return (

+ 2 - 2
services/web/frontend/js/features/group-management/components/publisher-managers.tsx

@@ -8,8 +8,8 @@ export default function PublisherManagers() {
   const { isReady } = useWaitForI18n()
   const { t } = useTranslation()
 
-  const groupId: string = getMeta('ol-groupId')
-  const groupName: string = getMeta('ol-groupName')
+  const groupId = getMeta('ol-groupId')
+  const groupName = getMeta('ol-groupName')
 
   const paths = useMemo(
     () => ({

+ 2 - 2
services/web/frontend/js/features/group-management/context/group-members-context.tsx

@@ -52,14 +52,14 @@ export function GroupMembersProvider({ children }: GroupMembersProviderProps) {
     selectAllNonManagedUsers,
     selectUser,
     unselectUser,
-  } = useUserSelection(getMeta('ol-users', []))
+  } = useUserSelection(getMeta('ol-users') || [])
 
   const [inviteUserInflightCount, setInviteUserInflightCount] = useState(0)
   const [inviteError, setInviteError] = useState<APIError>()
   const [removeMemberInflightCount, setRemoveMemberInflightCount] = useState(0)
   const [removeMemberError, setRemoveMemberError] = useState<APIError>()
 
-  const groupId: string = getMeta('ol-groupId')
+  const groupId = getMeta('ol-groupId')
 
   const paths = useMemo(
     () => ({

+ 2 - 4
services/web/frontend/js/features/ide-react/components/loading.tsx

@@ -54,7 +54,7 @@ export const Loading: FC<{
     }
   }, [projectJoined])
 
-  const LoadingScreenError = () => {
+  const getLoadingScreenError = (): string => {
     if (connectionState.error) {
       // NOTE: translations not ready yet
       return connectionState.error === 'io-not-loaded'
@@ -82,9 +82,7 @@ export const Loading: FC<{
         hasError={hasError}
       />
       {hasError && (
-        <p className="loading-screen-error">
-          <LoadingScreenError />
-        </p>
+        <p className="loading-screen-error">{getLoadingScreenError()}</p>
       )}
     </div>
   )

+ 10 - 5
services/web/frontend/js/features/ide-react/context/ide-react-context.tsx

@@ -86,9 +86,8 @@ export function createReactScopeValueStore(projectId: string) {
   return scopeStore
 }
 
-const projectId = window.project_id
-
 export const IdeReactProvider: FC = ({ children }) => {
+  const projectId = getMeta('ol-project_id')
   const [scopeStore] = useState(() => createReactScopeValueStore(projectId))
   const [eventEmitter] = useState(createIdeEventEmitter)
   const [scopeEventEmitter] = useState(
@@ -129,11 +128,10 @@ export const IdeReactProvider: FC = ({ children }) => {
         body: {
           error: errorObj,
           meta: metadata,
-          _csrf: window.csrfToken,
         },
       })
     },
-    [socket.socket, release]
+    [socket.socket, release, projectId]
   )
 
   // Populate scope values when joining project, then fire project:joined event
@@ -175,7 +173,14 @@ export const IdeReactProvider: FC = ({ children }) => {
       reportError,
       projectJoined,
     }),
-    [eventEmitter, eventLog, projectJoined, reportError, startedFreeTrial]
+    [
+      eventEmitter,
+      eventLog,
+      projectId,
+      projectJoined,
+      reportError,
+      startedFreeTrial,
+    ]
   )
 
   return (

+ 1 - 1
services/web/frontend/js/features/ide-react/context/permissions-context.tsx

@@ -47,7 +47,7 @@ export const PermissionsProvider: React.FC = ({ children }) => {
   const { permissionsLevel } = useEditorContext() as {
     permissionsLevel: PermissionsLevel
   }
-  const anonymous = getMeta('ol-anonymous') as boolean | undefined
+  const anonymous = getMeta('ol-anonymous')
 
   useEffect(() => {
     const activePermissionsMap = anonymous

+ 2 - 1
services/web/frontend/js/features/ide-react/context/review-panel/hooks/use-review-panel-state.ts

@@ -65,6 +65,7 @@ import {
   EditOperation,
 } from '../../../../../../../types/change'
 import { RangesTrackerWithResolvedThreadIds } from '@/features/ide-react/editor/document-container'
+import getMeta from '@/utils/meta'
 
 const dispatchReviewPanelEvent = (type: string, payload?: any) => {
   window.dispatchEvent(
@@ -89,7 +90,7 @@ const formatUser = (user: any): any => {
       avatar_text: 'A',
     }
   }
-  if (id === window.user_id) {
+  if (id === getMeta('ol-user_id')) {
     name = 'You'
     isSelf = true
   } else {

+ 2 - 1
services/web/frontend/js/features/ide-react/editor/document-container.ts

@@ -28,6 +28,7 @@ import {
   Version,
 } from '@/features/ide-react/editor/types/document'
 import { ThreadId } from '../../../../../types/review-panel/review-panel'
+import getMeta from '@/utils/meta'
 
 const MAX_PENDING_OP_SIZE = 64
 
@@ -737,7 +738,7 @@ export class DocumentContainer extends EventEmitter {
   private filterOps(ops: AnyOperation[]) {
     // Read-only token users can't see/edit comment, so we filter out comment
     // ops to avoid highlighting comment ranges.
-    if (window.isRestrictedTokenMember) {
+    if (getMeta('ol-isRestrictedTokenMember')) {
       return ops.filter(op => !isCommentOperation(op))
     } else {
       return ops

+ 2 - 2
services/web/frontend/js/features/ide-react/editor/share-js-doc.ts

@@ -17,6 +17,7 @@ import {
 } from '@/features/ide-react/editor/types/document'
 import { EditorFacade } from '@/features/source-editor/extensions/realtime'
 import { recordDocumentFirstChangeEvent } from '@/features/event-tracking/document-first-change-event'
+import getMeta from '@/utils/meta'
 
 // All times below are in milliseconds
 const SINGLE_USER_FLUSH_DELAY = 2000
@@ -327,8 +328,7 @@ export class ShareJsDoc extends EventEmitter {
 
   attachToCM6(cm6: EditorFacade) {
     this.attachToEditor(cm6, () => {
-      // @ts-ignore
-      cm6.attachShareJs(this._doc, window.maxDocLength)
+      cm6.attachShareJs(this._doc, getMeta('ol-maxDocLength'))
     })
   }
 

+ 2 - 1
services/web/frontend/js/features/pdf-preview/components/compile-time-warning-upgrade-prompt.tsx

@@ -3,6 +3,7 @@ import * as eventTracking from '@/infrastructure/event-tracking'
 import { useDetachCompileContext } from '@/shared/context/detach-compile-context'
 import usePersistedState from '@/shared/hooks/use-persisted-state'
 import { CompileTimeWarningUpgradePromptInner } from '@/features/pdf-preview/components/compile-time-warning-upgrade-prompt-inner'
+import getMeta from '@/utils/meta'
 
 function CompileTimeWarningUpgradePrompt() {
   const { isProjectOwner, deliveryLatencies, compiling, showLogs, error } =
@@ -50,7 +51,7 @@ function CompileTimeWarningUpgradePrompt() {
     handleNewCompile(deliveryLatencies.compileTimeServerE2E)
   }, [compiling, error, showLogs, deliveryLatencies, handleNewCompile])
 
-  if (!window.ExposedSettings.enableSubscriptions) {
+  if (!getMeta('ol-ExposedSettings').enableSubscriptions) {
     return null
   }
 

+ 3 - 2
services/web/frontend/js/features/pdf-preview/components/timeout-upgrade-prompt-new.tsx

@@ -7,6 +7,7 @@ import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-err
 import { Button } from 'react-bootstrap'
 import * as eventTracking from '../../../infrastructure/event-tracking'
 import { useFeatureFlag } from '@/shared/context/split-test-context'
+import getMeta from '@/utils/meta'
 
 function TimeoutUpgradePromptNew() {
   const {
@@ -29,7 +30,7 @@ function TimeoutUpgradePromptNew() {
   return (
     <>
       <CompileTimeout isProjectOwner={isProjectOwner} />
-      {window.ExposedSettings.enableSubscriptions && (
+      {getMeta('ol-ExposedSettings').enableSubscriptions && (
         <PreventTimeoutHelpMessage
           handleEnableStopOnFirstErrorClick={handleEnableStopOnFirstErrorClick}
           lastCompileOptions={lastCompileOptions}
@@ -55,7 +56,7 @@ const CompileTimeout = memo(function CompileTimeout({
     <PdfLogEntry
       headerTitle={t('your_compile_timed_out')}
       formattedContent={
-        window.ExposedSettings.enableSubscriptions && (
+        getMeta('ol-ExposedSettings').enableSubscriptions && (
           <>
             <p>
               {isProjectOwner

+ 1 - 1
services/web/frontend/js/features/pdf-preview/util/pdf-js-versions.js

@@ -65,7 +65,7 @@ async function importPDFJS213() {
 }
 
 async function importPDFJS() {
-  const variant = getMeta('ol-pdfjsVariant', 'default')
+  const variant = getMeta('ol-pdfjsVariant') || 'default'
 
   // NOTE: split test variants must have at least 3 characters
   switch (variant) {

+ 1 - 1
services/web/frontend/js/features/plans/utils/group-plan-pricing.js

@@ -20,7 +20,7 @@ export function createLocalizedGroupPlanPrice({
   licenseSize,
   currency,
   usage,
-  locale = window.i18n.currentLangCode || 'en',
+  locale = getMeta('ol-i18n').currentLangCode || 'en',
   formatCurrency,
 }) {
   const groupPlans = getMeta('ol-groupPlans')

+ 2 - 4
services/web/frontend/js/features/project-list/components/add-affiliation.tsx

@@ -2,14 +2,12 @@ import { useTranslation } from 'react-i18next'
 import { Button } from 'react-bootstrap'
 import { useProjectListContext } from '../context/project-list-context'
 import getMeta from '../../../utils/meta'
-import { Affiliation } from '../../../../../types/affiliation'
-import { ExposedSettings } from '../../../../../types/exposed-settings'
 import classNames from 'classnames'
 
 export function useAddAffiliation() {
   const { totalProjectsCount } = useProjectListContext()
-  const { isOverleaf } = getMeta('ol-ExposedSettings') as ExposedSettings
-  const userAffiliations = getMeta('ol-userAffiliations', []) as Affiliation[]
+  const { isOverleaf } = getMeta('ol-ExposedSettings')
+  const userAffiliations = getMeta('ol-userAffiliations') || []
 
   return {
     show: isOverleaf && totalProjectsCount > 0 && !userAffiliations.length,

+ 1 - 4
services/web/frontend/js/features/project-list/components/current-plan-widget/current-plan-widget.tsx

@@ -3,12 +3,9 @@ import IndividualPlan from './individual-plan'
 import GroupPlan from './group-plan'
 import CommonsPlan from './commons-plan'
 import getMeta from '../../../../utils/meta'
-import { Subscription } from '../../../../../../types/project/dashboard/subscription'
 
 function CurrentPlanWidget() {
-  const usersBestSubscription: Subscription | undefined = getMeta(
-    'ol-usersBestSubscription'
-  )
+  const usersBestSubscription = getMeta('ol-usersBestSubscription')
 
   if (!usersBestSubscription) {
     return null

+ 1 - 4
services/web/frontend/js/features/project-list/components/modals/projects-action-modal.tsx

@@ -103,10 +103,7 @@ type ErrorNotificationProps = {
 }
 
 function ErrorNotification({ error }: ErrorNotificationProps) {
-  const newNotificationStyle = getMeta(
-    'ol-newNotificationStyle',
-    false
-  ) as boolean
+  const newNotificationStyle = getMeta('ol-newNotificationStyle')
 
   if (newNotificationStyle) {
     return (

+ 1 - 4
services/web/frontend/js/features/project-list/components/modals/rename-project-modal.tsx

@@ -36,10 +36,7 @@ function RenameProjectModal({
   const { error, isError, isLoading, runAsync } = useAsync()
   const { toggleSelectedProject, updateProjectViewData } =
     useProjectListContext()
-  const newNotificationStyle = getMeta(
-    'ol-newNotificationStyle',
-    false
-  ) as boolean
+  const newNotificationStyle = getMeta('ol-newNotificationStyle')
 
   useEffect(() => {
     if (showModal) {

+ 3 - 5
services/web/frontend/js/features/project-list/components/new-project-button.tsx

@@ -1,8 +1,6 @@
 import { type JSXElementConstructor, useCallback, useState } from 'react'
 import { Dropdown, MenuItem } from 'react-bootstrap'
 import { useTranslation } from 'react-i18next'
-import { ExposedSettings } from '../../../../../types/exposed-settings'
-import type { PortalTemplate } from '../../../../../types/portal-template'
 import ControlledDropdown from '../../../shared/components/controlled-dropdown'
 import getMeta from '../../../utils/meta'
 import NewProjectButtonModal, {
@@ -46,10 +44,10 @@ function NewProjectButton({
   showAddAffiliationWidget,
 }: NewProjectButtonProps) {
   const { t } = useTranslation()
-  const { templateLinks } = getMeta('ol-ExposedSettings') as ExposedSettings
+  const { templateLinks } = getMeta('ol-ExposedSettings')
   const [modal, setModal] =
     useState<Nullable<NewProjectButtonModalVariant>>(null)
-  const portalTemplates = getMeta('ol-portalTemplates') as PortalTemplate[]
+  const portalTemplates = getMeta('ol-portalTemplates') || []
   const { show: enableAddAffiliationWidget } = useAddAffiliation()
 
   const sendTrackingEvent = useCallback(
@@ -202,7 +200,7 @@ function NewProjectButton({
               }
             />
           )}
-          {portalTemplates?.length > 0 ? (
+          {portalTemplates.length > 0 ? (
             <>
               <MenuItem divider />
               <MenuItem header>

+ 1 - 5
services/web/frontend/js/features/project-list/components/new-project-button/modal-content-new-project-form.tsx

@@ -42,16 +42,12 @@ function ModalContentNewProjectForm({ onCancel, template = 'none' }: Props) {
   const [projectName, setProjectName] = useState('')
   const { isLoading, isError, error, runAsync } = useAsync<NewProjectData>()
   const location = useLocation()
-  const newNotificationStyle = getMeta(
-    'ol-newNotificationStyle',
-    false
-  ) as boolean
+  const newNotificationStyle = getMeta('ol-newNotificationStyle')
 
   const createNewProject = () => {
     runAsync(
       postJSON('/project/new', {
         body: {
-          _csrf: window.csrfToken,
           projectName,
           template,
         },

+ 2 - 5
services/web/frontend/js/features/project-list/components/new-project-button/upload-project-modal.tsx

@@ -11,7 +11,6 @@ import Uppy from '@uppy/core'
 import { Dashboard } from '@uppy/react'
 import XHRUpload from '@uppy/xhr-upload'
 import getMeta from '../../../../utils/meta'
-import { ExposedSettings } from '../../../../../../types/exposed-settings'
 
 import '@uppy/core/dist/style.css'
 import '@uppy/dashboard/dist/style.css'
@@ -27,9 +26,7 @@ type UploadProjectModalProps = {
 
 function UploadProjectModal({ onHide, openProject }: UploadProjectModalProps) {
   const { t } = useTranslation()
-  const { maxUploadSize, projectUploadTimeout } = getMeta(
-    'ol-ExposedSettings'
-  ) as ExposedSettings
+  const { maxUploadSize, projectUploadTimeout } = getMeta('ol-ExposedSettings')
   const [ableToUpload, setAbleToUpload] = useState(false)
 
   const [uppy] = useState(() => {
@@ -44,7 +41,7 @@ function UploadProjectModal({ onHide, openProject }: UploadProjectModalProps) {
       .use(XHRUpload, {
         endpoint: '/project/new/upload',
         headers: {
-          'X-CSRF-TOKEN': window.csrfToken,
+          'X-CSRF-TOKEN': getMeta('ol-csrfToken'),
         },
         limit: 1,
         fieldName: 'qqfile', // "qqfile" is needed for our express multer middleware

+ 3 - 3
services/web/frontend/js/features/project-list/components/notifications/geo-banners.tsx

@@ -4,9 +4,9 @@ import INRBanner from './ads/inr-banner'
 import LATAMBanner from './ads/latam-banner'
 
 function GeoBanners() {
-  const showInrGeoBanner = getMeta('ol-showInrGeoBanner', false)
-  const showBrlGeoBanner = getMeta('ol-showBrlGeoBanner', false)
-  const showLATAMBanner = getMeta('ol-showLATAMBanner', false)
+  const showInrGeoBanner = getMeta('ol-showInrGeoBanner')
+  const showBrlGeoBanner = getMeta('ol-showBrlGeoBanner')
+  const showLATAMBanner = getMeta('ol-showLATAMBanner')
   return (
     <>
       {showBrlGeoBanner && <BRLBanner />}

+ 11 - 14
services/web/frontend/js/features/project-list/components/notifications/groups-and-enterprise-banner.tsx

@@ -5,12 +5,13 @@ import getMeta from '../../../../utils/meta'
 import customLocalStorage from '../../../../infrastructure/local-storage'
 import { useProjectListContext } from '../../context/project-list-context'
 import { useTranslation } from 'react-i18next'
-
-type variantsType = 'on-premise' | 'FOMO'
-const variants = ['on-premise', 'FOMO'] as variantsType[]
+import {
+  GroupsAndEnterpriseBannerVariant,
+  GroupsAndEnterpriseBannerVariants,
+} from '../../../../../../types/project/dashboard/notification'
 
 type urlForVariantsType = {
-  [key in variantsType]: string // eslint-disable-line no-unused-vars
+  [key in GroupsAndEnterpriseBannerVariant]: string // eslint-disable-line no-unused-vars
 }
 
 const urlForVariants: urlForVariantsType = {
@@ -18,23 +19,19 @@ const urlForVariants: urlForVariantsType = {
   FOMO: '/for/contact-sales-4',
 }
 
-type GroupsAndEnterpriseBannerVariant = (typeof variants)[number]
-
 let viewEventSent = false
 
 export default function GroupsAndEnterpriseBanner() {
   const { t } = useTranslation()
   const { totalProjectsCount } = useProjectListContext()
 
-  const showGroupsAndEnterpriseBanner: boolean = getMeta(
+  const showGroupsAndEnterpriseBanner = getMeta(
     'ol-showGroupsAndEnterpriseBanner'
   )
-  const groupsAndEnterpriseBannerVariant: GroupsAndEnterpriseBannerVariant =
-    getMeta('ol-groupsAndEnterpriseBannerVariant')
-  const newNotificationStyle = getMeta(
-    'ol-newNotificationStyle',
-    false
-  ) as boolean
+  const groupsAndEnterpriseBannerVariant = getMeta(
+    'ol-groupsAndEnterpriseBannerVariant'
+  )
+  const newNotificationStyle = getMeta('ol-newNotificationStyle')
 
   const hasDismissedGroupsAndEnterpriseBanner = hasRecentlyDismissedBanner()
 
@@ -99,7 +96,7 @@ export default function GroupsAndEnterpriseBanner() {
 }
 
 function isVariantValid(variant: GroupsAndEnterpriseBannerVariant) {
-  return variants.includes(variant)
+  return GroupsAndEnterpriseBannerVariants.includes(variant)
 }
 
 function BannerContent({

+ 2 - 6
services/web/frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirm-affiliation.tsx

@@ -9,7 +9,6 @@ import {
   postJSON,
 } from '../../../../../../infrastructure/fetch-json'
 import { UserEmailData } from '../../../../../../../../types/user-email'
-import { ExposedSettings } from '../../../../../../../../types/exposed-settings'
 import { Institution } from '../../../../../../../../types/institution'
 import { useLocation } from '../../../../../../shared/hooks/use-location'
 import { debugConsole } from '@/utils/debugging'
@@ -24,11 +23,8 @@ function ReconfirmAffiliation({
   institution,
 }: ReconfirmAffiliationProps) {
   const { t } = useTranslation()
-  const { samlInitPath } = getMeta('ol-ExposedSettings') as ExposedSettings
-  const newNotificationStyle = getMeta(
-    'ol-newNotificationStyle',
-    false
-  ) as boolean
+  const { samlInitPath } = getMeta('ol-ExposedSettings')
+  const newNotificationStyle = getMeta('ol-newNotificationStyle')
   const { error, isLoading, isError, isSuccess, runAsync } = useAsync()
   const [hasSent, setHasSent] = useState(false)
   const [isPending, setIsPending] = useState(false)

+ 4 - 7
services/web/frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirmation-info.tsx

@@ -1,16 +1,13 @@
 import Notification from '../../notification'
 import ReconfirmAffiliation from './reconfirm-affiliation'
 import getMeta from '../../../../../../utils/meta'
-import { UserEmailData } from '../../../../../../../../types/user-email'
 import ReconfirmationInfoSuccess from '../../../../../settings/components/emails/reconfirmation-info/reconfirmation-info-success'
 
 function ReconfirmationInfo() {
-  const allInReconfirmNotificationPeriods = getMeta(
-    'ol-allInReconfirmNotificationPeriods',
-    []
-  ) as UserEmailData[]
-  const userEmails = getMeta('ol-userEmails', []) as UserEmailData[]
-  const reconfirmedViaSAML = getMeta('ol-reconfirmedViaSAML') as string
+  const allInReconfirmNotificationPeriods =
+    getMeta('ol-allInReconfirmNotificationPeriods') || []
+  const userEmails = getMeta('ol-userEmails') || []
+  const reconfirmedViaSAML = getMeta('ol-reconfirmedViaSAML')
   return (
     <>
       {allInReconfirmNotificationPeriods.map(userEmail =>

+ 4 - 9
services/web/frontend/js/features/project-list/components/notifications/groups/common.tsx

@@ -6,18 +6,16 @@ import getMeta from '../../../../../utils/meta'
 import useAsyncDismiss from '../hooks/useAsyncDismiss'
 import useAsync from '../../../../../shared/hooks/use-async'
 import { FetchError, postJSON } from '../../../../../infrastructure/fetch-json'
-import { ExposedSettings } from '../../../../../../../types/exposed-settings'
 import {
   NotificationProjectInvite,
   Notification as NotificationType,
 } from '../../../../../../../types/project/dashboard/notification'
-import { User } from '../../../../../../../types/user'
 import GroupInvitationNotification from './group-invitation/group-invitation'
 import IEEERetirementBanner from '../ieee-retirement-banner'
 import { debugConsole } from '@/utils/debugging'
 
 function Common() {
-  const notifications = getMeta('ol-notifications', []) as NotificationType[]
+  const notifications = getMeta('ol-notifications') || []
   if (!notifications.length) {
     return null
   }
@@ -37,12 +35,9 @@ type CommonNotificationProps = {
 
 function CommonNotification({ notification }: CommonNotificationProps) {
   const { t } = useTranslation()
-  const { samlInitPath } = getMeta('ol-ExposedSettings') as ExposedSettings
-  const user = getMeta('ol-user', []) as Pick<User, 'features'>
-  const newNotificationStyle = getMeta(
-    'ol-newNotificationStyle',
-    false
-  ) as boolean
+  const { samlInitPath } = getMeta('ol-ExposedSettings')
+  const user = getMeta('ol-user')
+  const newNotificationStyle = getMeta('ol-newNotificationStyle')
   const { isLoading, isSuccess, error, runAsync } = useAsync<
     never,
     FetchError

+ 4 - 12
services/web/frontend/js/features/project-list/components/notifications/groups/confirm-email.tsx

@@ -9,15 +9,11 @@ import {
   postJSON,
   getUserFacingMessage,
 } from '../../../../../infrastructure/fetch-json'
-import { ExposedSettings } from '../../../../../../../types/exposed-settings'
 import { UserEmailData } from '../../../../../../../types/user-email'
 import { debugConsole } from '@/utils/debugging'
-import { Subscription } from '../../../../../../../types/project/dashboard/subscription'
 
 const ssoAvailable = ({ samlProviderId, affiliation }: UserEmailData) => {
-  const { hasSamlFeature, hasSamlBeta } = getMeta(
-    'ol-ExposedSettings'
-  ) as ExposedSettings
+  const { hasSamlFeature, hasSamlBeta } = getMeta('ol-ExposedSettings')
 
   if (!hasSamlFeature) {
     return false
@@ -60,9 +56,7 @@ function emailHasLicenceAfterConfirming(emailData: UserEmailData) {
 }
 
 function isOnFreeOrIndividualPlan() {
-  const subscription: Subscription | undefined = getMeta(
-    'ol-usersBestSubscription'
-  )
+  const subscription = getMeta('ol-usersBestSubscription')
   if (!subscription) {
     return false
   }
@@ -71,9 +65,7 @@ function isOnFreeOrIndividualPlan() {
 }
 
 const showConfirmEmail = (userEmail: UserEmailData) => {
-  const { emailConfirmationDisabled } = getMeta(
-    'ol-ExposedSettings'
-  ) as ExposedSettings
+  const { emailConfirmationDisabled } = getMeta('ol-ExposedSettings')
 
   return (
     !emailConfirmationDisabled &&
@@ -179,7 +171,7 @@ function ConfirmEmailNotification({ userEmail }: { userEmail: UserEmailData }) {
 
 function ConfirmEmail() {
   const { totalProjectsCount } = useProjectListContext()
-  const userEmails = getMeta('ol-userEmails', []) as UserEmailData[]
+  const userEmails = getMeta('ol-userEmails') || []
 
   if (!totalProjectsCount || !userEmails.length) {
     return null

+ 1 - 4
services/web/frontend/js/features/project-list/components/notifications/groups/group-invitation/group-invitation-cancel-subscription.tsx

@@ -20,10 +20,7 @@ export default function GroupInvitationCancelIndividualSubscriptionNotification(
   notification,
 }: GroupInvitationCancelIndividualSubscriptionNotificationProps) {
   const { t } = useTranslation()
-  const newNotificationStyle = getMeta(
-    'ol-newNotificationStyle',
-    false
-  ) as boolean
+  const newNotificationStyle = getMeta('ol-newNotificationStyle')
   const {
     messageOpts: { inviterName },
   } = notification

+ 1 - 4
services/web/frontend/js/features/project-list/components/notifications/groups/group-invitation/group-invitation-join.tsx

@@ -18,10 +18,7 @@ export default function GroupInvitationNotificationJoin({
   dismissGroupInviteNotification,
 }: GroupInvitationNotificationProps) {
   const { t } = useTranslation()
-  const newNotificationStyle = getMeta(
-    'ol-newNotificationStyle',
-    false
-  ) as boolean
+  const newNotificationStyle = getMeta('ol-newNotificationStyle')
   const {
     messageOpts: { inviterName },
   } = notification

+ 1 - 4
services/web/frontend/js/features/project-list/components/notifications/groups/group-invitation/group-invitation-successful.tsx

@@ -11,10 +11,7 @@ export default function GroupInvitationSuccessfulNotification({
   hideNotification,
 }: GroupInvitationSuccessfulNotificationProps) {
   const { t } = useTranslation()
-  const newNotificationStyle = getMeta(
-    'ol-newNotificationStyle',
-    false
-  ) as boolean
+  const newNotificationStyle = getMeta('ol-newNotificationStyle')
 
   return (
     <Notification

+ 1 - 1
services/web/frontend/js/features/project-list/components/notifications/groups/group-invitation/hooks/use-group-invitation-notification.tsx

@@ -59,7 +59,7 @@ export function useGroupInvitationNotification(
 
   const hasIndividualRecurlySubscription = getMeta(
     'ol-hasIndividualRecurlySubscription'
-  ) as boolean | undefined
+  )
 
   useEffect(() => {
     if (hasIndividualRecurlySubscription) {

+ 3 - 13
services/web/frontend/js/features/project-list/components/notifications/groups/institution.tsx

@@ -5,22 +5,12 @@ import Notification from '../notification'
 import Icon from '../../../../../shared/components/icon'
 import getMeta from '../../../../../utils/meta'
 import useAsyncDismiss from '../hooks/useAsyncDismiss'
-import { ExposedSettings } from '../../../../../../../types/exposed-settings'
-import { Institution as InstitutionType } from '../../../../../../../types/project/dashboard/notification'
 
 function Institution() {
   const { t } = useTranslation()
-  const { samlInitPath, appName } = getMeta(
-    'ol-ExposedSettings'
-  ) as ExposedSettings
-  const notificationsInstitution = getMeta(
-    'ol-notificationsInstitution',
-    []
-  ) as InstitutionType[]
-  const newNotificationStyle = getMeta(
-    'ol-newNotificationStyle',
-    false
-  ) as boolean
+  const { samlInitPath, appName } = getMeta('ol-ExposedSettings')
+  const notificationsInstitution = getMeta('ol-notificationsInstitution') || []
+  const newNotificationStyle = getMeta('ol-newNotificationStyle')
   const { handleDismiss } = useAsyncDismiss()
 
   if (!notificationsInstitution.length) {

+ 1 - 2
services/web/frontend/js/features/project-list/components/notifications/notification.tsx

@@ -40,8 +40,7 @@ function Notification({
   ...props
 }: NotificationProps) {
   newNotificationStyle =
-    newNotificationStyle ??
-    (getMeta('ol-newNotificationStyle', false) as boolean)
+    newNotificationStyle ?? getMeta('ol-newNotificationStyle')
 
   const [show, setShow] = useState(true)
 

+ 3 - 13
services/web/frontend/js/features/project-list/components/notifications/user-notifications.tsx

@@ -14,11 +14,6 @@ import classNames from 'classnames'
 import GeoBanners from './geo-banners'
 import AccessibilitySurveyBanner from './accessibility-survey-banner'
 
-type Subscription = {
-  groupId: string
-  groupName: string
-}
-
 const [enrollmentNotificationModule] = importOverleafModules(
   'managedGroupSubscriptionEnrollmentNotification'
 )
@@ -28,14 +23,9 @@ const EnrollmentNotification: JSXElementConstructor<{
 }> = enrollmentNotificationModule?.import.default
 
 function UserNotifications() {
-  const newNotificationStyle = getMeta(
-    'ol-newNotificationStyle',
-    false
-  ) as boolean
-  const groupSubscriptionsPendingEnrollment: Subscription[] = getMeta(
-    'ol-groupSubscriptionsPendingEnrollment',
-    []
-  )
+  const newNotificationStyle = getMeta('ol-newNotificationStyle')
+  const groupSubscriptionsPendingEnrollment =
+    getMeta('ol-groupSubscriptionsPendingEnrollment') || []
   const user = getMeta('ol-user')
 
   // Temporary workaround to prevent also showing groups/enterprise banner

+ 1 - 2
services/web/frontend/js/features/project-list/components/survey-widget.tsx

@@ -1,11 +1,10 @@
 import usePersistedState from '../../../shared/hooks/use-persisted-state'
 import getMeta from '../../../utils/meta'
-import { Survey } from '../../../../../types/project/dashboard/survey'
 import { useCallback } from 'react'
 import Close from '@/shared/components/close'
 
 export default function SurveyWidget() {
-  const survey: Survey = getMeta('ol-survey')
+  const survey = getMeta('ol-survey')
   const [dismissedSurvey, setDismissedSurvey] = usePersistedState(
     `dismissed-${survey?.name}`,
     false

+ 2 - 1
services/web/frontend/js/features/project-list/components/table/cells/action-buttons/delete-project-button.tsx

@@ -7,6 +7,7 @@ import DeleteProjectModal from '../../../modals/delete-project-modal'
 import useIsMounted from '../../../../../../shared/hooks/use-is-mounted'
 import { deleteProject } from '../../../../util/api'
 import { useProjectListContext } from '../../../../context/project-list-context'
+import getMeta from '@/utils/meta'
 
 type DeleteProjectButtonProps = {
   project: Project
@@ -31,7 +32,7 @@ function DeleteProjectButton({ project, children }: DeleteProjectButtonProps) {
   }, [isMounted])
 
   const isOwner = useMemo(() => {
-    return project.owner && window.user_id === project.owner.id
+    return project.owner && getMeta('ol-user_id') === project.owner.id
   }, [project])
 
   const handleDeleteProject = useCallback(async () => {

+ 2 - 1
services/web/frontend/js/features/project-list/components/table/cells/action-buttons/leave-project-button.tsx

@@ -7,6 +7,7 @@ import { useProjectListContext } from '../../../../context/project-list-context'
 import useIsMounted from '../../../../../../shared/hooks/use-is-mounted'
 import { leaveProject } from '../../../../util/api'
 import { Project } from '../../../../../../../../types/project/dashboard/api'
+import getMeta from '@/utils/meta'
 
 type LeaveProjectButtonProps = {
   project: Project
@@ -18,7 +19,7 @@ function LeaveProjectButton({ project, children }: LeaveProjectButtonProps) {
   const { t } = useTranslation()
   const text = t('leave')
   const isOwner = useMemo(() => {
-    return project.owner && window.user_id === project.owner.id
+    return project.owner && getMeta('ol-user_id') === project.owner.id
   }, [project])
   const [showModal, setShowModal] = useState(false)
   const isMounted = useIsMounted()

+ 3 - 7
services/web/frontend/js/features/project-list/components/welcome-message-new/welcome-message-create-new-project-dropdown.tsx

@@ -1,10 +1,8 @@
 import { useCallback, useState, forwardRef } from 'react'
 import { useTranslation } from 'react-i18next'
-import type { PortalTemplate } from '../../../../../../types/portal-template'
 import { sendMB } from '../../../../infrastructure/event-tracking'
 import getMeta from '../../../../utils/meta'
 import { NewProjectButtonModalVariant } from '../new-project-button/new-project-button-modal'
-import { ExposedSettings } from '../../../../../../types/exposed-settings'
 import {
   Dropdown,
   DropdownDivider,
@@ -61,11 +59,9 @@ function WelcomeMessageCreateNewProjectDropdown({
 }: WelcomeMessageCreateNewProjectDropdownProps) {
   const [showDropdown, setShowDropdown] = useState(false)
   const { t } = useTranslation()
-  const portalTemplates = getMeta('ol-portalTemplates') as
-    | PortalTemplate[]
-    | undefined
+  const portalTemplates = getMeta('ol-portalTemplates') || []
 
-  const { isOverleaf } = getMeta('ol-ExposedSettings') as ExposedSettings
+  const { isOverleaf } = getMeta('ol-ExposedSettings')
 
   const handleClick = useCallback(() => {
     sendMB('welcome-page-create-first-project-click', {
@@ -184,7 +180,7 @@ function WelcomeMessageCreateNewProjectDropdown({
                   {t('import_from_github')}
                 </button>
               )}
-              {(portalTemplates?.length ?? 0) > 0 ? (
+              {portalTemplates.length > 0 ? (
                 <>
                   <hr />
                   <div className="dropdown-header">

+ 1 - 4
services/web/frontend/js/features/project-list/components/welcome-message.tsx

@@ -6,7 +6,6 @@ import type { Nullable } from '../../../../../types/utils'
 import WelcomeMessageLink from './welcome-message-new/welcome-message-link'
 import WelcomeMessageCreateNewProjectDropdown from './welcome-message-new/welcome-message-create-new-project-dropdown'
 import getMeta from '@/utils/meta'
-import { ExposedSettings } from '../../../../../types/exposed-settings'
 import OLCard from '@/features/ui/components/ol/ol-card'
 
 export default function WelcomeMessage() {
@@ -14,9 +13,7 @@ export default function WelcomeMessage() {
   const [activeModal, setActiveModal] =
     useState<Nullable<NewProjectButtonModalVariant>>(null)
 
-  const { wikiEnabled, templatesEnabled } = getMeta(
-    'ol-ExposedSettings'
-  ) as ExposedSettings
+  const { wikiEnabled, templatesEnabled } = getMeta('ol-ExposedSettings')
 
   return (
     <>

+ 1 - 1
services/web/frontend/js/features/project-list/context/project-list-context.tsx

@@ -145,7 +145,7 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
   >('project-list-selected-tag-id', undefined)
   const [showCustomPicker, setShowCustomPicker] = useState(false)
 
-  const olTags: Tag[] = getMeta('ol-tags', [])
+  const olTags = getMeta('ol-tags') || []
 
   const [tags, setTags] = useState<Tag[]>(() =>
     // `tag.name` data may be null for some old users

+ 2 - 1
services/web/frontend/js/features/project-list/util/user.ts

@@ -1,7 +1,8 @@
 import { UserRef } from '../../../../../types/project/dashboard/api'
+import getMeta from '@/utils/meta'
 
 export function getUserName(user: UserRef) {
-  if (user?.id === window.user_id) {
+  if (user?.id === getMeta('ol-user_id')) {
     return 'You'
   }
 

+ 3 - 8
services/web/frontend/js/features/settings/components/account-info-section.tsx

@@ -5,7 +5,6 @@ import {
   postJSON,
 } from '../../../infrastructure/fetch-json'
 import getMeta from '../../../utils/meta'
-import { ExposedSettings } from '../../../../../types/exposed-settings'
 import useAsync from '../../../shared/hooks/use-async'
 import { useUserContext } from '../../../shared/context/user-context'
 import OLButton from '@/features/ui/components/ol/ol-button'
@@ -17,15 +16,11 @@ import FormText from '@/features/ui/components/bootstrap-5/form/form-text'
 
 function AccountInfoSection() {
   const { t } = useTranslation()
-  const { hasAffiliationsFeature } = getMeta(
-    'ol-ExposedSettings'
-  ) as ExposedSettings
+  const { hasAffiliationsFeature } = getMeta('ol-ExposedSettings')
   const isExternalAuthenticationSystemUsed = getMeta(
     'ol-isExternalAuthenticationSystemUsed'
-  ) as boolean
-  const shouldAllowEditingDetails = getMeta(
-    'ol-shouldAllowEditingDetails'
-  ) as boolean
+  )
+  const shouldAllowEditingDetails = getMeta('ol-shouldAllowEditingDetails')
   const {
     first_name: initialFirstName,
     last_name: initialLastName,

+ 2 - 7
services/web/frontend/js/features/settings/components/emails-section.tsx

@@ -10,7 +10,6 @@ import EmailsRow from './emails/row'
 import AddEmail from './emails/add-email'
 import Icon from '../../../shared/components/icon'
 import OLNotification from '@/features/ui/components/ol/ol-notification'
-import { ExposedSettings } from '../../../../../types/exposed-settings'
 import { LeaversSurveyAlert } from './leavers-survey-alert'
 
 function EmailsSectionContent() {
@@ -24,9 +23,7 @@ function EmailsSectionContent() {
   const userEmails = Object.values(userEmailsData.byId)
 
   // Only show the "add email" button if the user has permission to add a secondary email
-  const hideAddSecondaryEmail = getMeta(
-    'ol-cannot-add-secondary-email'
-  ) as boolean
+  const hideAddSecondaryEmail = getMeta('ol-cannot-add-secondary-email')
 
   return (
     <>
@@ -82,9 +79,7 @@ function EmailsSectionContent() {
 }
 
 function EmailsSection() {
-  const { hasAffiliationsFeature } = getMeta(
-    'ol-ExposedSettings'
-  ) as ExposedSettings
+  const { hasAffiliationsFeature } = getMeta('ol-ExposedSettings')
   if (!hasAffiliationsFeature) {
     return null
   }

+ 1 - 1
services/web/frontend/js/features/settings/components/emails/actions/remove.tsx

@@ -46,7 +46,7 @@ function Remove({ userEmailData, deleteEmailAsync }: RemoveProps) {
   const { t } = useTranslation()
   const { state, deleteEmail, resetLeaversSurveyExpiration } =
     useUserEmailsContext()
-  const isManaged = getMeta('ol-isManagedAccount', false)
+  const isManaged = getMeta('ol-isManagedAccount')
 
   const getTooltipText = () => {
     if (isManaged) {

+ 1 - 1
services/web/frontend/js/features/settings/components/emails/add-email.tsx

@@ -42,7 +42,7 @@ function AddEmail() {
     getEmails,
   } = useUserEmailsContext()
 
-  const emailAddressLimit = getMeta('ol-emailAddressLimit', 10)
+  const emailAddressLimit = getMeta('ol-emailAddressLimit') || 10
   const { ref: recaptchaRef, getReCaptchaToken } = useRecaptcha()
 
   useEffect(() => {

+ 1 - 2
services/web/frontend/js/features/settings/components/emails/add-email/sso-linking-info.tsx

@@ -1,7 +1,6 @@
 import { useState } from 'react'
 import { Trans, useTranslation } from 'react-i18next'
 import { DomainInfo } from './input'
-import { ExposedSettings } from '../../../../../../../types/exposed-settings'
 import getMeta from '../../../../../utils/meta'
 import { useLocation } from '../../../../../shared/hooks/use-location'
 import OLButton from '@/features/ui/components/ol/ol-button'
@@ -12,7 +11,7 @@ type SSOLinkingInfoProps = {
 }
 
 function SsoLinkingInfo({ domainInfo, email }: SSOLinkingInfoProps) {
-  const { samlInitPath } = getMeta('ol-ExposedSettings') as ExposedSettings
+  const { samlInitPath } = getMeta('ol-ExposedSettings')
   const { t } = useTranslation()
   const location = useLocation()
 

+ 3 - 6
services/web/frontend/js/features/settings/components/emails/reconfirmation-info.tsx

@@ -14,7 +14,6 @@ import { debugConsole } from '@/utils/debugging'
 import { ssoAvailableForInstitution } from '@/features/settings/utils/sso'
 import { Trans, useTranslation } from 'react-i18next'
 import useAsync from '@/shared/hooks/use-async'
-import { ExposedSettings } from '../../../../../../types/exposed-settings'
 import { useLocation } from '@/shared/hooks/use-location'
 import OLButton from '@/features/ui/components/ol/ol-button'
 import classnames from 'classnames'
@@ -24,13 +23,11 @@ type ReconfirmationInfoProps = {
 }
 
 function ReconfirmationInfo({ userEmailData }: ReconfirmationInfoProps) {
-  const reconfirmationRemoveEmail = getMeta(
-    'ol-reconfirmationRemoveEmail'
-  ) as string
-  const reconfirmedViaSAML = getMeta('ol-reconfirmedViaSAML') as string
+  const reconfirmationRemoveEmail = getMeta('ol-reconfirmationRemoveEmail')
+  const reconfirmedViaSAML = getMeta('ol-reconfirmedViaSAML')
 
   const { t } = useTranslation()
-  const { samlInitPath } = getMeta('ol-ExposedSettings') as ExposedSettings
+  const { samlInitPath } = getMeta('ol-ExposedSettings')
   const { error, isLoading, isError, isSuccess, runAsync } = useAsync()
   const { state, setLoading: setUserEmailsContextLoading } =
     useUserEmailsContext()

+ 1 - 2
services/web/frontend/js/features/settings/components/emails/row.tsx

@@ -8,7 +8,6 @@ import Actions from './actions'
 import { institutionAlreadyLinked } from '../../utils/selectors'
 import { useUserEmailsContext } from '../../context/user-email-context'
 import getMeta from '../../../../utils/meta'
-import { ExposedSettings } from '../../../../../../types/exposed-settings'
 import { ssoAvailableForInstitution } from '../../utils/sso'
 import ReconfirmationInfo from './reconfirmation-info'
 import { useLocation } from '../../../../shared/hooks/use-location'
@@ -61,7 +60,7 @@ type SSOAffiliationInfoProps = {
 }
 
 function SSOAffiliationInfo({ userEmailData }: SSOAffiliationInfoProps) {
-  const { samlInitPath } = getMeta('ol-ExposedSettings') as ExposedSettings
+  const { samlInitPath } = getMeta('ol-ExposedSettings')
   const { t } = useTranslation()
   const { state } = useUserEmailsContext()
   const location = useLocation()

+ 3 - 16
services/web/frontend/js/features/settings/components/emails/sso-alert.tsx

@@ -4,27 +4,14 @@ import Icon from '../../../../shared/components/icon'
 import getMeta from '../../../../utils/meta'
 import OLNotification from '@/features/ui/components/ol/ol-notification'
 
-type InstitutionLink = {
-  universityName: string
-  hasEntitlement?: boolean
-}
-
-type SAMLError = {
-  translatedMessage?: string
-  message?: string
-  tryAgain?: boolean
-}
-
 export function SSOAlert() {
   const { t } = useTranslation()
 
-  const institutionLinked: InstitutionLink | undefined = getMeta(
-    'ol-institutionLinked'
-  )
-  const institutionEmailNonCanonical: string | undefined = getMeta(
+  const institutionLinked = getMeta('ol-institutionLinked')
+  const institutionEmailNonCanonical = getMeta(
     'ol-institutionEmailNonCanonical'
   )
-  const samlError: SAMLError | undefined = getMeta('ol-samlError')
+  const samlError = getMeta('ol-samlError')
 
   const [infoClosed, setInfoClosed] = useState(false)
   const [warningClosed, setWarningClosed] = useState(false)

+ 2 - 3
services/web/frontend/js/features/settings/components/leave/modal-content.tsx

@@ -2,7 +2,6 @@ import { useState, Dispatch, SetStateAction } from 'react'
 import { useTranslation, Trans } from 'react-i18next'
 import getMeta from '../../../../utils/meta'
 import LeaveModalForm, { LeaveModalFormProps } from './modal-form'
-import { ExposedSettings } from '../../../../../../types/exposed-settings'
 import OLButton from '@/features/ui/components/ol/ol-button'
 import {
   OLModalBody,
@@ -23,8 +22,8 @@ function LeaveModalContentBlock({
   setIsFormValid,
 }: LeaveModalFormProps) {
   const { t } = useTranslation()
-  const { isOverleaf } = getMeta('ol-ExposedSettings') as ExposedSettings
-  const hasPassword = getMeta('ol-hasPassword') as boolean
+  const { isOverleaf } = getMeta('ol-ExposedSettings')
+  const hasPassword = getMeta('ol-hasPassword')
 
   if (isOverleaf && !hasPassword) {
     return (

+ 1 - 2
services/web/frontend/js/features/settings/components/leave/modal-form-error.tsx

@@ -1,7 +1,6 @@
 import { useTranslation, Trans } from 'react-i18next'
 import getMeta from '../../../../utils/meta'
 import { FetchError } from '../../../../infrastructure/fetch-json'
-import { ExposedSettings } from '../../../../../../types/exposed-settings'
 import OLNotification from '@/features/ui/components/ol/ol-notification'
 
 type LeaveModalFormErrorProps = {
@@ -10,7 +9,7 @@ type LeaveModalFormErrorProps = {
 
 function LeaveModalFormError({ error }: LeaveModalFormErrorProps) {
   const { t } = useTranslation()
-  const { isOverleaf } = getMeta('ol-ExposedSettings') as ExposedSettings
+  const { isOverleaf } = getMeta('ol-ExposedSettings')
 
   let errorMessage
   let errorTip = null

+ 1 - 1
services/web/frontend/js/features/settings/components/leave/modal-form.tsx

@@ -21,7 +21,7 @@ function LeaveModalForm({
   setIsFormValid,
 }: LeaveModalFormProps) {
   const { t } = useTranslation()
-  const userDefaultEmail = getMeta('ol-usersEmail') as string
+  const userDefaultEmail = getMeta('ol-usersEmail')!
   const location = useLocation()
 
   const [email, setEmail] = useState('')

+ 25 - 21
services/web/frontend/js/features/settings/components/linking-section.tsx

@@ -1,4 +1,4 @@
-import { useState, ElementType } from 'react'
+import { ElementType } from 'react'
 import { useTranslation } from 'react-i18next'
 import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
 import { useSSOContext, SSOSubscription } from '../context/sso-context'
@@ -8,29 +8,33 @@ import { useBroadcastUser } from '@/shared/hooks/user-channel/use-broadcast-user
 import { useFeatureFlag } from '@/shared/context/split-test-context'
 import OLNotification from '@/features/ui/components/ol/ol-notification'
 
+const availableIntegrationLinkingWidgets = importOverleafModules(
+  'integrationLinkingWidgets'
+) as any[]
+const availableReferenceLinkingWidgets = importOverleafModules(
+  'referenceLinkingWidgets'
+) as any[]
+const availableLangFeedbackLinkingWidgets = importOverleafModules(
+  'langFeedbackLinkingWidgets'
+) as any[]
+
 function LinkingSection() {
   useBroadcastUser()
   const { t } = useTranslation()
   const { subscriptions } = useSSOContext()
-  const ssoErrorMessage = getMeta('ol-ssoErrorMessage') as string
-  const projectSyncSuccessMessage = getMeta(
-    'ol-projectSyncSuccessMessage'
-  ) as string
-  const [integrationLinkingWidgets] = useState<any[]>(
-    () =>
-      getMeta('integrationLinkingWidgets') ||
-      importOverleafModules('integrationLinkingWidgets')
-  )
-  const [referenceLinkingWidgets] = useState<any[]>(
-    () =>
-      getMeta('referenceLinkingWidgets') ||
-      importOverleafModules('referenceLinkingWidgets')
-  )
-  const [langFeedbackLinkingWidgets] = useState<any[]>(
-    () =>
-      getMeta('langFeedbackLinkingWidgets') ||
-      importOverleafModules('langFeedbackLinkingWidgets')
-  )
+  const ssoErrorMessage = getMeta('ol-ssoErrorMessage')
+  const projectSyncSuccessMessage = getMeta('ol-projectSyncSuccessMessage')
+
+  // hide linking widgets in CI
+  const integrationLinkingWidgets = getMeta('ol-hideLinkingWidgets')
+    ? []
+    : availableIntegrationLinkingWidgets
+  const referenceLinkingWidgets = getMeta('ol-hideLinkingWidgets')
+    ? []
+    : availableReferenceLinkingWidgets
+  const langFeedbackLinkingWidgets = getMeta('ol-hideLinkingWidgets')
+    ? []
+    : availableLangFeedbackLinkingWidgets
 
   const oauth2ServerComponents = importOverleafModules('oauth2Server') as {
     import: { default: ElementType }
@@ -40,7 +44,7 @@ function LinkingSection() {
   const renderSyncSection =
     getMeta('ol-isSaas') || getMeta('ol-gitBridgeEnabled')
 
-  const showPersonalAccessTokenComponents: boolean =
+  const showPersonalAccessTokenComponents =
     getMeta('ol-showPersonalAccessToken') ||
     getMeta('ol-optionalPersonalAccessToken')
 

+ 3 - 4
services/web/frontend/js/features/settings/components/managed-account-alert.tsx

@@ -4,10 +4,9 @@ import Notification from '@/shared/components/notification'
 
 export default function ManagedAccountAlert() {
   const { t } = useTranslation()
-  const isManaged = getMeta('ol-isManagedAccount', false)
-  const currentManagedUserAdminEmail: string = getMeta(
-    'ol-currentManagedUserAdminEmail',
-    ''
+  const isManaged = getMeta('ol-isManagedAccount')
+  const currentManagedUserAdminEmail = getMeta(
+    'ol-currentManagedUserAdminEmail'
   )
 
   if (!isManaged) {

+ 5 - 9
services/web/frontend/js/features/settings/components/password-section.tsx

@@ -6,8 +6,6 @@ import {
   postJSON,
 } from '../../../infrastructure/fetch-json'
 import getMeta from '../../../utils/meta'
-import { ExposedSettings } from '../../../../../types/exposed-settings'
-import { PasswordStrengthOptions } from '../../../../../types/password-strength-options'
 import useAsync from '../../../shared/hooks/use-async'
 import OLButton from '@/features/ui/components/ol/ol-button'
 import OLNotification from '@/features/ui/components/ol/ol-notification'
@@ -24,7 +22,7 @@ type PasswordUpdateResult = {
 
 function PasswordSection() {
   const { t } = useTranslation()
-  const hideChangePassword = getMeta('ol-cannot-change-password') as boolean
+  const hideChangePassword = getMeta('ol-cannot-change-password')
   return (
     <>
       <h3>{t('change_password')}</h3>
@@ -53,11 +51,11 @@ function CanOnlyLogInThroughSSO() {
 
 function PasswordInnerSection() {
   const { t } = useTranslation()
-  const { isOverleaf } = getMeta('ol-ExposedSettings') as ExposedSettings
+  const { isOverleaf } = getMeta('ol-ExposedSettings')
   const isExternalAuthenticationSystemUsed = getMeta(
     'ol-isExternalAuthenticationSystemUsed'
-  ) as boolean
-  const hasPassword = getMeta('ol-hasPassword') as boolean
+  )
+  const hasPassword = getMeta('ol-hasPassword')
 
   if (isExternalAuthenticationSystemUsed && !isOverleaf) {
     return <p>{t('password_managed_externally')}</p>
@@ -78,9 +76,7 @@ function PasswordInnerSection() {
 
 function PasswordForm() {
   const { t } = useTranslation()
-  const passwordStrengthOptions = getMeta(
-    'ol-passwordStrengthOptions'
-  ) as PasswordStrengthOptions
+  const passwordStrengthOptions = getMeta('ol-passwordStrengthOptions')
 
   const [currentPassword, setCurrentPassword] = useState('')
   const [newPassword1, setNewPassword1] = useState('')

+ 1 - 4
services/web/frontend/js/features/settings/components/root.tsx

@@ -18,7 +18,6 @@ import { SSOProvider } from '../context/sso-context'
 import { SplitTestProvider } from '@/shared/context/split-test-context'
 import useWaitForI18n from '../../../shared/hooks/use-wait-for-i18n'
 import useScrollToIdOnLoad from '../../../shared/hooks/use-scroll-to-id-on-load'
-import { ExposedSettings } from '../../../../../types/exposed-settings'
 import { SSOAlert } from './emails/sso-alert'
 import OLRow from '@/features/ui/components/ol/ol-row'
 import OLCol from '@/features/ui/components/ol/ol-col'
@@ -45,9 +44,7 @@ function SettingsPageRoot() {
 
 function SettingsPageContent() {
   const { t } = useTranslation()
-  const { isOverleaf, labsEnabled } = getMeta(
-    'ol-ExposedSettings'
-  ) as ExposedSettings
+  const { isOverleaf, labsEnabled } = getMeta('ol-ExposedSettings')
 
   return (
     <UserProvider>

+ 2 - 5
services/web/frontend/js/features/settings/components/security-section.tsx

@@ -7,14 +7,11 @@ import OLButton from '@/features/ui/components/ol/ol-button'
 function SecuritySection() {
   const { t } = useTranslation()
 
-  const memberOfSSOEnabledGroups = getMeta(
-    'ol-memberOfSSOEnabledGroups',
-    []
-  ) as GroupSSOLinkingStatus[]
+  const memberOfSSOEnabledGroups = getMeta('ol-memberOfSSOEnabledGroups') || []
 
   return (
     <>
-      {memberOfSSOEnabledGroups?.length > 0 ? (
+      {memberOfSSOEnabledGroups.length > 0 ? (
         <>
           <h3>{t('security')}</h3>
           {memberOfSSOEnabledGroups.map(

+ 3 - 7
services/web/frontend/js/features/settings/context/sso-context.tsx

@@ -10,11 +10,7 @@ import { postJSON } from '../../../infrastructure/fetch-json'
 import useIsMounted from '../../../shared/hooks/use-is-mounted'
 import { set, cloneDeep } from 'lodash'
 import getMeta from '../../../utils/meta'
-import type {
-  OAuthProviders,
-  OAuthProvider,
-} from '../../../../../types/oauth-providers'
-import type { ThirdPartyIds } from '../../../../../types/third-party-ids'
+import type { OAuthProvider } from '../../../../../types/oauth-providers'
 
 export type SSOSubscription = {
   providerId: string
@@ -35,8 +31,8 @@ type SSOProviderProps = {
 
 export function SSOProvider({ children }: SSOProviderProps) {
   const isMounted = useIsMounted()
-  const oauthProviders = getMeta('ol-oauthProviders', {}) as OAuthProviders
-  const thirdPartyIds = getMeta('ol-thirdPartyIds') as ThirdPartyIds
+  const oauthProviders = getMeta('ol-oauthProviders') || {}
+  const thirdPartyIds = getMeta('ol-thirdPartyIds')
 
   const [subscriptions, setSubscriptions] = useState<
     Record<string, SSOSubscription>

+ 2 - 7
services/web/frontend/js/features/settings/utils/sso.ts

@@ -1,14 +1,11 @@
 import getMeta from '../../../utils/meta'
 import { DomainInfo } from '../components/emails/add-email/input'
-import { ExposedSettings } from '../../../../../types/exposed-settings'
 import { Institution } from '../../../../../types/institution'
 
 export const ssoAvailableForDomain = (
   domain: DomainInfo | null
 ): domain is DomainInfo => {
-  const { hasSamlBeta, hasSamlFeature } = getMeta(
-    'ol-ExposedSettings'
-  ) as ExposedSettings
+  const { hasSamlBeta, hasSamlFeature } = getMeta('ol-ExposedSettings')
   if (!hasSamlFeature || !domain || !domain.confirmed || !domain.university) {
     return false
   }
@@ -19,9 +16,7 @@ export const ssoAvailableForDomain = (
 }
 
 export const ssoAvailableForInstitution = (institution: Institution | null) => {
-  const { hasSamlBeta, hasSamlFeature } = getMeta(
-    'ol-ExposedSettings'
-  ) as ExposedSettings
+  const { hasSamlBeta, hasSamlFeature } = getMeta('ol-ExposedSettings')
   if (
     !hasSamlFeature ||
     !institution ||

+ 2 - 1
services/web/frontend/js/features/share-project-modal/components/link-sharing.jsx

@@ -14,6 +14,7 @@ import { sendMB } from '../../../infrastructure/event-tracking'
 import { getJSON } from '../../../infrastructure/fetch-json'
 import useAbortController from '../../../shared/hooks/use-abort-controller'
 import { debugConsole } from '@/utils/debugging'
+import getMeta from '@/utils/meta'
 
 export default function LinkSharing() {
   const [inflight, setInflight] = useState(false)
@@ -249,7 +250,7 @@ function AccessToken({ token, tokenHashPrefix, path, tooltipId }) {
 
   let origin = window.location.origin
   if (isAdmin) {
-    origin = window.ExposedSettings.siteUrl
+    origin = getMeta('ol-ExposedSettings').siteUrl
   }
   const link = `${origin}${path}${token}${
     tokenHashPrefix ? `#${tokenHashPrefix}` : ''

+ 2 - 1
services/web/frontend/js/features/share-project-modal/components/share-modal-body.tsx

@@ -9,6 +9,7 @@ import { useEditorContext } from '../../../shared/context/editor-context'
 import { useProjectContext } from '../../../shared/context/project-context'
 import { useMemo } from 'react'
 import RecaptchaConditions from '../../../shared/components/recaptcha-conditions'
+import getMeta from '@/utils/meta'
 
 export default function ShareModalBody() {
   const { members, invites, features } = useProjectContext()
@@ -56,7 +57,7 @@ export default function ShareModalBody() {
         <SendInvitesNotice />
       )}
 
-      {!window.ExposedSettings.recaptchaDisabled?.invite && (
+      {!getMeta('ol-ExposedSettings').recaptchaDisabled?.invite && (
         <RecaptchaConditions />
       )}
     </>

+ 2 - 1
services/web/frontend/js/features/share-project-modal/utils/api.js

@@ -5,10 +5,11 @@ import {
   putJSON,
 } from '../../../infrastructure/fetch-json'
 import { executeV2Captcha } from './captcha'
+import getMeta from '@/utils/meta'
 
 export function sendInvite(projectId, email, privileges) {
   return executeV2Captcha(
-    window.ExposedSettings.recaptchaDisabled?.invite
+    getMeta('ol-ExposedSettings').recaptchaDisabled?.invite
   ).then(grecaptchaResponse => {
     return postJSON(`/project/${projectId}/invite`, {
       body: {

+ 2 - 1
services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal-source-picker.tsx

@@ -6,6 +6,7 @@ import {
 import Icon from '../../../../shared/components/icon'
 import { Button } from 'react-bootstrap'
 import { useTranslation } from 'react-i18next'
+import getMeta from '@/utils/meta'
 
 export const FigureModalSourcePicker: FC = () => {
   const { t } = useTranslation()
@@ -13,7 +14,7 @@ export const FigureModalSourcePicker: FC = () => {
     hasLinkedProjectFileFeature,
     hasLinkedProjectOutputFileFeature,
     hasLinkUrlFeature,
-  } = window.ExposedSettings
+  } = getMeta('ol-ExposedSettings')
   return (
     <div className="figure-modal-source-selector">
       <div className="figure-modal-source-button-grid">

+ 2 - 1
services/web/frontend/js/features/source-editor/components/figure-modal/file-sources/figure-modal-other-project-source.tsx

@@ -22,6 +22,7 @@ import { FileRelocator } from '../file-relocator'
 import { useTranslation } from 'react-i18next'
 import { waitForFileTreeUpdate } from '../../../extensions/figure-modal'
 import { useCodeMirrorViewContext } from '../../codemirror-editor'
+import getMeta from '@/utils/meta'
 
 function suggestName(path: string) {
   const parts = path.split('/')
@@ -36,7 +37,7 @@ export const FigureModalOtherProjectSource: FC = () => {
   const { loading: projectsLoading, data: projects, error } = useUserProjects()
   const [selectedProject, setSelectedProject] = useState<null | Project>(null)
   const { hasLinkedProjectFileFeature, hasLinkedProjectOutputFileFeature } =
-    window.ExposedSettings
+    getMeta('ol-ExposedSettings')
   const [usingOutputFiles, setUsingOutputFiles] = useState<boolean>(
     !hasLinkedProjectFileFeature
   )

+ 3 - 4
services/web/frontend/js/features/source-editor/components/figure-modal/file-sources/figure-modal-upload-source.tsx

@@ -16,8 +16,7 @@ import { FileRelocator } from '../file-relocator'
 import { useTranslation } from 'react-i18next'
 import { useCodeMirrorViewContext } from '../../codemirror-editor'
 import { waitForFileTreeUpdate } from '../../../extensions/figure-modal'
-
-const maxFileSize = window.ExposedSettings.maxUploadSize
+import getMeta from '@/utils/meta'
 
 /* eslint-disable no-unused-vars */
 export enum FileUploadStatus {
@@ -46,7 +45,7 @@ export const FigureModalUploadFileSource: FC = () => {
       allowMultipleUploadBatches: false,
       restrictions: {
         maxNumberOfFiles: 1,
-        maxFileSize: maxFileSize || null,
+        maxFileSize: getMeta('ol-ExposedSettings').maxUploadSize,
         allowedFileTypes: ['image/*', '.pdf'],
       },
       autoProceed: false,
@@ -55,7 +54,7 @@ export const FigureModalUploadFileSource: FC = () => {
       .use(XHRUpload, {
         endpoint: `/project/${projectId}/upload?folder_id=${rootFile.id}`,
         headers: {
-          'X-CSRF-TOKEN': window.csrfToken,
+          'X-CSRF-TOKEN': getMeta('ol-csrfToken'),
         },
         // limit: maxConnections || 1,
         limit: 1,

+ 1 - 2
services/web/frontend/js/features/source-editor/components/grammarly-advert.tsx

@@ -7,10 +7,9 @@ import GrammarlyLogo from '@/shared/svgs/grammarly-logo'
 import * as eventTracking from '../../../infrastructure/event-tracking'
 import useWaitForGrammarlyCheck from '@/shared/hooks/use-wait-for-grammarly-check'
 import getMeta from '@/utils/meta'
-import { ExposedSettings } from '../../../../../types/exposed-settings'
 
 export default function GrammarlyAdvert() {
-  const { isOverleaf } = getMeta('ol-ExposedSettings') as ExposedSettings
+  const { isOverleaf } = getMeta('ol-ExposedSettings')
 
   const [show, setShow] = useState(false)
   const { t } = useTranslation()

+ 2 - 1
services/web/frontend/js/features/source-editor/components/toolbar/insert-figure-dropdown.tsx

@@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'
 import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
 import { useCodeMirrorViewContext } from '../codemirror-editor'
 import { insertFigure } from '../../extensions/toolbar/commands'
+import getMeta from '@/utils/meta'
 
 export const InsertFigureDropdown = memo(function InsertFigureDropdown() {
   const { t } = useTranslation()
@@ -26,7 +27,7 @@ export const InsertFigureDropdown = memo(function InsertFigureDropdown() {
     hasLinkedProjectFileFeature,
     hasLinkedProjectOutputFileFeature,
     hasLinkUrlFeature,
-  } = window.ExposedSettings
+  } = getMeta('ol-ExposedSettings')
   return (
     <ToolbarButtonMenu
       id="toolbar-figure"

+ 4 - 2
services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts

@@ -59,6 +59,7 @@ import { DocumentContainer } from '@/features/ide-react/editor/document-containe
 import { useLayoutContext } from '@/shared/context/layout-context'
 import { debugConsole } from '@/utils/debugging'
 import { useMetadataContext } from '@/features/ide-react/context/metadata-context'
+import { useUserContext } from '@/shared/context/user-context'
 
 function useCodeMirrorScope(view: EditorView) {
   const { fileTreeData } = useFileTreeData()
@@ -82,6 +83,7 @@ function useCodeMirrorScope(view: EditorView) {
   const [docName] = useScopeValue<string>('editor.open_doc_name')
   const [trackChanges] = useScopeValue<boolean>('editor.trackChanges')
 
+  const { id: userId } = useUserContext()
   const { userSettings } = useUserSettingsContext()
   const {
     fontFamily,
@@ -175,12 +177,12 @@ function useCodeMirrorScope(view: EditorView) {
 
     if (currentDoc) {
       if (trackChanges) {
-        currentDoc.track_changes_as = window.user.id || 'anonymous'
+        currentDoc.track_changes_as = userId || 'anonymous'
       } else {
         currentDoc.track_changes_as = null
       }
     }
-  }, [currentDoc, trackChanges])
+  }, [userId, currentDoc, trackChanges])
 
   useEffect(() => {
     if (lineHeight && fontSize) {

+ 1 - 5
services/web/frontend/js/features/subscription/components/dashboard/group-settings-button.tsx

@@ -1,7 +1,6 @@
 import { RowLink } from '@/features/subscription/components/dashboard/row-link'
 import getMeta from '@/utils/meta'
 import { useTranslation } from 'react-i18next'
-import { ExposedSettings } from '../../../../../../types/exposed-settings'
 import { ManagedGroupSubscription } from '../../../../../../types/subscription/dashboard/subscription'
 
 export default function GroupSettingsButton({
@@ -11,10 +10,7 @@ export default function GroupSettingsButton({
 }) {
   const { t } = useTranslation()
 
-  const { groupSSOEnabled } = getMeta(
-    'ol-ExposedSettings',
-    {}
-  ) as ExposedSettings
+  const { groupSSOEnabled } = getMeta('ol-ExposedSettings')
 
   const subscriptionHasManagedUsers =
     subscription.features?.managedUsers !== false

+ 1 - 3
services/web/frontend/js/features/subscription/components/dashboard/group-subscription-membership.tsx

@@ -22,9 +22,7 @@ export default function GroupSubscriptionMembership({
   }
 
   // Hide leave group button for managed users
-  const hideLeaveButton = getMeta(
-    'ol-cannot-leave-group-subscription'
-  ) as boolean
+  const hideLeaveButton = getMeta('ol-cannot-leave-group-subscription')
 
   return (
     <div>

+ 2 - 5
services/web/frontend/js/features/subscription/components/dashboard/managed-group-subscriptions.tsx

@@ -10,7 +10,7 @@ function ManagedGroupAdministrator({
 }: {
   subscription: ManagedGroupSubscription
 }) {
-  const usersEmail = getMeta('ol-usersEmail') as string
+  const usersEmail = getMeta('ol-usersEmail')
   const values = {
     planName: subscription.planLevelName,
     groupName: subscription.teamName || '',
@@ -90,10 +90,7 @@ export default function ManagedGroupSubscriptions() {
     return null
   }
 
-  const groupSettingsEnabledFor = getMeta(
-    'ol-groupSettingsEnabledFor',
-    []
-  ) as string[]
+  const groupSettingsEnabledFor = getMeta('ol-groupSettingsEnabledFor') || []
 
   return (
     <>

+ 3 - 2
services/web/frontend/js/features/subscription/components/dashboard/managed-institution.tsx

@@ -2,9 +2,10 @@ import { useCallback, useState } from 'react'
 import { Trans, useTranslation } from 'react-i18next'
 import { postJSON } from '../../../../infrastructure/fetch-json'
 import { useSubscriptionDashboardContext } from '../../context/subscription-dashboard-context'
-import { Institution } from './managed-institutions'
+import { ManagedInstitution as Institution } from '../../../../../../types/subscription/dashboard/managed-institution'
 import { RowLink } from './row-link'
 import { debugConsole } from '@/utils/debugging'
+import getMeta from '@/utils/meta'
 
 type ManagedInstitutionProps = {
   institution: Institution
@@ -84,7 +85,7 @@ export default function ManagedInstitution({
               }
             >
               {institution.metricsEmail.optedOutUserIds.includes(
-                window.user_id!
+                getMeta('ol-user_id')!
               )
                 ? t('subscribe')
                 : t('unsubscribe')}

Някои файлове не бяха показани, защото твърде много файлове са промени