feat: add code block line wrap toggle
Adds a chat code block wrap toggle in markdown code block headers Persists and restores the setting across desktop/web settings Adds localized labels and OpenChamber search entry for the new option
This commit is contained in:
@@ -23,6 +23,7 @@ import { renderMarkdownBlocks, renderMarkdownSync } from './markdown/markdownCor
|
||||
import { ensureMarkdownShikiTheme, getMarkdownSyntaxVars } from './markdown/markdownTheme';
|
||||
import {
|
||||
attachMarkdownInteractions,
|
||||
applyMarkdownCodeBlockWrapState,
|
||||
decorateMarkdown,
|
||||
type DecorateContext,
|
||||
type DecorateLabels,
|
||||
@@ -863,6 +864,8 @@ const useDecorateContext = (
|
||||
const labels: DecorateLabels = React.useMemo(() => ({
|
||||
copy: 'Copy code',
|
||||
copied: 'Copied',
|
||||
enableCodeWrap: t('markdownRenderer.code.actions.enableWrapTitle'),
|
||||
disableCodeWrap: t('markdownRenderer.code.actions.disableWrapTitle'),
|
||||
copyTable: t('markdownRenderer.table.actions.copyTitle'),
|
||||
downloadTable: t('markdownRenderer.table.actions.downloadTitle'),
|
||||
copyDiagram: t('markdownRenderer.mermaid.actions.copySourceTitle'),
|
||||
@@ -871,6 +874,12 @@ const useDecorateContext = (
|
||||
previewTitle: t('terminalView.preview.openTitle'),
|
||||
}), [t]);
|
||||
|
||||
const codeBlockLineWrap = useUIStore((state) => state.codeBlockLineWrap);
|
||||
const setCodeBlockLineWrap = useUIStore((state) => state.setCodeBlockLineWrap);
|
||||
const toggleCodeBlockLineWrap = React.useCallback(() => {
|
||||
setCodeBlockLineWrap(!useUIStore.getState().codeBlockLineWrap);
|
||||
}, [setCodeBlockLineWrap]);
|
||||
|
||||
return React.useMemo<DecorateContext>(() => {
|
||||
const colors = mermaidColorsFromTheme(currentTheme);
|
||||
const mode = useUIStore.getState().mermaidRenderingMode;
|
||||
@@ -884,8 +893,8 @@ const useDecorateContext = (
|
||||
return {};
|
||||
}
|
||||
});
|
||||
return { labels, renderMermaid, onPreviewLoopback };
|
||||
}, [currentTheme, labels, onPreviewLoopback]);
|
||||
return { labels, codeBlockLineWrap, onToggleCodeBlockLineWrap: toggleCodeBlockLineWrap, renderMermaid, onPreviewLoopback };
|
||||
}, [currentTheme, labels, codeBlockLineWrap, toggleCodeBlockLineWrap, onPreviewLoopback]);
|
||||
};
|
||||
|
||||
// Runs the async render pipeline into the container and keeps a stable
|
||||
@@ -994,6 +1003,13 @@ const useMorphdomMarkdown = ({
|
||||
target.style.setProperty(key, value);
|
||||
}
|
||||
}, [containerRef, syntaxVars]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
const target = container?.querySelector<HTMLElement>('[data-markdown-content]') ?? container;
|
||||
if (!target) return;
|
||||
applyMarkdownCodeBlockWrapState(target, ctx.codeBlockLineWrap, ctx.labels);
|
||||
}, [containerRef, ctx.codeBlockLineWrap, ctx.labels]);
|
||||
};
|
||||
|
||||
const markdownContentClassName = (variant: MarkdownVariant): string =>
|
||||
|
||||
@@ -11,6 +11,8 @@ export type MermaidRender = { svg?: string; ascii?: string };
|
||||
export type DecorateLabels = {
|
||||
copy: string;
|
||||
copied: string;
|
||||
enableCodeWrap: string;
|
||||
disableCodeWrap: string;
|
||||
copyTable: string;
|
||||
downloadTable: string;
|
||||
copyDiagram: string;
|
||||
@@ -21,6 +23,8 @@ export type DecorateLabels = {
|
||||
|
||||
export type DecorateContext = {
|
||||
labels: DecorateLabels;
|
||||
codeBlockLineWrap: boolean;
|
||||
onToggleCodeBlockLineWrap?: () => void;
|
||||
// Renders a mermaid block source to svg/ascii using current theme colors.
|
||||
renderMermaid: (source: string) => MermaidRender;
|
||||
onPreviewLoopback?: (url: string) => void;
|
||||
@@ -36,6 +40,7 @@ const ICONS = {
|
||||
copy: spriteIcon('file-copy'),
|
||||
check: spriteIcon('check'),
|
||||
download: spriteIcon('download'),
|
||||
textWrap: spriteIcon('text-wrap'),
|
||||
} as const;
|
||||
|
||||
const ICON_BTN_CLASS =
|
||||
@@ -56,6 +61,37 @@ const makeIconButton = (icon: keyof typeof ICONS, title: string, slot: string):
|
||||
return button;
|
||||
};
|
||||
|
||||
const applyCodeBlockWrapState = (wrapper: HTMLElement, enabled: boolean, labels: DecorateLabels): void => {
|
||||
const body = wrapper.querySelector<HTMLElement>('[data-md-code-body]');
|
||||
const pre = wrapper.querySelector<HTMLElement>('pre');
|
||||
const code = wrapper.querySelector<HTMLElement>('pre code');
|
||||
const wrapButton = wrapper.querySelector<HTMLButtonElement>('[data-md-action="toggle-code-wrap"]');
|
||||
wrapper.setAttribute('data-code-wrap', enabled ? 'true' : 'false');
|
||||
body?.classList.toggle('overflow-x-auto', !enabled);
|
||||
body?.classList.toggle('overflow-x-hidden', enabled);
|
||||
pre?.classList.toggle('whitespace-pre-wrap', enabled);
|
||||
pre?.classList.toggle('break-words', enabled);
|
||||
code?.classList.toggle('whitespace-pre-wrap', enabled);
|
||||
code?.classList.toggle('break-words', enabled);
|
||||
if (wrapButton) {
|
||||
const title = enabled ? labels.disableCodeWrap : labels.enableCodeWrap;
|
||||
wrapButton.setAttribute('title', title);
|
||||
wrapButton.setAttribute('aria-label', title);
|
||||
wrapButton.classList.toggle('text-foreground', enabled);
|
||||
wrapButton.classList.toggle('opacity-100', enabled);
|
||||
wrapButton.classList.toggle('text-muted-foreground', !enabled);
|
||||
wrapButton.classList.toggle('opacity-65', !enabled);
|
||||
wrapButton.setAttribute('aria-pressed', enabled ? 'true' : 'false');
|
||||
}
|
||||
};
|
||||
|
||||
export const applyMarkdownCodeBlockWrapState = (root: HTMLElement, enabled: boolean, labels: DecorateLabels): void => {
|
||||
const wrappers = root.querySelectorAll<HTMLElement>('[data-component="markdown-code"]');
|
||||
for (const wrapper of Array.from(wrappers)) {
|
||||
applyCodeBlockWrapState(wrapper, enabled, labels);
|
||||
}
|
||||
};
|
||||
|
||||
const flashCopied = (button: HTMLButtonElement, copiedTitle: string, restore: keyof typeof ICONS, restoreTitle: string): void => {
|
||||
setHtml(button, ICONS.check);
|
||||
button.setAttribute('title', copiedTitle);
|
||||
@@ -78,7 +114,7 @@ const decorateInlineCode = (root: HTMLElement): void => {
|
||||
}
|
||||
};
|
||||
|
||||
const decorateCodeBlocks = (root: HTMLElement, labels: DecorateLabels): void => {
|
||||
const decorateCodeBlocks = (root: HTMLElement, ctx: DecorateContext): void => {
|
||||
const blocks = root.querySelectorAll<HTMLPreElement>('pre');
|
||||
for (const pre of Array.from(blocks)) {
|
||||
// Skip mermaid placeholders (handled separately).
|
||||
@@ -104,11 +140,17 @@ const decorateCodeBlocks = (root: HTMLElement, labels: DecorateLabels): void =>
|
||||
const langLabel = document.createElement('span');
|
||||
langLabel.className = 'font-mono text-[13px] text-muted-foreground';
|
||||
langLabel.textContent = language;
|
||||
const copyBtn = makeIconButton('copy', labels.copy, 'copy-code');
|
||||
const copyBtn = makeIconButton('copy', ctx.labels.copy, 'copy-code');
|
||||
const wrapBtn = makeIconButton('textWrap', ctx.codeBlockLineWrap ? ctx.labels.disableCodeWrap : ctx.labels.enableCodeWrap, 'toggle-code-wrap');
|
||||
header.appendChild(langLabel);
|
||||
header.appendChild(copyBtn);
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'flex items-center gap-1';
|
||||
actions.appendChild(wrapBtn);
|
||||
actions.appendChild(copyBtn);
|
||||
header.appendChild(actions);
|
||||
|
||||
const body = document.createElement('div');
|
||||
body.setAttribute('data-md-code-body', '');
|
||||
body.className = 'px-3 py-2.5 overflow-x-auto';
|
||||
|
||||
parent.replaceChild(wrapper, pre);
|
||||
@@ -117,6 +159,7 @@ const decorateCodeBlocks = (root: HTMLElement, labels: DecorateLabels): void =>
|
||||
body.appendChild(pre);
|
||||
wrapper.appendChild(header);
|
||||
wrapper.appendChild(body);
|
||||
applyCodeBlockWrapState(wrapper, ctx.codeBlockLineWrap, ctx.labels);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -332,7 +375,7 @@ const decorateLinks = (root: HTMLElement, ctx: DecorateContext): void => {
|
||||
export const decorateMarkdown = (root: HTMLElement, ctx: DecorateContext): void => {
|
||||
decorateInlineCode(root);
|
||||
decorateMermaid(root, ctx);
|
||||
decorateCodeBlocks(root, ctx.labels);
|
||||
decorateCodeBlocks(root, ctx);
|
||||
decorateTables(root, ctx.labels);
|
||||
decorateLinks(root, ctx);
|
||||
};
|
||||
@@ -387,6 +430,12 @@ export const attachMarkdownInteractions = (
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'toggle-code-wrap') {
|
||||
event.preventDefault();
|
||||
ctx.onToggleCodeBlockLineWrap?.();
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle table menus
|
||||
if (action === 'table-copy-toggle' || action === 'table-download-toggle') {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -144,7 +144,7 @@ const VisualSectionContent: React.FC = () => {
|
||||
|
||||
// Chat section: User message rendering, Diff layout, Mobile status bar, Show reasoning traces, Follow-up behavior, Persist draft
|
||||
const ChatSectionContent: React.FC = () => {
|
||||
return <OpenChamberVisualSettings visibleSettings={['sessionAssist', 'chatRenderMode', 'messageTransport', 'activityRenderMode', 'userMessageRendering', 'mermaidRendering', 'reasoning', 'showToolFileIcons', 'showTurnChangedFiles', 'expandedTools', 'collapsibleUserMessages', 'stickyUserHeader', 'wideChatLayout', 'splitAssistantMessageActions', 'diffLayout', 'dotfiles', 'fileViewerPreview', 'followUpBehavior', 'persistDraft', 'inputSpellcheck']} />;
|
||||
return <OpenChamberVisualSettings visibleSettings={['sessionAssist', 'chatRenderMode', 'messageTransport', 'activityRenderMode', 'userMessageRendering', 'mermaidRendering', 'reasoning', 'showToolFileIcons', 'showTurnChangedFiles', 'expandedTools', 'collapsibleUserMessages', 'stickyUserHeader', 'wideChatLayout', 'codeBlockLineWrap', 'splitAssistantMessageActions', 'diffLayout', 'dotfiles', 'fileViewerPreview', 'followUpBehavior', 'persistDraft', 'inputSpellcheck']} />;
|
||||
};
|
||||
|
||||
// Sessions section: Default model & agent, Session retention
|
||||
|
||||
@@ -245,7 +245,7 @@ const normalizeUserMessageRenderingMode = (mode: unknown): 'markdown' | 'plain'
|
||||
return mode === 'markdown' ? 'markdown' : 'plain';
|
||||
};
|
||||
|
||||
type VisibleSetting = 'sessionAssist' | 'theme' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'collapsibleUserMessages' | 'stickyUserHeader' | 'wideChatLayout' | 'splitAssistantMessageActions' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'fileViewerPreview' | 'reasoning' | 'showToolFileIcons' | 'showTurnChangedFiles' | 'expandedTools' | 'followUpBehavior' | 'terminalQuickKeys' | 'fileEditorKeymap' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage' | 'expandedEditorToolbar';
|
||||
type VisibleSetting = 'sessionAssist' | 'theme' | 'pwaInstallName' | 'pwaOrientation' | 'mobileKeyboardMode' | 'timeFormat' | 'weekStart' | 'fontSize' | 'terminalFontSize' | 'spacing' | 'inputBarOffset' | 'mermaidRendering' | 'userMessageRendering' | 'chatRenderMode' | 'messageTransport' | 'activityRenderMode' | 'collapsibleUserMessages' | 'stickyUserHeader' | 'wideChatLayout' | 'codeBlockLineWrap' | 'splitAssistantMessageActions' | 'diffLayout' | 'mobileStatusBar' | 'dotfiles' | 'fileViewerPreview' | 'reasoning' | 'showToolFileIcons' | 'showTurnChangedFiles' | 'expandedTools' | 'followUpBehavior' | 'terminalQuickKeys' | 'fileEditorKeymap' | 'persistDraft' | 'inputSpellcheck' | 'reportUsage' | 'expandedEditorToolbar';
|
||||
|
||||
interface OpenChamberVisualSettingsProps {
|
||||
/** Which settings to show. If undefined, shows all. */
|
||||
@@ -279,6 +279,8 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
const setExpandedEditorToolbar = useUIStore(state => state.setExpandedEditorToolbar);
|
||||
const wideChatLayoutEnabled = useUIStore(state => state.wideChatLayoutEnabled);
|
||||
const setWideChatLayoutEnabled = useUIStore(state => state.setWideChatLayoutEnabled);
|
||||
const codeBlockLineWrap = useUIStore(state => state.codeBlockLineWrap);
|
||||
const setCodeBlockLineWrap = useUIStore(state => state.setCodeBlockLineWrap);
|
||||
const chatRenderMode = useUIStore(state => state.chatRenderMode);
|
||||
const setChatRenderMode = useUIStore(state => state.setChatRenderMode);
|
||||
const activityRenderMode = useUIStore(state => state.activityRenderMode);
|
||||
@@ -561,6 +563,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
|| shouldShow('collapsibleUserMessages')
|
||||
|| shouldShow('stickyUserHeader')
|
||||
|| shouldShow('wideChatLayout')
|
||||
|| shouldShow('codeBlockLineWrap')
|
||||
|| shouldShow('splitAssistantMessageActions')
|
||||
|| shouldShow('diffLayout')
|
||||
|| shouldShow('dotfiles')
|
||||
@@ -1779,7 +1782,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(shouldShow('sessionAssist') || shouldShow('collapsibleUserMessages') || shouldShow('stickyUserHeader') || shouldShow('wideChatLayout') || shouldShow('splitAssistantMessageActions') || shouldShow('dotfiles') || shouldShow('fileViewerPreview') || shouldShow('persistDraft') || shouldShow('showToolFileIcons') || shouldShow('showTurnChangedFiles') || (!isMobile && shouldShow('inputSpellcheck')) || shouldShow('reasoning')) && (
|
||||
{(shouldShow('sessionAssist') || shouldShow('collapsibleUserMessages') || shouldShow('stickyUserHeader') || shouldShow('wideChatLayout') || shouldShow('codeBlockLineWrap') || shouldShow('splitAssistantMessageActions') || shouldShow('dotfiles') || shouldShow('fileViewerPreview') || shouldShow('persistDraft') || shouldShow('showToolFileIcons') || shouldShow('showTurnChangedFiles') || (!isMobile && shouldShow('inputSpellcheck')) || shouldShow('reasoning')) && (
|
||||
<section className="p-2 space-y-0.5">
|
||||
{shouldShow('sessionAssist') && (
|
||||
<>
|
||||
@@ -1980,6 +1983,30 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shouldShow('codeBlockLineWrap') && (
|
||||
<div
|
||||
data-settings-item="chat.code-block-line-wrap"
|
||||
className="group flex cursor-pointer items-center gap-2 py-0.5"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-pressed={codeBlockLineWrap}
|
||||
onClick={() => setCodeBlockLineWrap(!codeBlockLineWrap)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === ' ' || event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
setCodeBlockLineWrap(!codeBlockLineWrap);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
checked={codeBlockLineWrap}
|
||||
onChange={setCodeBlockLineWrap}
|
||||
ariaLabel={t('settings.openchamber.visual.field.codeBlockLineWrapAria')}
|
||||
/>
|
||||
<span className="typography-ui-label text-foreground">{t('settings.openchamber.visual.field.codeBlockLineWrap')}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shouldShow('showToolFileIcons') && (
|
||||
<div
|
||||
data-settings-item="chat.tool-file-icons"
|
||||
|
||||
@@ -645,6 +645,7 @@ export interface SettingsPayload {
|
||||
showOpenCodeUpdateNotifications?: boolean;
|
||||
openCodeUpdateToastDismissedVersion?: string;
|
||||
showToolFileIcons?: boolean;
|
||||
codeBlockLineWrap?: boolean;
|
||||
showTurnChangedFiles?: boolean;
|
||||
showExpandedBashTools?: boolean;
|
||||
showExpandedEditTools?: boolean;
|
||||
|
||||
@@ -136,6 +136,7 @@ export type DesktopSettings = {
|
||||
showOpenCodeUpdateNotifications?: boolean;
|
||||
openCodeUpdateToastDismissedVersion?: string;
|
||||
showToolFileIcons?: boolean;
|
||||
codeBlockLineWrap?: boolean;
|
||||
showTurnChangedFiles?: boolean;
|
||||
showExpandedBashTools?: boolean;
|
||||
showExpandedEditTools?: boolean;
|
||||
|
||||
@@ -1737,6 +1737,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.visual.field.expandedEditorToolbar': 'Always show editor toolbar (docked under the file tabs)',
|
||||
'settings.openchamber.visual.field.wideChatLayoutAria': 'Wide chat layout',
|
||||
'settings.openchamber.visual.field.wideChatLayout': 'Wide Chat Layout',
|
||||
'settings.openchamber.visual.field.codeBlockLineWrapAria': 'Wrap code block lines',
|
||||
'settings.openchamber.visual.field.codeBlockLineWrap': 'Wrap Code Block Lines',
|
||||
'settings.openchamber.visual.field.showSplitAssistantMessageActionsAria': 'Inline assistant actions',
|
||||
'settings.openchamber.visual.field.showSplitAssistantMessageActions': 'Inline Assistant Actions',
|
||||
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': 'Show Copy Answer, Save as image, and Read aloud on assistant text blocks that appear before later tool calls in the same response.',
|
||||
|
||||
@@ -2670,6 +2670,8 @@ export const dict = {
|
||||
'sortableTabsStrip.aria.tabs': 'Tabs',
|
||||
'openChamberLogo.aria.logo': 'OpenChamber logo',
|
||||
'markdownRenderer.table.actions.copyTitle': 'Copy table',
|
||||
'markdownRenderer.code.actions.enableWrapTitle': 'Enable line wrap',
|
||||
'markdownRenderer.code.actions.disableWrapTitle': 'Disable line wrap',
|
||||
'markdownRenderer.table.actions.downloadTitle': 'Download table',
|
||||
'markdownRenderer.table.toast.downloadedAsFormat': 'Table downloaded as {format}',
|
||||
'markdownRenderer.mermaid.actions.copyTitle': 'Copy',
|
||||
|
||||
@@ -1708,6 +1708,8 @@ export const settingsDict = {
|
||||
"settings.openchamber.visual.field.showSplitAssistantMessageActions": "Acciones en línea del asistente",
|
||||
"settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip": "Muestra Copiar respuesta, Guardar como imagen y Leer en voz alta en bloques de texto del asistente que aparecen antes de llamadas a herramientas posteriores dentro de la misma respuesta.",
|
||||
"settings.openchamber.visual.field.showToolFileIconsAria": "Mostrar iconos de archivos de herramientas",
|
||||
"settings.openchamber.visual.field.codeBlockLineWrapAria": "Ajustar líneas de bloques de código",
|
||||
"settings.openchamber.visual.field.codeBlockLineWrap": "Ajustar líneas de bloques de código",
|
||||
"settings.openchamber.visual.field.showToolFileIcons": "Mostrar iconos de archivos de herramientas",
|
||||
"settings.openchamber.visual.field.showTurnChangedFilesAria": "Mostrar archivos cambiados en turnos completados",
|
||||
"settings.openchamber.visual.field.showTurnChangedFiles": "Mostrar archivos cambiados en turnos completados",
|
||||
|
||||
@@ -2636,6 +2636,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
"sortableTabsStrip.aria.tabs": "Pestañas",
|
||||
"openChamberLogo.aria.logo": "Logo de OpenChamber",
|
||||
"markdownRenderer.table.actions.copyTitle": "Copiar tabla",
|
||||
"markdownRenderer.code.actions.enableWrapTitle": "Activar ajuste de línea",
|
||||
"markdownRenderer.code.actions.disableWrapTitle": "Desactivar ajuste de línea",
|
||||
"markdownRenderer.table.actions.downloadTitle": "Descargar tabla",
|
||||
"markdownRenderer.table.toast.downloadedAsFormat": "Tabla descargada como {format}",
|
||||
"markdownRenderer.mermaid.actions.copyTitle": "Copiar",
|
||||
|
||||
@@ -1644,6 +1644,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.visual.field.showSplitAssistantMessageActions': 'Actions intégrées de l\'assistant',
|
||||
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': 'Afficher Copier la réponse, Enregistrer en image et Lire à haute voix sur les blocs de texte de l\'assistant qui apparaissent avant les appels d\'outil suivants dans la même réponse.',
|
||||
'settings.openchamber.visual.field.showToolFileIconsAria': 'Afficher les icônes des fichiers d\'outils',
|
||||
'settings.openchamber.visual.field.codeBlockLineWrapAria': 'Renvoyer à la ligne les blocs de code',
|
||||
'settings.openchamber.visual.field.codeBlockLineWrap': 'Renvoyer à la ligne les blocs de code',
|
||||
'settings.openchamber.visual.field.showToolFileIcons': 'Afficher les icônes des fichiers d\'outils',
|
||||
'settings.openchamber.visual.field.showDotfilesAria': 'Afficher les fichiers de points',
|
||||
'settings.openchamber.visual.field.showDotfiles': 'Afficher les fichiers cachés',
|
||||
|
||||
@@ -2440,6 +2440,8 @@ export const dict = {
|
||||
'sortableTabsStrip.aria.tabs': 'Onglets',
|
||||
'openChamberLogo.aria.logo': 'Logo OpenChamber',
|
||||
'markdownRenderer.table.actions.copyTitle': 'Copier le tableau',
|
||||
'markdownRenderer.code.actions.enableWrapTitle': 'Activer le retour à la ligne',
|
||||
'markdownRenderer.code.actions.disableWrapTitle': 'Désactiver le retour à la ligne',
|
||||
'markdownRenderer.table.actions.downloadTitle': 'Télécharger le tableau',
|
||||
'markdownRenderer.table.toast.downloadedAsFormat': 'Table téléchargée sous le nom {format}',
|
||||
'markdownRenderer.mermaid.actions.copyTitle': 'Copie',
|
||||
|
||||
@@ -1741,6 +1741,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.visual.field.showSplitAssistantMessageActions': 'インラインアシスタントアクション',
|
||||
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': '同じ応答内の後のツール呼び出しの前に表示されるアシスタントテキストブロックに、回答コピー、画像保存、読み上げを表示します。',
|
||||
'settings.openchamber.visual.field.showToolFileIconsAria': 'ツールファイルアイコンを表示',
|
||||
'settings.openchamber.visual.field.codeBlockLineWrapAria': 'コードブロックの行を折り返す',
|
||||
'settings.openchamber.visual.field.codeBlockLineWrap': 'コードブロックの行を折り返す',
|
||||
'settings.openchamber.visual.field.showToolFileIcons': 'ツールファイルアイコンを表示',
|
||||
'settings.openchamber.visual.field.showTurnChangedFilesAria': '完了したターンの変更ファイルを表示',
|
||||
'settings.openchamber.visual.field.showTurnChangedFiles': '完了したターンの変更ファイルを表示',
|
||||
|
||||
@@ -2666,6 +2666,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
'sortableTabsStrip.aria.tabs': 'タブ',
|
||||
'openChamberLogo.aria.logo': 'OpenChamberロゴ',
|
||||
'markdownRenderer.table.actions.copyTitle': 'テーブルをコピー',
|
||||
'markdownRenderer.code.actions.enableWrapTitle': '行折り返しを有効にする',
|
||||
'markdownRenderer.code.actions.disableWrapTitle': '行折り返しを無効にする',
|
||||
'markdownRenderer.table.actions.downloadTitle': 'テーブルをダウンロード',
|
||||
'markdownRenderer.table.toast.downloadedAsFormat': 'テーブルを{format}としてダウンロードしました',
|
||||
'markdownRenderer.mermaid.actions.copyTitle': 'コピー',
|
||||
|
||||
@@ -1708,6 +1708,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.visual.field.showSplitAssistantMessageActions': '인라인 어시스턴트 작업',
|
||||
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': '같은 응답에서 이후 도구 호출 앞에 나타나는 어시스턴트 텍스트 블록에 답변 복사, 이미지로 저장, 소리 내어 읽기 작업을 표시합니다.',
|
||||
'settings.openchamber.visual.field.showToolFileIconsAria': '도구 파일 아이콘 표시',
|
||||
'settings.openchamber.visual.field.codeBlockLineWrapAria': '코드 블록 줄 바꿈',
|
||||
'settings.openchamber.visual.field.codeBlockLineWrap': '코드 블록 줄 바꿈',
|
||||
'settings.openchamber.visual.field.showToolFileIcons': '도구 파일 아이콘 표시',
|
||||
'settings.openchamber.visual.field.showTurnChangedFilesAria': '완료된 턴의 변경 파일 표시',
|
||||
'settings.openchamber.visual.field.showTurnChangedFiles': '완료된 턴의 변경 파일 표시',
|
||||
|
||||
@@ -2670,6 +2670,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
'sortableTabsStrip.aria.tabs': '탭',
|
||||
'openChamberLogo.aria.logo': 'OpenChamber 로고',
|
||||
'markdownRenderer.table.actions.copyTitle': '표 복사',
|
||||
'markdownRenderer.code.actions.enableWrapTitle': '줄 바꿈 켜기',
|
||||
'markdownRenderer.code.actions.disableWrapTitle': '줄 바꿈 끄기',
|
||||
'markdownRenderer.table.actions.downloadTitle': '표 다운로드',
|
||||
'markdownRenderer.table.toast.downloadedAsFormat': '{format} 형식으로 다운로드됨',
|
||||
'markdownRenderer.mermaid.actions.copyTitle': '복사',
|
||||
|
||||
@@ -989,6 +989,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': 'Pokaż Kopiuj odpowiedź, Zapisz jako obraz i Odczytaj na głos w blokach tekstu asystenta, które pojawiają się przed późniejszymi wywołaniami narzędzi w tej samej odpowiedzi.',
|
||||
'settings.openchamber.visual.field.showToolFileIcons': 'Pokaż ikony plików narzędzi',
|
||||
'settings.openchamber.visual.field.showToolFileIconsAria': 'Pokaż ikony plików narzędzi',
|
||||
'settings.openchamber.visual.field.codeBlockLineWrapAria': 'Zawijaj wiersze bloków kodu',
|
||||
'settings.openchamber.visual.field.codeBlockLineWrap': 'Zawijaj wiersze bloków kodu',
|
||||
'settings.openchamber.visual.field.showTurnChangedFiles': 'Pokaż zmienione pliki w ukończonych turach',
|
||||
'settings.openchamber.visual.field.showTurnChangedFilesAria': 'Pokaż zmienione pliki w ukończonych turach',
|
||||
'settings.openchamber.visual.field.spacingDensity': 'Gęstość odstępów',
|
||||
|
||||
@@ -2161,6 +2161,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
'markdownRenderer.mermaid.actions.downloadSvgTitle': 'Pobierz SVG',
|
||||
'markdownRenderer.mermaid.toast.downloadFailed': 'Nie udało się pobrać diagramu',
|
||||
'markdownRenderer.table.actions.copyTitle': 'Kopiuj tabelę',
|
||||
'markdownRenderer.code.actions.enableWrapTitle': 'Włącz zawijanie wierszy',
|
||||
'markdownRenderer.code.actions.disableWrapTitle': 'Wyłącz zawijanie wierszy',
|
||||
'markdownRenderer.table.actions.downloadTitle': 'Pobierz tabelę',
|
||||
'markdownRenderer.table.toast.downloadedAsFormat': 'Tabela została pobrana jako {format}',
|
||||
'mcpDropdown.actions.openAria': 'Serwery MCP',
|
||||
|
||||
@@ -1708,6 +1708,8 @@ export const settingsDict = {
|
||||
"settings.openchamber.visual.field.showSplitAssistantMessageActions": "Ações inline do assistente",
|
||||
"settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip": "Mostra Copiar resposta, Salvar como imagem e Ler em voz alta em blocos de texto do assistente que aparecem antes de chamadas de ferramenta posteriores na mesma resposta.",
|
||||
"settings.openchamber.visual.field.showToolFileIconsAria": "Mostrar ícones de arquivos de ferramentas",
|
||||
"settings.openchamber.visual.field.codeBlockLineWrapAria": "Quebrar linhas de blocos de código",
|
||||
"settings.openchamber.visual.field.codeBlockLineWrap": "Quebrar linhas de blocos de código",
|
||||
"settings.openchamber.visual.field.showToolFileIcons": "Mostrar ícones de arquivos de ferramentas",
|
||||
"settings.openchamber.visual.field.showTurnChangedFilesAria": "Mostrar arquivos alterados em turnos concluídos",
|
||||
"settings.openchamber.visual.field.showTurnChangedFiles": "Mostrar arquivos alterados em turnos concluídos",
|
||||
|
||||
@@ -2636,6 +2636,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
"sortableTabsStrip.aria.tabs": "Abas",
|
||||
"openChamberLogo.aria.logo": "Logo do OpenChamber",
|
||||
"markdownRenderer.table.actions.copyTitle": "Copiar tabela",
|
||||
"markdownRenderer.code.actions.enableWrapTitle": "Ativar quebra de linha",
|
||||
"markdownRenderer.code.actions.disableWrapTitle": "Desativar quebra de linha",
|
||||
"markdownRenderer.table.actions.downloadTitle": "Baixar tabela",
|
||||
"markdownRenderer.table.toast.downloadedAsFormat": "Tabela baixada como {format}",
|
||||
"markdownRenderer.mermaid.actions.copyTitle": "Copiar",
|
||||
|
||||
@@ -1708,6 +1708,8 @@ export const settingsDict = {
|
||||
"settings.openchamber.visual.field.showSplitAssistantMessageActions": "Вбудовані дії асистента",
|
||||
"settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip": "Показує «Скопіювати відповідь», «Зберегти як зображення» та озвучення для текстових блоків асистента, які з’являються перед пізнішими викликами інструментів у тій самій відповіді.",
|
||||
"settings.openchamber.visual.field.showToolFileIconsAria": "Показати значки файлів інструментів",
|
||||
"settings.openchamber.visual.field.codeBlockLineWrapAria": "Переносити рядки блоків коду",
|
||||
"settings.openchamber.visual.field.codeBlockLineWrap": "Переносити рядки блоків коду",
|
||||
"settings.openchamber.visual.field.showToolFileIcons": "Показати значки файлів інструментів",
|
||||
"settings.openchamber.visual.field.showTurnChangedFilesAria": "Показати змінені файли для завершених ходів",
|
||||
"settings.openchamber.visual.field.showTurnChangedFiles": "Показати змінені файли для завершених ходів",
|
||||
|
||||
@@ -2636,6 +2636,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
"sortableTabsStrip.aria.tabs": "Вкладки",
|
||||
"openChamberLogo.aria.logo": "Логотип OpenChamber",
|
||||
"markdownRenderer.table.actions.copyTitle": "Скопіювати таблицю",
|
||||
"markdownRenderer.code.actions.enableWrapTitle": "Увімкнути перенесення рядків",
|
||||
"markdownRenderer.code.actions.disableWrapTitle": "Вимкнути перенесення рядків",
|
||||
"markdownRenderer.table.actions.downloadTitle": "Завантажити таблицю",
|
||||
"markdownRenderer.table.toast.downloadedAsFormat": "Таблицю завантажено як {format}",
|
||||
"markdownRenderer.mermaid.actions.copyTitle": "Копіювати",
|
||||
|
||||
@@ -1708,6 +1708,8 @@ export const settingsDict = {
|
||||
'settings.openchamber.visual.field.showSplitAssistantMessageActions': '内联助手操作',
|
||||
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': '在同一条回复中,如果助手文本块后面还有工具调用,则显示“复制回答”“保存为图片”和“朗读”操作。',
|
||||
'settings.openchamber.visual.field.showToolFileIconsAria': '显示工具文件图标',
|
||||
'settings.openchamber.visual.field.codeBlockLineWrapAria': '代码块自动换行',
|
||||
'settings.openchamber.visual.field.codeBlockLineWrap': '代码块自动换行',
|
||||
'settings.openchamber.visual.field.showToolFileIcons': '显示工具文件图标',
|
||||
'settings.openchamber.visual.field.showTurnChangedFilesAria': '显示已完成回合的更改文件',
|
||||
'settings.openchamber.visual.field.showTurnChangedFiles': '显示已完成回合的更改文件',
|
||||
|
||||
@@ -2636,6 +2636,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
'sortableTabsStrip.aria.tabs': '标签页',
|
||||
'openChamberLogo.aria.logo': 'OpenChamber 标志',
|
||||
'markdownRenderer.table.actions.copyTitle': '复制表格',
|
||||
'markdownRenderer.code.actions.enableWrapTitle': '启用自动换行',
|
||||
'markdownRenderer.code.actions.disableWrapTitle': '禁用自动换行',
|
||||
'markdownRenderer.table.actions.downloadTitle': '下载表格',
|
||||
'markdownRenderer.table.toast.downloadedAsFormat': '表格已下载为 {format}',
|
||||
'markdownRenderer.mermaid.actions.copyTitle': '复制',
|
||||
|
||||
@@ -1624,6 +1624,8 @@
|
||||
'settings.openchamber.visual.field.showSplitAssistantMessageActions': '行內助理操作',
|
||||
'settings.openchamber.visual.field.showSplitAssistantMessageActionsTooltip': '在同一則回覆中,如果助理文字區塊後方還有工具呼叫,則顯示「複製回答」「儲存為圖片」和「朗讀」操作。',
|
||||
'settings.openchamber.visual.field.showToolFileIconsAria': '顯示工具檔案圖示',
|
||||
'settings.openchamber.visual.field.codeBlockLineWrapAria': '程式碼區塊自動換行',
|
||||
'settings.openchamber.visual.field.codeBlockLineWrap': '程式碼區塊自動換行',
|
||||
'settings.openchamber.visual.field.showToolFileIcons': '顯示工具檔案圖示',
|
||||
'settings.openchamber.visual.field.showTurnChangedFilesAria': '顯示已完成回合的變更檔案',
|
||||
'settings.openchamber.visual.field.showTurnChangedFiles': '顯示已完成回合的變更檔案',
|
||||
|
||||
@@ -2633,6 +2633,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
'sortableTabsStrip.aria.tabs': '分頁',
|
||||
'openChamberLogo.aria.logo': 'OpenChamber 標誌',
|
||||
'markdownRenderer.table.actions.copyTitle': '複製表格',
|
||||
'markdownRenderer.code.actions.enableWrapTitle': '啟用自動換行',
|
||||
'markdownRenderer.code.actions.disableWrapTitle': '停用自動換行',
|
||||
'markdownRenderer.table.actions.downloadTitle': '下載表格',
|
||||
'markdownRenderer.table.toast.downloadedAsFormat': '表格已下載為 {format}',
|
||||
'markdownRenderer.mermaid.actions.copyTitle': '複製',
|
||||
|
||||
@@ -507,6 +507,9 @@ const applyDesktopUiPreferences = (settings: DesktopSettings) => {
|
||||
if (typeof settings.showToolFileIcons === 'boolean' && settings.showToolFileIcons !== store.showToolFileIcons) {
|
||||
store.setShowToolFileIcons(settings.showToolFileIcons);
|
||||
}
|
||||
if (typeof settings.codeBlockLineWrap === 'boolean' && settings.codeBlockLineWrap !== store.codeBlockLineWrap) {
|
||||
store.setCodeBlockLineWrap(settings.codeBlockLineWrap);
|
||||
}
|
||||
if (typeof settings.showTurnChangedFiles === 'boolean' && settings.showTurnChangedFiles !== store.showTurnChangedFiles) {
|
||||
store.setShowTurnChangedFiles(settings.showTurnChangedFiles);
|
||||
}
|
||||
@@ -1044,6 +1047,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
|
||||
if (typeof candidate.showToolFileIcons === 'boolean') {
|
||||
result.showToolFileIcons = candidate.showToolFileIcons;
|
||||
}
|
||||
if (typeof candidate.codeBlockLineWrap === 'boolean') {
|
||||
result.codeBlockLineWrap = candidate.codeBlockLineWrap;
|
||||
}
|
||||
if (typeof candidate.showTurnChangedFiles === 'boolean') {
|
||||
result.showTurnChangedFiles = candidate.showTurnChangedFiles;
|
||||
}
|
||||
|
||||
@@ -203,6 +203,12 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [
|
||||
titleKey: 'settings.openchamber.visual.field.wideChatLayout',
|
||||
keywords: ['layout', 'wide', 'messages'],
|
||||
},
|
||||
{
|
||||
id: 'chat.code-block-line-wrap',
|
||||
page: 'chat',
|
||||
titleKey: 'settings.openchamber.visual.field.codeBlockLineWrap',
|
||||
keywords: ['code', 'wrap', 'line wrap', 'markdown'],
|
||||
},
|
||||
{
|
||||
id: 'chat.inline-assistant-actions',
|
||||
page: 'chat',
|
||||
|
||||
@@ -639,6 +639,7 @@ interface UIStore {
|
||||
showOpenCodeUpdateNotifications: boolean;
|
||||
inputSpellcheckEnabled: boolean;
|
||||
wideChatLayoutEnabled: boolean;
|
||||
codeBlockLineWrap: boolean;
|
||||
showToolFileIcons: boolean;
|
||||
showTurnChangedFiles: boolean;
|
||||
showExpandedBashTools: boolean;
|
||||
@@ -787,6 +788,7 @@ interface UIStore {
|
||||
setShowOpenCodeUpdateNotifications: (value: boolean) => void;
|
||||
setInputSpellcheckEnabled: (value: boolean) => void;
|
||||
setWideChatLayoutEnabled: (value: boolean) => void;
|
||||
setCodeBlockLineWrap: (value: boolean) => void;
|
||||
setShowToolFileIcons: (value: boolean) => void;
|
||||
setShowTurnChangedFiles: (value: boolean) => void;
|
||||
setShowExpandedBashTools: (value: boolean) => void;
|
||||
@@ -929,6 +931,7 @@ export const useUIStore = create<UIStore>()(
|
||||
showOpenCodeUpdateNotifications: true,
|
||||
inputSpellcheckEnabled: false,
|
||||
wideChatLayoutEnabled: false,
|
||||
codeBlockLineWrap: true,
|
||||
showToolFileIcons: true,
|
||||
showTurnChangedFiles: false,
|
||||
showExpandedBashTools: false,
|
||||
@@ -2043,6 +2046,9 @@ export const useUIStore = create<UIStore>()(
|
||||
setWideChatLayoutEnabled: (value) => {
|
||||
set({ wideChatLayoutEnabled: value });
|
||||
},
|
||||
setCodeBlockLineWrap: (value) => {
|
||||
set({ codeBlockLineWrap: value });
|
||||
},
|
||||
setShowToolFileIcons: (value) => {
|
||||
set({ showToolFileIcons: value });
|
||||
},
|
||||
@@ -2306,6 +2312,7 @@ export const useUIStore = create<UIStore>()(
|
||||
showOpenCodeUpdateNotifications: state.showOpenCodeUpdateNotifications,
|
||||
inputSpellcheckEnabled: state.inputSpellcheckEnabled,
|
||||
wideChatLayoutEnabled: state.wideChatLayoutEnabled,
|
||||
codeBlockLineWrap: state.codeBlockLineWrap,
|
||||
showToolFileIcons: state.showToolFileIcons,
|
||||
showTurnChangedFiles: state.showTurnChangedFiles,
|
||||
showExpandedBashTools: state.showExpandedBashTools,
|
||||
|
||||
Reference in New Issue
Block a user