mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 21:23:45 +01:00
Merge pull request #35056 from overleaf/mj-command-palette-improvements
[web] Command palette improvements GitOrigin-RevId: 3d6e11697ed4726b7ecada1744d24a15291ad646
This commit is contained in:
committed by
Copybot
parent
4ef7324263
commit
912fb2dcd8
@@ -310,6 +310,7 @@
|
||||
"close": "",
|
||||
"close_dialog": "",
|
||||
"close_other_tabs": "",
|
||||
"close_tab": "",
|
||||
"close_tabs_to_the_right": "",
|
||||
"clsi_maintenance": "",
|
||||
"clsi_unavailable": "",
|
||||
@@ -680,6 +681,10 @@
|
||||
"features_like_track_changes": "",
|
||||
"feedback": "",
|
||||
"figure": "",
|
||||
"figure_from_another_project": "",
|
||||
"figure_from_computer": "",
|
||||
"figure_from_project_files": "",
|
||||
"figure_from_url": "",
|
||||
"file": "",
|
||||
"file_action_created": "",
|
||||
"file_action_deleted": "",
|
||||
@@ -976,25 +981,15 @@
|
||||
"inr_discount_modal_info": "",
|
||||
"inr_discount_modal_title": "",
|
||||
"insert": "",
|
||||
"insert_citation": "",
|
||||
"insert_column_left": "",
|
||||
"insert_column_right": "",
|
||||
"insert_cross_reference": "",
|
||||
"insert_display_math": "",
|
||||
"insert_figure": "",
|
||||
"insert_figure_from_another_project": "",
|
||||
"insert_figure_from_computer": "",
|
||||
"insert_figure_from_project_files": "",
|
||||
"insert_figure_from_url": "",
|
||||
"insert_from_another_project": "",
|
||||
"insert_from_project_files": "",
|
||||
"insert_from_url": "",
|
||||
"insert_image": "",
|
||||
"insert_inline_math": "",
|
||||
"insert_link": "",
|
||||
"insert_row_above": "",
|
||||
"insert_row_below": "",
|
||||
"insert_table": "",
|
||||
"insert_x_columns_left": "",
|
||||
"insert_x_columns_right": "",
|
||||
"insert_x_rows_above": "",
|
||||
@@ -1055,6 +1050,8 @@
|
||||
"join_x_managed_enterprise_group": "",
|
||||
"joined_team": "",
|
||||
"joining": "",
|
||||
"jump_to_line_x": "",
|
||||
"jump_to_line_x_column_y": "",
|
||||
"jump_to_location_in_pdf": "",
|
||||
"justify": "",
|
||||
"kb_suggestions_enquiry": "",
|
||||
@@ -1444,6 +1441,7 @@
|
||||
"pdf_compile_in_progress_error": "",
|
||||
"pdf_compile_rate_limit_hit": "",
|
||||
"pdf_compile_try_again": "",
|
||||
"pdf_in_different_tab": "",
|
||||
"pdf_only": "",
|
||||
"pdf_preview": "",
|
||||
"pdf_preview_error": "",
|
||||
@@ -2145,6 +2143,7 @@
|
||||
"to_pull_results_directly_from_your_reference_manager_enable_one_of_the_available_reference_manager_integrations": "",
|
||||
"to_use_text_wrapping_in_your_table_make_sure_you_include_the_array_package": "",
|
||||
"toggle_compile_options_menu": "",
|
||||
"toggle_focus_mode": "",
|
||||
"toggle_group": "",
|
||||
"toggle_personal_library": "",
|
||||
"toggle_unknown_group": "",
|
||||
|
||||
+6
-6
@@ -111,17 +111,17 @@ const CommandPaletteBody: FC<CommandPaletteBodyProps> = ({ show, onHide }) => {
|
||||
>
|
||||
<button
|
||||
role="menuitem"
|
||||
onMouseEnter={() => setSelectedIndex(index)}
|
||||
onMouseMove={() => setSelectedIndex(index)}
|
||||
onClick={() => runResult(result)}
|
||||
>
|
||||
<div className="command-palette-result-title">
|
||||
{result.title}
|
||||
{result.description && (
|
||||
<span className="command-palette-result-description">
|
||||
{result.description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{result.description && (
|
||||
<div className="command-palette-result-description">
|
||||
{result.description}
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
|
||||
+30
-10
@@ -1,7 +1,7 @@
|
||||
import useDebounce from '@/shared/hooks/use-debounce'
|
||||
import useCommandPaletteSources from './use-command-palette-sources'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { CommandPaletteSearchResult } from '../types'
|
||||
import { CommandPaletteSearchResult, CommandPaletteSource } from '../types'
|
||||
|
||||
const useCommandPaletteResults = (query: string) => {
|
||||
const debouncedQuery = useDebounce(query, 25)
|
||||
@@ -9,17 +9,37 @@ const useCommandPaletteResults = (query: string) => {
|
||||
const [results, setResults] = useState<CommandPaletteSearchResult[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (debouncedQuery) {
|
||||
const res = sources.map(source => source.search(debouncedQuery))
|
||||
setResults(res.flat().sort((a, b) => b.score - a.score))
|
||||
} else {
|
||||
const res = sources
|
||||
.filter(s => 'defaults' in s)
|
||||
.map(source => source.defaults!())
|
||||
setResults(res.flat().sort((a, b) => b.score - a.score))
|
||||
}
|
||||
const res = getSourcesMatchingQuery(debouncedQuery, sources).map(
|
||||
({ source, query }) =>
|
||||
query ? source.search(query) : (source.defaults?.() ?? [])
|
||||
)
|
||||
setResults(res.flat().sort((a, b) => b.score - a.score))
|
||||
}, [debouncedQuery, sources])
|
||||
return results
|
||||
}
|
||||
|
||||
const getSourcesMatchingQuery = (
|
||||
query: string,
|
||||
sources: CommandPaletteSource[]
|
||||
): { query: string; source: CommandPaletteSource }[] => {
|
||||
// No query matches all sources
|
||||
if (!query) return sources.map(source => ({ source, query }))
|
||||
|
||||
// If the query matches a prefix, return all the sources with that prefix
|
||||
const matchingSources = sources.filter(
|
||||
source => source.prefix && query.startsWith(source.prefix)
|
||||
)
|
||||
if (matchingSources.length > 0) {
|
||||
return matchingSources.map(source => ({
|
||||
source,
|
||||
query: query.slice(source.prefix!.length).trim(),
|
||||
}))
|
||||
}
|
||||
|
||||
// otherwise return all sources that don't require a prefix match
|
||||
return sources
|
||||
.filter(source => !source.prefixRequired)
|
||||
.map(source => ({ source, query: query.trim() }))
|
||||
}
|
||||
|
||||
export default useCommandPaletteResults
|
||||
|
||||
+5
-2
@@ -2,13 +2,16 @@ import { useMemo } from 'react'
|
||||
import { CommandPaletteSource } from '../types'
|
||||
import useCommandRegistrySource from './use-command-registry-source'
|
||||
import useFileTreeCommandSource from './use-file-tree-command-source'
|
||||
import { useJumpToLineCommandSource } from './use-jump-to-line-command-source'
|
||||
|
||||
const useCommandPaletteSources = (): CommandPaletteSource[] => {
|
||||
const fileTreeSource = useFileTreeCommandSource()
|
||||
const commandRegistrySource = useCommandRegistrySource()
|
||||
const jumpToLineSource = useJumpToLineCommandSource()
|
||||
|
||||
const sources = useMemo(
|
||||
() => [fileTreeSource, commandRegistrySource],
|
||||
[fileTreeSource, commandRegistrySource]
|
||||
() => [fileTreeSource, commandRegistrySource, jumpToLineSource],
|
||||
[fileTreeSource, commandRegistrySource, jumpToLineSource]
|
||||
)
|
||||
return sources
|
||||
}
|
||||
|
||||
+77
-33
@@ -5,42 +5,85 @@ import {
|
||||
Command,
|
||||
useCommandRegistry,
|
||||
} from '@/features/ide-react/context/command-registry-context'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const ENABLED_COMMANDS: string[] = [
|
||||
'new_file',
|
||||
'new_folder',
|
||||
'upload_file',
|
||||
'open-settings',
|
||||
'show_version_history',
|
||||
'word_count',
|
||||
'view-pdf-presentation-mode',
|
||||
'comment',
|
||||
'compile',
|
||||
'stop-compile',
|
||||
'recompile-from-scratch',
|
||||
'synctex-sync-to-pdf',
|
||||
'synctex-sync-to-code',
|
||||
'insert-inline-math',
|
||||
'insert-display-math',
|
||||
'insert-figure-from-computer',
|
||||
'insert-figure-from-project-files',
|
||||
'insert-figure-from-another-project',
|
||||
'insert-figure-from-url',
|
||||
'insert-table',
|
||||
'insert-citation',
|
||||
'insert-link',
|
||||
'insert-cross-reference',
|
||||
]
|
||||
type CommandGroup = {
|
||||
label: string
|
||||
actions: string[]
|
||||
}
|
||||
|
||||
const useCommandRegistrySource = (): CommandPaletteSource => {
|
||||
const { t } = useTranslation()
|
||||
const elements: (CommandGroup | string)[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: t('file'),
|
||||
actions: ['new_file', 'new_folder', 'upload_file'],
|
||||
},
|
||||
{
|
||||
label: t('view'),
|
||||
actions: [
|
||||
'change-layout-side-by-side',
|
||||
'change-layout-editor-only',
|
||||
'change-layout-pdf-only',
|
||||
'change-layout-detached-pdf',
|
||||
'change-layout-focus-mode',
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('insert'),
|
||||
actions: [
|
||||
'insert-inline-math',
|
||||
'insert-display-math',
|
||||
'insert-figure-from-computer',
|
||||
'insert-figure-from-project-files',
|
||||
'insert-figure-from-another-project',
|
||||
'insert-figure-from-url',
|
||||
'insert-table',
|
||||
'insert-citation',
|
||||
'insert-link',
|
||||
'insert-cross-reference',
|
||||
],
|
||||
},
|
||||
'open-settings',
|
||||
'show_version_history',
|
||||
'word_count',
|
||||
'view-pdf-presentation-mode',
|
||||
'comment',
|
||||
'compile',
|
||||
'stop-compile',
|
||||
'recompile-from-scratch',
|
||||
'synctex-sync-to-pdf',
|
||||
'synctex-sync-to-code',
|
||||
'close-tab',
|
||||
'close-other-tabs',
|
||||
],
|
||||
[t]
|
||||
)
|
||||
const { registry } = useCommandRegistry()
|
||||
|
||||
const commands = useMemo(() => {
|
||||
const enabled = new Set(ENABLED_COMMANDS)
|
||||
return [...registry.values()].filter(
|
||||
c => enabled.has(c.id) && !c.disabled && c.handler
|
||||
)
|
||||
}, [registry])
|
||||
const ret: Command[] = []
|
||||
for (const element of elements) {
|
||||
if (typeof element === 'string') {
|
||||
const command = registry.get(element)
|
||||
if (command && !command.disabled && command.handler) {
|
||||
ret.push(command)
|
||||
}
|
||||
} else {
|
||||
for (const action of element.actions) {
|
||||
const command = registry.get(action)
|
||||
if (command && !command.disabled && command.handler) {
|
||||
ret.push({
|
||||
...command,
|
||||
label: `${element.label}: ${command.label}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}, [registry, elements])
|
||||
|
||||
const defaults = useCallback((): CommandPaletteSearchResult[] => {
|
||||
return commands.map(command => ({
|
||||
@@ -53,7 +96,7 @@ const useCommandRegistrySource = (): CommandPaletteSource => {
|
||||
const index = useMemo(() => {
|
||||
const miniSearch = new MiniSearch<Command>({
|
||||
fields: ['label'],
|
||||
storeFields: ['id'],
|
||||
storeFields: ['id', 'label'],
|
||||
idField: 'id',
|
||||
})
|
||||
miniSearch.addAll(commands)
|
||||
@@ -63,17 +106,18 @@ const useCommandRegistrySource = (): CommandPaletteSource => {
|
||||
return useMemo<CommandPaletteSource>(
|
||||
() => ({
|
||||
id: 'command-registry',
|
||||
prefix: '>',
|
||||
search(query) {
|
||||
const results = index.search(query, {
|
||||
prefix: true,
|
||||
fuzzy: term => (term.length > 3 ? 0.2 : false),
|
||||
})
|
||||
return results.flatMap(({ id, score }) => {
|
||||
return results.flatMap(({ id, label, score }) => {
|
||||
const command = registry.get(id)
|
||||
if (!command?.handler) return []
|
||||
return [
|
||||
{
|
||||
title: command.label,
|
||||
title: label,
|
||||
onSelect: () => command.handler!({ location: 'command-palette' }),
|
||||
score,
|
||||
},
|
||||
|
||||
+30
-6
@@ -9,9 +9,12 @@ import { useEditorManagerContext } from '@/features/ide-react/context/editor-man
|
||||
|
||||
type FlatFileTree = { path: string; name: string; id: string }[]
|
||||
|
||||
const FILE_POSITION_REGEX = /:(\d+)(?:,(\d+))?$/
|
||||
|
||||
const useFileTreeCommandSource = (): CommandPaletteSource => {
|
||||
const { fileTreeData } = useFileTreeData()
|
||||
const { openDocWithId, openFileWithId } = useEditorManagerContext()
|
||||
|
||||
const flatFileTree = useMemo(
|
||||
() => flattenFileTree(fileTreeData),
|
||||
[fileTreeData]
|
||||
@@ -22,7 +25,11 @@ const useFileTreeCommandSource = (): CommandPaletteSource => {
|
||||
)
|
||||
|
||||
const onSelect = useCallback(
|
||||
async (id: string) => {
|
||||
async (
|
||||
id: string,
|
||||
line: number | undefined,
|
||||
column: number | undefined
|
||||
) => {
|
||||
if (!fileTreeData) {
|
||||
return
|
||||
}
|
||||
@@ -31,7 +38,10 @@ const useFileTreeCommandSource = (): CommandPaletteSource => {
|
||||
return
|
||||
}
|
||||
if (file.type === 'doc') {
|
||||
await openDocWithId(file.entity._id)
|
||||
await openDocWithId(file.entity._id, {
|
||||
gotoLine: line,
|
||||
gotoColumn: column,
|
||||
})
|
||||
} else if (file.type === 'fileRef') {
|
||||
openFileWithId(file.entity._id)
|
||||
} else {
|
||||
@@ -49,8 +59,9 @@ const useFileTreeCommandSource = (): CommandPaletteSource => {
|
||||
.slice(0, 10)
|
||||
.map(({ path, name, id }) => ({
|
||||
title: name,
|
||||
description: path === name ? undefined : path,
|
||||
onSelect: () => onSelect(id),
|
||||
description:
|
||||
path === name ? undefined : path.split('/').slice(0, -1).join('/'),
|
||||
onSelect: () => onSelect(id, undefined, undefined),
|
||||
score: 1,
|
||||
}))
|
||||
|
||||
@@ -64,14 +75,27 @@ const useFileTreeCommandSource = (): CommandPaletteSource => {
|
||||
if (!index) {
|
||||
return []
|
||||
}
|
||||
const fileLocationMatch = query.match(FILE_POSITION_REGEX)
|
||||
let line: number | undefined = undefined
|
||||
let column: number | undefined = undefined
|
||||
if (fileLocationMatch) {
|
||||
line = parseInt(fileLocationMatch[1], 10)
|
||||
column = fileLocationMatch[2]
|
||||
? parseInt(fileLocationMatch[2], 10)
|
||||
: undefined
|
||||
query = query.slice(0, fileLocationMatch.index)
|
||||
}
|
||||
|
||||
const result = index.search(query, {
|
||||
prefix: true,
|
||||
fuzzy: term => (term.length > 3 ? 0.2 : false),
|
||||
})
|
||||
|
||||
return result.map(({ path, name, id, score }) => ({
|
||||
title: name,
|
||||
description: path === name ? undefined : path,
|
||||
onSelect: () => onSelect(id),
|
||||
description:
|
||||
path === name ? undefined : path.split('/').slice(0, -1).join('/'),
|
||||
onSelect: () => onSelect(id, line, column),
|
||||
score,
|
||||
}))
|
||||
},
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context'
|
||||
import { CommandPaletteSource } from '../types'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const JUMP_REGEX = /^(\d+)(?:,(\d+))?$/
|
||||
|
||||
export const useJumpToLineCommandSource = (): CommandPaletteSource => {
|
||||
const { jumpToLine } = useEditorManagerContext()
|
||||
const { t } = useTranslation()
|
||||
return useMemo(
|
||||
() => ({
|
||||
id: 'jump-to-line-source',
|
||||
prefix: ':',
|
||||
prefixRequired: true,
|
||||
search: (query: string) => {
|
||||
const match = query.match(JUMP_REGEX)
|
||||
if (!match) {
|
||||
return []
|
||||
}
|
||||
const line = parseInt(match[1], 10)
|
||||
const column = match[2] ? parseInt(match[2], 10) : undefined
|
||||
|
||||
return [
|
||||
{
|
||||
onSelect: () => jumpToLine({ gotoLine: line, gotoColumn: column }),
|
||||
title:
|
||||
column != null
|
||||
? t('jump_to_line_x_column_y', {
|
||||
line,
|
||||
column,
|
||||
})
|
||||
: t('jump_to_line_x', {
|
||||
line,
|
||||
}),
|
||||
score: 1,
|
||||
},
|
||||
]
|
||||
},
|
||||
}),
|
||||
[jumpToLine, t]
|
||||
)
|
||||
}
|
||||
@@ -9,4 +9,6 @@ export type CommandPaletteSource = {
|
||||
id: string
|
||||
search(query: string): CommandPaletteSearchResult[]
|
||||
defaults?(): CommandPaletteSearchResult[]
|
||||
prefix?: string
|
||||
prefixRequired?: boolean
|
||||
}
|
||||
|
||||
+16
-5
@@ -1,4 +1,4 @@
|
||||
import { useRef, useEffect } from 'react'
|
||||
import { useRef, useEffect, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useFileTreeCreateName } from '../../contexts/file-tree-create-name'
|
||||
import {
|
||||
@@ -36,6 +36,7 @@ export default function FileTreeCreateNameInput({
|
||||
|
||||
// the value is stored in a context provider, so it's available elsewhere in the form
|
||||
const { name, setName, touchedName, validName } = useFileTreeCreateName()
|
||||
const nameFocusedOnce = useRef(false)
|
||||
|
||||
// focus the first part of the filename if needed
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
@@ -45,15 +46,24 @@ export default function FileTreeCreateNameInput({
|
||||
window.requestAnimationFrame(() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus()
|
||||
inputRef.current.setSelectionRange(
|
||||
0,
|
||||
inputRef.current.value.lastIndexOf('.')
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [focusName])
|
||||
|
||||
const onFocus = useCallback(() => {
|
||||
if (nameFocusedOnce.current) return
|
||||
if (!focusName) return
|
||||
window.requestAnimationFrame(() => {
|
||||
if (!inputRef.current) return
|
||||
nameFocusedOnce.current = true
|
||||
inputRef.current.setSelectionRange(
|
||||
0,
|
||||
inputRef.current.value.lastIndexOf('.')
|
||||
)
|
||||
})
|
||||
}, [focusName])
|
||||
|
||||
return (
|
||||
<OLFormGroup controlId="new-doc-name" className={classes.formGroup}>
|
||||
<OLFormLabel>{label || t('file_name')}</OLFormLabel>
|
||||
@@ -65,6 +75,7 @@ export default function FileTreeCreateNameInput({
|
||||
onChange={event => setName(event.target.value)}
|
||||
ref={inputRef}
|
||||
disabled={inFlight}
|
||||
onFocus={onFocus}
|
||||
/>
|
||||
{touchedName && !validName && (
|
||||
<div className="notification-list">
|
||||
|
||||
+69
@@ -8,6 +8,10 @@ import {
|
||||
import ChangeLayoutOptions from './change-layout-options'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import OLTooltip from '@/shared/components/ol/ol-tooltip'
|
||||
import { useCommandProvider } from '../../hooks/use-command-provider'
|
||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
|
||||
import { useLayoutContext } from '@/shared/context/layout-context'
|
||||
|
||||
export default function ChangeLayoutButton() {
|
||||
const { t } = useTranslation()
|
||||
@@ -17,6 +21,71 @@ export default function ChangeLayoutButton() {
|
||||
'ide-redesign-toolbar-button-icon'
|
||||
)
|
||||
|
||||
const {
|
||||
detachIsLinked,
|
||||
detachRole,
|
||||
handleChangeLayout,
|
||||
handleDetach,
|
||||
focusMode,
|
||||
setFocusMode,
|
||||
} = useLayoutContext()
|
||||
|
||||
const { sendEvent } = useEditorAnalytics()
|
||||
const focusModeEnabled = useFeatureFlag('focus-mode')
|
||||
|
||||
const detachable = 'BroadcastChannel' in window
|
||||
|
||||
const waitingForDetachedLink = !detachIsLinked && detachRole === 'detacher'
|
||||
|
||||
useCommandProvider(
|
||||
() => [
|
||||
{
|
||||
id: 'change-layout-side-by-side',
|
||||
handler: () => handleChangeLayout('sideBySide'),
|
||||
label: t('split_view'),
|
||||
},
|
||||
{
|
||||
id: 'change-layout-editor-only',
|
||||
handler: () => handleChangeLayout('flat', 'editor'),
|
||||
label: t('editor_only'),
|
||||
},
|
||||
{
|
||||
id: 'change-layout-pdf-only',
|
||||
handler: () => handleChangeLayout('flat', 'pdf'),
|
||||
label: t('pdf_only'),
|
||||
},
|
||||
{
|
||||
id: 'change-layout-detached-pdf',
|
||||
handler: () => handleDetach(),
|
||||
disabled: !detachable || waitingForDetachedLink,
|
||||
label: t('pdf_in_different_tab'),
|
||||
},
|
||||
...(focusModeEnabled
|
||||
? [
|
||||
{
|
||||
id: 'change-layout-focus-mode',
|
||||
handler: () => {
|
||||
setFocusMode(!focusMode)
|
||||
sendEvent('focus-mode-toggle', { focusMode: !focusMode })
|
||||
},
|
||||
label: t('toggle_focus_mode'),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
[
|
||||
handleChangeLayout,
|
||||
t,
|
||||
detachable,
|
||||
handleDetach,
|
||||
waitingForDetachedLink,
|
||||
focusModeEnabled,
|
||||
focusMode,
|
||||
setFocusMode,
|
||||
sendEvent,
|
||||
]
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="ide-redesign-toolbar-button-container">
|
||||
<OLDropdown className="toolbar-item layout-dropdown" align="end">
|
||||
|
||||
@@ -10,6 +10,8 @@ import { useCallback, useMemo, useState } from 'react'
|
||||
import { throttle } from 'lodash'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import classNames from 'classnames'
|
||||
import { useCommandProvider } from '@/features/ide-react/hooks/use-command-provider'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export const TabsContainer = () => {
|
||||
const {
|
||||
@@ -20,9 +22,11 @@ export const TabsContainer = () => {
|
||||
makeTabPermanent,
|
||||
setContextMenuTarget,
|
||||
setHeaderSlot,
|
||||
closeOtherTabs,
|
||||
} = useTabsContext()
|
||||
const { openEntity } = useFileTreeOpenContext()
|
||||
const [hovered, setHovered] = useState<boolean>(false)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const openContextMenu = useCallback(
|
||||
(coords: { top: number; left: number }, tabId: string) => {
|
||||
@@ -86,6 +90,32 @@ export const TabsContainer = () => {
|
||||
}
|
||||
}, [])
|
||||
|
||||
useCommandProvider(() => {
|
||||
if (tabs.length === 0) {
|
||||
return
|
||||
}
|
||||
if (!openEntity?.entity._id) {
|
||||
return
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'close-tab',
|
||||
label: t('close_tab'),
|
||||
handler: () => {
|
||||
closeTab(openEntity.entity._id)
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'close-other-tabs',
|
||||
label: t('close_other_tabs'),
|
||||
handler: () => {
|
||||
closeOtherTabs(openEntity.entity._id)
|
||||
},
|
||||
},
|
||||
]
|
||||
}, [tabs, t, openEntity, closeTab, closeOtherTabs])
|
||||
|
||||
return (
|
||||
<div className="editor-tabs-container">
|
||||
<div className="review-panel-header-slot" ref={setHeaderSlot} />
|
||||
|
||||
+10
-14
@@ -102,7 +102,7 @@ export const useToolbarMenuBarEditorCommands = () => {
|
||||
{
|
||||
id: 'insert-inline-math',
|
||||
menuLabel: t('inline_math'),
|
||||
label: t('insert_inline_math'),
|
||||
label: t('inline_math'),
|
||||
handler: () => {
|
||||
commands.wrapInInlineMath(view)
|
||||
view.focus()
|
||||
@@ -111,7 +111,7 @@ export const useToolbarMenuBarEditorCommands = () => {
|
||||
{
|
||||
id: 'insert-display-math',
|
||||
menuLabel: t('display_math'),
|
||||
label: t('insert_display_math'),
|
||||
label: t('display_math'),
|
||||
handler: () => {
|
||||
commands.wrapInDisplayMath(view)
|
||||
view.focus()
|
||||
@@ -119,7 +119,7 @@ export const useToolbarMenuBarEditorCommands = () => {
|
||||
},
|
||||
{
|
||||
menuLabel: t('upload_from_computer'),
|
||||
label: t('insert_figure_from_computer'),
|
||||
label: t('figure_from_computer'),
|
||||
id: 'insert-figure-from-computer',
|
||||
handler: () => {
|
||||
openFigureModal(FigureModalSource.FILE_UPLOAD)
|
||||
@@ -127,7 +127,7 @@ export const useToolbarMenuBarEditorCommands = () => {
|
||||
},
|
||||
{
|
||||
menuLabel: t('from_project_files'),
|
||||
label: t('insert_figure_from_project_files'),
|
||||
label: t('figure_from_project_files'),
|
||||
id: 'insert-figure-from-project-files',
|
||||
handler: () => {
|
||||
openFigureModal(FigureModalSource.FILE_TREE)
|
||||
@@ -149,7 +149,7 @@ export const useToolbarMenuBarEditorCommands = () => {
|
||||
return [
|
||||
{
|
||||
menuLabel: t('from_another_project'),
|
||||
label: t('insert_figure_from_another_project'),
|
||||
label: t('figure_from_another_project'),
|
||||
id: 'insert-figure-from-another-project',
|
||||
handler: () => {
|
||||
openFigureModal(FigureModalSource.OTHER_PROJECT)
|
||||
@@ -179,7 +179,7 @@ export const useToolbarMenuBarEditorCommands = () => {
|
||||
return [
|
||||
{
|
||||
menuLabel: t('from_url'),
|
||||
label: t('insert_figure_from_url'),
|
||||
label: t('figure_from_url'),
|
||||
id: 'insert-figure-from-url',
|
||||
handler: () => {
|
||||
openFigureModal(FigureModalSource.FROM_URL)
|
||||
@@ -206,8 +206,7 @@ export const useToolbarMenuBarEditorCommands = () => {
|
||||
return [
|
||||
{
|
||||
id: 'insert-table',
|
||||
menuLabel: t('table'),
|
||||
label: t('insert_table'),
|
||||
label: t('table'),
|
||||
handler: () => {
|
||||
commands.insertTable(view, 3, 3)
|
||||
view.focus()
|
||||
@@ -215,8 +214,7 @@ export const useToolbarMenuBarEditorCommands = () => {
|
||||
},
|
||||
{
|
||||
id: 'insert-citation',
|
||||
menuLabel: t('citation'),
|
||||
label: t('insert_citation'),
|
||||
label: t('citation'),
|
||||
handler: () => {
|
||||
commands.insertCite(view)
|
||||
view.focus()
|
||||
@@ -224,8 +222,7 @@ export const useToolbarMenuBarEditorCommands = () => {
|
||||
},
|
||||
{
|
||||
id: 'insert-link',
|
||||
menuLabel: t('link'),
|
||||
label: t('insert_link'),
|
||||
label: t('link'),
|
||||
handler: () => {
|
||||
commands.wrapInHref(view)
|
||||
view.focus()
|
||||
@@ -233,8 +230,7 @@ export const useToolbarMenuBarEditorCommands = () => {
|
||||
},
|
||||
{
|
||||
id: 'insert-cross-reference',
|
||||
menuLabel: t('cross_reference'),
|
||||
label: t('insert_cross_reference'),
|
||||
label: t('cross_reference'),
|
||||
handler: () => {
|
||||
commands.insertRef(view)
|
||||
view.focus()
|
||||
|
||||
@@ -83,22 +83,19 @@
|
||||
background-color: var(--command-palette-selected-bg);
|
||||
color: var(--command-palette-selected-color);
|
||||
}
|
||||
|
||||
&.command-palette-result-selected .command-palette-result-description {
|
||||
color: var(--command-palette-selected-color);
|
||||
}
|
||||
}
|
||||
|
||||
.command-palette-result-title {
|
||||
color: var(--content-primary-themed);
|
||||
font-size: var(--font-size-03);
|
||||
font-size: var(--font-size-02);
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.command-palette-result-description {
|
||||
font-size: var(--font-size-02);
|
||||
color: var(--content-secondary-themed);
|
||||
margin-left: var(--spacing-02);
|
||||
color: var(--content-placeholder-themed);
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,6 +402,7 @@
|
||||
"close": "Close",
|
||||
"close_dialog": "Close dialog",
|
||||
"close_other_tabs": "Close other tabs",
|
||||
"close_tab": "Close tab",
|
||||
"close_tabs_to_the_right": "Close tabs to the right",
|
||||
"clsi_maintenance": "The compile servers are down for maintenance, and will be back shortly.",
|
||||
"clsi_unavailable": "Sorry, the compile server for your project was temporarily unavailable. Please try again in a few moments.",
|
||||
@@ -888,6 +889,10 @@
|
||||
"fedramp_li_saas": "FedRAMP® Li-SaaS authorized solution",
|
||||
"feedback": "Feedback",
|
||||
"figure": "Figure",
|
||||
"figure_from_another_project": "Figure from another project",
|
||||
"figure_from_computer": "Figure from your computer",
|
||||
"figure_from_project_files": "Figure from project files",
|
||||
"figure_from_url": "Figure from URL",
|
||||
"file": "File",
|
||||
"file_action_created": "Created",
|
||||
"file_action_deleted": "Deleted",
|
||||
@@ -1244,25 +1249,15 @@
|
||||
"inr_discount_modal_title": "70% off all Overleaf premium plans for users in India",
|
||||
"inr_discount_offer_plans_page_banner": "__flag__ <b>Great news!</b> We’ve applied a <b>70% discount</b> to premium plans for our users in India. Check out the new lower prices below.",
|
||||
"insert": "Insert",
|
||||
"insert_citation": "Insert citation",
|
||||
"insert_column_left": "Insert column left",
|
||||
"insert_column_right": "Insert column right",
|
||||
"insert_cross_reference": "Insert cross reference",
|
||||
"insert_display_math": "Insert display math",
|
||||
"insert_figure": "Insert figure",
|
||||
"insert_figure_from_another_project": "Insert figure from another project",
|
||||
"insert_figure_from_computer": "Insert figure from your computer",
|
||||
"insert_figure_from_project_files": "Insert figure from project files",
|
||||
"insert_figure_from_url": "Insert figure from URL",
|
||||
"insert_from_another_project": "Insert from another project",
|
||||
"insert_from_project_files": "Insert from project files",
|
||||
"insert_from_url": "Insert from URL",
|
||||
"insert_image": "Insert image",
|
||||
"insert_inline_math": "Insert inline math",
|
||||
"insert_link": "Insert link",
|
||||
"insert_row_above": "Insert row above",
|
||||
"insert_row_below": "Insert row below",
|
||||
"insert_table": "Insert table",
|
||||
"insert_x_columns_left": "Insert __columns__ columns left",
|
||||
"insert_x_columns_right": "Insert __columns__ columns right",
|
||||
"insert_x_rows_above": "Insert __rows__ rows above",
|
||||
@@ -1357,6 +1352,8 @@
|
||||
"joined_team": "You have joined the group subscription managed by __inviterName__",
|
||||
"joining": "Joining",
|
||||
"july": "July",
|
||||
"jump_to_line_x": "Jump to line __line__",
|
||||
"jump_to_line_x_column_y": "Jump to line __line__, column __column__",
|
||||
"jump_to_location_in_pdf": "Jump to location in PDF",
|
||||
"june": "June",
|
||||
"justify": "Justify",
|
||||
@@ -1894,6 +1891,7 @@
|
||||
"pdf_compile_in_progress_error": "A previous compile is still running. Please wait a minute and try compiling again.",
|
||||
"pdf_compile_rate_limit_hit": "Compile rate limit hit",
|
||||
"pdf_compile_try_again": "Please wait for your other compile to finish before trying again.",
|
||||
"pdf_in_different_tab": "PDF in different tab",
|
||||
"pdf_only": "PDF only",
|
||||
"pdf_preview": "PDF preview",
|
||||
"pdf_preview_error": "There was a problem displaying the compilation results for this project.",
|
||||
@@ -2758,6 +2756,7 @@
|
||||
"to_pull_results_directly_from_your_reference_manager_enable_one_of_the_available_reference_manager_integrations": "To pull results directly from your reference manager, <0>enable one of the available reference manager integrations</0>.",
|
||||
"to_use_text_wrapping_in_your_table_make_sure_you_include_the_array_package": "<0>Please note:</0> To use text wrapping in your table, make sure you include the <1>array</1> package in your document preamble:",
|
||||
"toggle_compile_options_menu": "Toggle compile options menu",
|
||||
"toggle_focus_mode": "Toggle focus mode",
|
||||
"toggle_unknown_group": "Toggle unknown group",
|
||||
"token": "Token",
|
||||
"token_access_failure": "Cannot grant access; contact the project owner for help",
|
||||
|
||||
Reference in New Issue
Block a user