feat(grid): flatten multiline cell text

This commit is contained in:
ptma authored and GitHub committed 2026-08-13 11:40:22 +08:00
1 parent 01e67ee9a4
commit ee08f356e9
17 files changed
+97 -30

No files matched your search

@@ -321,6 +321,7 @@ const editQueryResultMaxRows = ref(settingsStore.editorSettings.queryResultMaxRo
const editInfiniteScroll = ref(settingsStore.editorSettings.infiniteScroll);
const editRegexMaxMatchCount = ref(settingsStore.editorSettings.regexMaxMatchCount);
const editAutoCalculateTotalRows = ref(settingsStore.editorSettings.autoCalculateTotalRows);
const editFlatteningMultiLineText = ref(settingsStore.editorSettings.flatteningMultiLineText);
const editTableColumnTemplateRows = ref<TableColumnTemplateGridRow[]>(tableColumnTemplateRowsFromSettings(settingsStore.editorSettings.tableColumnTemplateFields));
const editTableColumnTemplateDatabaseType = ref<DatabaseType>(TABLE_COLUMN_TEMPLATE_DATABASE_TYPES[0] ?? "mysql");
const editSqlVariableSyntaxOverrides = ref<SqlVariableSyntaxOverrides>(normalizeSqlVariableSyntaxOverrides(settingsStore.editorSettings.sqlVariableSyntaxOverrides));
@@ -488,6 +489,7 @@ function currentEditorSettingsDraft(): EditorSettingsDraft {
compactColumnHeaderActions: editCompactColumnHeaderActions.value,
dataGridQuickEntry: editDataGridQuickEntry.value,
dataGridAutoTransposeSingleRow: editDataGridAutoTransposeSingleRow.value,
flatteningMultiLineText: editFlatteningMultiLineText.value,
pageSize: editPageSize.value,
tableOpenPageSize: editTableOpenPageSize.value,
queryResultMaxRowsEnabled: editQueryResultMaxRowsEnabled.value,
@@ -761,6 +763,7 @@ function syncEditorSettingsDraftFromStore() {
editCompactColumnHeaderActions.value = settingsStore.editorSettings.compactColumnHeaderActions;
editDataGridQuickEntry.value = settingsStore.editorSettings.dataGridQuickEntry;
editDataGridAutoTransposeSingleRow.value = settingsStore.editorSettings.dataGridAutoTransposeSingleRow;
editFlatteningMultiLineText.value = settingsStore.editorSettings.flatteningMultiLineText;
editPageSize.value = settingsStore.editorSettings.pageSize;
editTableOpenPageSize.value = settingsStore.editorSettings.tableOpenPageSize;
editQueryResultMaxRowsEnabled.value = settingsStore.editorSettings.queryResultMaxRowsEnabled;
@@ -1006,6 +1009,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
editDataGridQuickEntry.value = DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry;
editDataGridAutoTransposeSingleRow.value = DEFAULT_EDITOR_SETTINGS.dataGridAutoTransposeSingleRow;
editFlatteningMultiLineText.value = DEFAULT_EDITOR_SETTINGS.flatteningMultiLineText;
editPageSize.value = DEFAULT_EDITOR_SETTINGS.pageSize;
editTableOpenPageSize.value = DEFAULT_EDITOR_SETTINGS.tableOpenPageSize;
editQueryResultMaxRowsEnabled.value = DEFAULT_EDITOR_SETTINGS.queryResultMaxRowsEnabled;
@@ -1074,6 +1078,7 @@ function resetAllDefaults() {
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
editDataGridQuickEntry.value = DEFAULT_EDITOR_SETTINGS.dataGridQuickEntry;
editDataGridAutoTransposeSingleRow.value = DEFAULT_EDITOR_SETTINGS.dataGridAutoTransposeSingleRow;
editFlatteningMultiLineText.value = DEFAULT_EDITOR_SETTINGS.flatteningMultiLineText;
editPageSize.value = DEFAULT_EDITOR_SETTINGS.pageSize;
editTableOpenPageSize.value = DEFAULT_EDITOR_SETTINGS.tableOpenPageSize;
editQueryResultMaxRowsEnabled.value = DEFAULT_EDITOR_SETTINGS.queryResultMaxRowsEnabled;
@@ -4877,6 +4882,17 @@ onUnmounted(() => {
</div>
<Switch id="data-grid-auto-transpose-single-row" v-model="editDataGridAutoTransposeSingleRow" />
</div>
<div class="flex items-center justify-between gap-4 rounded-md border bg-muted/20 px-3 py-2">
<div class="space-y-1">
<Label for="flattening-multi-line">
{{ t("settings.flatteningMultiLineText") }}
</Label>
<p class="text-xs text-muted-foreground">
{{ t("settings.flatteningMultiLineTextDescription") }}
</p>
</div>
<Switch id="flattening-multi-line" v-model="editFlatteningMultiLineText" />
</div>
</div>
<template v-if="!isWeb">
@@ -557,6 +557,7 @@ const dataGridSearchMode = computed(() => settingsStore.editorSettings.dataGridS
const compactDataGridToolbar = computed(() => isDataGridToolbarCompact(dataGridTopbarWidth.value, dataGridViewportWidth.value, DATA_GRID_CONDITION_TOOLBAR_MIN_WIDTH));
const infiniteScrollEnabled = computed(() => props.paginationEnabled && settingsStore.editorSettings.infiniteScroll);
const infiniteScrollMaxRows = computed(() => continuousQueryResultMaxRows(settingsStore.editorSettings.queryResultMaxRowsEnabled, settingsStore.editorSettings.queryResultMaxRows));
const flatteningMultiLineEnabled = computed(() => settingsStore.editorSettings.flatteningMultiLineText);
const expandedCellEditor = ref<{ rowId: number; col: number } | null>(null);
function headerColumnComment(column: string): string {
@@ -5840,7 +5841,7 @@ function booleanNullTextHitFromCanvasEvent(item: RowItem, hit: { rowIndex: numbe
const canvasRect = canvas?.getBoundingClientRect();
const cellRect = canvasCellViewportRect(hit.rowIndex, hit.visibleColIdx);
if (!canvasRect || !cellRect) return false;
const text = firstLineCellDisplayValue(formatCellCached(item.data[actualColIdx], actualColIdx));
const text = firstLineCellDisplayValue(formatCellCached(item.data[actualColIdx], actualColIdx), flatteningMultiLineEnabled.value);
if (!text) return false;
const textWidth = measureCellTextWidthCached(text, `italic 400 ${tableFontSize.value}px ${tableFontFamily.value}`);
if (textWidth <= 0) return false;
@@ -6105,6 +6106,7 @@ function drawCanvasGrid() {
rightAlignedActionCell: canvasRightAlignedActionCell.value,
columnIsBoolean: isBooleanGridColumn,
booleanDisplayMode: booleanDisplayMode.value,
flatteningMultiLineEnabled: flatteningMultiLineEnabled.value,
});
}
@@ -6122,6 +6124,7 @@ watch(showDataGridTopbar, () => nextTick(observeDataGridTopbarWidth), {
});
watch(columnAligns, () => scheduleCanvasDraw());
watch(booleanDisplayMode, () => scheduleCanvasDraw());
watch(flatteningMultiLineEnabled, () => scheduleCanvasDraw());
watch(colorizeDataGridCellTypes, () => scheduleCanvasDraw());
watch(
[
@@ -10284,7 +10287,7 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
<template v-if="draftCellPlaceholder(displayItems[cell.recordIndex], cell.valueIndex)">
<span class="text-muted-foreground/70 italic">{{ draftCellPlaceholder(displayItems[cell.recordIndex], cell.valueIndex) }}</span>
</template>
<template v-else>{{ firstLineCellDisplayValue(cell.display) }}</template>
<template v-else>{{ firstLineCellDisplayValue(cell.display, flatteningMultiLineEnabled) }}</template>
<div v-if="cellDetailButtonVisible(cell.recordIndex, cell.valueIndex)" class="absolute right-2 top-1/2 flex -translate-y-1/2 items-center gap-1">
<LightDropdownMenu
v-if="canQuickDownloadCellValue(cell.recordIndex, cell.valueIndex)"
@@ -11051,13 +11054,13 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
</div>
</template>
<template v-else-if="booleanCellsUseCheckbox && isBooleanGridCell(item, col.actualColIdx) && item.data[col.actualColIdx] === null && canEditCellItem(item, col.actualColIdx)">
<span class="italic text-muted-foreground cursor-pointer select-none" @click.stop="cycleBooleanGridCell(item, col.actualColIdx, $event)">{{ firstLineCellDisplayValue(formatCellCached(item.data[col.actualColIdx], col.actualColIdx)) }}</span>
<span class="italic text-muted-foreground cursor-pointer select-none" @click.stop="cycleBooleanGridCell(item, col.actualColIdx, $event)">{{ firstLineCellDisplayValue(formatCellCached(item.data[col.actualColIdx], col.actualColIdx), flatteningMultiLineEnabled) }}</span>
</template>
<template v-else>
<template v-if="draftCellPlaceholder(item, col.actualColIdx)">
<span class="text-muted-foreground/70 italic">{{ draftCellPlaceholder(item, col.actualColIdx) }}</span>
</template>
<template v-else>{{ firstLineCellDisplayValue(formatCellCached(item.data[col.actualColIdx], col.actualColIdx)) }}</template>
<template v-else>{{ firstLineCellDisplayValue(formatCellCached(item.data[col.actualColIdx], col.actualColIdx), flatteningMultiLineEnabled) }}</template>
<div v-if="cellDetailButtonVisible(item.displayIndex, col.actualColIdx)" class="absolute right-2 top-1/2 flex -translate-y-1/2 items-center gap-1">
<LightDropdownMenu
v-if="canQuickDownloadCellValue(item.displayIndex, col.actualColIdx)"
+2
View File
@@ -5621,6 +5621,8 @@ export default {
autoCalculateTotalRows: "Auto-calculate total row count",
autoCalculateTotalRowsDescription: "Run COUNT(*) automatically after each query to show the total matching rows. Off by default to keep large queries fast — calculate it on demand from the result footer.",
infiniteScrollDescription: "Automatically load the next batch when scrolling to the bottom.",
flatteningMultiLineText: "Flattening Multi-line Text",
flatteningMultiLineTextDescription: "Multi-line text merged into single line display, with line breaks shown as ¶ .",
regexMaxMatchCount: "Maximum select-all matches",
regexMaxMatchCountDescription: "Limits the number of selections created by Select All Matches; search counting remains complete (100–10000).",
tableColumnTemplateFields: "New Table Preset Fields",
+2
View File
@@ -5376,6 +5376,8 @@ export default withEnglishFallback({
autoCalculateTotalRows: "Calcular automáticamente el total de filas",
autoCalculateTotalRowsDescription: "Ejecuta COUNT(*) automáticamente tras cada consulta para mostrar el total de filas coincidentes. Desactivado por defecto para mantener rápidas las consultas grandes; puedes calcularlo cuando quieras desde el pie de resultados.",
infiniteScrollDescription: "Carga automáticamente el siguiente lote al desplazarse hasta el final.",
flatteningMultiLineText: "Aplastar texto multilínea",
flatteningMultiLineTextDescription: "El texto multilínea se fusiona en una sola línea de visualización, con los saltos de línea mostrados como ¶ .",
regexMaxMatchCount: "Máximo de coincidencias al seleccionar todas",
regexMaxMatchCountDescription: "Limita el número de selecciones creadas por Seleccionar todas las coincidencias; el conteo de búsqueda permanece completo (100–10000).",
tableColumnTemplateFields: "Campos predefinidos para tablas nuevas",
+2
View File
@@ -5376,6 +5376,8 @@ export default withEnglishFallback({
autoCalculateTotalRows: "Calcola automaticamente il totale delle righe",
autoCalculateTotalRowsDescription: "Esegue COUNT(*) automaticamente dopo ogni query per mostrare il totale delle righe corrispondenti. Disattivato per impostazione predefinita per mantenere veloci le query grandi; puoi calcolarlo all'occorrenza dal piè di pagina dei risultati.",
infiniteScrollDescription: "Carica automaticamente il blocco successivo quando scorri fino in fondo.",
flatteningMultiLineText: "Pianifica automaticamente i testi multi-riga",
flatteningMultiLineTextDescription: "Se attivo, i testi multi-riga nei risultati vengono automaticamente pianificati in una singola colonna.",
regexMaxMatchCount: "Numero massimo di corrispondenze per Seleziona tutte",
regexMaxMatchCountDescription: "Limita il numero di selezioni create da Seleziona tutte le corrispondenze; il conteggio della ricerca rimane completo (100–10000).",
tableColumnTemplateFields: "Campi predefiniti per nuove tabelle",
+2
View File
@@ -5801,6 +5801,8 @@ export default withEnglishFallback({
autoCalculateTotalRows: "総行数を自動計算",
autoCalculateTotalRowsDescription: "クエリごとに COUNT(*) を自動実行し、一致する総行数を表示します。大きなクエリを高速に保つため既定はオフです。結果フッターから必要に応じて計算できます。",
infiniteScrollDescription: "末尾までスクロールしたときに、次のデータを自動的に読み込みます。",
flatteningMultiLineText: "多行テキストを展開",
flatteningMultiLineTextDescription: "複数行のテキストを1行に結合して表示し、改行文字は¶として表示する。",
regexMaxMatchCount: "すべての一致を選択する最大数",
regexMaxMatchCountDescription: "「すべての一致を選択」で一度に作成する選択範囲の数を制限します。検索の一致数は完全に集計されます(100〜10000)。",
exportRowLimitEnabled: "エクスポート行数を制限",
+2
View File
@@ -5119,6 +5119,8 @@ export default withEnglishFallback({
autoCalculateTotalRows: "전체 행 수 자동 집계",
autoCalculateTotalRowsDescription: "각 쿼리 후 자동으로 COUNT(*)를 실행하여 일치하는 전체 행 수를 표시합니다. 대규모 쿼리를 빠르게 유지하기 위해 기본적으로 꺼져 있으며, 결과 푸터에서 요청 시 집계할 수 있습니다.",
infiniteScrollDescription: "목록 하단으로 스크롤할 때 다음 데이터 묶음을 자동으로 불러옵니다.",
flatteningMultiLineText: "멀티라인 텍스트 단순화",
flatteningMultiLineTextDescription: "다중 행 텍스트를 단일 행으로 병합하여 표시하고, 개행 문자는 ¶로 표시합니다.",
tableColumnTemplateFields: "새 테이블 미리 정의된 필드",
tableColumnTemplateFieldsDescription: "데이터베이스 유형을 선택한 다음 새 테이블을 만들 때 사용할 미리 정의된 필드 타입을 구성하세요.",
tableColumnTemplateAdd: "필드 추가",
+2
View File
@@ -5378,6 +5378,8 @@ export default withEnglishFallback({
autoCalculateTotalRows: "Calcular automaticamente o total de linhas",
autoCalculateTotalRowsDescription: "Executa COUNT(*) automaticamente após cada consulta para mostrar o total de linhas correspondentes. Desativado por padrão para manter consultas grandes rápidas; você pode calculá-lo quando quiser no rodapé dos resultados.",
infiniteScrollDescription: "Carregar automaticamente o próximo lote ao rolar até o final.",
flatteningMultiLineText: "Achatar texto multilinha",
flatteningMultiLineTextDescription: "Exibir texto multilinha em uma única linha.",
regexMaxMatchCount: "Máximo de correspondências para selecionar todas",
regexMaxMatchCountDescription: "Limita o número de seleções criadas por Selecionar todas as correspondências; a contagem da pesquisa permanece completa (100–10000).",
tableColumnTemplateFields: "Campos predefinidos para novas tabelas",
+2
View File
@@ -5543,6 +5543,8 @@ export default withEnglishFallback({
autoCalculateTotalRows: "自动统计总行数",
autoCalculateTotalRowsDescription: "每次查询后自动执行 COUNT(*) 显示匹配的总行数。默认关闭以保证大查询速度 —— 可在结果栏按需手动统计。",
infiniteScrollDescription: "滚动到底部时自动加载下一批数据,无需手动翻页或加载。",
flatteningMultiLineText: "扁平化多行文本",
flatteningMultiLineTextDescription: "多行文本合并为单行显示,换行符显示为 ¶ 。",
regexMaxMatchCount: "全部选中最大数量",
regexMaxMatchCountDescription: "限制“全部选中”一次创建的选区数量;搜索匹配仍完整统计(100–10000)。",
tableColumnTemplateFields: "新建表预设字段",
+2
View File
@@ -4716,6 +4716,8 @@ export default withEnglishFallback({
autoCalculateTotalRows: "自動統計總筆數",
autoCalculateTotalRowsDescription: "每次查詢後自動執行 COUNT(*) 顯示符合的總筆數。預設關閉以確保大型查詢速度 —— 可在結果列按需手動統計。",
infiniteScrollDescription: "滾動到底部時自動載入下一批資料,無需手動翻頁或載入。",
flatteningMultiLineText: "扁平多行文本",
flatteningMultiLineTextDescription: "多行文本合併為單行顯示,換行符顯示為 ¶ 。",
regexMaxMatchCount: "全部選取最大數量",
regexMaxMatchCountDescription: "限制「全部選取」一次建立的選取範圍數量;搜尋符合結果仍會完整統計(100–10000)。",
tableColumnTemplateFields: "新建表預設欄位",
+26 -10
View File
@@ -3,22 +3,38 @@ import { firstLineCellDisplayValue } from "@/lib/dataGrid/cellValue";
describe("firstLineCellDisplayValue", () => {
it("shows only the first line in fixed-height cells", () => {
expect(firstLineCellDisplayValue("111\n222")).toBe("111");
expect(firstLineCellDisplayValue("111\r\n222")).toBe("111");
expect(firstLineCellDisplayValue("111\r222")).toBe("111");
expect(firstLineCellDisplayValue(null, false)).toBe(null);
expect(firstLineCellDisplayValue("111\n222", false)).toBe("111");
expect(firstLineCellDisplayValue("111\r\n222", false)).toBe("111");
expect(firstLineCellDisplayValue("111\r222", false)).toBe("111");
expect(firstLineCellDisplayValue("single line")).toBe("single line");
expect(firstLineCellDisplayValue(null, true)).toBe(null);
expect(firstLineCellDisplayValue("111\n222", true)).toBe("111¶222");
expect(firstLineCellDisplayValue("111\r\n222", true)).toBe("111¶222");
expect(firstLineCellDisplayValue("111\r222", true)).toBe("111¶222");
expect(firstLineCellDisplayValue("single line", true)).toBe("single line");
});
it("skips leading blank lines without stripping content indentation", () => {
expect(firstLineCellDisplayValue("\nINNODB MONITOR OUTPUT\nbody")).toBe("INNODB MONITOR OUTPUT");
expect(firstLineCellDisplayValue("\r\nINNODB MONITOR OUTPUT\r\nbody")).toBe("INNODB MONITOR OUTPUT");
expect(firstLineCellDisplayValue("\rINNODB MONITOR OUTPUT\rbody")).toBe("INNODB MONITOR OUTPUT");
expect(firstLineCellDisplayValue(" \n indented content\nbody")).toBe(" indented content");
expect(firstLineCellDisplayValue("\nINNODB MONITOR OUTPUT\nbody", false)).toBe("INNODB MONITOR OUTPUT");
expect(firstLineCellDisplayValue("\r\nINNODB MONITOR OUTPUT\r\nbody", false)).toBe("INNODB MONITOR OUTPUT");
expect(firstLineCellDisplayValue("\rINNODB MONITOR OUTPUT\rbody", false)).toBe("INNODB MONITOR OUTPUT");
expect(firstLineCellDisplayValue(" \n indented content\nbody", false)).toBe(" indented content");
expect(firstLineCellDisplayValue("\nINNODB MONITOR OUTPUT\nbody", true)).toBe("¶INNODB MONITOR OUTPUT¶body");
expect(firstLineCellDisplayValue("\r\nINNODB MONITOR OUTPUT\r\nbody", true)).toBe("¶INNODB MONITOR OUTPUT¶body");
expect(firstLineCellDisplayValue("\rINNODB MONITOR OUTPUT\rbody", true)).toBe("¶INNODB MONITOR OUTPUT¶body");
expect(firstLineCellDisplayValue(" \n indented content\nbody", true)).toBe(" ¶ indented content¶body");
});
it("preserves values that do not contain a content line", () => {
expect(firstLineCellDisplayValue("")).toBe("");
expect(firstLineCellDisplayValue(" ")).toBe(" ");
expect(firstLineCellDisplayValue("\n\r\n \r")).toBe("\n\r\n \r");
expect(firstLineCellDisplayValue("", false)).toBe("");
expect(firstLineCellDisplayValue(" ", false)).toBe(" ");
expect(firstLineCellDisplayValue("\n\r\n \r", false)).toBe("\n\r\n \r");
expect(firstLineCellDisplayValue("", true)).toBe("");
expect(firstLineCellDisplayValue(" ", true)).toBe(" ");
expect(firstLineCellDisplayValue("\n\r\n \r", true)).toBe("¶¶ ¶");
});
});
@@ -30,6 +30,10 @@ describe("dataGridRuntime", () => {
expect(dataGridSource).toContain(":title=\"t('grid.queryError')\"");
});
it("redraws canvas rows when multiline flattening changes", () => {
expect(dataGridSource).toContain("watch(flatteningMultiLineEnabled, () => scheduleCanvasDraw());");
});
it("disposes registered resources in reverse order and only once", () => {
const scope = createDataGridRuntimeScope();
const calls: string[] = [];
@@ -95,6 +95,7 @@ export interface DrawCanvasDataGridOptions {
colorizeDataTypes?: boolean;
rightAlignedActionCell?: CanvasRightAlignedActionCell | null;
booleanDisplayMode?: "checkbox" | "dropdown";
flatteningMultiLineEnabled: boolean;
}
type NumericCanvasContext = CanvasRenderingContext2D & {
@@ -353,6 +354,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
rightAlignedActionCell,
columnIsBoolean,
booleanDisplayMode = "dropdown",
flatteningMultiLineEnabled,
} = options;
// 框选热路径:整次绘制只判断一次。常见情况(单矩形 / 多列且每段都是多格)可跳过逐格 kind 查询
const paintSelectionOuterFrame = dataGridSelectionUsesOuterFrame(selectionFrames);
@@ -575,7 +577,7 @@ export function drawCanvasDataGrid(options: DrawCanvasDataGridOptions) {
}
} else {
const rawDisplayText = item.isDraft && value === null ? (draftCellPlaceholder ?? "") : formatCell(value, actualColIdx);
const displayText = isEditingThisCell ? "" : firstLineCellDisplayValue(rawDisplayText);
const displayText = isEditingThisCell ? "" : firstLineCellDisplayValue(rawDisplayText, flatteningMultiLineEnabled);
const text = isEditingThisCell ? displayText : fitCanvasText(ctx, displayText, cellMaxWidth, isBooleanNullCell ? "left" : isRightAlign ? "right" : "left");
const anchorX = isBooleanNullCell ? alignCanvasPixel(drawX + colWidth / 2, scaleX) : textAnchorX;
ctx.fillText(text, anchorX, textY);
+19 -15
View File
@@ -10,26 +10,30 @@ export function displayCellValue(value: CellValue): string {
return String(value);
}
export function firstLineCellDisplayValue(value: string): string {
export function firstLineCellDisplayValue(value: string, flatteningMultiLine: boolean): string {
const lineBreakPattern = /\r\n|\r|\n/g;
const firstLineBreak = lineBreakPattern.exec(value);
if (!firstLineBreak) return value;
if (flatteningMultiLine) {
return value && value.replaceAll(lineBreakPattern, "¶");
} else {
const firstLineBreak = lineBreakPattern.exec(value);
if (!firstLineBreak) return value;
const firstLine = value.slice(0, firstLineBreak.index);
if (/\S/u.test(firstLine)) return firstLine;
const firstLine = value.slice(0, firstLineBreak.index);
if (/\S/u.test(firstLine)) return firstLine;
let lineStart = lineBreakPattern.lastIndex;
let lineStart = lineBreakPattern.lastIndex;
while (lineStart <= value.length) {
const lineBreak = lineBreakPattern.exec(value);
const lineEnd = lineBreak?.index ?? value.length;
const line = value.slice(lineStart, lineEnd);
if (/\S/u.test(line)) return line;
if (!lineBreak) return value;
lineStart = lineBreakPattern.lastIndex;
while (lineStart <= value.length) {
const lineBreak = lineBreakPattern.exec(value);
const lineEnd = lineBreak?.index ?? value.length;
const line = value.slice(lineStart, lineEnd);
if (/\S/u.test(line)) return line;
if (!lineBreak) return value;
lineStart = lineBreakPattern.lastIndex;
}
return value;
}
return value;
}
export function limitDataGridCellDisplay(value: string, maxLength = DATA_GRID_CELL_DISPLAY_MAX_LENGTH): string {
@@ -43,6 +43,7 @@ export const EDITOR_SETTINGS_DRAFT_KEYS = [
"infiniteScroll",
"regexMaxMatchCount",
"autoCalculateTotalRows",
"flatteningMultiLineText",
"tableColumnTemplateFields",
"shortcuts",
"sqlFormatter",
@@ -180,6 +180,7 @@ export const SETTINGS_SEARCH_DEFINITIONS: readonly SettingsSearchDefinition[] =
{ id: "data-grid-infinite-scroll", category: "data", titleKey: "settings.infiniteScroll", descriptionKey: "settings.infiniteScrollDescription", targetId: "data" },
{ id: "data-grid-auto-transpose", category: "data", titleKey: "settings.dataGridAutoTransposeSingleRow", descriptionKey: "settings.dataGridAutoTransposeSingleRowDescription", targetId: "data" },
{ id: "data-grid-quick-entry", category: "data", titleKey: "settings.dataGridQuickEntry", descriptionKey: "settings.dataGridQuickEntryDescription", targetId: "data" },
{ id: "data-grid-flattening-multi-line", category: "data", titleKey: "settings.flatteningMultiLineText", descriptionKey: "settings.flatteningMultiLineTextDescription", targetId: "data" },
{ id: "appearance-toolbar", category: "appearance", titleKey: "settings.toolbarTitle", descriptionKey: "settings.toolbarHiddenHint", targetId: "appearance" },
{ id: "appearance-exclusive-sidebar-panels", category: "appearance", titleKey: "settings.exclusiveRightSidebarPanels", descriptionKey: "settings.exclusiveRightSidebarPanelsDescription", targetId: "appearance" },
...createToolbarVisibilitySettingsSearchDefinitions(),
+4
View File
@@ -533,6 +533,7 @@ export interface EditorSettings {
infiniteScroll: boolean;
/** Preserved for downgrade compatibility; current clients use queryResultMaxRows. */
infiniteScrollMaxRows: number;
flatteningMultiLineText: boolean;
regexMaxMatchCount: number;
autoCalculateTotalRows: boolean;
mongoViewMode: "document" | "table";
@@ -725,6 +726,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
queryResultMaxRows: DEFAULT_QUERY_RESULT_MAX_ROWS,
infiniteScroll: false,
infiniteScrollMaxRows: 5000,
flatteningMultiLineText: false,
regexMaxMatchCount: 1000,
autoCalculateTotalRows: false,
mongoViewMode: "document",
@@ -1062,6 +1064,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
queryResultMaxRows: normalizeQueryResultMaxRows(settings.queryResultMaxRows),
infiniteScroll: settings.infiniteScroll ?? DEFAULT_EDITOR_SETTINGS.infiniteScroll,
infiniteScrollMaxRows: typeof settings.infiniteScrollMaxRows === "number" && settings.infiniteScrollMaxRows >= 1000 && settings.infiniteScrollMaxRows <= 50000 ? Math.round(settings.infiniteScrollMaxRows) : DEFAULT_EDITOR_SETTINGS.infiniteScrollMaxRows,
flatteningMultiLineText: settings.flatteningMultiLineText ?? DEFAULT_EDITOR_SETTINGS.flatteningMultiLineText,
regexMaxMatchCount: typeof settings.regexMaxMatchCount === "number" && Number.isFinite(settings.regexMaxMatchCount) && settings.regexMaxMatchCount >= 100 && settings.regexMaxMatchCount <= 10000 ? Math.round(settings.regexMaxMatchCount) : DEFAULT_EDITOR_SETTINGS.regexMaxMatchCount,
autoCalculateTotalRows: settings.autoCalculateTotalRows ?? DEFAULT_EDITOR_SETTINGS.autoCalculateTotalRows,
mongoViewMode: settings.mongoViewMode === "table" ? "table" : DEFAULT_EDITOR_SETTINGS.mongoViewMode,
@@ -1706,6 +1709,7 @@ export const useSettingsStore = defineStore("settings", () => {
if (partial.continueOnErrorOnBatch !== undefined) editorSettings.value.continueOnErrorOnBatch = partial.continueOnErrorOnBatch === true;
if (partial.clickTableNavigationTarget !== undefined) editorSettings.value.clickTableNavigationTarget = normalizeClickTableNavigationTarget(partial.clickTableNavigationTarget);
if (partial.completionTriggerMode !== undefined) editorSettings.value.completionTriggerMode = normalizeCompletionTriggerMode(partial.completionTriggerMode);
if (partial.flatteningMultiLineText !== undefined) editorSettings.value.flatteningMultiLineText = partial.flatteningMultiLineText;
}
function updateEditorSettings(partial: Partial<EditorSettings>) {