From d70e0b1c0edd9711474dc3652a592a25bbbea74e Mon Sep 17 00:00:00 2001 From: M Fahru Date: Tue, 15 Nov 2022 13:10:05 -0500 Subject: [PATCH] Implement grammarly warning alert on cm6 users GitOrigin-RevId: ed272bbc385faa69811ec1891075906cdca1c984 --- services/web/app/views/project/editor.pug | 2 + .../components/grammarly-warning.tsx | 65 +++++++ .../grammarly-warning-controller.js | 9 + services/web/frontend/js/ide.js | 1 + .../web/frontend/stylesheets/app/editor.less | 29 +++ .../components/grammarly-warning.test.js | 184 ++++++++++++++++++ 6 files changed, 290 insertions(+) create mode 100644 services/web/frontend/js/features/source-editor/components/grammarly-warning.tsx create mode 100644 services/web/frontend/js/features/source-editor/controllers/grammarly-warning-controller.js create mode 100644 services/web/test/frontend/features/source-editor/components/grammarly-warning.test.js diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug index 7f648aba3b..d13e60f6f0 100644 --- a/services/web/app/views/project/editor.pug +++ b/services/web/app/views/project/editor.pug @@ -65,6 +65,8 @@ block content span.sr-only #{translate("close")} .system-message-content(ng-bind-html="htmlContent") + grammarly-warning() + include ./editor/main script(type="text/ng-template", id="genericMessageModalTemplate") diff --git a/services/web/frontend/js/features/source-editor/components/grammarly-warning.tsx b/services/web/frontend/js/features/source-editor/components/grammarly-warning.tsx new file mode 100644 index 0000000000..4d1ca92121 --- /dev/null +++ b/services/web/frontend/js/features/source-editor/components/grammarly-warning.tsx @@ -0,0 +1,65 @@ +import { useCallback, useEffect, useState } from 'react' +import { Button } from 'react-bootstrap' +import customLocalStorage from '../../../infrastructure/local-storage' +import useScopeValue from '../../../shared/hooks/use-scope-value' +import grammarlyExtensionPresent from '../../../shared/utils/grammarly' +import getMeta from '../../../utils/meta' + +export default function GrammarlyWarning() { + const [show, setShow] = useState(false) + const [newSourceEditor] = useScopeValue('editor.newSourceEditor') + const [showRichText] = useScopeValue('editor.showRichText') + const grammarly = grammarlyExtensionPresent() + const hasDismissedGrammarlyWarning = customLocalStorage.getItem( + 'editor.has_dismissed_grammarly_warning' + ) + + useEffect(() => { + const showGrammarlyWarning = + !hasDismissedGrammarlyWarning && + grammarly && + newSourceEditor && + !showRichText + + if (showGrammarlyWarning) { + setShow(true) + } + }, [grammarly, hasDismissedGrammarlyWarning, newSourceEditor, showRichText]) + + const handleClose = useCallback(() => { + setShow(false) + customLocalStorage.setItem('editor.has_dismissed_grammarly_warning', true) + }, []) + + if (!getMeta('ol-showNewSourceEditorOption')) { + return null + } + + if (!show) { + return null + } + + return ( +
+ +
+ A browser extension, for example Grammarly, may be slowing down + Overleaf.{' '} + + Find out how to avoid this + +
+
+ ) +} diff --git a/services/web/frontend/js/features/source-editor/controllers/grammarly-warning-controller.js b/services/web/frontend/js/features/source-editor/controllers/grammarly-warning-controller.js new file mode 100644 index 0000000000..e1a9679394 --- /dev/null +++ b/services/web/frontend/js/features/source-editor/controllers/grammarly-warning-controller.js @@ -0,0 +1,9 @@ +import App from '../../../base' +import { react2angular } from 'react2angular' +import { rootContext } from '../../../shared/context/root-context' +import GrammarlyWarning from '../components/grammarly-warning' + +App.component( + 'grammarlyWarning', + react2angular(rootContext.use(GrammarlyWarning)) +) diff --git a/services/web/frontend/js/ide.js b/services/web/frontend/js/ide.js index b55b2d38de..aebac4eb93 100644 --- a/services/web/frontend/js/ide.js +++ b/services/web/frontend/js/ide.js @@ -65,6 +65,7 @@ import './features/pdf-preview/controllers/pdf-preview-controller' import './features/share-project-modal/controllers/react-share-project-modal-controller' import './features/source-editor/controllers/editor-switch-controller' import './features/source-editor/controllers/cm6-switch-away-survey-controller' +import './features/source-editor/controllers/grammarly-warning-controller' import { cleanupServiceWorker } from './utils/service-worker-cleanup' import { reportCM6Perf } from './infrastructure/cm6-performance' diff --git a/services/web/frontend/stylesheets/app/editor.less b/services/web/frontend/stylesheets/app/editor.less index 2a296df545..784cc0f18c 100644 --- a/services/web/frontend/stylesheets/app/editor.less +++ b/services/web/frontend/stylesheets/app/editor.less @@ -827,3 +827,32 @@ CodeMirror font-size: @font-size-small; } } + +.grammarly-warning { + width: 500px; + + &.alert.alert-info { + padding: @padding-sm; + } + + .btn.close { + background-color: transparent; + color: @white; + opacity: 1; + } + + .warning-content { + padding-right: @alert-padding; + font-size: @font-size-small; + margin-right: 32px; + + .warning-link { + font-weight: 700; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } +} diff --git a/services/web/test/frontend/features/source-editor/components/grammarly-warning.test.js b/services/web/test/frontend/features/source-editor/components/grammarly-warning.test.js new file mode 100644 index 0000000000..f1b5c1cc1e --- /dev/null +++ b/services/web/test/frontend/features/source-editor/components/grammarly-warning.test.js @@ -0,0 +1,184 @@ +import sinon from 'sinon' +import fetchMock from 'fetch-mock' +import { expect } from 'chai' +import { fireEvent, screen, waitFor } from '@testing-library/react' +import { renderWithEditorContext } from '../../../helpers/render-with-context' +import GrammarlyWarning from '../../../../../frontend/js/features/source-editor/components/grammarly-warning' +import * as grammarlyModule from '../../../../../frontend/js/shared/utils/grammarly' +import localStorage from '../../../../../frontend/js/infrastructure/local-storage' + +describe('', function () { + let grammarlyStub + + before(function () { + window.metaAttributesCache = new Map() + }) + + beforeEach(function () { + grammarlyStub = sinon.stub(grammarlyModule, 'default') + }) + + afterEach(function () { + window.metaAttributesCache = new Map() + grammarlyStub.restore() + fetchMock.reset() + localStorage.clear() + }) + + it('shows warning when grammarly is available', async function () { + grammarlyStub.returns(true) + window.metaAttributesCache.set('ol-showNewSourceEditorOption', true) + + renderWithEditorContext(, { + scope: { + editor: { + newSourceEditor: true, + }, + }, + }) + + await screen.findByText( + 'A browser extension, for example Grammarly, may be slowing down Overleaf.' + ) + await screen.findByRole('button', { name: 'Close' }) + await screen.findByRole('link', { name: 'Find out how to avoid this' }) + }) + + it('does not show warning when grammarly is not available', async function () { + grammarlyStub.returns(false) + window.metaAttributesCache.set('ol-showNewSourceEditorOption', true) + + renderWithEditorContext(, { + scope: { + editor: { + newSourceEditor: true, + }, + }, + }) + + await waitFor(() => { + expect( + screen.queryByText( + 'A browser extension, for example Grammarly, may be slowing down Overleaf.' + ) + ).to.not.exist + }) + }) + + it('does not show warning when user has dismissed the warning', async function () { + grammarlyStub.returns(true) + localStorage.setItem('editor.has_dismissed_grammarly_warning', true) + window.metaAttributesCache.set('ol-showNewSourceEditorOption', true) + + renderWithEditorContext(, { + scope: { + editor: { + newSourceEditor: true, + }, + }, + }) + + await waitFor(() => { + expect( + screen.queryByText( + 'A browser extension, for example Grammarly, may be slowing down Overleaf.' + ) + ).to.not.exist + }) + }) + + it('does not show warning when user does not have CM6', async function () { + grammarlyStub.returns(true) + window.metaAttributesCache.set('ol-showNewSourceEditorOption', false) + + renderWithEditorContext() + + await waitFor(() => { + expect( + screen.queryByText( + 'A browser extension, for example Grammarly, may be slowing down Overleaf.' + ) + ).to.not.exist + }) + }) + + it('does not show warning when user have ace as their preference', async function () { + grammarlyStub.returns(true) + window.metaAttributesCache.set('ol-showNewSourceEditorOption', true) + + renderWithEditorContext(, { + scope: { + editor: { + newSourceEditor: false, + }, + }, + }) + + await waitFor(() => { + expect( + screen.queryByText( + 'A browser extension, for example Grammarly, may be slowing down Overleaf.' + ) + ).to.not.exist + }) + }) + + it('does not show warning when user have rich text as their preference', async function () { + grammarlyStub.returns(true) + window.metaAttributesCache.set('ol-showNewSourceEditorOption', true) + + renderWithEditorContext(, { + scope: { + editor: { + newSourceEditor: true, + showRichText: true, + }, + }, + }) + + await waitFor(() => { + expect( + screen.queryByText( + 'A browser extension, for example Grammarly, may be slowing down Overleaf.' + ) + ).to.not.exist + }) + }) + + it('hides warning if close button is pressed', async function () { + grammarlyStub.returns(true) + window.metaAttributesCache.set('ol-showNewSourceEditorOption', true) + + renderWithEditorContext(, { + scope: { + editor: { + newSourceEditor: true, + }, + }, + }) + + const warningText = + 'A browser extension, for example Grammarly, may be slowing down Overleaf.' + + await screen.findByText(warningText) + + const hasDismissedGrammarlyWarning = localStorage.getItem( + 'editor.has_dismissed_grammarly_warning' + ) + + expect(hasDismissedGrammarlyWarning).to.equal(null) + + const closeButton = screen.getByRole('button', { name: 'Close' }) + fireEvent.click(closeButton) + + expect(screen.queryByText(warningText)).to.not.exist + + await waitFor(() => { + const hasDismissedGrammarlyWarning = localStorage.getItem( + 'editor.has_dismissed_grammarly_warning' + ) + + expect(hasDismissedGrammarlyWarning).to.equal(true) + }) + }) +})