Skip to content

Commit

Permalink
Onboarding Nudges and PDF opener button more obvious
Browse files Browse the repository at this point in the history
  • Loading branch information
blackforestboi committed Jun 7, 2024
1 parent 23f65e6 commit 7995f6f
Show file tree
Hide file tree
Showing 22 changed files with 947 additions and 15 deletions.
2 changes: 1 addition & 1 deletion external/@worldbrain/memex-common
1 change: 1 addition & 0 deletions src/annotations/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export default class DirectLinkingBackground {
comment: openToComment,
bookmark: openToBookmark,
list: openToCollections,
bookmarksNudge: false,
}
const actionPair = Object.entries(actions).findIndex((pair) => {
return pair[1]
Expand Down
16 changes: 16 additions & 0 deletions src/content-scripts/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
export const SIDEBAR_SEARCH_RESULT_LIMIT = 10

export const IGNORE_CLICK_OUTSIDE_CLASS = 'ignore-react-onclickoutside'

export const ONBOARDING_NUDGES_STORAGE = '@onboarding-nudges'

export const ONBOARDING_NUDGES_DEFAULT = {
enabled: true, // should the onboarding nudge be shown at all
bookmarksCount: 5, // how many times a page has been scrolled down
youtubeSummaryCount: 5, // how many times a youtube video has been opened
youtubeTimestampCount: 0, // how many times a youtube video has been opened
pageSummaryCount: 0, // how many times did the user encounter a long article worth summarising
}
export const ONBOARDING_NUDGES_MAX_COUNT = {
bookmarksCount: 5, // how many times a page has been scrolled down
youtubeSummaryCount: 5, // how many times a youtube video has been opened
youtubeTimestampCount: 0, // how many times a youtube video has been opened
pageSummaryCount: 0, // how many times did the user encounter a long article worth summarising
}
61 changes: 61 additions & 0 deletions src/content-scripts/content_script/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ import {
import type { RemoteSearchInterface } from 'src/search/background/types'
import * as anchoring from '@worldbrain/memex-common/lib/annotations'
import type { TaskState } from 'ui-logic-core/lib/types'
import debounce from 'lodash/debounce'
import { updateNudgesCounter } from 'src/util/nudges-utils'

// Content Scripts are separate bundles of javascript code that can be loaded
// on demand by the browser, as needed. This main function manages the initialisation
Expand Down Expand Up @@ -1533,6 +1535,38 @@ export async function main(
})
}

const embedElements = document.getElementsByTagName('embed')

if (embedElements.length > 0) {
inPageUI.loadOnDemandInPageUI({
component: 'pdf-open-button',
options: {
embedElements,
contentScriptsBG,
},
})
}
const imageElements = document.getElementsByTagName('img')

const disabledServices = [
'https://www.facebook.com/',
'https://www.instagram.com/',
'https://www.pinterest.com/',
]

if (
imageElements.length > 0 &&
!disabledServices.some((url) => fullPageUrl.includes(url))
) {
inPageUI.loadOnDemandInPageUI({
component: 'img-action-buttons',
options: {
imageElements,
contentScriptsBG,
},
})
}

// Function to track when the subscription has been updated by going to our website (which the user arrives through a redirect)
if (fullPageUrl === 'https://memex.garden/upgradeSuccessful') {
const h2Element = document.querySelector('h2')
Expand Down Expand Up @@ -1602,6 +1636,33 @@ export async function main(
}
}

// Function to track when to show the nudges
let tabOpenedTime = Date.now()

async function checkScrollPosition() {
const scrollPosition = window.scrollY
const pageHeight = document.documentElement.scrollHeight
const elapsedTime = Date.now() - tabOpenedTime

if (scrollPosition > 0.3 * pageHeight && elapsedTime > 1) {
const shouldShow = await updateNudgesCounter(
'bookmarksCount',
browser,
)
if (shouldShow) {
await inPageUI.showRibbon({ action: 'bookmarksNudge' })
}
}
}

const debouncedCheckScrollPosition = debounce(checkScrollPosition, 2000)

document.addEventListener('scroll', debouncedCheckScrollPosition)

function removeScrollListener() {
window.removeEventListener('scroll', checkScrollPosition)
}

if (analyticsBG && hasActivity) {
try {
await trackPageActivityIndicatorHit(analyticsBG)
Expand Down
21 changes: 21 additions & 0 deletions src/content-scripts/content_script/in-page-ui-injections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { renderErrorDisplay } from '../../search-injection/error-display'
import { renderSearchDisplay } from '../../search-injection/search-display'
import type { ContentScriptRegistry, InPageUIInjectionsMain } from './types'
import { renderUpgradeModal } from 'src/search-injection/upgrade-modal-display'
import { handleRenderPDFOpenButton } from 'src/search-injection/pdf-open-button'
import { handleRenderImgActionButtons } from 'src/search-injection/img-action-buttons'

export const main: InPageUIInjectionsMain = async ({
inPageUI,
Expand Down Expand Up @@ -36,8 +38,27 @@ export const main: InPageUIInjectionsMain = async ({
syncSettings,
syncSettingsBG,
annotationsFunctions,
upgradeModalProps.browserAPIs,
)
}
} else if (component === 'pdf-open-button') {
await handleRenderPDFOpenButton(
syncSettings,
syncSettingsBG,
annotationsFunctions,
upgradeModalProps.browserAPIs,
options.embedElements,
options.contentScriptsBG,
)
} else if (component === 'img-action-buttons') {
await handleRenderImgActionButtons(
syncSettings,
syncSettingsBG,
annotationsFunctions,
upgradeModalProps.browserAPIs,
options.imageElements,
options.contentScriptsBG,
)
} else if (component === 'search-engine-integration') {
const url = window.location.href
const matched = utils.matchURL(url)
Expand Down
1 change: 1 addition & 0 deletions src/content-scripts/content_script/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { SearchDisplayProps } from 'src/search-injection/search-display'
import type { RemoteSyncSettingsInterface } from 'src/sync-settings/background/types'
import type { SyncSettingsStore } from 'src/sync-settings/util'
import type { UpgradeModalProps } from 'src/search-injection/upgrade-modal-display'
import { Browser } from 'webextension-polyfill'

export interface ContentScriptRegistry {
registerRibbonScript(main: RibbonScriptMain): Promise<void>
Expand Down
64 changes: 63 additions & 1 deletion src/in-page-ui/ribbon/react/components/ribbon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ import { OverlayModals } from '@worldbrain/memex-common/lib/common-ui/components
import DeleteConfirmModal from 'src/overview/delete-confirm-modal/components/DeleteConfirmModal'
import { AnnotationsSidebarInPageEventEmitter } from 'src/sidebar/annotations-sidebar/types'
import { AuthenticatedUser } from '@worldbrain/memex-common/lib/authentication/types'
import { renderNudgeTooltip } from 'src/util/nudges-utils'

export interface Props extends RibbonSubcomponentProps {
currentUser: AuthenticatedUser
setRef?: (el: HTMLElement) => void
ribbonRef: React.RefObject<Ribbon>
isExpanded: boolean
theme: MemexThemeVariant
isRibbonEnabled: boolean
Expand All @@ -72,6 +74,8 @@ export interface Props extends RibbonSubcomponentProps {
toggleFeed: () => void
showFeed: boolean
toggleAskAI: (instaExecute: boolean) => void
showBookmarksNudge: boolean
setShowBookmarksNudge: (value: boolean, snooze: boolean) => void
toggleRabbitHole: () => void
toggleQuickSearch: () => void
openPDFinViewer: () => void
Expand Down Expand Up @@ -116,7 +120,7 @@ export default class Ribbon extends Component<Props, State> {
private annotationCreateRef // TODO: Figure out how to properly type refs to onClickOutside HOCs

private spacePickerRef = createRef<HTMLDivElement>()
private bookmarkButtonRef = createRef<HTMLDivElement>()
private memexLogoRef = createRef<HTMLDivElement>()

private tutorialButtonRef = createRef<HTMLDivElement>()
private feedButtonRef = createRef<HTMLDivElement>()
Expand Down Expand Up @@ -280,6 +284,45 @@ export default class Ribbon extends Component<Props, State> {
)
}

private getHotKey(
name: string,
size: 'small' | 'medium',
): JSX.Element | string {
const elData = this.shortcutsData.get(name)
const short: Shortcut = this.keyboardShortcuts[name]

if (!elData) {
return null
}

let source = elData.tooltip

if (['createBookmark'].includes(name)) {
source = this.props.bookmark.isBookmarked
? elData.toggleOff
: elData.toggleOn
}
if (['toggleSidebar'].includes(name)) {
source = this.props.sidebar.isSidebarOpen
? elData.toggleOff
: elData.toggleOn
}

return short.shortcut && short.enabled ? (
<TooltipContent>
{
<KeyboardShortcuts
size={size ?? 'small'}
keys={short.shortcut.split('+')}
getRootElement={this.props.getRootElement}
/>
}
</TooltipContent>
) : (
source
)
}

private hideListPicker = () => {
this.props.lists.setShowListsPicker(false)
}
Expand Down Expand Up @@ -909,6 +952,22 @@ export default class Ribbon extends Component<Props, State> {
)
}

renderBookmarksNudge = () => {
if (this.props.showBookmarksNudge) {
return renderNudgeTooltip(
'Looks like an article worth saving!',
'Hover over the brain icon or use hotkeys to save with Memex.',
this.getHotKey('createBookmark', 'medium'),
'450px',
() => this.props.setShowBookmarksNudge(false, false), // hide forever
() => this.props.setShowBookmarksNudge(false, true), // snooze
this.props.getRootElement,
this.memexLogoRef.current,
'top-end',
)
}
}

renderFeedButton() {
const topRight = this.props.ribbonPosition === 'topRight'
const bottomRight = this.props.ribbonPosition === 'bottomRight'
Expand Down Expand Up @@ -1987,6 +2046,8 @@ export default class Ribbon extends Component<Props, State> {
ribbonPosition={this.props.ribbonPosition}
isYoutube={isYoutube}
>
{this.renderBookmarksNudge()}

{this.props.hasFeedActivity && (
<FeedActivityDotBox>
<FeedActivityDot
Expand Down Expand Up @@ -2020,6 +2081,7 @@ export default class Ribbon extends Component<Props, State> {
? 'greyScale7'
: 'greyScale5'
}
containerRef={this.memexLogoRef}
/>
)}
</IconContainer>
Expand Down
1 change: 0 additions & 1 deletion src/in-page-ui/ribbon/react/components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export interface RibbonSubcomponentProps {
contentSharingBG: ContentSharingInterface
bgScriptBG: RemoteBGScriptInterface<'caller'>
onListShare?: SpacePickerDependencies['onListShare']
selectRibbonPositionOption: (option) => void
hasFeedActivity: boolean
showConfirmDeletion: boolean
}
Expand Down
16 changes: 15 additions & 1 deletion src/in-page-ui/ribbon/react/containers/ribbon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface RibbonContainerProps extends RibbonContainerOptions {
analyticsBG: AnalyticsCoreInterface
theme: MemexThemeVariant
browserAPIs: Browser
// setVisibility: (visibility: boolean) => void
}

export default class RibbonContainer extends StatefulUIElement<
Expand Down Expand Up @@ -137,6 +138,12 @@ export default class RibbonContainer extends StatefulUIElement<
this.props.setRibbonShouldAutoHide(true)
} else if (event.action === 'list') {
this.processEvent('setShowListsPicker', { value: true })
} else if (event.action === 'bookmarksNudge') {
this.props.inPageUI.hideRibbon()
this.processEvent('setShowBookmarksNudge', {
value: true,
snooze: null,
})
}
}

Expand Down Expand Up @@ -167,7 +174,7 @@ export default class RibbonContainer extends StatefulUIElement<
this.state.themeVariant || this.props.theme,
})
}}
ref={this.ribbonRef}
ribbonRef={this.ribbonRef}
setRef={this.props.setRef}
getListDetailsById={(id) => {
const listDetails = this.props.annotationsCache.getListByLocalId(
Expand All @@ -191,6 +198,13 @@ export default class RibbonContainer extends StatefulUIElement<
!this.state.showRemoveMenu,
)
}}
showBookmarksNudge={this.state.showBookmarksNudge}
setShowBookmarksNudge={(value, snooze) => {
this.processEvent('setShowBookmarksNudge', {
value,
snooze,
})
}}
toggleAskAI={(instaExecute: boolean) => {
this.processEvent('toggleAskAI', instaExecute)
}}
Expand Down
24 changes: 24 additions & 0 deletions src/in-page-ui/ribbon/react/containers/ribbon/logic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { getTelegramUserDisplayName } from '@worldbrain/memex-common/lib/telegra
import type { AnalyticsCoreInterface } from '@worldbrain/memex-common/lib/analytics/types'
import type { MemexThemeVariant } from '@worldbrain/memex-common/lib/common-ui/styles/types'
import { AuthenticatedUser } from '@worldbrain/memex-common/lib/authentication/types'
import { disableNudgeType } from 'src/util/nudges-utils'

export type PropKeys<Base, ValueCondition> = keyof Pick<
Base,
Expand Down Expand Up @@ -67,6 +68,7 @@ export interface RibbonContainerState {
annotations: number
search: ValuesOf<componentTypes.RibbonSearchProps>
pausing: ValuesOf<componentTypes.RibbonPausingProps>
showBookmarksNudge: boolean
hasFeedActivity: boolean
isTrial: boolean
signupDate: number
Expand All @@ -88,6 +90,7 @@ export type RibbonContainerEvents = UIEvent<
setTutorialId: { tutorialIdToOpen: string }
toggleShowTutorial: null
toggleFeed: null
setShowBookmarksNudge: { value: boolean; snooze: boolean }
toggleReadingView: null
toggleAskAI: boolean | null
deletePage: null
Expand Down Expand Up @@ -198,6 +201,7 @@ export class RibbonContainerLogic extends UILogic<
themeVariant: null,
showRabbitHoleButton: false,
showConfirmDeletion: false,
showBookmarksNudge: false,
}
}

Expand Down Expand Up @@ -960,6 +964,26 @@ export class RibbonContainerLogic extends UILogic<
listId: event.value,
})
}
setShowBookmarksNudge: EventHandler<'setShowBookmarksNudge'> = async ({
event,
}) => {
if (event.value) {
this.dependencies.setRibbonShouldAutoHide(false)
} else {
this.dependencies.setRibbonShouldAutoHide(true)
}

this.emitMutation({
showBookmarksNudge: { $set: event.value },
})

if (!event.snooze) {
await disableNudgeType(
'bookmarksCount',
this.dependencies.browserAPIs,
)
}
}

setShowListsPicker: EventHandler<'setShowListsPicker'> = async ({
event,
Expand Down
7 changes: 6 additions & 1 deletion src/in-page-ui/shared-state/shared-in-page-ui-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,12 @@ export class SharedInPageUIState implements SharedInPageUIInterface {
return
}

await this._setState('ribbon', true)
if (options?.action === 'bookmarksNudge') {
await this._setState('ribbon', false)
} else {
await this._setState('ribbon', true)
}

maybeEmitAction()
}

Expand Down
Loading

0 comments on commit 7995f6f

Please sign in to comment.