Merge pull request #16156 from overleaf/jdt-writefull-notif

Writefull editor notification

GitOrigin-RevId: 1a5077164682dbec67738af0684d364571802816
This commit is contained in:
Eric Mc Sween 2023-12-13 07:31:57 -05:00 committed by Copybot
parent 771f07d7ad
commit a9d7f99446
14 changed files with 77 additions and 23 deletions

View file

@ -851,6 +851,7 @@ module.exports = {
sourceEditorComponents: [],
sourceEditorCompletionSources: [],
sourceEditorSymbolPalette: [],
writefullEditorPromotion: [],
langFeedbackLinkingWidgets: [],
integrationLinkingWidgets: [],
referenceLinkingWidgets: [],

View file

@ -1448,8 +1448,11 @@
"work_offline": "",
"work_with_non_overleaf_users": "",
"writefull": "",
"writefull_how_to": "",
"writefull_learn_more": "",
"writefull_prompt_body": "",
"writefull_prompt_title": "",
"writefull_settings_description": "",
"writefull_settings_instructions": "",
"x_changes_in": "",
"x_changes_in_plural": "",
"x_price_for_first_month": "",

View file

@ -12,6 +12,7 @@ type EnableWidgetProps = {
title: string
description: string
helpPath: string
helpTextOverride?: string
hasFeature?: boolean
isPremiumFeature?: boolean
statusIndicator?: ReactNode
@ -27,6 +28,7 @@ export function EnableWidget({
title,
description,
helpPath,
helpTextOverride,
hasFeature,
isPremiumFeature,
statusIndicator,
@ -37,6 +39,7 @@ export function EnableWidget({
disabled,
}: EnableWidgetProps) {
const { t } = useTranslation()
const helpText = helpTextOverride || t('learn_more')
return (
<div className="settings-widget-container">
@ -51,7 +54,7 @@ export function EnableWidget({
<p className="small">
{description}{' '}
<a href={helpPath} target="_blank" rel="noreferrer">
{t('learn_more')}
{helpText}
</a>
</p>
{children}

View file

@ -2,19 +2,12 @@ import { useCallback, useEffect, useState } from 'react'
import MaterialIcon from '@/shared/components/material-icon'
import * as eventTracking from '../../../infrastructure/event-tracking'
import customLocalStorage from '../../../infrastructure/local-storage'
import grammarlyExtensionPresent from '../../../shared/utils/grammarly'
import useWaitForGrammarlyCheck from '@/shared/hooks/use-wait-for-grammarly-check'
export default function GrammarlyAdvert() {
const [show, setShow] = useState(false)
// grammarly can take some time to load, and wont tell us when they do... so we need to run the check after a bit of time
const [grammarlyInstalled, setGrammarlyInstalled] = useState(false)
useEffect(() => {
const timer = setTimeout(
() => setGrammarlyInstalled(grammarlyExtensionPresent()),
5000
)
return () => clearTimeout(timer)
}, [])
// grammarly can take some time to load, we should assume its installed and hide until we know for sure
const grammarlyInstalled = useWaitForGrammarlyCheck({ initialState: false })
useEffect(() => {
const hasDismissedGrammarlyAdvert = customLocalStorage.getItem(

View file

@ -1,7 +1,15 @@
import { lazy, memo, Suspense } from 'react'
import { lazy, memo, Suspense, ElementType } from 'react'
import { FullSizeLoadingSpinner } from '../../../shared/components/loading-spinner'
import withErrorBoundary from '../../../infrastructure/error-boundary'
import { ErrorBoundaryFallback } from '../../../shared/components/error-boundary-fallback'
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
const writefullPromotion = importOverleafModules(
'writefullEditorPromotion'
) as {
import: { default: ElementType }
path: string
}[]
const CodeMirrorEditor = lazy(
() =>
@ -11,6 +19,9 @@ const CodeMirrorEditor = lazy(
function SourceEditor() {
return (
<Suspense fallback={<FullSizeLoadingSpinner delay={500} />}>
{writefullPromotion.map(({ import: { default: Component }, path }) => (
<Component key={path} />
))}
<CodeMirrorEditor />
</Suspense>
)

View file

@ -19,6 +19,7 @@ import { useSplitTestContext } from '../../../../../shared/context/split-test-co
import { User } from '../../../../../../../types/user'
import { useUserContext } from '../../../../../shared/context/user-context'
import grammarlyExtensionPresent from '../../../../../shared/utils/grammarly'
import { EditorTutorials } from '../../../../../../../types/tutorial'
import { debugConsole } from '../../../../../utils/debugging'
const DELAY_BEFORE_SHOWING_PROMOTION = 1000
@ -26,11 +27,6 @@ const NEW_USER_CUTOFF_TIME = new Date(2023, 8, 20).getTime()
const NOW_TIME = new Date().getTime()
const GRAMMARLY_CUTOFF_TIME = new Date(2023, 9, 10).getTime()
type EditorTutorials = {
inactiveTutorials: [string]
deactivateTutorial: (key: string) => void
}
const editorContextPropTypes = {
inactiveTutorials: PropTypes.arrayOf(PropTypes.string).isRequired,
deactivateTutorial: PropTypes.func.isRequired,

View file

@ -0,0 +1,29 @@
import { useEffect, useState } from 'react'
/**
*
* @param {number} delay how long to wait before checking for grammarly in ms
* @param {boolean} initialState the initial state we should set grammarlyInstalled to before checking after the delay
* @returns {boolean} a stateful boolean which is initially false, then updates to reflect whether grammarly is installed after the delay to check
*/
export default function useWaitForGrammarlyCheck({
delay = 3000,
initialState = false,
}) {
const [grammarlyInstalled, setGrammarlyInstalled] = useState(() => {
return initialState
})
useEffect(() => {
const timer = setTimeout(
() => setGrammarlyInstalled(grammarlyExtensionPresent()),
delay
)
return () => clearTimeout(timer)
}, [delay])
return grammarlyInstalled
}
function grammarlyExtensionPresent() {
return !!document.querySelector('grammarly-desktop-integration')
}

View file

@ -1,10 +1,10 @@
function WritefullLogo({ width = '40', height = '40' }) {
function WritefullLogo({ width = '40', height = '40', background = 'none' }) {
return (
<svg
width={width}
height={height}
viewBox="0 0 40 40"
fill="none"
fill={background}
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
style={{ verticalAlign: 'middle' }}

View file

@ -752,3 +752,9 @@ CodeMirror
grammarly-extension[data-grammarly-shadow-root='true'] {
z-index: 1;
}
.writefull-notification {
margin: 48px 64px;
width: 80%;
max-width: 560px;
}

View file

@ -149,3 +149,4 @@
@import 'modules/git-bridge-modal.less';
@import 'modules/group-settings.less';
@import 'modules/overleaf-integration.less';
@import 'modules/writefull.less';

View file

@ -0,0 +1,3 @@
.writefull-logo-bg {
background-color: #fff;
}

View file

@ -2105,8 +2105,11 @@
"work_with_word_users_blurb": "__appName__ is so easy to get started with that youll be able to invite your non-LaTeX colleagues to contribute directly to your LaTeX documents. Theyll be productive from day one and be able to pick up small amounts of LaTeX as they go.",
"would_you_like_to_see_a_university_subscription": "Would you like to see a university-wide __appName__ subscription at your university?",
"writefull": "Writefull",
"writefull_how_to": "[COPY PLACEHOLDER] to use Writefull, select some text and press the shortcut key. a toolbar will appear with the results.",
"writefull_settings_description": "Writefull is a new feature that helps you improve your writing by highlighting common errors and suggesting improvements.",
"writefull_learn_more": "Learn more about Writefull for Overleaf",
"writefull_prompt_body": "Turn on the new Writefull integration to get AI-powered language feedback specifically tailored to research writing.",
"writefull_prompt_title": "AI writing assistance in Overleaf",
"writefull_settings_description": "Get free AI-based language feedback specifically tailored for research writing with Writefull for Overleaf. Plus, if you upgrade to Writefull Premium you can use TeXGPT to generate LaTeX code—use OVERLEAF10 at the checkout to get 10% off.",
"writefull_settings_instructions": "To use Writefull, log in from the Writefull toolbar in the Overleaf editor. And dont forget, if you upgrade to Writefull Premium you can use TeXGPT to generate LaTeX code—use OVERLEAF10 at the checkout to get 10% off.",
"x_changes_in": "__count__ change in",
"x_changes_in_plural": "__count__ changes in",
"x_collaborators_per_project": "__collaboratorsCount__ collaborators per project",

View file

@ -0,0 +1,5 @@
// todo: maybe change this to just tutorials, and move it from editor context to user context?
export type EditorTutorials = {
inactiveTutorials: [string]
deactivateTutorial: (key: string) => void
}

View file

@ -42,6 +42,6 @@ declare global {
useRecaptchaNet?: boolean
}
expectingLinkedFileRefreshedSocketFor?: string | null
writefull?: Map<string, any>
writefull?: any
}
}