vault backup: 2025-04-25 13:28:07
This commit is contained in:
42
.obsidian/workspace.json
vendored
42
.obsidian/workspace.json
vendored
@@ -4,21 +4,17 @@
|
|||||||
"type": "split",
|
"type": "split",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "b1afd552ee0aa86f",
|
"id": "30ead2b90dc646d4",
|
||||||
"type": "tabs",
|
"type": "tabs",
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": "343836aed0394d03",
|
"id": "e53cb40c752bba12",
|
||||||
"type": "leaf",
|
"type": "leaf",
|
||||||
"state": {
|
"state": {
|
||||||
"type": "markdown",
|
"type": "empty",
|
||||||
"state": {
|
"state": {},
|
||||||
"file": "WORK & PROJECTS/Mol/Планы и диаграммы/Разработка - ЭПИКИ.md",
|
|
||||||
"mode": "source",
|
|
||||||
"source": false
|
|
||||||
},
|
|
||||||
"icon": "lucide-file",
|
"icon": "lucide-file",
|
||||||
"title": "Разработка - ЭПИКИ"
|
"title": "New tab"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -78,7 +74,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"direction": "horizontal",
|
"direction": "horizontal",
|
||||||
"width": 469.5
|
"width": 314.5
|
||||||
},
|
},
|
||||||
"right": {
|
"right": {
|
||||||
"id": "5a12b65cf742d665",
|
"id": "5a12b65cf742d665",
|
||||||
@@ -140,17 +136,24 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"type": "outline",
|
"type": "outline",
|
||||||
"state": {
|
"state": {
|
||||||
"file": "WORK & PROJECTS/Mol/Планы и диаграммы/Разработка - ЭПИКИ.md",
|
"file": "WORK & PROJECTS/Ulab/Доступы к точкам.md"
|
||||||
"followCursor": false,
|
|
||||||
"showSearch": false,
|
|
||||||
"searchQuery": ""
|
|
||||||
},
|
},
|
||||||
"icon": "lucide-list",
|
"icon": "lucide-list",
|
||||||
"title": "Outline of Разработка - ЭПИКИ"
|
"title": "Структура Доступы к точкам"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c463341da3a7d6da",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "git-view",
|
||||||
|
"state": {},
|
||||||
|
"icon": "git-pull-request",
|
||||||
|
"title": "Source Control"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"currentTab": 3
|
"currentTab": 4
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"direction": "horizontal",
|
"direction": "horizontal",
|
||||||
@@ -168,12 +171,10 @@
|
|||||||
"obsidian-git:Open Git source control": false
|
"obsidian-git:Open Git source control": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"active": "343836aed0394d03",
|
"active": "e53cb40c752bba12",
|
||||||
"lastOpenFiles": [
|
"lastOpenFiles": [
|
||||||
"WORK & PROJECTS/Mol/Планы и диаграммы/План СИЛА.md",
|
"WORK & PROJECTS/Mol/Code Chunks/Tiptap resizeTableColumnWidth.md",
|
||||||
"PERSONAL PROJECTS/P2EP/cdRead.canvas",
|
"PERSONAL PROJECTS/P2EP/cdRead.canvas",
|
||||||
"conflict-files-obsidian-git.md",
|
|
||||||
"My/Diet 2.md",
|
|
||||||
"PERSONAL PROJECTS/P2EP/pseudoCode/MakeSmallChar.md",
|
"PERSONAL PROJECTS/P2EP/pseudoCode/MakeSmallChar.md",
|
||||||
"PERSONAL PROJECTS/P2EP/pseudoCode/FUN_80015674 Update Entity Stats.md",
|
"PERSONAL PROJECTS/P2EP/pseudoCode/FUN_80015674 Update Entity Stats.md",
|
||||||
"PERSONAL PROJECTS/P2EP/pseudoCode/FUN_800453e0 SystemEventManager.md",
|
"PERSONAL PROJECTS/P2EP/pseudoCode/FUN_800453e0 SystemEventManager.md",
|
||||||
@@ -184,6 +185,7 @@
|
|||||||
"PERSONAL PROJECTS/PS1 DOCS/PSX code - Load texture from SECTOR!.md",
|
"PERSONAL PROJECTS/PS1 DOCS/PSX code - Load texture from SECTOR!.md",
|
||||||
"PERSONAL PROJECTS/PS1 DOCS/PSX code texture load unpack show and fade.md",
|
"PERSONAL PROJECTS/PS1 DOCS/PSX code texture load unpack show and fade.md",
|
||||||
"PERSONAL PROJECTS/Persona 1/Script Format.md",
|
"PERSONAL PROJECTS/Persona 1/Script Format.md",
|
||||||
|
"My/Diet 2.md",
|
||||||
"WORK & PROJECTS/Mol/Серверы/git.moldev.ru.md",
|
"WORK & PROJECTS/Mol/Серверы/git.moldev.ru.md",
|
||||||
"PERSONAL PROJECTS/PS1 DOCS/CD_Execute_Reading.md",
|
"PERSONAL PROJECTS/PS1 DOCS/CD_Execute_Reading.md",
|
||||||
"PERSONAL PROJECTS/PS1 DOCS/PSX code logo show.md",
|
"PERSONAL PROJECTS/PS1 DOCS/PSX code logo show.md",
|
||||||
|
|||||||
318
WORK & PROJECTS/Mol/Code Chunks/Tiptap resizeTableColumnWidth.md
Normal file
318
WORK & PROJECTS/Mol/Code Chunks/Tiptap resizeTableColumnWidth.md
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
|
||||||
|
# Creating a Tiptap Plugin for Resizable Table Columns in Vue 3
|
||||||
|
|
||||||
|
To create a Tiptap plugin that allows users to resize table columns and persists the width information in the node's JSON, you'll need to:
|
||||||
|
|
||||||
|
1. Extend the Table extension
|
||||||
|
2. Add resize handles
|
||||||
|
3. Handle mouse events for resizing
|
||||||
|
4. Store column widths in node attributes
|
||||||
|
|
||||||
|
Here's a comprehensive solution:
|
||||||
|
|
||||||
|
## 1. First, install required dependencies (if not already installed)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @tiptap/vue-3 @tiptap/pm @tiptap/starter-kit @tiptap/extension-table @tiptap/extension-table-row @tiptap/extension-table-header @tiptap/extension-table-cell
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Create a custom Table extension with resizable columns
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ResizableTableExtension.js
|
||||||
|
import { Table } from '@tiptap/extension-table'
|
||||||
|
import { Plugin, PluginKey } from '@tiptap/pm/state'
|
||||||
|
|
||||||
|
export const ResizableTableExtension = Table.extend({
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
...this.parent?.(),
|
||||||
|
colWidths: {
|
||||||
|
default: null,
|
||||||
|
parseHTML: element => element.getAttribute('data-col-widths'),
|
||||||
|
renderHTML: attributes => {
|
||||||
|
if (!attributes.colWidths) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'data-col-widths': attributes.colWidths.join(',')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addProseMirrorPlugins() {
|
||||||
|
const parentPlugins = this.parent?.() || []
|
||||||
|
|
||||||
|
return [
|
||||||
|
...parentPlugins,
|
||||||
|
resizableColumnsPlugin({
|
||||||
|
handleWidth: 5,
|
||||||
|
cellMinWidth: 25,
|
||||||
|
onColumnResize: (colIndex, width, colWidths, node) => {
|
||||||
|
const transaction = this.editor.state.tr.setNodeMarkup(
|
||||||
|
this.editor.state.selection.$anchor.posAtIndex(0),
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
...node.attrs,
|
||||||
|
colWidths: colWidths
|
||||||
|
}
|
||||||
|
)
|
||||||
|
this.editor.view.dispatch(transaction)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function resizableColumnsPlugin(options) {
|
||||||
|
const pluginKey = new PluginKey('resizableColumns')
|
||||||
|
let resizing = false
|
||||||
|
let startX, startWidth, colIndex, table, colWidths
|
||||||
|
|
||||||
|
return new Plugin({
|
||||||
|
key: pluginKey,
|
||||||
|
props: {
|
||||||
|
handleDOMEvents: {
|
||||||
|
mousedown: (view, event) => {
|
||||||
|
const target = event.target
|
||||||
|
if (target.classList.contains('column-resize-handle')) {
|
||||||
|
event.preventDefault()
|
||||||
|
startResizing(view, target, event.clientX)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mousemove: (view, event) => {
|
||||||
|
if (!resizing) return
|
||||||
|
const tableRect = table.getBoundingClientRect()
|
||||||
|
const totalWidth = tableRect.width
|
||||||
|
const newWidth = startWidth + (event.clientX - startX)
|
||||||
|
const percentageWidth = (newWidth / totalWidth) * 100
|
||||||
|
|
||||||
|
// Update the column widths array
|
||||||
|
const newColWidths = [...colWidths]
|
||||||
|
newColWidths[colIndex] = Math.max(options.cellMinWidth, percentageWidth)
|
||||||
|
|
||||||
|
// Apply the new widths to the table columns
|
||||||
|
applyColumnWidths(table, newColWidths)
|
||||||
|
|
||||||
|
if (options.onColumnResize) {
|
||||||
|
const node = view.state.doc.nodeAt(view.state.selection.$anchor.posAtIndex(0))
|
||||||
|
options.onColumnResize(colIndex, percentageWidth, newColWidths, node)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
mouseup: () => {
|
||||||
|
if (resizing) {
|
||||||
|
resizing = false
|
||||||
|
document.body.style.cursor = ''
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
view: (view) => {
|
||||||
|
return {
|
||||||
|
update: (view) => {
|
||||||
|
// Add resize handles to columns
|
||||||
|
const tables = view.dom.querySelectorAll('table')
|
||||||
|
tables.forEach(table => {
|
||||||
|
const rows = table.querySelectorAll('tr')
|
||||||
|
if (rows.length === 0) return
|
||||||
|
|
||||||
|
// Remove existing handles
|
||||||
|
const existingHandles = table.querySelectorAll('.column-resize-handle')
|
||||||
|
existingHandles.forEach(handle => handle.remove())
|
||||||
|
|
||||||
|
// Get column widths from node attributes or calculate equal distribution
|
||||||
|
const node = view.state.doc.nodeAt(view.state.selection.$anchor.posAtIndex(0))
|
||||||
|
const attrs = node?.attrs || {}
|
||||||
|
colWidths = attrs.colWidths
|
||||||
|
? attrs.colWidths.split(',').map(Number)
|
||||||
|
: Array(rows[0].cells.length).fill(100 / rows[0].cells.length)
|
||||||
|
|
||||||
|
// Apply initial column widths
|
||||||
|
applyColumnWidths(table, colWidths)
|
||||||
|
|
||||||
|
// Add resize handles
|
||||||
|
const firstRow = rows[0]
|
||||||
|
Array.from(firstRow.cells).forEach((cell, index) => {
|
||||||
|
const handle = document.createElement('div')
|
||||||
|
handle.className = 'column-resize-handle'
|
||||||
|
handle.style.position = 'absolute'
|
||||||
|
handle.style.top = '0'
|
||||||
|
handle.style.right = '0'
|
||||||
|
handle.style.width = `${options.handleWidth}px`
|
||||||
|
handle.style.height = '100%'
|
||||||
|
handle.style.cursor = 'col-resize'
|
||||||
|
handle.style.backgroundColor = 'transparent'
|
||||||
|
handle.style.zIndex = '10'
|
||||||
|
|
||||||
|
cell.style.position = 'relative'
|
||||||
|
cell.appendChild(handle)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function startResizing(view, handle, clientX) {
|
||||||
|
resizing = true
|
||||||
|
startX = clientX
|
||||||
|
const cell = handle.parentElement
|
||||||
|
const row = cell.parentElement
|
||||||
|
table = row.closest('table')
|
||||||
|
colIndex = Array.from(row.cells).indexOf(cell)
|
||||||
|
|
||||||
|
// Get current column widths
|
||||||
|
const node = view.state.doc.nodeAt(view.state.selection.$anchor.posAtIndex(0))
|
||||||
|
const attrs = node?.attrs || {}
|
||||||
|
colWidths = attrs.colWidths
|
||||||
|
? attrs.colWidths.split(',').map(Number)
|
||||||
|
: Array(row.cells.length).fill(100 / row.cells.length)
|
||||||
|
|
||||||
|
const tableRect = table.getBoundingClientRect()
|
||||||
|
const totalWidth = tableRect.width
|
||||||
|
startWidth = (colWidths[colIndex] / 100) * totalWidth
|
||||||
|
|
||||||
|
document.body.style.cursor = 'col-resize'
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyColumnWidths(table, widths) {
|
||||||
|
const rows = table.querySelectorAll('tr')
|
||||||
|
if (rows.length === 0) return
|
||||||
|
|
||||||
|
rows.forEach(row => {
|
||||||
|
Array.from(row.cells).forEach((cell, index) => {
|
||||||
|
cell.style.width = `${widths[index]}%`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Use the extension in your Vue 3 component
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<editor-content :editor="editor" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import Table from '@tiptap/extension-table'
|
||||||
|
import TableRow from '@tiptap/extension-table-row'
|
||||||
|
import TableHeader from '@tiptap/extension-table-header'
|
||||||
|
import TableCell from '@tiptap/extension-table-cell'
|
||||||
|
import { ResizableTableExtension } from './ResizableTableExtension'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
EditorContent,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
editor: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.editor = new Editor({
|
||||||
|
extensions: [
|
||||||
|
StarterKit,
|
||||||
|
ResizableTableExtension,
|
||||||
|
TableRow,
|
||||||
|
TableHeader,
|
||||||
|
TableCell.configure({
|
||||||
|
HTMLAttributes: {
|
||||||
|
style: 'position: relative;',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
content: `
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Header 1</th>
|
||||||
|
<th>Header 2</th>
|
||||||
|
<th>Header 3</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Cell 1</td>
|
||||||
|
<td>Cell 2</td>
|
||||||
|
<td>Cell 3</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeUnmount() {
|
||||||
|
this.editor.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.column-resize-handle {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 5px;
|
||||||
|
height: 100%;
|
||||||
|
cursor: col-resize;
|
||||||
|
background-color: transparent;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-resize-handle:hover {
|
||||||
|
background-color: #adf;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 1rem 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 8px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Features of This Implementation:
|
||||||
|
|
||||||
|
1. **Column Width Storage**: Column widths are stored in the table node's attributes as a comma-separated string of percentages.
|
||||||
|
|
||||||
|
2. **Resize Handles**: Adds invisible resize handles to each column that become active on hover.
|
||||||
|
|
||||||
|
3. **Persistence**: When you save the editor content to JSON, the column widths are preserved in the node attributes.
|
||||||
|
|
||||||
|
4. **Initialization**: If no widths are specified, columns are initially equally distributed.
|
||||||
|
|
||||||
|
5. **Minimum Width**: Enforces a minimum column width to prevent columns from becoming too narrow.
|
||||||
|
|
||||||
|
## Usage Notes:
|
||||||
|
|
||||||
|
- The plugin adds resize handles to the right side of each column header
|
||||||
|
- Users can drag these handles to resize columns
|
||||||
|
- The widths are stored as percentages to maintain responsiveness
|
||||||
|
- The data is saved in the table node's attributes and will be included when you call `editor.getJSON()`
|
||||||
|
|
||||||
|
## Customization Options:
|
||||||
|
|
||||||
|
You can adjust these parameters in the plugin options:
|
||||||
|
- `handleWidth`: Width of the resize handle in pixels
|
||||||
|
- `cellMinWidth`: Minimum column width in percentage
|
||||||
|
- `onColumnResize`: Callback function when a column is resized
|
||||||
|
|
||||||
|
This implementation provides a complete solution for resizable table columns in Tiptap with Vue 3, with proper persistence of column widths in the document JSON.
|
||||||
Reference in New Issue
Block a user