From 8515c9f082e8abd859e01515642a767741a04de7 Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Tue, 14 Jan 2025 17:05:15 +0100 Subject: [PATCH 1/3] Make info table column alignment configurable REDMINE-20878 --- .../scrolled/config/locales/new/info_table.de.yml | 15 +++++++++++++++ .../scrolled/config/locales/new/info_table.en.yml | 15 +++++++++++++++ .../src/contentElements/infoTable/InfoTable.js | 2 ++ .../infoTable/InfoTable.module.css | 15 +++++++++++++++ .../src/contentElements/infoTable/editor.js | 11 ++++++++++- .../src/contentElements/infoTable/stories.js | 11 ++++++++++- 6 files changed, 67 insertions(+), 2 deletions(-) diff --git a/entry_types/scrolled/config/locales/new/info_table.de.yml b/entry_types/scrolled/config/locales/new/info_table.de.yml index 35a6491a0..0d3b2b87c 100644 --- a/entry_types/scrolled/config/locales/new/info_table.de.yml +++ b/entry_types/scrolled/config/locales/new/info_table.de.yml @@ -7,6 +7,21 @@ de: name: Info-Tabelle tabs: general: Info-Tabelle + attributes: + labelColumnAlign: + label: "Text-Ausrichtung (Erste Spalte) " + values: + auto: "(Automatisch)" + left: Linksbündig + center: Zentriert + right: Rechtsbündig + valueColumnAlign: + label: "Text-Ausrichtung (Zweite Spalte)" + values: + auto: "(Automatisch)" + left: Linksbündig + center: Zentriert + right: Rechtsbündig help_texts: shortcuts: |-
diff --git a/entry_types/scrolled/config/locales/new/info_table.en.yml b/entry_types/scrolled/config/locales/new/info_table.en.yml index 3899014e4..6b5f2d6ae 100644 --- a/entry_types/scrolled/config/locales/new/info_table.en.yml +++ b/entry_types/scrolled/config/locales/new/info_table.en.yml @@ -7,6 +7,21 @@ en: name: Info Table tabs: general: Info Table + attributes: + labelColumnAlign: + label: "Text align (First column)" + values: + auto: "(Auto)" + left: Left + center: Center + right: Right + valueColumnAlign: + label: "Text align (Second column)" + values: + auto: "(Auto)" + left: Left + center: Center + right: Right help_texts: shortcuts: |-
diff --git a/entry_types/scrolled/package/src/contentElements/infoTable/InfoTable.js b/entry_types/scrolled/package/src/contentElements/infoTable/InfoTable.js index 46c9cbdab..cd826e3c8 100644 --- a/entry_types/scrolled/package/src/contentElements/infoTable/InfoTable.js +++ b/entry_types/scrolled/package/src/contentElements/infoTable/InfoTable.js @@ -18,6 +18,8 @@ export function InfoTable({configuration, sectionProps}) { return ( Date: Tue, 14 Jan 2025 17:12:08 +0100 Subject: [PATCH 2/3] Collapse empty first column of info table REDMINE-20878 --- .../spec/frontend/EditableTable-spec.js | 45 +++++++++++++++++++ .../infoTable/InfoTable.module.css | 15 +++++++ .../src/contentElements/infoTable/stories.js | 41 +++++++++++++++++ .../package/src/frontend/EditableTable.js | 9 +++- 4 files changed, 108 insertions(+), 2 deletions(-) diff --git a/entry_types/scrolled/package/spec/frontend/EditableTable-spec.js b/entry_types/scrolled/package/spec/frontend/EditableTable-spec.js index 04f8cc852..252cf1d95 100644 --- a/entry_types/scrolled/package/spec/frontend/EditableTable-spec.js +++ b/entry_types/scrolled/package/spec/frontend/EditableTable-spec.js @@ -146,4 +146,49 @@ describe('EditableTable', () => { expect(container.querySelector('sup')).toHaveTextContent('3') expect(container.querySelector('sub')).toHaveTextContent('2') }); + + it('renders blank class on blank cells', () => { + const value = [ + { + type: 'row', + children: [ + { + type: 'label', + children: [ + {text: ''} + ] + }, + { + type: 'value', + children: [ + {text: 'Value'} + ] + } + ] + }, + { + type: 'row', + children: [ + { + type: 'label', + children: [ + {text: 'Label'} + ] + }, + { + type: 'value', + children: [ + {text: ''} + ] + } + ] + } + ]; + + render(); + + expect( + screen.queryAllByRole('cell').map(cell => cell.getAttribute('data-blank')) + ).toEqual(['', null, null, '']); + }); }); diff --git a/entry_types/scrolled/package/src/contentElements/infoTable/InfoTable.module.css b/entry_types/scrolled/package/src/contentElements/infoTable/InfoTable.module.css index 5ef8f2560..e7658f7b0 100644 --- a/entry_types/scrolled/package/src/contentElements/infoTable/InfoTable.module.css +++ b/entry_types/scrolled/package/src/contentElements/infoTable/InfoTable.module.css @@ -51,3 +51,18 @@ .selected { --table-border-color: color-mix(in srgb, transparent, currentColor); } + +/* + Hide first column if: + - the table is not selected and + - the first column is blank (= the table does not have a first + column cell that is not blank) and + - the table is not completely blank (= has a non blank cell) +*/ +.table:not(.selected):not(:has(td:not([data-blank]):first-child)):has(td:not([data-blank])) td:first-child { + display: none; +} + +.table:not(.selected):not(:has(td:not([data-blank]):first-child)):has(td:not([data-blank])) td:last-child { + padding-left: 0; +} diff --git a/entry_types/scrolled/package/src/contentElements/infoTable/stories.js b/entry_types/scrolled/package/src/contentElements/infoTable/stories.js index 31087e629..e2adacfb7 100644 --- a/entry_types/scrolled/package/src/contentElements/infoTable/stories.js +++ b/entry_types/scrolled/package/src/contentElements/infoTable/stories.js @@ -51,6 +51,47 @@ storiesOfContentElement(module, { ] }, variants: [ + { + name: 'With empty first columns', + configuration: { + value: [ + { + type: 'row', + children: [ + { + type: 'label', + children: [ + {text: ''} + ] + }, + { + type: 'value', + children: [ + {text: 'First column hidden'}, + ] + } + ] + }, + { + type: 'row', + children: [ + { + type: 'label', + children: [ + {text: ''} + ] + }, + { + type: 'value', + children: [ + {text: 'No padding left in second column'} + ] + } + ] + } + ] + } + }, { name: 'With custom column alignment', configuration: { diff --git a/entry_types/scrolled/package/src/frontend/EditableTable.js b/entry_types/scrolled/package/src/frontend/EditableTable.js index 3a783d12d..96410e308 100644 --- a/entry_types/scrolled/package/src/frontend/EditableTable.js +++ b/entry_types/scrolled/package/src/frontend/EditableTable.js @@ -3,6 +3,7 @@ import classNames from 'classnames'; import {withInlineEditingAlternative} from './inlineEditing'; import {Text} from './Text'; +import {utils} from './utils'; import { renderLink, @@ -79,7 +80,7 @@ export function createRenderElement({labelScaleCategory, valueScaleCategory}) { return renderLink({attributes, children, element}); case 'label': return ( - + {children} @@ -87,7 +88,7 @@ export function createRenderElement({labelScaleCategory, valueScaleCategory}) { ); default: return ( - + {children} @@ -96,3 +97,7 @@ export function createRenderElement({labelScaleCategory, valueScaleCategory}) { } } } + +function cellAttributes(element) { + return utils.isBlankEditableTextValue([element]) ? {'data-blank': ''} : {}; +} From 60f346bb6aaadc8595171473e7cc2c8eabfbb244 Mon Sep 17 00:00:00 2001 From: Tim Fischbach Date: Tue, 14 Jan 2025 18:06:23 +0100 Subject: [PATCH 3/3] Fix arrow navigation inside multi line info table cells REDMINE-20913 --- .../EditableTable/withFixedColumns-spec.js | 81 +++++++++++++++++++ .../withFixedColumns/handleTableNavigation.js | 9 ++- .../EditableTable/withFixedColumns/helpers.js | 30 ++++++- 3 files changed, 116 insertions(+), 4 deletions(-) diff --git a/entry_types/scrolled/package/spec/frontend/inlineEditing/EditableTable/withFixedColumns-spec.js b/entry_types/scrolled/package/spec/frontend/inlineEditing/EditableTable/withFixedColumns-spec.js index 9844e90e1..838038e43 100644 --- a/entry_types/scrolled/package/spec/frontend/inlineEditing/EditableTable/withFixedColumns-spec.js +++ b/entry_types/scrolled/package/spec/frontend/inlineEditing/EditableTable/withFixedColumns-spec.js @@ -1862,6 +1862,61 @@ describe('handleTableNavigation', () => { }); }); + it('allows moving the cursor up inside multi line cell', () => { + const editor = withFixedColumns( + + + + Row 1, Col 2 + + + + + A{'\n'} + B + + + + ); + + const event = new KeyboardEvent('keydown', {key: 'ArrowUp'}); + handleTableNavigation(editor, event); + + expect(editor.selection).toEqual({ + anchor: {path: [1, 1, 0], offset: 3}, + focus: {path: [1, 1, 0], offset: 3} + }); + }); + + it('moves the cursor up to the last line of a multi line cell', () => { + const editor = withFixedColumns( + + + + + A{'\n'} + B{'\n'} + C + + + + + + Row 2, Col 2 + + + + ); + + const event = new KeyboardEvent('keydown', {key: 'ArrowUp'}); + handleTableNavigation(editor, event); + + expect(editor.selection).toEqual({ + anchor: {path: [0, 1, 0], offset: 4}, + focus: {path: [0, 1, 0], offset: 4} + }); + }); + it('moves the cursor to the cell below when pressing ArrowDown', () => { const editor = withFixedColumns( @@ -1886,4 +1941,30 @@ describe('handleTableNavigation', () => { focus: {path: [1, 1, 0], offset: 0} }); }); + + it('allows moving the cursor down inside multi line cell', () => { + const editor = withFixedColumns( + + + + + A{'\n'} + B + + + + + Row 2, Col 2 + + + ); + + const event = new KeyboardEvent('keydown', {key: 'ArrowDown'}); + handleTableNavigation(editor, event); + + expect(editor.selection).toEqual({ + anchor: {path: [0, 1, 0], offset: 1}, + focus: {path: [0, 1, 0], offset: 1} + }); + }); }); diff --git a/entry_types/scrolled/package/src/frontend/inlineEditing/EditableTable/withFixedColumns/handleTableNavigation.js b/entry_types/scrolled/package/src/frontend/inlineEditing/EditableTable/withFixedColumns/handleTableNavigation.js index 8b3d6a3a4..8f66016ff 100644 --- a/entry_types/scrolled/package/src/frontend/inlineEditing/EditableTable/withFixedColumns/handleTableNavigation.js +++ b/entry_types/scrolled/package/src/frontend/inlineEditing/EditableTable/withFixedColumns/handleTableNavigation.js @@ -12,16 +12,19 @@ export function handleTableNavigation(editor, event) { const [, cellPath] = cellMatch; const rowPath = cellPath.slice(0, -1); - if (event.key === 'ArrowUp') { + if (event.key === 'ArrowUp' && Cell.inFirstLine(editor, selection.anchor)) { event.preventDefault(); if (rowPath[rowPath.length - 1] > 0) { const previousRowPath = Path.previous(rowPath); const targetPath = [...previousRowPath, cellPath[cellPath.length - 1]]; - Transforms.select(editor, Editor.start(editor, targetPath)); + Transforms.select( + editor, + Cell.getPointAtStartOfLastLine(editor, Editor.start(editor, targetPath).path) + ); } - } else if (event.key === 'ArrowDown') { + } else if (event.key === 'ArrowDown' && Cell.inLastLine(editor, selection.anchor)) { event.preventDefault(); const nextRowPath = Path.next(rowPath); diff --git a/entry_types/scrolled/package/src/frontend/inlineEditing/EditableTable/withFixedColumns/helpers.js b/entry_types/scrolled/package/src/frontend/inlineEditing/EditableTable/withFixedColumns/helpers.js index cc563e721..e68c4a3c5 100644 --- a/entry_types/scrolled/package/src/frontend/inlineEditing/EditableTable/withFixedColumns/helpers.js +++ b/entry_types/scrolled/package/src/frontend/inlineEditing/EditableTable/withFixedColumns/helpers.js @@ -1,4 +1,4 @@ -import {Editor} from 'slate'; +import {Editor, Node} from 'slate'; export const Row = { match(editor) { @@ -49,6 +49,34 @@ export const Cell = { }); return cellMatch; + }, + + inFirstLine(editor, point) { + const [node] = Editor.node(editor, point.path); + const text = Node.string(node); + + const firstLineBreak = text.indexOf('\n'); + + return firstLineBreak === -1 || point.offset <= firstLineBreak; + }, + + inLastLine(editor, point) { + const [node] = Editor.node(editor, point.path); + const text = Node.string(node); + + const lastLineBreak = text.lastIndexOf('\n'); + + return lastLineBreak === -1 || point.offset > lastLineBreak; + }, + + getPointAtStartOfLastLine(editor, cellPath) { + const [node] = Editor.node(editor, cellPath); + const text = Node.string(node); + + const lastLineBreak = text.lastIndexOf('\n'); + const offset = lastLineBreak === -1 ? 0 : lastLineBreak + 1; + + return {path: cellPath, offset}; } }