claritykit-svelte
Version:
A comprehensive Svelte component library focused on accessibility, ADHD-optimized design, developer experience, and full SSR compatibility
260 lines (259 loc) • 9.54 kB
JavaScript
import { Node, mergeAttributes } from '@tiptap/core';
import { createSvelteNodeView } from '../utils/svelte-node-view';
import DatabaseViewBlock from '../components/DatabaseViewBlock.svelte';
/**
* DatabaseView Extension for TipTap
*
* Provides inline database views with multiple display modes (table, kanban, gallery, etc.)
* Integrates with PostgreSQL materialized views and event sourcing
*/
export const DatabaseViewExtension = Node.create({
name: 'databaseView',
group: 'block',
atom: true,
draggable: true,
addOptions() {
return {
HTMLAttributes: {},
dictionary: {
insertDatabaseView: 'Insert database view',
selectViewType: 'Select view type',
configureView: 'Configure view',
},
};
},
addAttributes() {
return {
id: {
default: null,
parseHTML: element => element.getAttribute('data-id'),
renderHTML: attributes => {
if (!attributes.id) {
return {};
}
return {
'data-id': attributes.id,
};
},
},
source: {
default: null,
parseHTML: element => element.getAttribute('data-source'),
renderHTML: attributes => {
if (!attributes.source) {
return {};
}
return {
'data-source': attributes.source,
};
},
},
viewType: {
default: 'table',
parseHTML: element => element.getAttribute('data-view-type') || 'table',
renderHTML: attributes => ({
'data-view-type': attributes.viewType,
}),
},
config: {
default: {},
parseHTML: element => {
const config = element.getAttribute('data-config');
if (config) {
try {
return JSON.parse(config);
}
catch {
return {};
}
}
return {};
},
renderHTML: attributes => {
if (!attributes.config || Object.keys(attributes.config).length === 0) {
return {};
}
return {
'data-config': JSON.stringify(attributes.config),
};
},
},
title: {
default: '',
parseHTML: element => element.getAttribute('data-title') || '',
renderHTML: attributes => {
if (!attributes.title) {
return {};
}
return {
'data-title': attributes.title,
};
},
},
filters: {
default: [],
parseHTML: element => {
const filters = element.getAttribute('data-filters');
if (filters) {
try {
return JSON.parse(filters);
}
catch {
return [];
}
}
return [];
},
renderHTML: attributes => {
if (!attributes.filters || attributes.filters.length === 0) {
return {};
}
return {
'data-filters': JSON.stringify(attributes.filters),
};
},
},
sorting: {
default: null,
parseHTML: element => {
const sorting = element.getAttribute('data-sorting');
if (sorting) {
try {
return JSON.parse(sorting);
}
catch {
return null;
}
}
return null;
},
renderHTML: attributes => {
if (!attributes.sorting) {
return {};
}
return {
'data-sorting': JSON.stringify(attributes.sorting),
};
},
},
editable: {
default: true,
parseHTML: element => element.getAttribute('data-editable') !== 'false',
renderHTML: attributes => ({
'data-editable': attributes.editable,
}),
},
realtime: {
default: false,
parseHTML: element => element.getAttribute('data-realtime') === 'true',
renderHTML: attributes => ({
'data-realtime': attributes.realtime,
}),
},
};
},
parseHTML() {
return [
{
tag: 'div[data-type="database-view"]',
},
];
},
renderHTML({ HTMLAttributes }) {
return [
'div',
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
'data-type': 'database-view',
class: 'database-view-block',
}),
];
},
addCommands() {
return {
insertDatabaseView: (options = {}) => ({ commands, editor }) => {
const { source = '', viewType = 'table', config = {} } = options;
// Generate unique ID for the view
const id = `dbview-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
return commands.insertContent({
type: this.name,
attrs: {
id,
source,
viewType,
config: {
...config,
defaultView: viewType,
views: config.views || {
table: {
columns: [],
allowAdd: true,
allowEdit: true,
allowDelete: true,
},
kanban: {
columns: [
{ id: 'todo', title: 'To Do' },
{ id: 'in_progress', title: 'In Progress' },
{ id: 'done', title: 'Done' },
],
groupBy: 'status',
},
gallery: {
columns: 3,
titleField: 'title',
imageField: 'image',
descriptionField: 'description',
},
list: {
columns: [],
showCheckbox: true,
expandable: true,
},
calendar: {
dateField: 'date',
titleField: 'title',
},
gantt: {
startField: 'start_date',
endField: 'end_date',
titleField: 'title',
progressField: 'progress',
},
},
},
title: config.title || 'Database View',
filters: [],
sorting: null,
editable: true,
realtime: false,
},
});
},
updateDatabaseView: (options) => ({ commands, editor, state }) => {
const { selection } = state;
const node = selection.$head.node();
if (node.type.name !== this.name) {
return false;
}
return commands.updateAttributes(this.name, options);
},
};
},
addNodeView() {
return createSvelteNodeView({
component: DatabaseViewBlock,
className: 'database-view-node-wrapper',
tag: 'div',
});
},
addKeyboardShortcuts() {
return {
// Mod-Shift-D to insert database view
'Mod-Shift-d': () => this.editor.commands.insertDatabaseView(),
};
},
// Add to slash commands
addProseMirrorPlugins() {
return [];
},
});