mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
Merge pull request #23597 from overleaf/dp-integrations-panel
Add integration panel to new editor GitOrigin-RevId: 85e038c645e40d0ea596ed35d31448caa232e298
This commit is contained in:
@@ -983,6 +983,7 @@ module.exports = {
|
||||
toastGenerators: [],
|
||||
editorSidebarComponents: [],
|
||||
fileTreeToolbarComponents: [],
|
||||
integrationPanelComponents: [],
|
||||
},
|
||||
|
||||
moduleImportSequence: [
|
||||
|
||||
@@ -301,6 +301,7 @@
|
||||
"confirming": "",
|
||||
"conflicting_paths_found": "",
|
||||
"congratulations_youve_successfully_join_group": "",
|
||||
"connect_overleaf_with_github": "",
|
||||
"connected_users": "",
|
||||
"connection_lost": "",
|
||||
"contact_group_admin": "",
|
||||
@@ -423,6 +424,7 @@
|
||||
"draft_sso_configuration": "",
|
||||
"drag_here": "",
|
||||
"drag_here_paste_an_image_or": "",
|
||||
"dropbox": "",
|
||||
"dropbox_checking_sync_status": "",
|
||||
"dropbox_duplicate_project_names": "",
|
||||
"dropbox_duplicate_project_names_suggestion": "",
|
||||
@@ -615,6 +617,7 @@
|
||||
"git_bridge_modal_use_previous_token": "",
|
||||
"git_integration": "",
|
||||
"git_integration_info": "",
|
||||
"github": "",
|
||||
"github_commit_message_placeholder": "",
|
||||
"github_credentials_expired": "",
|
||||
"github_empty_repository_error": "",
|
||||
@@ -791,6 +794,7 @@
|
||||
"institution_has_overleaf_subscription": "",
|
||||
"institution_templates": "",
|
||||
"institutional_leavers_survey_notification": "",
|
||||
"integrate_overleaf_with_dropbox": "",
|
||||
"integrations": "",
|
||||
"integrations_like_github": "",
|
||||
"interested_in_cheaper_personal_plan": "",
|
||||
@@ -888,6 +892,7 @@
|
||||
"link_accounts": "",
|
||||
"link_accounts_and_add_email": "",
|
||||
"link_institutional_email_get_started": "",
|
||||
"link_overleaf_with_git": "",
|
||||
"link_sharing": "",
|
||||
"link_sharing_is_off_short": "",
|
||||
"link_sharing_is_on": "",
|
||||
@@ -1191,6 +1196,7 @@
|
||||
"plus_x_additional_licenses_for_a_total_of_y_users": "",
|
||||
"postal_code": "",
|
||||
"postal_code_sentence_case": "",
|
||||
"premium": "",
|
||||
"premium_feature": "",
|
||||
"premium_features": "",
|
||||
"premium_plan_label": "",
|
||||
|
||||
@@ -17,7 +17,7 @@ import { OnlineUsersProvider } from '@/features/ide-react/context/online-users-c
|
||||
import { OutlineProvider } from '@/features/ide-react/context/outline-context'
|
||||
import { PermissionsProvider } from '@/features/ide-react/context/permissions-context'
|
||||
import { ProjectProvider } from '@/shared/context/project-context'
|
||||
import { RailTabProvider } from '@/features/ide-redesign/contexts/rail-tab-context'
|
||||
import { RailProvider } from '@/features/ide-redesign/contexts/rail-context'
|
||||
import { ProjectSettingsProvider } from '@/features/editor-left-menu/context/project-settings-context'
|
||||
import { ReferencesProvider } from '@/features/ide-react/context/references-context'
|
||||
import { SnapshotProvider } from '@/features/ide-react/context/snapshot-context'
|
||||
@@ -49,7 +49,7 @@ export const ReactContextRoot: FC<{ providers?: Record<string, FC> }> = ({
|
||||
PermissionsProvider,
|
||||
ProjectProvider,
|
||||
ProjectSettingsProvider,
|
||||
RailTabProvider,
|
||||
RailProvider,
|
||||
ReferencesProvider,
|
||||
SnapshotProvider,
|
||||
SplitTestProvider,
|
||||
@@ -83,9 +83,9 @@ export const ReactContextRoot: FC<{ providers?: Record<string, FC> }> = ({
|
||||
<Providers.OnlineUsersProvider>
|
||||
<Providers.MetadataProvider>
|
||||
<Providers.OutlineProvider>
|
||||
<Providers.RailTabProvider>
|
||||
<Providers.RailProvider>
|
||||
{children}
|
||||
</Providers.RailTabProvider>
|
||||
</Providers.RailProvider>
|
||||
</Providers.OutlineProvider>
|
||||
</Providers.MetadataProvider>
|
||||
</Providers.OnlineUsersProvider>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import OLBadge from '@/features/ui/components/ol/ol-badge'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function IntegrationCard({
|
||||
onClick,
|
||||
title,
|
||||
description,
|
||||
icon,
|
||||
showPaywallBadge,
|
||||
}: {
|
||||
onClick: () => void
|
||||
title: string
|
||||
description: string
|
||||
icon: React.ReactNode
|
||||
showPaywallBadge: boolean
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<button onClick={onClick} className="integrations-panel-card-button">
|
||||
<div className="integrations-panel-card-contents">
|
||||
{icon}
|
||||
<div className="integrations-panel-card-inner">
|
||||
<header className="integrations-panel-card-header">
|
||||
<div className="integrations-panel-card-title">{title}</div>
|
||||
{showPaywallBadge && (
|
||||
<OLBadge
|
||||
prepend={<MaterialIcon type="star" />}
|
||||
bg="light"
|
||||
className="integrations-panel-card-premium-badge"
|
||||
>
|
||||
{t('premium')}
|
||||
</OLBadge>
|
||||
)}
|
||||
</header>
|
||||
<p className="integrations-panel-card-description">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ElementType } from 'react'
|
||||
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import OlButton from '@/features/ui/components/ol/ol-button'
|
||||
import { useRailContext } from '../../contexts/rail-context'
|
||||
|
||||
const integrationPanelComponents = importOverleafModules(
|
||||
'integrationPanelComponents'
|
||||
) as { import: { default: ElementType }; path: string }[]
|
||||
|
||||
export default function IntegrationsPanel() {
|
||||
const { handlePaneCollapse } = useRailContext()
|
||||
|
||||
return (
|
||||
<div className="integrations-panel">
|
||||
<header className="integrations-panel-header">
|
||||
<h4 className="integrations-panel-title">Integrations</h4>
|
||||
<OlButton onClick={handlePaneCollapse} variant="ghost" size="sm">
|
||||
<MaterialIcon type="close" />
|
||||
</OlButton>
|
||||
</header>
|
||||
{integrationPanelComponents.map(
|
||||
({ import: { default: Component }, path }) => (
|
||||
<Component key={path} />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useRailTabContext } from '../../contexts/rail-tab-context'
|
||||
import { useRailContext } from '../../contexts/rail-context'
|
||||
import { usePdfPreviewContext } from '@/features/pdf-preview/components/pdf-preview-provider'
|
||||
import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context'
|
||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
@@ -11,7 +11,7 @@ function PdfErrorState() {
|
||||
// TODO ide-redesign-cleanup: rename showLogs to something else and check usages
|
||||
const { showLogs } = useCompileContext()
|
||||
const { t } = useTranslation()
|
||||
const { setSelectedTab: setSelectedRailTab } = useRailTabContext()
|
||||
const { setSelectedTab: setSelectedRailTab } = useRailContext()
|
||||
const newEditor = useFeatureFlag('editor-redesign')
|
||||
|
||||
if (!newEditor || (!loadingError && !showLogs)) {
|
||||
|
||||
@@ -6,15 +6,15 @@ import MaterialIcon, {
|
||||
import { Panel } from 'react-resizable-panels'
|
||||
import { useLayoutContext } from '@/shared/context/layout-context'
|
||||
import { ErrorIndicator, ErrorPane } from './errors'
|
||||
import { RailTabKey, useRailTabContext } from '../contexts/rail-tab-context'
|
||||
import { RailTabKey, useRailContext } from '../contexts/rail-context'
|
||||
import FileTreeOutlinePanel from './file-tree-outline-panel'
|
||||
import { ChatIndicator, ChatPane } from './chat'
|
||||
import getMeta from '@/utils/meta'
|
||||
import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle'
|
||||
import { HorizontalToggler } from '@/features/ide-react/components/resize/horizontal-toggler'
|
||||
import { useRail } from '../hooks/use-rail'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import classNames from 'classnames'
|
||||
import IntegrationsPanel from './integrations-panel/integrations-panel'
|
||||
|
||||
type RailElement = {
|
||||
icon: AvailableUnfilledIcon
|
||||
@@ -41,7 +41,7 @@ const RAIL_TABS: RailElement[] = [
|
||||
{
|
||||
key: 'integrations',
|
||||
icon: 'integration_instructions',
|
||||
component: <>Integrations</>,
|
||||
component: <IntegrationsPanel />,
|
||||
},
|
||||
{
|
||||
key: 'review-panel',
|
||||
@@ -66,14 +66,15 @@ const RAIL_TABS: RailElement[] = [
|
||||
export const RailLayout = () => {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
selectedTab,
|
||||
setSelectedTab,
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
panelRef,
|
||||
handlePaneCollapse,
|
||||
handlePaneExpand,
|
||||
togglePane,
|
||||
} = useRail()
|
||||
const { selectedTab, setSelectedTab } = useRailTabContext()
|
||||
} = useRailContext()
|
||||
|
||||
const { setLeftMenuShown } = useLayoutContext()
|
||||
const railActions: RailAction[] = useMemo(
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
|
||||
import {
|
||||
createContext,
|
||||
Dispatch,
|
||||
FC,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { ImperativePanelHandle } from 'react-resizable-panels'
|
||||
|
||||
export type RailTabKey =
|
||||
| 'file-tree'
|
||||
| 'integrations'
|
||||
| 'review-panel'
|
||||
| 'chat'
|
||||
| 'errors'
|
||||
|
||||
const RailContext = createContext<
|
||||
| {
|
||||
selectedTab: RailTabKey
|
||||
setSelectedTab: Dispatch<SetStateAction<RailTabKey>>
|
||||
isOpen: boolean
|
||||
setIsOpen: Dispatch<SetStateAction<boolean>>
|
||||
panelRef: React.RefObject<ImperativePanelHandle>
|
||||
togglePane: () => void
|
||||
handlePaneExpand: () => void
|
||||
handlePaneCollapse: () => void
|
||||
resizing: boolean
|
||||
setResizing: Dispatch<SetStateAction<boolean>>
|
||||
}
|
||||
| undefined
|
||||
>(undefined)
|
||||
|
||||
export const RailProvider: FC = ({ children }) => {
|
||||
const [isOpen, setIsOpen] = useState(true)
|
||||
const [resizing, setResizing] = useState(false)
|
||||
const panelRef = useRef<ImperativePanelHandle>(null)
|
||||
useCollapsiblePanel(isOpen, panelRef)
|
||||
|
||||
const togglePane = useCallback(() => {
|
||||
setIsOpen(value => !value)
|
||||
}, [])
|
||||
|
||||
const handlePaneExpand = useCallback(() => {
|
||||
setIsOpen(true)
|
||||
}, [])
|
||||
|
||||
const handlePaneCollapse = useCallback(() => {
|
||||
setIsOpen(false)
|
||||
}, [])
|
||||
|
||||
// NOTE: The file tree **MUST** be the first tab to be opened
|
||||
// since it is responsible for opening the initial document.
|
||||
const [selectedTab, setSelectedTab] = useState<RailTabKey>('file-tree')
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
selectedTab,
|
||||
setSelectedTab,
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
panelRef,
|
||||
togglePane,
|
||||
handlePaneExpand,
|
||||
handlePaneCollapse,
|
||||
resizing,
|
||||
setResizing,
|
||||
}),
|
||||
[
|
||||
selectedTab,
|
||||
setSelectedTab,
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
panelRef,
|
||||
togglePane,
|
||||
handlePaneExpand,
|
||||
handlePaneCollapse,
|
||||
resizing,
|
||||
setResizing,
|
||||
]
|
||||
)
|
||||
|
||||
return <RailContext.Provider value={value}>{children}</RailContext.Provider>
|
||||
}
|
||||
|
||||
export const useRailContext = () => {
|
||||
const context = useContext(RailContext)
|
||||
if (!context) {
|
||||
throw new Error('useRailContext is only available inside RailProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import {
|
||||
createContext,
|
||||
Dispatch,
|
||||
FC,
|
||||
SetStateAction,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
export type RailTabKey =
|
||||
| 'file-tree'
|
||||
| 'integrations'
|
||||
| 'review-panel'
|
||||
| 'chat'
|
||||
| 'errors'
|
||||
|
||||
const RailTabContext = createContext<
|
||||
| {
|
||||
selectedTab: RailTabKey
|
||||
setSelectedTab: Dispatch<SetStateAction<RailTabKey>>
|
||||
}
|
||||
| undefined
|
||||
>(undefined)
|
||||
|
||||
export const RailTabProvider: FC = ({ children }) => {
|
||||
// NOTE: The file tree **MUST** be the first tab to be opened
|
||||
// since it is responsible for opening the initial document.
|
||||
const [selectedTab, setSelectedTab] = useState<RailTabKey>('file-tree')
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
selectedTab,
|
||||
setSelectedTab,
|
||||
}),
|
||||
[selectedTab, setSelectedTab]
|
||||
)
|
||||
|
||||
return (
|
||||
<RailTabContext.Provider value={value}>{children}</RailTabContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useRailTabContext = () => {
|
||||
const context = useContext(RailTabContext)
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useRailTabContext is only available inside RailTabProvider'
|
||||
)
|
||||
}
|
||||
return context
|
||||
}
|
||||
@@ -1,7 +1,35 @@
|
||||
import { useRail } from './use-rail'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
|
||||
import { ImperativePanelHandle } from 'react-resizable-panels'
|
||||
|
||||
// FIXME: This is temporary, to avoid clashing with the existing usePdfPane
|
||||
// which uses the layout context. That's the correct approach.
|
||||
export const usePdfPane = () => {
|
||||
// FIXME: This is temporary, to avoid clashing with the existing usePdfPane
|
||||
// which uses the layout context. That's the correct approach.
|
||||
return useRail()
|
||||
const [isOpen, setIsOpen] = useState(true)
|
||||
const [resizing, setResizing] = useState(false)
|
||||
const panelRef = useRef<ImperativePanelHandle>(null)
|
||||
useCollapsiblePanel(isOpen, panelRef)
|
||||
|
||||
const togglePane = useCallback(() => {
|
||||
setIsOpen(value => !value)
|
||||
}, [])
|
||||
|
||||
const handlePaneExpand = useCallback(() => {
|
||||
setIsOpen(true)
|
||||
}, [])
|
||||
|
||||
const handlePaneCollapse = useCallback(() => {
|
||||
setIsOpen(false)
|
||||
}, [])
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
panelRef,
|
||||
togglePane,
|
||||
handlePaneExpand,
|
||||
handlePaneCollapse,
|
||||
resizing,
|
||||
setResizing,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel'
|
||||
import { ImperativePanelHandle } from 'react-resizable-panels'
|
||||
|
||||
export const useRail = () => {
|
||||
const [isOpen, setIsOpen] = useState(true)
|
||||
const [resizing, setResizing] = useState(false)
|
||||
const panelRef = useRef<ImperativePanelHandle>(null)
|
||||
useCollapsiblePanel(isOpen, panelRef)
|
||||
|
||||
const togglePane = useCallback(() => {
|
||||
setIsOpen(value => !value)
|
||||
}, [])
|
||||
|
||||
const handlePaneExpand = useCallback(() => {
|
||||
setIsOpen(true)
|
||||
}, [])
|
||||
|
||||
const handlePaneCollapse = useCallback(() => {
|
||||
setIsOpen(false)
|
||||
}, [])
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
panelRef,
|
||||
togglePane,
|
||||
handlePaneExpand,
|
||||
handlePaneCollapse,
|
||||
resizing,
|
||||
setResizing,
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
function DropboxLogo() {
|
||||
function DropboxLogo({ size = 40 }: { size?: number }) {
|
||||
return (
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 40 40"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -1,8 +1,8 @@
|
||||
function GitBridgeLogo() {
|
||||
function GitBridgeLogo({ size = 40 }: { size?: number }) {
|
||||
return (
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 40 40"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -1,8 +1,8 @@
|
||||
function GithubLogo() {
|
||||
function GithubLogo({ size = 40 }: { size?: number }) {
|
||||
return (
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 40 40"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -42,3 +42,4 @@
|
||||
@import 'menu-bar';
|
||||
@import 'invite';
|
||||
@import 'upgrade-prompt';
|
||||
@import 'integrations-panel';
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
.integrations-panel {
|
||||
background-color: var(--white);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.integrations-panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--spacing-03) var(--spacing-04);
|
||||
}
|
||||
|
||||
.integrations-panel-title {
|
||||
font-size: var(--font-size-02);
|
||||
color: var(--content-primary);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.integrations-panel-card-button {
|
||||
all: unset;
|
||||
background-color: var(--white);
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg-light-secondary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.integrations-panel-card-contents {
|
||||
display: flex;
|
||||
margin: 0 var(--spacing-04);
|
||||
padding: var(--spacing-04) 0;
|
||||
gap: var(--spacing-04);
|
||||
border-bottom: 1px solid var(--border-divider);
|
||||
}
|
||||
|
||||
.integrations-panel-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.material-symbols {
|
||||
background: linear-gradient(
|
||||
245.63deg,
|
||||
#214475 0%,
|
||||
#254c84 28.54%,
|
||||
#6597e0 96.69%
|
||||
);
|
||||
color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
}
|
||||
|
||||
.integrations-panel-card-inner {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.integrations-panel-card-premium-badge {
|
||||
color: var(--content-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.integrations-panel-card-title {
|
||||
font-size: var(--font-size-02);
|
||||
color: var(--content-primary);
|
||||
}
|
||||
|
||||
.integrations-panel-card-description {
|
||||
font-size: var(--font-size-01);
|
||||
color: var(--content-secondary);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -392,6 +392,7 @@
|
||||
"confirming": "Confirming",
|
||||
"conflicting_paths_found": "Conflicting Paths Found",
|
||||
"congratulations_youve_successfully_join_group": "Congratulations! You‘ve successfully joined the group subscription.",
|
||||
"connect_overleaf_with_github": "Connect __appName__ with Github for easy project syncing and real-time version control.",
|
||||
"connected_users": "Connected Users",
|
||||
"connecting": "Connecting",
|
||||
"connection_lost": "Connection lost",
|
||||
@@ -1029,6 +1030,7 @@
|
||||
"institutional": "Institutional",
|
||||
"institutional_leavers_survey_notification": "Provide some quick feedback to receive a 25% discount on an annual subscription!",
|
||||
"institutional_login_unknown": "Sorry, we don’t know which institution issued that email address. You can browse our <a href=\"__link__\">list of institutions</a> to find yours, or you can use one of the other options below.",
|
||||
"integrate_overleaf_with_dropbox": "Integrate __appName__ with Dropbox to sync your LaTeX projects and keep files up to date automatically.",
|
||||
"integrations": "Integrations",
|
||||
"integrations_like_github": "Integrations like GitHub Sync",
|
||||
"interested_in_cheaper_personal_plan": "Would you be interested in the cheaper <0>__price__</0> Personal plan?",
|
||||
@@ -1168,6 +1170,7 @@
|
||||
"link_accounts": "Link Accounts",
|
||||
"link_accounts_and_add_email": "Link Accounts and Add Email",
|
||||
"link_institutional_email_get_started": "Link an institutional email address to your account to get started.",
|
||||
"link_overleaf_with_git": "Link __appName__ with Git for seamless project syncing and version control across your repositories.",
|
||||
"link_sharing": "Link sharing",
|
||||
"link_sharing_is_off_short": "Link sharing is off",
|
||||
"link_sharing_is_on": "Link sharing is on",
|
||||
@@ -1599,6 +1602,7 @@
|
||||
"position": "Position",
|
||||
"postal_code": "Postal Code",
|
||||
"postal_code_sentence_case": "Postal code",
|
||||
"premium": "Premium",
|
||||
"premium_feature": "Premium feature",
|
||||
"premium_features": "Premium features",
|
||||
"premium_plan_label": "You’re using <b>Overleaf Premium</b>",
|
||||
|
||||
Reference in New Issue
Block a user