diff --git a/apps/admin/src/pages/contents/ContentEditor.css b/apps/admin/src/pages/contents/ContentEditor.css
index d805df8..94f616c 100644
--- a/apps/admin/src/pages/contents/ContentEditor.css
+++ b/apps/admin/src/pages/contents/ContentEditor.css
@@ -206,11 +206,11 @@
cursor: pointer;
}
-.editor-toolbar__button[title='Geri al']::before {
+.editor-toolbar__button[data-toolbar-icon='undo']::before {
background-image: url('./assets/icons/undo.svg');
}
-.editor-toolbar__button[title='İleri al']::before {
+.editor-toolbar__button[data-toolbar-icon='redo']::before {
background-image: url('./assets/icons/redo.svg');
}
@@ -1791,6 +1791,87 @@ body.editor-image-dialog-open .editor-image-context-toolbar {
border: none;
}
+.table-text-alignment {
+ --table-align-surface: #ffffff;
+ --table-align-surface-active: #dbeafe;
+ --table-align-ink: #64748b;
+ --table-align-ink-active: #1d4ed8;
+ --table-align-border: #e2e8f0;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 8px 12px;
+}
+
+.table-text-alignment__label {
+ color: #374151;
+ font-size: 12px;
+ font-weight: 600;
+}
+
+.table-text-alignment__controls {
+ display: inline-grid;
+ grid-template-columns: repeat(3, 32px);
+ gap: 4px;
+ padding: 4px;
+ border: 1px solid var(--table-align-border);
+ border-radius: 8px;
+ background: #f8fafc;
+}
+
+.table-text-alignment__button {
+ display: inline-flex;
+ width: 32px;
+ height: 28px;
+ align-items: center;
+ justify-content: center;
+ border: 0;
+ border-radius: 6px;
+ color: var(--table-align-ink);
+ background: transparent;
+}
+
+.table-text-alignment__button:hover,
+.table-text-alignment__button:focus-visible {
+ color: var(--table-align-ink-active);
+ background: var(--table-align-surface);
+ outline: none;
+ box-shadow: 0 0 0 2px #bfdbfe;
+}
+
+.table-text-alignment__button.is-active {
+ color: var(--table-align-ink-active);
+ background: var(--table-align-surface-active);
+}
+
+.table-text-alignment__icon {
+ display: flex;
+ width: 14px;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.table-text-alignment__icon i {
+ display: block;
+ width: 14px;
+ height: 1.5px;
+ border-radius: 999px;
+ background: currentColor;
+}
+
+.table-text-alignment__icon i:nth-child(2) {
+ width: 9px;
+}
+
+.table-text-alignment__icon--center i:nth-child(2) {
+ align-self: center;
+}
+
+.table-text-alignment__icon--right i:nth-child(2) {
+ align-self: flex-end;
+}
+
/* Color Picker Styles */
.table-action-item-with-submenu {
position: relative;
diff --git a/apps/admin/src/pages/contents/ContentEditor.jsx b/apps/admin/src/pages/contents/ContentEditor.jsx
index de5dd85..02ce1cc 100644
--- a/apps/admin/src/pages/contents/ContentEditor.jsx
+++ b/apps/admin/src/pages/contents/ContentEditor.jsx
@@ -20,7 +20,6 @@ import { LexicalComposer } from '@lexical/react/LexicalComposer'
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'
import { ContentEditable } from '@lexical/react/LexicalContentEditable'
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'
-import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'
import {
$createParagraphNode,
@@ -28,6 +27,7 @@ import {
$getRoot,
$getSelection,
$getNodeByKey,
+ $isElementNode,
$isRangeSelection,
$isNodeSelection,
$isTextNode,
@@ -36,7 +36,6 @@ import {
CAN_REDO_COMMAND,
CAN_UNDO_COMMAND,
COMMAND_PRIORITY_LOW,
- FORMAT_ELEMENT_COMMAND,
FORMAT_TEXT_COMMAND,
REDO_COMMAND,
SELECTION_CHANGE_COMMAND,
@@ -72,6 +71,7 @@ import TableHoverActionsPlugin from './plugins/TableHoverActionsPlugin.jsx'
import TableCellResizerPlugin from './plugins/TableCellResizerPlugin.jsx'
import TableSelectionPlugin from './plugins/TableSelectionPlugin.jsx'
import TableCellFocusPlugin from './plugins/TableCellFocusPlugin.jsx'
+import EditorHistoryPlugin from './plugins/EditorHistoryPlugin.jsx'
import ImagePlugin, { INSERT_IMAGE_COMMAND } from './plugins/ImagePlugin.jsx'
import ImageHandlersPlugin from './plugins/ImageHandlersPlugin.jsx'
import VideoPlugin, { INSERT_VIDEO_COMMAND } from './plugins/VideoPlugin.jsx'
@@ -90,7 +90,9 @@ import {
$createTableWithDimensions,
$createTableNode,
$createTableRowNode,
- $createTableCellNode
+ $createTableCellNode,
+ $isTableCellNode,
+ $setTableCellTextAlignment,
} from './nodes/TableNode.jsx'
import { PhotoIcon, TrashIcon } from '@heroicons/react/24/outline'
import MediaPickerModal from './components/MediaPickerModal.jsx'
@@ -1874,7 +1876,7 @@ export default function ContentEditor() {
placeholder={}
ErrorBoundary={LexicalErrorBoundary}
/>
-
+
@@ -3196,6 +3198,23 @@ const BLOCK_OPTIONS = [
{ value: 'code', label: 'Kod Bloğu' },
]
+function getNearestEditableBlock(node) {
+ if (!node) return null
+
+ let current = $isElementNode(node) ? node : node.getParent()
+ while (current) {
+ if ($isTableCellNode(current)) {
+ return current
+ }
+ if ($isElementNode(current) && !current.isInline()) {
+ return current
+ }
+ current = current.getParent()
+ }
+
+ return null
+}
+
function Toolbar({
openMediaPicker = null,
isTableSelectorOpen = false,
@@ -3428,6 +3447,35 @@ function Toolbar({
[editor, blockType]
)
+ const applyBlockAlignment = useCallback((alignment) => {
+ editor.update(() => {
+ const selection = $getSelection()
+ if (!$isRangeSelection(selection)) return
+
+ const targets = new Map()
+ selection.getNodes().forEach((node) => {
+ const target = getNearestEditableBlock(node)
+ if (target) {
+ targets.set(target.getKey(), target)
+ }
+ })
+
+ // A collapsed caret in an empty block may not yield a node list.
+ if (targets.size === 0) {
+ const target = getNearestEditableBlock(selection.anchor.getNode())
+ if (target) targets.set(target.getKey(), target)
+ }
+
+ targets.forEach((target) => {
+ if ($isTableCellNode(target)) {
+ $setTableCellTextAlignment(target, alignment)
+ } else {
+ target.setFormat(alignment)
+ }
+ })
+ })
+ }, [editor])
+
const updateToolbar = useCallback(() => {
editor.getEditorState().read(() => {
const selection = $getSelection()
@@ -3444,17 +3492,9 @@ function Toolbar({
})
const anchorNode = selection.anchor.getNode()
- let element = null
-
- if (anchorNode.getType() === 'root') {
- element = anchorNode.getLastChild()
- if (!element) {
- setBlockType('paragraph')
- setBlockFormat('left')
- }
- } else {
- element = anchorNode.getTopLevelElementOrThrow()
- }
+ let element = anchorNode.getType() === 'root'
+ ? anchorNode.getLastChild()
+ : getNearestEditableBlock(anchorNode)
if (!element || element.getType() === 'root') {
element = element?.getLastChild() || null
@@ -3672,12 +3712,16 @@ function Toolbar({
return (
editor.dispatchCommand(UNDO_COMMAND, undefined)}
disabled={!canUndo}
/>
editor.dispatchCommand(REDO_COMMAND, undefined)}
disabled={!canRedo}
/>
@@ -3813,19 +3857,19 @@ function Toolbar({
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'left')}
+ onClick={() => applyBlockAlignment('left')}
active={blockFormat === 'left'}
>
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'center')}
+ onClick={() => applyBlockAlignment('center')}
active={blockFormat === 'center'}
>
editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, 'right')}
+ onClick={() => applyBlockAlignment('right')}
active={blockFormat === 'right'}
>
@@ -3833,7 +3877,7 @@ function Toolbar({
)
}
-function ToolbarButton({ children, onClick, active = false, disabled = false, title }) {
+function ToolbarButton({ children, onClick, active = false, disabled = false, title, ariaKeyShortcuts, icon }) {
return (