From 81903bb79da4f4f1b160979d0f55439c57a76b2d Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Thu, 23 May 2024 12:12:23 -0400 Subject: [PATCH] Merge pull request #18175 from overleaf/jdt-new-bib-file-prompt [Web] Add opportunistic prompts for third party references GitOrigin-RevId: d794df16781d0db707423f23ab12f40a13604907 --- .../src/Features/Project/ProjectController.js | 7 +++ .../Features/Tutorial/TutorialController.js | 1 + services/web/config/settings.defaults.js | 3 +- .../web/frontend/extracted-translations.json | 3 ++ .../modes/file-tree-create-new-doc.jsx | 10 ++++- .../components/source-editor.tsx | 6 +-- .../shared/hooks/promotions/use-tutorial.tsx | 44 ++++++++++++------- .../web/frontend/stylesheets/main-style.less | 1 + .../modules/third-party-references.less | 29 ++++++++++++ services/web/locales/en.json | 3 ++ .../book-illlustration.svg | 7 +++ 11 files changed, 90 insertions(+), 24 deletions(-) create mode 100644 services/web/frontend/stylesheets/modules/third-party-references.less create mode 100644 services/web/public/img/third-party-references/book-illlustration.svg diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index 485eedaef7..ad7cc45934 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -607,6 +607,13 @@ const ProjectController = { ) } }, + tprPromptAssignment(cb) { + if (anonymous) { + cb() + } else { + SplitTestHandler.getAssignment(req, res, 'bib-file-tpr-prompt', cb) + } + }, compileLogEventsAssignment(cb) { SplitTestHandler.getAssignment(req, res, 'compile-log-events', cb) }, diff --git a/services/web/app/src/Features/Tutorial/TutorialController.js b/services/web/app/src/Features/Tutorial/TutorialController.js index 5c5a3ba307..1390e18931 100644 --- a/services/web/app/src/Features/Tutorial/TutorialController.js +++ b/services/web/app/src/Features/Tutorial/TutorialController.js @@ -7,6 +7,7 @@ const VALID_KEYS = [ 'table-generator-promotion', 'writefull-integration', 'writefull-oauth-promotion', + 'bib-file-tpr-prompt', ] async function completeTutorial(req, res, next) { diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index 0c6b456264..d0c80dd274 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -899,6 +899,7 @@ module.exports = { tprFileViewRefreshError: [], tprFileViewRefreshButton: [], tprFileViewNotOriginalImporter: [], + newFilePromotions: [], contactUsModal: [], editorToolbarButtons: [], sourceEditorExtensions: [], @@ -907,7 +908,7 @@ module.exports = { sourceEditorCompletionSources: [], sourceEditorSymbolPalette: [], sourceEditorToolbarComponents: [], - writefullEditorPromotion: [], + editorPromotions: [], langFeedbackLinkingWidgets: [], integrationLinkingWidgets: [], referenceLinkingWidgets: [], diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 5ece4880a8..34ab4a8c21 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -56,6 +56,7 @@ "add_your_first_group_member_now": "", "added_by_on": "", "adding": "", + "adding_a_bibliography": "", "additional_certificate": "", "additional_licenses": "", "address_line_1": "", @@ -106,6 +107,7 @@ "beta_program_already_participating": "", "beta_program_benefits": "", "beta_program_not_participating": "", + "better_bibliographies": "", "binary_history_error": "", "blank_project": "", "blocked_filename": "", @@ -333,6 +335,7 @@ "duplicate_file": "", "duplicate_projects": "", "each_user_will_have_access_to": "", + "easily_import_and_sync_your_references": "", "easily_manage_your_project_files_everywhere": "", "edit": "", "edit_dictionary": "", diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-create-new-doc.jsx b/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-create-new-doc.jsx index c47a8b5f8e..0f11ef1761 100644 --- a/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-create-new-doc.jsx +++ b/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-create-new-doc.jsx @@ -5,12 +5,14 @@ import { useFileTreeCreateName } from '../../../contexts/file-tree-create-name' import { useFileTreeCreateForm } from '../../../contexts/file-tree-create-form' import * as eventTracking from '../../../../../infrastructure/event-tracking' import ErrorMessage from '../error-message' +import importOverleafModules from '../../../../../../macros/import-overleaf-module.macro' + +const newFilePromotionComponents = importOverleafModules('newFilePromotions') export default function FileTreeCreateNewDoc() { const { name, validName } = useFileTreeCreateName() const { setValid } = useFileTreeCreateForm() const { error, finishCreatingDoc, inFlight } = useFileTreeActionable() - // form validation: name is valid useEffect(() => { setValid(validName) @@ -33,8 +35,12 @@ export default function FileTreeCreateNewDoc() { return (
- {error && } + {newFilePromotionComponents.map( + ({ import: { default: Component }, path }) => ( + + ) + )} ) } diff --git a/services/web/frontend/js/features/source-editor/components/source-editor.tsx b/services/web/frontend/js/features/source-editor/components/source-editor.tsx index 7466912998..8fdd2ba14b 100644 --- a/services/web/frontend/js/features/source-editor/components/source-editor.tsx +++ b/services/web/frontend/js/features/source-editor/components/source-editor.tsx @@ -5,9 +5,7 @@ import { ErrorBoundaryFallback } from '../../../shared/components/error-boundary import importOverleafModules from '../../../../macros/import-overleaf-module.macro' import GrammarlyAdvert from './grammarly-advert' -const writefullPromotion = importOverleafModules( - 'writefullEditorPromotion' -) as { +const editorPromotions = importOverleafModules('editorPromotions') as { import: { default: ElementType } path: string }[] @@ -20,7 +18,7 @@ const CodeMirrorEditor = lazy( function SourceEditor() { return ( }> - {writefullPromotion.map(({ import: { default: Component }, path }) => ( + {editorPromotions.map(({ import: { default: Component }, path }) => ( ))} diff --git a/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx b/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx index 5d98de682e..5b898fbe5f 100644 --- a/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx +++ b/services/web/frontend/js/shared/hooks/promotions/use-tutorial.tsx @@ -50,26 +50,35 @@ export const useTutorial = ( }, [completeTutorial]) // try to show the popup if we don't already have one showing, returns true if it can show, false if it can't - const tryShowingPopup = useCallback(() => { - if (currentPopup === null) { + const tryShowingPopup = useCallback( + (eventName: string = 'promo-prompt') => { + if (currentPopup === null) { + setCurrentPopup(tutorialKey) + setShowPopup(true) + eventTracking.sendMB(eventName, eventData) + return true + } + return false + }, + [currentPopup, setCurrentPopup, tutorialKey, eventData] + ) + + const clearPopup = useCallback(() => { + // popups should only clear themselves, in cases they need to cleanup or shouldnt show anymore + // allow forcing the clear if needed, eg: higher prio alert needs to show + if (currentPopup === tutorialKey) { + setCurrentPopup(null) + setShowPopup(false) + } + }, [setCurrentPopup, setShowPopup, currentPopup, tutorialKey]) + + const clearAndShow = useCallback( + (eventName: string = 'promo-prompt') => { setCurrentPopup(tutorialKey) setShowPopup(true) - eventTracking.sendMB('promo-prompt', eventData) - return true - } - return false - }, [currentPopup, setCurrentPopup, tutorialKey, eventData]) - - const clearPopup = useCallback( - (force: boolean = false) => { - // popups should only clear themselves, in cases they need to cleanup or shouldnt show anymore - // allow forcing the clear if needed, eg: higher prio alert needs to show - if (force || currentPopup === tutorialKey) { - setCurrentPopup(null) - setShowPopup(false) - } + eventTracking.sendMB(eventName, eventData) }, - [setCurrentPopup, setShowPopup, currentPopup, tutorialKey] + [setCurrentPopup, setShowPopup, tutorialKey, eventData] ) return { @@ -78,6 +87,7 @@ export const useTutorial = ( maybeLater, tryShowingPopup, clearPopup, + clearAndShow, showPopup, } } diff --git a/services/web/frontend/stylesheets/main-style.less b/services/web/frontend/stylesheets/main-style.less index e87af30da9..6426fc3db0 100644 --- a/services/web/frontend/stylesheets/main-style.less +++ b/services/web/frontend/stylesheets/main-style.less @@ -154,3 +154,4 @@ @import 'modules/group-settings.less'; @import 'modules/overleaf-integration.less'; @import 'modules/writefull.less'; +@import 'modules/third-party-references.less'; diff --git a/services/web/frontend/stylesheets/modules/third-party-references.less b/services/web/frontend/stylesheets/modules/third-party-references.less new file mode 100644 index 0000000000..ec44a7d8c6 --- /dev/null +++ b/services/web/frontend/stylesheets/modules/third-party-references.less @@ -0,0 +1,29 @@ +/* Animation keyframes */ +@keyframes slide-in { + 0% { + transform: translateY(25%); + opacity: 0; + bottom: 12px; + } + 100% { + transform: translateY(0); + opacity: 1; + bottom: 16px; + } +} + +.fade-slide-in { + opacity: 0; + transition: opacity 0.3s ease; /* Smooth opacity transition */ + animation: slide-in 0.3s ease-in-out forwards; + animation-delay: 300ms; +} + +.tpr-editor-prompt-container { + position: absolute; + z-index: 1; + bottom: 16px; + right: 16px; + width: 90%; + max-width: @popover-dark-max-width; +} diff --git a/services/web/locales/en.json b/services/web/locales/en.json index feb4073019..fdcf40d44b 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -77,6 +77,7 @@ "added": "added", "added_by_on": "Added by __name__ on __date__", "adding": "Adding", + "adding_a_bibliography": "Adding a bibliography?", "additional_certificate": "Additional certificate", "additional_licenses": "Your subscription includes <0>__additionalLicenses__ additional license(s) for a total of <1>__totalLicenses__ licenses.", "address": "Address", @@ -163,6 +164,7 @@ "beta_program_not_participating": "You are not enrolled in the Beta Program", "beta_program_opt_in_action": "Opt-In to Beta Program", "beta_program_opt_out_action": "Opt-Out of Beta Program", + "better_bibliographies": "Better bibliographies", "bibliographies": "Bibliographies", "binary_history_error": "Preview not available for this file type", "blank_project": "Blank Project", @@ -469,6 +471,7 @@ "duplicate_file": "Duplicate File", "duplicate_projects": "This user has projects with duplicate names", "each_user_will_have_access_to": "Each user will have access to", + "easily_import_and_sync_your_references": "Easily import and sync your references from Zotero or Mendeley when you upgrade your Overleaf plan.", "easily_manage_your_project_files_everywhere": "Easily manage your project files, everywhere", "edit": "Edit", "edit_dictionary": "Edit Dictionary", diff --git a/services/web/public/img/third-party-references/book-illlustration.svg b/services/web/public/img/third-party-references/book-illlustration.svg new file mode 100644 index 0000000000..e79278cf9a --- /dev/null +++ b/services/web/public/img/third-party-references/book-illlustration.svg @@ -0,0 +1,7 @@ + + + + + + +