From 0ecebefb0ca8b99fed063031355f8a1d659248e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Alby?= Date: Mon, 19 Apr 2021 14:38:03 +0200 Subject: [PATCH] Merge pull request #3804 from overleaf/msm-react-publish-button [ReactNavigationToolbar] Submit button GitOrigin-RevId: 9b40e09f001b44bd2f5035469f0d0c852fea7199 --- .../BrandVariations/BrandVariationsHandler.js | 98 +++++++++---------- services/web/config/settings.defaults.coffee | 2 + .../web/frontend/extracted-translations.json | 1 + .../components/toolbar-header.js | 18 +++- .../js/shared/context/application-context.js | 4 +- .../js/shared/context/compile-context.js | 47 +++++++++ .../js/shared/context/editor-context.js | 28 +++++- .../js/shared/context/root-context.js | 9 +- .../BrandVariationsHandlerTests.js | 30 ++++++ 9 files changed, 173 insertions(+), 64 deletions(-) create mode 100644 services/web/frontend/js/shared/context/compile-context.js diff --git a/services/web/app/src/Features/BrandVariations/BrandVariationsHandler.js b/services/web/app/src/Features/BrandVariations/BrandVariationsHandler.js index fa02fba9dc..bcc4d71e25 100644 --- a/services/web/app/src/Features/BrandVariations/BrandVariationsHandler.js +++ b/services/web/app/src/Features/BrandVariations/BrandVariationsHandler.js @@ -1,86 +1,82 @@ -/* eslint-disable - node/handle-callback-err, - max-len, - no-return-assign, - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -let BrandVariationsHandler const OError = require('@overleaf/o-error') const url = require('url') const settings = require('settings-sharelatex') const logger = require('logger-sharelatex') const V1Api = require('../V1/V1Api') +const sanitizeHtml = require('sanitize-html') -module.exports = BrandVariationsHandler = { - getBrandVariationById(brandVariationId, callback) { - if (callback == null) { - callback = function (error, brandVariationDetails) {} - } - if (brandVariationId == null || brandVariationId === '') { - return callback(new Error('Branding variation id not provided')) - } - logger.log({ brandVariationId }, 'fetching brand variation details from v1') - return V1Api.request( - { - uri: `/api/v2/brand_variations/${brandVariationId}` - }, - function (error, response, brandVariationDetails) { - if (error != null) { - OError.tag(error, 'error getting brand variation details', { - brandVariationId - }) - return callback(error) - } - _formatBrandVariationDetails(brandVariationDetails) - return callback(null, brandVariationDetails) - } - ) - } +module.exports = { + getBrandVariationById } -var _formatBrandVariationDetails = function (details) { +function getBrandVariationById(brandVariationId, callback) { + if (brandVariationId == null || brandVariationId === '') { + return callback(new Error('Branding variation id not provided')) + } + logger.log({ brandVariationId }, 'fetching brand variation details from v1') + V1Api.request( + { + uri: `/api/v2/brand_variations/${brandVariationId}` + }, + function (error, response, brandVariationDetails) { + if (error != null) { + OError.tag(error, 'error getting brand variation details', { + brandVariationId + }) + return callback(error) + } + formatBrandVariationDetails(brandVariationDetails) + sanitizeBrandVariationDetails(brandVariationDetails) + callback(null, brandVariationDetails) + } + ) +} + +function formatBrandVariationDetails(details) { if (details.export_url != null) { - details.export_url = _setV1AsHostIfRelativeURL(details.export_url) + details.export_url = setV1AsHostIfRelativeURL(details.export_url) } if (details.home_url != null) { - details.home_url = _setV1AsHostIfRelativeURL(details.home_url) + details.home_url = setV1AsHostIfRelativeURL(details.home_url) } if (details.logo_url != null) { - details.logo_url = _setV1AsHostIfRelativeURL(details.logo_url) + details.logo_url = setV1AsHostIfRelativeURL(details.logo_url) } if (details.journal_guidelines_url != null) { - details.journal_guidelines_url = _setV1AsHostIfRelativeURL( + details.journal_guidelines_url = setV1AsHostIfRelativeURL( details.journal_guidelines_url ) } if (details.journal_cover_url != null) { - details.journal_cover_url = _setV1AsHostIfRelativeURL( + details.journal_cover_url = setV1AsHostIfRelativeURL( details.journal_cover_url ) } if (details.submission_confirmation_page_logo_url != null) { - details.submission_confirmation_page_logo_url = _setV1AsHostIfRelativeURL( + details.submission_confirmation_page_logo_url = setV1AsHostIfRelativeURL( details.submission_confirmation_page_logo_url ) } if (details.publish_menu_icon != null) { - return (details.publish_menu_icon = _setV1AsHostIfRelativeURL( + details.publish_menu_icon = setV1AsHostIfRelativeURL( details.publish_menu_icon - )) + ) } } -var _setV1AsHostIfRelativeURL = urlString => +function sanitizeBrandVariationDetails(details) { + if (details.submit_button_html) { + details.submit_button_html = sanitizeHtml( + details.submit_button_html, + settings.modules.sanitize.options + ) + } +} + +function setV1AsHostIfRelativeURL(urlString) { // The first argument is the base URL to resolve against if the second argument is not absolute. // As it only applies if the second argument is not absolute, we can use it to transform relative URLs into // absolute ones using v1 as the host. If the URL is absolute (e.g. a filepicker one), then the base // argument is just ignored - url.resolve(settings.apis.v1.url, urlString) + return url.resolve(settings.apis.v1.url, urlString) +} diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index ac40c0ad43..4d42b0764b 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -718,6 +718,7 @@ module.exports = settings = 'img': [ 'alt', 'class', 'src', 'style' ] 'source': [ 'src', 'type' ] 'span': [ 'class', 'id', 'style' ] + 'strong': [ 'style' ] 'table': [ 'border', 'class', 'id', 'style' ] 'td': [ 'colspan', 'rowspan', 'headers', 'style' ] 'th': [ 'abbr', 'headers', 'colspan', 'rowspan', 'scope', 'sorted', 'style' ] @@ -728,6 +729,7 @@ module.exports = settings = # modules to import (an empty array for each set of modules) createFileModes: [] gitBridge: [] + publishModal: [] } csp: { diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 4ec8d5de7b..12bf485eab 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -212,6 +212,7 @@ "stop_compile": "", "stop_on_validation_error": "", "store_your_work": "", + "submit": "", "sure_you_want_to_delete": "", "sync_project_to_github_explanation": "", "sync_to_dropbox": "", diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.js b/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.js index 9c255d8997..41a40fcd1f 100644 --- a/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.js +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.js @@ -10,6 +10,10 @@ import TrackChangesToggleButton from './track-changes-toggle-button' import HistoryToggleButton from './history-toggle-button' import ShareProjectButton from './share-project-button' import PdfToggleButton from './pdf-toggle-button' +import importOverleafModules from '../../../../macros/import-overleaf-module.macro' + +const [publishModalModules] = importOverleafModules('publishModal') +const PublishButton = publishModalModules?.import.default function ToolbarHeader({ cobranding, @@ -53,14 +57,18 @@ function ToolbarHeader({
+ + {!isRestrictedTokenMember && ( + + )} + {PublishButton && } {!isRestrictedTokenMember && ( <> - + + {children} + + + ) +} + +CompileProvider.propTypes = { + children: PropTypes.any, + $scope: PropTypes.any.isRequired +} + +export function useCompileContext(propTypes) { + const data = useContext(CompileContext) + PropTypes.checkPropTypes(propTypes, data, 'data', 'CompileContext.Provider') + return data +} diff --git a/services/web/frontend/js/shared/context/editor-context.js b/services/web/frontend/js/shared/context/editor-context.js index 117e10a901..2ba9e4e845 100644 --- a/services/web/frontend/js/shared/context/editor-context.js +++ b/services/web/frontend/js/shared/context/editor-context.js @@ -11,14 +11,22 @@ EditorContext.Provider.propTypes = { cobranding: PropTypes.shape({ logoImgUrl: PropTypes.string.isRequired, brandVariationName: PropTypes.string.isRequired, - brandVariationHomeUrl: PropTypes.string.isRequired + brandVariationId: PropTypes.number.isRequired, + brandId: PropTypes.number.isRequired, + brandVariationHomeUrl: PropTypes.string.isRequired, + publishGuideHtml: PropTypes.string, + partner: PropTypes.string, + brandedMenu: PropTypes.string, + submitBtnHtml: PropTypes.string }), loading: PropTypes.bool, + projectRootDocId: PropTypes.string, projectId: PropTypes.string.isRequired, projectName: PropTypes.string.isRequired, renameProject: PropTypes.func.isRequired, isProjectOwner: PropTypes.bool, - isRestrictedTokenMember: PropTypes.bool + isRestrictedTokenMember: PropTypes.bool, + rootFolder: PropTypes.object }) } @@ -34,7 +42,13 @@ export function EditorProvider({ children, ide, settings }) { ? { logoImgUrl: window.brandVariation.logo_url, brandVariationName: window.brandVariation.name, - brandVariationHomeUrl: window.brandVariation.home_url + brandVariationId: window.brandVariation.id, + brandId: window.brandVariation.brand_id, + brandVariationHomeUrl: window.brandVariation.home_url, + publishGuideHtml: window.brandVariation.publish_guide_html, + partner: window.brandVariation.partner, + brandedMenu: window.brandVariation.branded_menu, + submitBtnHtml: window.brandVariation.submit_button_html } : undefined @@ -45,11 +59,15 @@ export function EditorProvider({ children, ide, settings }) { const [loading] = useScopeValue('state.loading', ide.$scope) + const [projectRootDocId] = useScopeValue('project.rootDoc_id', ide.$scope) + const [projectName, setProjectName] = useScopeValue( 'project.name', ide.$scope ) + const [rootFolder] = useScopeValue('rootFolder', ide.$scope) + const renameProject = useCallback( newName => { setProjectName(oldName => { @@ -82,10 +100,12 @@ export function EditorProvider({ children, ide, settings }) { cobranding, loading, projectId: window.project_id, + projectRootDocId, projectName: projectName || '', // initially might be empty in Angular renameProject, isProjectOwner: ownerId === window.user.id, - isRestrictedTokenMember: window.isRestrictedTokenMember + isRestrictedTokenMember: window.isRestrictedTokenMember, + rootFolder } return ( diff --git a/services/web/frontend/js/shared/context/root-context.js b/services/web/frontend/js/shared/context/root-context.js index b1fde159a1..7d577a4eda 100644 --- a/services/web/frontend/js/shared/context/root-context.js +++ b/services/web/frontend/js/shared/context/root-context.js @@ -5,14 +5,17 @@ import { EditorProvider } from './editor-context' import createSharedContext from 'react2angular-shared-context' import { ChatProvider } from '../../features/chat/context/chat-context' import { LayoutProvider } from './layout-context' +import { CompileProvider } from './compile-context' export function ContextRoot({ children, ide, settings }) { return ( - - {children} - + + + {children} + + ) diff --git a/services/web/test/unit/src/BrandVariations/BrandVariationsHandlerTests.js b/services/web/test/unit/src/BrandVariations/BrandVariationsHandlerTests.js index 6162ce4a6f..aa0fa8b6c6 100644 --- a/services/web/test/unit/src/BrandVariations/BrandVariationsHandlerTests.js +++ b/services/web/test/unit/src/BrandVariations/BrandVariationsHandlerTests.js @@ -28,6 +28,16 @@ describe('BrandVariationsHandler', function () { v1: { url: 'http://overleaf.example.com' } + }, + modules: { + sanitize: { + options: { + allowedTags: ['br', 'strong'], + allowedAttributes: { + strong: ['style'] + } + } + } } } this.V1Api = { request: sinon.stub() } @@ -107,5 +117,25 @@ describe('BrandVariationsHandler', function () { } ) }) + + it("should sanitize 'submit_button_html'", function (done) { + this.mockedBrandVariationDetails.submit_button_html = + '
AGU Journal' + this.V1Api.request.callsArgWith( + 1, + null, + { statusCode: 200 }, + this.mockedBrandVariationDetails + ) + return this.BrandVariationsHandler.getBrandVariationById( + '12', + (err, brandVariationDetails) => { + expect(brandVariationDetails.submit_button_html).to.equal( + '
AGU Journalhello' + ) + return done() + } + ) + }) }) })