From 9ceacf5d647c4c754a0812bbf6abea87ebf02f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Thu, 24 Sep 2026 11:42:44 +0300 Subject: [PATCH] Html Toolbar yetenekli hale getirdik --- .github/instructions/lowcode.instructions.md | 4 +- .../EntityFrameworkCore/PlatformDbContext.cs | 6 +- .../20260922201238_Initial.Designer.cs | 5 +- .../Migrations/20260922201238_Initial.cs | 6 +- .../PlatformDbContextModelSnapshot.cs | 5 +- ui/src/proxy/reports/data.ts | 27 ----- .../editor-options/optionSpecs.ts | 21 ++++ .../editor-options/presets.ts | 81 +------------ ui/src/views/form/notes/NoteModal.tsx | 11 +- ui/src/views/form/shared/formItems.ts | 11 +- ui/src/views/forum/forum/CreatePostModal.tsx | 66 +--------- ui/src/views/list/CardView.tsx | 3 +- ui/src/views/list/SchedulerView.tsx | 3 +- ui/src/views/list/TodoBoard.tsx | 11 +- ui/src/views/list/shared/editingForm.ts | 14 ++- .../views/shared/HtmlEditorToolbarItems.tsx | 46 ------- ui/src/views/shared/htmlEditorOptions.ts | 114 ++++++++++++++++++ 17 files changed, 185 insertions(+), 249 deletions(-) delete mode 100644 ui/src/proxy/reports/data.ts delete mode 100644 ui/src/views/shared/HtmlEditorToolbarItems.tsx create mode 100644 ui/src/views/shared/htmlEditorOptions.ts diff --git a/.github/instructions/lowcode.instructions.md b/.github/instructions/lowcode.instructions.md index 6ac1b5e1..5a67176c 100644 --- a/.github/instructions/lowcode.instructions.md +++ b/.github/instructions/lowcode.instructions.md @@ -921,7 +921,7 @@ anahtarları listeler; ortak anahtarlar (§4.3.3) her editörde geçerlidir. | `dxColorBox` | Renk | String (16) | `editAlphaChannel`, `keyStep` | | `dxSlider` | Tek değerli kaydırıcı | Sayı | `min`, `max`, `tooltip.showMode` | | `dxRangeSlider` | Aralık kaydırıcı | Sayı | `min`, `max`, `showRange` | -| `dxHtmlEditor` | Zengin metin | String (16) | `toolbar.items`, `toolbar.multiline`, `valueType` (`html`/`markdown`), `mediaResizing.enabled`, `imageUpload.uploadUrl`, `imageUpload.fileUploadMode` | +| `dxHtmlEditor` | Zengin metin | String (16) | `toolbar.items`, `toolbar.multiline`, `valueType` (`html`/`markdown`), `mediaResizing.enabled`, `imageUpload.tabs`, `imageUpload.uploadUrl`, `imageUpload.fileUploadMode`, `tableResizing.enabled`, `tableContextMenu.enabled`. Boş `editorOptions` da tam araç çubuğu, dosyadan/URL'den resim, resim boyutlandırma ve tablo araçlarıyla gelir (`ui/src/views/shared/htmlEditorOptions.ts`); yazılan değer varsayılanın üstüne tek seviye birleştirilir, yalnızca farkı yaz. | | `dxImageUpload` | Görsel yükleme (platform) | String (16) | `uploadUrl`\*, `accept`\*, `capture`\*, `multiple`\*, `maxFileSize`\*, `width`\*, `height`\* | | `dxImageViewer` | Görsel gösterimi (platform) | String (16) | `width`, `height` | @@ -2086,7 +2086,7 @@ Dizi elemanları yaprak sayılmaz; `toolbar.items` tek bir `json` alanıdır. | Görsel | `uploadUrl`\*, `fileFieldName`, `accept`\*, `capture`\*, `multiple`\*, `maxFileSize`\* | `dxImageUpload`, `dxImageViewer` | | Seçim | `text` (`dxCheckBox`), `switchedOnText`/`switchedOffText` (`dxSwitch`), `layout` (`dxRadioGroup`), `editAlphaChannel`/`keyStep` (`dxColorBox`) | ilgili editör | | Kaydırıcı | `min`, `max`, `step`, `tooltip.enabled`, `tooltip.showMode`, `showRange` | `dxSlider`, `dxRangeSlider` | -| HTML | `valueType`, `toolbar.multiline`, `toolbar.items`, `mediaResizing.enabled`, `imageUpload.fileUploadMode`, `imageUpload.uploadUrl` | `dxHtmlEditor` | +| HTML | `valueType`, `toolbar.multiline`, `toolbar.items`, `mediaResizing.enabled`, `imageUpload.tabs`, `imageUpload.fileUploadMode`, `imageUpload.uploadUrl`, `tableResizing.enabled`, `tableContextMenu.enabled` | `dxHtmlEditor` | | Liste sütunu | `format` (metin hâli), `encodeHtml`, `buttons` | hepsi — bu üçü **grid sütununu** da etkiler | `\*` işaretli anahtarlar backend'de tipli DTO'ya (`GridBoxOptionsDto`, `TagBoxOptionsDto`, diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs index 0e1fcb8c..61b31ca4 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/EntityFrameworkCore/PlatformDbContext.cs @@ -567,7 +567,7 @@ public class PlatformDbContext : b.Property(x => x.Type).IsRequired().HasMaxLength(64); b.Property(x => x.Subject).IsRequired().HasMaxLength(256); - b.Property(x => x.Content).IsRequired().HasMaxLength(2048); + b.Property(x => x.Content).IsRequired(); b.Property(x => x.EntityName).IsRequired().HasMaxLength(128); b.Property(x => x.EntityId).IsRequired().HasMaxLength(64); }); @@ -1186,7 +1186,7 @@ public class PlatformDbContext : b.Property(x => x.Title).IsRequired().HasMaxLength(256); b.Property(x => x.Excerpt).IsRequired().HasMaxLength(512); - b.Property(x => x.Content).IsRequired().HasMaxLength(4096); + b.Property(x => x.Content).IsRequired(); b.Property(x => x.Category).IsRequired().HasMaxLength(64); b.Property(x => x.PublishDate).IsRequired(); b.Property(x => x.Attachments).HasMaxLength(2048); @@ -1302,7 +1302,7 @@ public class PlatformDbContext : b.ToTable(TableNameResolver.GetFullTableName(nameof(TableNameEnum.SocialPost)), Prefix.DbSchema); b.ConfigureByConvention(); - b.Property(x => x.Content).IsRequired().HasMaxLength(4096); + b.Property(x => x.Content).IsRequired(); b.Property(x => x.LikeCount).HasDefaultValue(0); b.Property(x => x.IsOwnPost).HasDefaultValue(false); b.Property(x => x.IsLiked).HasDefaultValue(false); diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260922201238_Initial.Designer.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260922201238_Initial.Designer.cs index 2d88711b..2531a24c 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260922201238_Initial.Designer.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260922201238_Initial.Designer.cs @@ -855,7 +855,6 @@ namespace Sozsoft.Platform.Migrations b.Property("Content") .IsRequired() - .HasMaxLength(4096) .HasColumnType("nvarchar(max)"); b.Property("CreationTime") @@ -4305,8 +4304,7 @@ namespace Sozsoft.Platform.Migrations b.Property("Content") .IsRequired() - .HasMaxLength(2048) - .HasColumnType("nvarchar(2048)"); + .HasColumnType("nvarchar(max)"); b.Property("CreationTime") .HasColumnType("datetime2") @@ -5624,7 +5622,6 @@ namespace Sozsoft.Platform.Migrations b.Property("Content") .IsRequired() - .HasMaxLength(4096) .HasColumnType("nvarchar(max)"); b.Property("CreationTime") diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260922201238_Initial.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260922201238_Initial.cs index bd5e1e75..eb648833 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260922201238_Initial.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260922201238_Initial.cs @@ -519,7 +519,7 @@ namespace Sozsoft.Platform.Migrations TenantId = table.Column(type: "uniqueidentifier", nullable: true), Title = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), Excerpt = table.Column(type: "nvarchar(512)", maxLength: 512, nullable: false), - Content = table.Column(type: "nvarchar(max)", maxLength: 4096, nullable: false), + Content = table.Column(type: "nvarchar(max)", nullable: false), ImageUrl = table.Column(type: "nvarchar(max)", nullable: true), Category = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), UserId = table.Column(type: "uniqueidentifier", nullable: true), @@ -662,7 +662,7 @@ namespace Sozsoft.Platform.Migrations EntityId = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), Type = table.Column(type: "nvarchar(64)", maxLength: 64, nullable: false), Subject = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: false), - Content = table.Column(type: "nvarchar(2048)", maxLength: 2048, nullable: false), + Content = table.Column(type: "nvarchar(max)", nullable: false), FilesJson = table.Column(type: "nvarchar(max)", nullable: true), CreationTime = table.Column(type: "datetime2", nullable: false), CreatorId = table.Column(type: "uniqueidentifier", nullable: true), @@ -802,7 +802,7 @@ namespace Sozsoft.Platform.Migrations Id = table.Column(type: "uniqueidentifier", nullable: false), TenantId = table.Column(type: "uniqueidentifier", nullable: true), UserId = table.Column(type: "uniqueidentifier", nullable: true), - Content = table.Column(type: "nvarchar(max)", maxLength: 4096, nullable: false), + Content = table.Column(type: "nvarchar(max)", nullable: false), LikeCount = table.Column(type: "int", nullable: false, defaultValue: 0), IsLiked = table.Column(type: "bit", nullable: false, defaultValue: false), IsOwnPost = table.Column(type: "bit", nullable: false, defaultValue: false), diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index cc5f41c0..8c435c51 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -852,7 +852,6 @@ namespace Sozsoft.Platform.Migrations b.Property("Content") .IsRequired() - .HasMaxLength(4096) .HasColumnType("nvarchar(max)"); b.Property("CreationTime") @@ -4302,8 +4301,7 @@ namespace Sozsoft.Platform.Migrations b.Property("Content") .IsRequired() - .HasMaxLength(2048) - .HasColumnType("nvarchar(2048)"); + .HasColumnType("nvarchar(max)"); b.Property("CreationTime") .HasColumnType("datetime2") @@ -5621,7 +5619,6 @@ namespace Sozsoft.Platform.Migrations b.Property("Content") .IsRequired() - .HasMaxLength(4096) .HasColumnType("nvarchar(max)"); b.Property("CreationTime") diff --git a/ui/src/proxy/reports/data.ts b/ui/src/proxy/reports/data.ts deleted file mode 100644 index 1139700c..00000000 --- a/ui/src/proxy/reports/data.ts +++ /dev/null @@ -1,27 +0,0 @@ -export const sizeValues = ['8pt', '10pt', '12pt', '14pt', '18pt', '24pt', '36pt'] -export const fontValues = [ - 'Arial', - 'Courier New', - 'Georgia', - 'Impact', - 'Lucida Console', - 'Tahoma', - 'Times New Roman', - 'Verdana', -] -export const headerValues = [false, 1, 2, 3, 4, 5] -export const fontSizeOptions = { - inputAttr: { - 'aria-label': 'Font size', - }, -} -export const fontFamilyOptions = { - inputAttr: { - 'aria-label': 'Font family', - }, -} -export const headerOptions = { - inputAttr: { - 'aria-label': 'Font family', - }, -} diff --git a/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/optionSpecs.ts b/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/optionSpecs.ts index 41b676fe..760a706e 100644 --- a/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/optionSpecs.ts +++ b/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/optionSpecs.ts @@ -939,6 +939,27 @@ export const optionSpecs: OptionSpec[] = [ group: 'html', editors: ['dxHtmlEditor'], }, + { + path: 'imageUpload.tabs', + label: 'imageUpload.tabs', + type: 'json', + group: 'html', + editors: ['dxHtmlEditor'], + }, + { + path: 'tableResizing.enabled', + label: 'tableResizing.enabled', + type: 'boolean', + group: 'html', + editors: ['dxHtmlEditor'], + }, + { + path: 'tableContextMenu.enabled', + label: 'tableContextMenu.enabled', + type: 'boolean', + group: 'html', + editors: ['dxHtmlEditor'], + }, // ── Liste sütununu da etkileyenler ─────────────────────────────── { diff --git a/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/presets.ts b/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/presets.ts index 7d950752..a7323927 100644 --- a/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/presets.ts +++ b/ui/src/views/admin/listForm/edit/json-row-operations/editor-options/presets.ts @@ -2,6 +2,7 @@ * Hazır editorOptions kalıpları. Her kalıp mevcut JSON ile birleştirilir, * diğer ayarları silmez. */ +import { buildHtmlEditorDefaults } from '@/views/shared/htmlEditorOptions' import type { JsonObject } from './jsonUtils' export type EditorOptionsPreset = { @@ -13,84 +14,8 @@ export type EditorOptionsPreset = { value: JsonObject } -const htmlToolbarItems = [ - 'undo', - 'redo', - 'separator', - 'size', - 'font', - 'separator', - 'bold', - 'italic', - 'strike', - 'underline', - 'separator', - 'alignLeft', - 'alignCenter', - 'alignRight', - 'alignJustify', - 'separator', - 'orderedList', - 'bulletList', - 'separator', - 'header', - 'separator', - 'color', - 'background', - 'separator', - 'link', - 'image', - 'separator', - 'clear', - 'codeBlock', - 'blockquote', - 'separator', - 'insertTable', - 'deleteTable', - 'insertRowAbove', - 'insertRowBelow', - 'deleteRow', - 'insertColumnLeft', - 'insertColumnRight', - 'deleteColumn', - 'cellProperties', - 'tableProperties', -] - -const buildHtmlEditorOptions = (): JsonObject => ({ - mediaResizing: { enabled: true }, - imageUpload: { tabs: ['file', 'url'], fileUploadMode: 'base64' }, - toolbar: { - multiline: true, - items: htmlToolbarItems.map((name) => { - if (name === 'size') { - return { - name, - acceptedValues: ['8pt', '10pt', '12pt', '14pt', '18pt', '24pt', '36pt'], - options: { inputAttr: { 'aria-label': 'Font size' } }, - } - } - if (name === 'font') { - return { - name, - acceptedValues: [ - 'Arial', - 'Courier New', - 'Georgia', - 'Impact', - 'Lucida Console', - 'Tahoma', - 'Times New Roman', - 'Verdana', - ], - options: { inputAttr: { 'aria-label': 'Font family' } }, - } - } - if (name === 'header') return { name, acceptedValues: [false, 1, 2, 3, 4, 5] } - return { name } - }), - }, -}) +const buildHtmlEditorOptions = (): JsonObject => + JSON.parse(JSON.stringify(buildHtmlEditorDefaults())) as JsonObject export const editorOptionsPresets: EditorOptionsPreset[] = [ { diff --git a/ui/src/views/form/notes/NoteModal.tsx b/ui/src/views/form/notes/NoteModal.tsx index 4620c204..87bb711f 100644 --- a/ui/src/views/form/notes/NoteModal.tsx +++ b/ui/src/views/form/notes/NoteModal.tsx @@ -6,8 +6,8 @@ import { FaFileAlt, FaFileUpload, FaPlus, FaTrash } from 'react-icons/fa' import { Field, FieldProps, Form, Formik } from 'formik' import * as Yup from 'yup' import { noteService } from '@/services/note.service' -import { HtmlEditor, ImageUpload, MediaResizing } from 'devextreme-react/html-editor' -import { HtmlEditorToolbar } from '@/views/shared/HtmlEditorToolbarItems' +import { HtmlEditor } from 'devextreme-react/html-editor' +import { htmlEditorDefaults } from '@/views/shared/htmlEditorOptions' import { useLocalization } from '@/utils/hooks/useLocalization' const validationSchema = Yup.object({ @@ -147,15 +147,12 @@ function NoteModalContent({ {({ field }: FieldProps) => ( setFieldValue('content', e.value)} height={isMaximized ? '50vh' : 220} placeholder={translate('::App.ListFormNoteModal.NoteModalContent')} - > - - - - + /> )} diff --git a/ui/src/views/form/shared/formItems.ts b/ui/src/views/form/shared/formItems.ts index 7f3d18c0..17691326 100644 --- a/ui/src/views/form/shared/formItems.ts +++ b/ui/src/views/form/shared/formItems.ts @@ -7,7 +7,11 @@ import { captionize } from 'devextreme/core/utils/inflector' import type { GroupItem } from 'devextreme/ui/form' import type { ColumnFormatDto, GridDto } from '@/proxy/form/models' import { getEditingFormGroups } from '../../list/Utils' -import { isFormItemVisible, resolveEditorType } from '../../list/shared/editingForm' +import { + isFormItemVisible, + resolveEditorType, + withEditorTypeDefaults, +} from '../../list/shared/editingForm' import type { GridColumnData } from '../../list/GridColumnData' import type { RowMode, SimpleItemWithColData } from '../types' @@ -71,7 +75,10 @@ const toFormItem = ( editorType2: colFormat.editorType2, editorType: resolveEditorType(colFormat.editorType2), colSpan: colFormat.colSpan, - editorOptions: { ...lookupEditorOptions, ...parsedEditorOptions }, + editorOptions: withEditorTypeDefaults(colFormat.editorType2, { + ...lookupEditorOptions, + ...parsedEditorOptions, + }), colData: colFormat, tagBoxOptions: colFormat.tagBoxOptions, gridBoxOptions: colFormat.gridBoxOptions, diff --git a/ui/src/views/forum/forum/CreatePostModal.tsx b/ui/src/views/forum/forum/CreatePostModal.tsx index 3ce1da50..e9233733 100644 --- a/ui/src/views/forum/forum/CreatePostModal.tsx +++ b/ui/src/views/forum/forum/CreatePostModal.tsx @@ -1,19 +1,12 @@ import { FaTimes } from 'react-icons/fa' import { Formik, Form, Field, FieldProps } from 'formik' import * as Yup from 'yup' -import { HtmlEditor, ImageUpload, Item, MediaResizing, Toolbar } from 'devextreme-react/html-editor' +import { HtmlEditor } from 'devextreme-react/html-editor' +import { htmlEditorDefaults } from '@/views/shared/htmlEditorOptions' import { useStoreState } from '@/store/store' import { useLocalization } from '@/utils/hooks/useLocalization' import { Button, FormContainer, FormItem } from '@/components/ui' import { stripHtml } from './utils' -import { - fontFamilyOptions, - fontSizeOptions, - fontValues, - headerOptions, - headerValues, - sizeValues, -} from '@/proxy/reports/data' interface PostFormValues { content: string @@ -112,6 +105,7 @@ export function CreatePostModal({ onClose, onSubmit, parentPostId }: CreatePostM {({ field }: FieldProps) => ( setFieldValue('content', e.value)} - > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + /> )} diff --git a/ui/src/views/list/CardView.tsx b/ui/src/views/list/CardView.tsx index 3c1560a6..35d20df7 100644 --- a/ui/src/views/list/CardView.tsx +++ b/ui/src/views/list/CardView.tsx @@ -57,7 +57,7 @@ import { getMobilePopupPosition, shouldUseMobileEditPopup, } from './shared/editPopup' -import { getValueByField, resolveEditorType } from './shared/editingForm' +import { getValueByField, resolveEditorType, withEditorTypeDefaults } from './shared/editingForm' import { createEditingFormHandlers } from './shared/editingFormScripts' import { flattenColumns } from './shared/columns' import { getInitialExtraFilters, mergeFilterTriplets } from './shared/hooks' @@ -811,6 +811,7 @@ const CardView = (props: CardViewProps) => { } catch (error) { console.error('EditorOptions parse error:', item.fieldName, error) } + editorOptions = withEditorTypeDefaults(item.editorType2, editorOptions) if (column.sourceDbType === DbTypeEnum.Date) { editorOptions = { diff --git a/ui/src/views/list/SchedulerView.tsx b/ui/src/views/list/SchedulerView.tsx index b1a595d8..7ca315de 100644 --- a/ui/src/views/list/SchedulerView.tsx +++ b/ui/src/views/list/SchedulerView.tsx @@ -39,7 +39,7 @@ import { getMobilePopupPosition, shouldUseMobileEditPopup, } from './shared/editPopup' -import { getValueByField, resolveEditorType } from './shared/editingForm' +import { getValueByField, resolveEditorType, withEditorTypeDefaults } from './shared/editingForm' import { flattenGridColumns } from './shared/columns' import { useListFormCustomSources, useListFormGridDto, useWidgetGroupHeight } from './shared/hooks' @@ -567,6 +567,7 @@ const SchedulerView = (props: SchedulerViewProps) => { } catch (err) { console.log(err) } + editorOptions = withEditorTypeDefaults(i.editorType2, editorOptions) const listFormField = i const listFormColumn = listFormColumns.find( diff --git a/ui/src/views/list/TodoBoard.tsx b/ui/src/views/list/TodoBoard.tsx index 5d3e0700..61c0c115 100644 --- a/ui/src/views/list/TodoBoard.tsx +++ b/ui/src/views/list/TodoBoard.tsx @@ -2,8 +2,8 @@ import type { GridDto, TodoOptionDto } from '@/proxy/form/models' import type { IdentityUserDto } from '@/proxy/admin/models' import type { DataGridRef } from 'devextreme-react/data-grid' import type CustomStore from 'devextreme/data/custom_store' -import { HtmlEditor, ImageUpload, MediaResizing } from 'devextreme-react/html-editor' -import { HtmlEditorToolbar } from '@/views/shared/HtmlEditorToolbarItems' +import { HtmlEditor } from 'devextreme-react/html-editor' +import { htmlEditorDefaults } from '@/views/shared/htmlEditorOptions' import Toolbar, { Item as ToolbarItem } from 'devextreme-react/toolbar' import TagBox from 'devextreme-react/tag-box' import { AVATAR_URL } from '@/constants/app.constant' @@ -1089,6 +1089,7 @@ const TodoBoardContent = ({
{activeTab === 'description' && options.descriptionExpr && ( - - - - + /> )} {activeTab === 'timesheet' && options.timesheetExpr && (
diff --git a/ui/src/views/list/shared/editingForm.ts b/ui/src/views/list/shared/editingForm.ts index 329e06e3..16731987 100644 --- a/ui/src/views/list/shared/editingForm.ts +++ b/ui/src/views/list/shared/editingForm.ts @@ -15,6 +15,7 @@ import { } from '@/proxy/form/models' import { scanBarcode } from '@/components/shared/BarcodeScanner/scanBarcode' import { getNextSequenceValue } from '@/services/form.service' +import { mergeHtmlEditorOptions } from '@/views/shared/htmlEditorOptions' import type { EditorType2, RowMode, SimpleItemWithColData } from '../../form/types' import { autoNumber, @@ -423,6 +424,15 @@ export const resolveEditorType = (editorType2?: EditorType2) => { return editorType2 } +/** Editör tipinin platform varsayılanlarını yapılandırmadan gelen editorOptions'ın altına serer. */ +export const withEditorTypeDefaults = >( + editorType2: EditorType2 | undefined, + editorOptions: T, +): T => + editorType2 === 'dxHtmlEditor' + ? mergeHtmlEditorOptions(editorOptions) + : editorOptions + type FormItemMapperParams = { mode: RowMode translate: (key: string) => string @@ -484,11 +494,11 @@ export const createFormItemMapper = ({ ? { readOnly: true } : {} - const editorOptions: EditorOptionsWithButtons = { + const editorOptions: EditorOptionsWithButtons = withEditorTypeDefaults(colFormat.editorType2, { ...defaultEditorOptions, ...parsedEditorOptions, ...forcedEditorOptions, - } + }) if (editorOptions.buttons) { editorOptions.buttons = editorOptions.buttons.map((btn: any) => { diff --git a/ui/src/views/shared/HtmlEditorToolbarItems.tsx b/ui/src/views/shared/HtmlEditorToolbarItems.tsx deleted file mode 100644 index e3f13601..00000000 --- a/ui/src/views/shared/HtmlEditorToolbarItems.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/** - * DevExtreme HtmlEditor için standart araç çubuğu. - * NoteModal ve TodoBoard içinde birebir aynı ~40 satırlık tanım tekrar ediyordu. - */ -import { Item, Toolbar } from 'devextreme-react/html-editor' -import { - fontFamilyOptions, - fontSizeOptions, - fontValues, - headerOptions, - headerValues, - sizeValues, -} from '@/proxy/reports/data' - -export const HtmlEditorToolbar = () => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -) diff --git a/ui/src/views/shared/htmlEditorOptions.ts b/ui/src/views/shared/htmlEditorOptions.ts new file mode 100644 index 00000000..6ad2a383 --- /dev/null +++ b/ui/src/views/shared/htmlEditorOptions.ts @@ -0,0 +1,114 @@ +/** + * Uygulamadaki bütün HtmlEditor'ların ortak varsayılanı. Sabit ekranlar (not, todo, forum) + * `` ile, dinamik formlar `mergeHtmlEditorOptions` ile + * aynı yetenek setini alır; ListForm'daki "HTML toolbar" kalıbı da buradan beslenir. + * Değerler JSON'a yazılabilir kalmalıdır — kalıp editorOptions kolonuna olduğu gibi kaydedilir. + */ +import type { Properties as HtmlEditorProperties } from 'devextreme/ui/html_editor' + +const sizeValues = ['8pt', '10pt', '12pt', '14pt', '18pt', '24pt', '36pt'] +const fontValues = [ + 'Arial', + 'Courier New', + 'Georgia', + 'Impact', + 'Lucida Console', + 'Segoe UI', + 'Tahoma', + 'Times New Roman', + 'Verdana', +] +const headerValues = [false, 1, 2, 3, 4, 5, 6] + +type ToolbarItem = NonNullable['items']>[number] + +export const htmlToolbarItemNames = [ + 'undo', + 'redo', + 'separator', + 'header', + 'size', + 'font', + 'separator', + 'bold', + 'italic', + 'underline', + 'strike', + 'superscript', + 'subscript', + 'separator', + 'color', + 'background', + 'separator', + 'alignLeft', + 'alignCenter', + 'alignRight', + 'alignJustify', + 'separator', + 'orderedList', + 'bulletList', + 'decreaseIndent', + 'increaseIndent', + 'separator', + 'blockquote', + 'codeBlock', + 'link', + 'image', + 'separator', + 'insertTable', + 'insertHeaderRow', + 'insertRowAbove', + 'insertRowBelow', + 'deleteRow', + 'insertColumnLeft', + 'insertColumnRight', + 'deleteColumn', + 'cellProperties', + 'tableProperties', + 'deleteTable', + 'separator', + 'clear', +] as const + +const toToolbarItem = (name: (typeof htmlToolbarItemNames)[number]): ToolbarItem => { + if (name === 'size') { + return { name, acceptedValues: sizeValues, options: { inputAttr: { 'aria-label': 'Font size' } } } + } + if (name === 'font') { + return { name, acceptedValues: fontValues, options: { inputAttr: { 'aria-label': 'Font family' } } } + } + if (name === 'header') { + return { name, acceptedValues: headerValues, options: { inputAttr: { 'aria-label': 'Header' } } } + } + return { name } +} + +export const buildHtmlEditorDefaults = () => + ({ + toolbar: { multiline: true, items: htmlToolbarItemNames.map(toToolbarItem) }, + // Varsayılan sekme yalnızca 'url'dir; bilgisayardan resim seçmek için 'file' açıkça verilmeli. + imageUpload: { tabs: ['file', 'url'], fileUploadMode: 'base64' }, + mediaResizing: { enabled: true }, + tableResizing: { enabled: true }, + tableContextMenu: { enabled: true }, + }) satisfies HtmlEditorProperties + +export const htmlEditorDefaults = buildHtmlEditorDefaults() + +const nestedKeys = ['toolbar', 'imageUpload', 'mediaResizing', 'tableResizing', 'tableContextMenu'] as const + +/** + * Yapılandırmadan gelen editorOptions'ı varsayılanın üstüne yazar. İç içe bloklar tek seviye + * birleştirilir; aksi hâlde yalnızca `imageUpload.fileUploadMode` yazan eski bir kayıt + * dosya sekmesini ve toolbar'ı sessizce kaybederdi. + */ +export const mergeHtmlEditorOptions = >(options: T): T => { + const merged: Record = { ...htmlEditorDefaults, ...options } + for (const key of nestedKeys) { + const own = options[key] + if (own && typeof own === 'object' && !Array.isArray(own)) { + merged[key] = { ...htmlEditorDefaults[key], ...own } + } + } + return merged as T +}