added blocks

This commit is contained in:
Arjun 2026-03-18 22:56:00 +05:30
parent a10e97110d
commit fc3298f389
10 changed files with 966 additions and 0 deletions

View file

@ -46,6 +46,7 @@
"radix-ui": "^1.4.3",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"recharts": "^3.8.0",
"sonner": "^2.0.7",
"streamdown": "^1.6.10",
"tailwind-merge": "^3.4.0",

View file

@ -9,6 +9,10 @@ import TaskList from '@tiptap/extension-task-list'
import TaskItem from '@tiptap/extension-task-item'
import { ImageUploadPlaceholderExtension, createImageUploadHandler } from '@/extensions/image-upload'
import { TaskBlockExtension } from '@/extensions/task-block'
import { ImageBlockExtension } from '@/extensions/image-block'
import { EmbedBlockExtension } from '@/extensions/embed-block'
import { ChartBlockExtension } from '@/extensions/chart-block'
import { TableBlockExtension } from '@/extensions/table-block'
import { Markdown } from 'tiptap-markdown'
import { useEffect, useCallback, useMemo, useRef, useState } from 'react'
@ -136,6 +140,14 @@ function getMarkdownWithBlankLines(editor: Editor): string {
blocks.push(listLines.join('\n'))
} else if (node.type === 'taskBlock') {
blocks.push('```task\n' + (node.attrs?.data as string || '{}') + '\n```')
} else if (node.type === 'imageBlock') {
blocks.push('```image\n' + (node.attrs?.data as string || '{}') + '\n```')
} else if (node.type === 'embedBlock') {
blocks.push('```embed\n' + (node.attrs?.data as string || '{}') + '\n```')
} else if (node.type === 'chartBlock') {
blocks.push('```chart\n' + (node.attrs?.data as string || '{}') + '\n```')
} else if (node.type === 'tableBlock') {
blocks.push('```table\n' + (node.attrs?.data as string || '{}') + '\n```')
} else if (node.type === 'codeBlock') {
const lang = (node.attrs?.language as string) || ''
blocks.push('```' + lang + '\n' + nodeToText(node) + '\n```')
@ -429,6 +441,10 @@ export function MarkdownEditor({
}),
ImageUploadPlaceholderExtension,
TaskBlockExtension,
ImageBlockExtension,
EmbedBlockExtension,
ChartBlockExtension,
TableBlockExtension,
WikiLink.configure({
onCreate: wikiLinks?.onCreate
? (path) => {

View file

@ -0,0 +1,173 @@
import { mergeAttributes, Node } from '@tiptap/react'
import { ReactNodeViewRenderer, NodeViewWrapper } from '@tiptap/react'
import { X, BarChart3 } from 'lucide-react'
import { blocks } from '@x/shared'
import { useState, useEffect } from 'react'
import {
LineChart, Line,
BarChart, Bar,
PieChart, Pie, Cell,
XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend,
} from 'recharts'
const CHART_COLORS = ['#8884d8', '#82ca9d', '#ffc658', '#ff7300', '#0088fe', '#00c49f']
function ChartBlockView({ node, deleteNode }: { node: { attrs: Record<string, unknown> }; deleteNode: () => void }) {
const raw = node.attrs.data as string
let config: blocks.ChartBlock | null = null
try {
config = blocks.ChartBlockSchema.parse(JSON.parse(raw))
} catch {
// fallback below
}
const [fileData, setFileData] = useState<Record<string, unknown>[] | null>(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
if (!config?.source) return
setLoading(true)
setError(null)
;(window as unknown as { ipc: { invoke: (channel: string, args: Record<string, string>) => Promise<string> } })
.ipc.invoke('workspace:readFile', { path: config.source, encoding: 'utf-8' })
.then((content: string) => {
const parsed = JSON.parse(content)
if (Array.isArray(parsed)) {
setFileData(parsed)
} else {
setError('Source file must contain a JSON array')
}
})
.catch((err: Error) => {
setError(err.message || 'Failed to load data file')
})
.finally(() => setLoading(false))
}, [config?.source])
if (!config) {
return (
<NodeViewWrapper className="chart-block-wrapper" data-type="chart-block">
<div className="chart-block-card chart-block-error">
<BarChart3 size={16} />
<span>Invalid chart block</span>
</div>
</NodeViewWrapper>
)
}
const data = config.data || fileData
const renderChart = () => {
if (loading) return <div className="chart-block-loading">Loading data...</div>
if (error) return <div className="chart-block-error-msg">{error}</div>
if (!data || data.length === 0) return <div className="chart-block-empty">No data</div>
return (
<ResponsiveContainer width="100%" height={250}>
{config!.chart === 'line' ? (
<LineChart data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey={config!.x} />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey={config!.y} stroke="#8884d8" />
</LineChart>
) : config!.chart === 'bar' ? (
<BarChart data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey={config!.x} />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey={config!.y} fill="#8884d8" />
</BarChart>
) : (
<PieChart>
<Tooltip />
<Legend />
<Pie data={data} dataKey={config!.y} nameKey={config!.x} cx="50%" cy="50%" outerRadius={80} label>
{data.map((_, index) => (
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
))}
</Pie>
</PieChart>
)}
</ResponsiveContainer>
)
}
return (
<NodeViewWrapper className="chart-block-wrapper" data-type="chart-block">
<div className="chart-block-card">
<button
className="chart-block-delete"
onClick={deleteNode}
aria-label="Delete chart block"
>
<X size={14} />
</button>
{config.title && <div className="chart-block-title">{config.title}</div>}
{renderChart()}
</div>
</NodeViewWrapper>
)
}
export const ChartBlockExtension = Node.create({
name: 'chartBlock',
group: 'block',
atom: true,
selectable: true,
draggable: false,
addAttributes() {
return {
data: {
default: '{}',
},
}
},
parseHTML() {
return [
{
tag: 'pre',
priority: 60,
getAttrs(element) {
const code = element.querySelector('code')
if (!code) return false
const cls = code.className || ''
if (cls.includes('language-chart')) {
return { data: code.textContent || '{}' }
}
return false
},
},
]
},
renderHTML({ HTMLAttributes }: { HTMLAttributes: Record<string, unknown> }) {
return ['div', mergeAttributes(HTMLAttributes, { 'data-type': 'chart-block' })]
},
addNodeView() {
return ReactNodeViewRenderer(ChartBlockView)
},
addStorage() {
return {
markdown: {
serialize(state: { write: (text: string) => void; closeBlock: (node: unknown) => void }, node: { attrs: { data: string } }) {
state.write('```chart\n' + node.attrs.data + '\n```')
state.closeBlock(node)
},
parse: {
// handled by parseHTML
},
},
}
},
})

View file

@ -0,0 +1,143 @@
import { mergeAttributes, Node } from '@tiptap/react'
import { ReactNodeViewRenderer, NodeViewWrapper } from '@tiptap/react'
import { X, ExternalLink } from 'lucide-react'
import { blocks } from '@x/shared'
function getEmbedUrl(provider: string, url: string): string | null {
if (provider === 'youtube') {
// Handle youtube.com/watch?v=X and youtu.be/X
const match = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/)
if (match) return `https://www.youtube.com/embed/${match[1]}`
}
if (provider === 'figma') {
// Convert www.figma.com/design/:key/... → embed.figma.com/design/:key?embed-host=rowboat
const figmaMatch = url.match(/figma\.com\/(design|board|proto)\/([\w-]+)/)
if (figmaMatch) {
return `https://embed.figma.com/${figmaMatch[1]}/${figmaMatch[2]}?embed-host=rowboat`
}
// Legacy /file/ URLs
const legacyMatch = url.match(/figma\.com\/file\/([\w-]+)/)
if (legacyMatch) {
return `https://embed.figma.com/design/${legacyMatch[1]}?embed-host=rowboat`
}
}
return null
}
function EmbedBlockView({ node, deleteNode }: { node: { attrs: Record<string, unknown> }; deleteNode: () => void }) {
const raw = node.attrs.data as string
let config: blocks.EmbedBlock | null = null
try {
config = blocks.EmbedBlockSchema.parse(JSON.parse(raw))
} catch {
// fallback below
}
if (!config) {
return (
<NodeViewWrapper className="embed-block-wrapper" data-type="embed-block">
<div className="embed-block-card embed-block-error">
<ExternalLink size={16} />
<span>Invalid embed block</span>
</div>
</NodeViewWrapper>
)
}
const embedUrl = getEmbedUrl(config.provider, config.url)
return (
<NodeViewWrapper className="embed-block-wrapper" data-type="embed-block">
<div className="embed-block-card">
<button
className="embed-block-delete"
onClick={deleteNode}
aria-label="Delete embed block"
>
<X size={14} />
</button>
{embedUrl ? (
<div className="embed-block-iframe-container">
<iframe
src={embedUrl}
className="embed-block-iframe"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
sandbox="allow-scripts allow-same-origin allow-popups"
/>
</div>
) : (
<a
href={config.url}
target="_blank"
rel="noopener noreferrer"
className="embed-block-link"
>
<ExternalLink size={14} />
{config.url}
</a>
)}
{config.caption && (
<div className="embed-block-caption">{config.caption}</div>
)}
</div>
</NodeViewWrapper>
)
}
export const EmbedBlockExtension = Node.create({
name: 'embedBlock',
group: 'block',
atom: true,
selectable: true,
draggable: false,
addAttributes() {
return {
data: {
default: '{}',
},
}
},
parseHTML() {
return [
{
tag: 'pre',
priority: 60,
getAttrs(element) {
const code = element.querySelector('code')
if (!code) return false
const cls = code.className || ''
if (cls.includes('language-embed')) {
return { data: code.textContent || '{}' }
}
return false
},
},
]
},
renderHTML({ HTMLAttributes }: { HTMLAttributes: Record<string, unknown> }) {
return ['div', mergeAttributes(HTMLAttributes, { 'data-type': 'embed-block' })]
},
addNodeView() {
return ReactNodeViewRenderer(EmbedBlockView)
},
addStorage() {
return {
markdown: {
serialize(state: { write: (text: string) => void; closeBlock: (node: unknown) => void }, node: { attrs: { data: string } }) {
state.write('```embed\n' + node.attrs.data + '\n```')
state.closeBlock(node)
},
parse: {
// handled by parseHTML
},
},
}
},
})

View file

@ -0,0 +1,104 @@
import { mergeAttributes, Node } from '@tiptap/react'
import { ReactNodeViewRenderer, NodeViewWrapper } from '@tiptap/react'
import { X, ImageIcon } from 'lucide-react'
import { blocks } from '@x/shared'
function ImageBlockView({ node, deleteNode }: { node: { attrs: Record<string, unknown> }; deleteNode: () => void }) {
const raw = node.attrs.data as string
let config: blocks.ImageBlock | null = null
try {
config = blocks.ImageBlockSchema.parse(JSON.parse(raw))
} catch {
// fallback below
}
if (!config) {
return (
<NodeViewWrapper className="image-block-wrapper" data-type="image-block">
<div className="image-block-card image-block-error">
<ImageIcon size={16} />
<span>Invalid image block</span>
</div>
</NodeViewWrapper>
)
}
return (
<NodeViewWrapper className="image-block-wrapper" data-type="image-block">
<div className="image-block-card">
<button
className="image-block-delete"
onClick={deleteNode}
aria-label="Delete image block"
>
<X size={14} />
</button>
<img
src={config.src}
alt={config.alt || ''}
className="image-block-img"
/>
{config.caption && (
<div className="image-block-caption">{config.caption}</div>
)}
</div>
</NodeViewWrapper>
)
}
export const ImageBlockExtension = Node.create({
name: 'imageBlock',
group: 'block',
atom: true,
selectable: true,
draggable: false,
addAttributes() {
return {
data: {
default: '{}',
},
}
},
parseHTML() {
return [
{
tag: 'pre',
priority: 60,
getAttrs(element) {
const code = element.querySelector('code')
if (!code) return false
const cls = code.className || ''
if (cls.includes('language-image')) {
return { data: code.textContent || '{}' }
}
return false
},
},
]
},
renderHTML({ HTMLAttributes }: { HTMLAttributes: Record<string, unknown> }) {
return ['div', mergeAttributes(HTMLAttributes, { 'data-type': 'image-block' })]
},
addNodeView() {
return ReactNodeViewRenderer(ImageBlockView)
},
addStorage() {
return {
markdown: {
serialize(state: { write: (text: string) => void; closeBlock: (node: unknown) => void }, node: { attrs: { data: string } }) {
state.write('```image\n' + node.attrs.data + '\n```')
state.closeBlock(node)
},
parse: {
// handled by parseHTML
},
},
}
},
})

View file

@ -0,0 +1,124 @@
import { mergeAttributes, Node } from '@tiptap/react'
import { ReactNodeViewRenderer, NodeViewWrapper } from '@tiptap/react'
import { X, Table2 } from 'lucide-react'
import { blocks } from '@x/shared'
function TableBlockView({ node, deleteNode }: { node: { attrs: Record<string, unknown> }; deleteNode: () => void }) {
const raw = node.attrs.data as string
let config: blocks.TableBlock | null = null
try {
config = blocks.TableBlockSchema.parse(JSON.parse(raw))
} catch {
// fallback below
}
if (!config) {
return (
<NodeViewWrapper className="table-block-wrapper" data-type="table-block">
<div className="table-block-card table-block-error">
<Table2 size={16} />
<span>Invalid table block</span>
</div>
</NodeViewWrapper>
)
}
return (
<NodeViewWrapper className="table-block-wrapper" data-type="table-block">
<div className="table-block-card">
<button
className="table-block-delete"
onClick={deleteNode}
aria-label="Delete table block"
>
<X size={14} />
</button>
{config.title && <div className="table-block-title">{config.title}</div>}
<div className="table-block-scroll">
<table className="table-block-table">
<thead>
<tr>
{config.columns.map((col) => (
<th key={col}>{col}</th>
))}
</tr>
</thead>
<tbody>
{config.data.map((row, i) => (
<tr key={i}>
{config!.columns.map((col) => (
<td key={col}>{String(row[col] ?? '')}</td>
))}
</tr>
))}
{config.data.length === 0 && (
<tr>
<td colSpan={config.columns.length} className="table-block-empty">
No data
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</NodeViewWrapper>
)
}
export const TableBlockExtension = Node.create({
name: 'tableBlock',
group: 'block',
atom: true,
selectable: true,
draggable: false,
addAttributes() {
return {
data: {
default: '{}',
},
}
},
parseHTML() {
return [
{
tag: 'pre',
priority: 60,
getAttrs(element) {
const code = element.querySelector('code')
if (!code) return false
const cls = code.className || ''
if (cls.includes('language-table')) {
return { data: code.textContent || '{}' }
}
return false
},
},
]
},
renderHTML({ HTMLAttributes }: { HTMLAttributes: Record<string, unknown> }) {
return ['div', mergeAttributes(HTMLAttributes, { 'data-type': 'table-block' })]
},
addNodeView() {
return ReactNodeViewRenderer(TableBlockView)
},
addStorage() {
return {
markdown: {
serialize(state: { write: (text: string) => void; closeBlock: (node: unknown) => void }, node: { attrs: { data: string } }) {
state.write('```table\n' + node.attrs.data + '\n```')
state.closeBlock(node)
},
parse: {
// handled by parseHTML
},
},
}
},
})

View file

@ -608,6 +608,230 @@
color: color-mix(in srgb, var(--foreground) 55%, transparent);
}
/* Shared block styles (image, embed, chart, table) */
.tiptap-editor .ProseMirror .image-block-wrapper,
.tiptap-editor .ProseMirror .embed-block-wrapper,
.tiptap-editor .ProseMirror .chart-block-wrapper,
.tiptap-editor .ProseMirror .table-block-wrapper {
margin: 8px 0;
}
.tiptap-editor .ProseMirror .image-block-card,
.tiptap-editor .ProseMirror .embed-block-card,
.tiptap-editor .ProseMirror .chart-block-card,
.tiptap-editor .ProseMirror .table-block-card {
position: relative;
padding: 12px 14px;
border: 1px solid var(--border);
border-radius: 8px;
background-color: color-mix(in srgb, var(--muted) 40%, transparent);
cursor: default;
transition: background-color 0.15s ease;
}
.tiptap-editor .ProseMirror .image-block-card:hover,
.tiptap-editor .ProseMirror .embed-block-card:hover,
.tiptap-editor .ProseMirror .chart-block-card:hover,
.tiptap-editor .ProseMirror .table-block-card:hover {
background-color: color-mix(in srgb, var(--muted) 70%, transparent);
}
.tiptap-editor .ProseMirror .image-block-wrapper.ProseMirror-selectednode .image-block-card,
.tiptap-editor .ProseMirror .embed-block-wrapper.ProseMirror-selectednode .embed-block-card,
.tiptap-editor .ProseMirror .chart-block-wrapper.ProseMirror-selectednode .chart-block-card,
.tiptap-editor .ProseMirror .table-block-wrapper.ProseMirror-selectednode .table-block-card {
outline: 2px solid var(--primary);
outline-offset: 1px;
}
.tiptap-editor .ProseMirror .image-block-delete,
.tiptap-editor .ProseMirror .embed-block-delete,
.tiptap-editor .ProseMirror .chart-block-delete,
.tiptap-editor .ProseMirror .table-block-delete {
position: absolute;
top: 6px;
right: 6px;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border: none;
border-radius: 4px;
background: none;
cursor: pointer;
color: color-mix(in srgb, var(--foreground) 40%, transparent);
opacity: 0;
transition: opacity 0.15s ease, background-color 0.15s ease;
}
.tiptap-editor .ProseMirror .image-block-card:hover .image-block-delete,
.tiptap-editor .ProseMirror .embed-block-card:hover .embed-block-delete,
.tiptap-editor .ProseMirror .chart-block-card:hover .chart-block-delete,
.tiptap-editor .ProseMirror .table-block-card:hover .table-block-delete {
opacity: 1;
}
.tiptap-editor .ProseMirror .image-block-delete:hover,
.tiptap-editor .ProseMirror .embed-block-delete:hover,
.tiptap-editor .ProseMirror .chart-block-delete:hover,
.tiptap-editor .ProseMirror .table-block-delete:hover {
background-color: color-mix(in srgb, var(--foreground) 8%, transparent);
color: var(--foreground);
}
/* Image block */
.tiptap-editor .ProseMirror .image-block-img {
max-width: 100%;
border-radius: 4px;
}
.tiptap-editor .ProseMirror .image-block-caption {
margin-top: 6px;
font-size: 12px;
color: color-mix(in srgb, var(--foreground) 55%, transparent);
text-align: center;
}
.tiptap-editor .ProseMirror .image-block-error {
display: flex;
align-items: center;
gap: 6px;
color: color-mix(in srgb, var(--foreground) 55%, transparent);
font-size: 13px;
}
/* Embed block */
.tiptap-editor .ProseMirror .embed-block-iframe-container {
position: relative;
width: 100%;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
border-radius: 4px;
overflow: hidden;
}
.tiptap-editor .ProseMirror .embed-block-iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
.tiptap-editor .ProseMirror .embed-block-link {
display: flex;
align-items: center;
gap: 6px;
padding: 8px;
font-size: 13px;
color: var(--primary);
text-decoration: none;
word-break: break-all;
}
.tiptap-editor .ProseMirror .embed-block-caption {
margin-top: 6px;
font-size: 12px;
color: color-mix(in srgb, var(--foreground) 55%, transparent);
text-align: center;
}
.tiptap-editor .ProseMirror .embed-block-error {
display: flex;
align-items: center;
gap: 6px;
color: color-mix(in srgb, var(--foreground) 55%, transparent);
font-size: 13px;
}
/* Chart block */
.tiptap-editor .ProseMirror .chart-block-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 8px;
color: var(--foreground);
}
.tiptap-editor .ProseMirror .chart-block-loading,
.tiptap-editor .ProseMirror .chart-block-empty {
display: flex;
align-items: center;
justify-content: center;
height: 120px;
font-size: 13px;
color: color-mix(in srgb, var(--foreground) 45%, transparent);
}
.tiptap-editor .ProseMirror .chart-block-error-msg {
display: flex;
align-items: center;
justify-content: center;
height: 120px;
font-size: 13px;
color: #ef4444;
}
.tiptap-editor .ProseMirror .chart-block-error {
display: flex;
align-items: center;
gap: 6px;
color: color-mix(in srgb, var(--foreground) 55%, transparent);
font-size: 13px;
}
/* Table block */
.tiptap-editor .ProseMirror .table-block-title {
font-size: 14px;
font-weight: 600;
margin-bottom: 8px;
color: var(--foreground);
}
.tiptap-editor .ProseMirror .table-block-scroll {
overflow-x: auto;
}
.tiptap-editor .ProseMirror .table-block-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.tiptap-editor .ProseMirror .table-block-table th {
text-align: left;
padding: 6px 10px;
border-bottom: 2px solid var(--border);
font-weight: 600;
color: var(--foreground);
white-space: nowrap;
}
.tiptap-editor .ProseMirror .table-block-table td {
padding: 5px 10px;
border-bottom: 1px solid var(--border);
color: color-mix(in srgb, var(--foreground) 80%, transparent);
}
.tiptap-editor .ProseMirror .table-block-table tr:last-child td {
border-bottom: none;
}
.tiptap-editor .ProseMirror .table-block-empty {
text-align: center;
color: color-mix(in srgb, var(--foreground) 45%, transparent);
padding: 16px 10px !important;
}
.tiptap-editor .ProseMirror .table-block-error {
display: flex;
align-items: center;
gap: 6px;
color: color-mix(in srgb, var(--foreground) 55%, transparent);
font-size: 13px;
}
/* Dark mode overrides */
.dark .tiptap-editor .ProseMirror {
color: rgba(255, 255, 255, 0.9);

View file

@ -0,0 +1,36 @@
import { z } from 'zod';
export const ImageBlockSchema = z.object({
src: z.string(),
alt: z.string().optional(),
caption: z.string().optional(),
});
export type ImageBlock = z.infer<typeof ImageBlockSchema>;
export const EmbedBlockSchema = z.object({
provider: z.enum(['youtube', 'figma', 'generic']),
url: z.string().url(),
caption: z.string().optional(),
});
export type EmbedBlock = z.infer<typeof EmbedBlockSchema>;
export const ChartBlockSchema = z.object({
chart: z.enum(['line', 'bar', 'pie']),
title: z.string().optional(),
data: z.array(z.record(z.string(), z.unknown())).optional(),
source: z.string().optional(),
x: z.string(),
y: z.string(),
});
export type ChartBlock = z.infer<typeof ChartBlockSchema>;
export const TableBlockSchema = z.object({
columns: z.array(z.string()),
data: z.array(z.record(z.string(), z.unknown())),
title: z.string().optional(),
});
export type TableBlock = z.infer<typeof TableBlockSchema>;

View file

@ -8,6 +8,7 @@ export * as agentSchedule from './agent-schedule.js';
export * as agentScheduleState from './agent-schedule-state.js';
export * as serviceEvents from './service-events.js'
export * as inlineTask from './inline-task.js';
export * as blocks from './blocks.js';
export * as frontmatter from './frontmatter.js';
export * as bases from './bases.js';
export { PrefixLogger };

144
apps/x/pnpm-lock.yaml generated
View file

@ -238,6 +238,9 @@ importers:
react-dom:
specifier: ^19.2.0
version: 19.2.3(react@19.2.3)
recharts:
specifier: ^3.8.0
version: 3.8.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react-is@16.13.1)(react@19.2.3)(redux@5.0.1)
sonner:
specifier: ^2.0.7
version: 2.0.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@ -2547,6 +2550,17 @@ packages:
'@react-pdf/types@2.9.2':
resolution: {integrity: sha512-dufvpKId9OajLLbgn9q7VLUmyo1Jf+iyGk2ZHmCL8nIDtL8N1Ejh9TH7+pXXrR0tdie1nmnEb5Bz9U7g4hI4/g==}
'@reduxjs/toolkit@2.11.2':
resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==}
peerDependencies:
react: ^16.9.0 || ^17.0.0 || ^18 || ^19
react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0
peerDependenciesMeta:
react:
optional: true
react-redux:
optional: true
'@remirror/core-constants@3.0.0':
resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
@ -2907,6 +2921,9 @@ packages:
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@standard-schema/utils@0.3.0':
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
'@swc/helpers@0.5.18':
resolution: {integrity: sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==}
@ -4270,6 +4287,9 @@ packages:
supports-color:
optional: true
decimal.js-light@2.5.1:
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
decode-named-character-reference@1.2.0:
resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}
@ -4500,6 +4520,9 @@ packages:
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
engines: {node: '>= 0.4'}
es-toolkit@1.45.1:
resolution: {integrity: sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==}
es6-error@4.1.1:
resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==}
@ -5178,6 +5201,12 @@ packages:
immediate@3.0.6:
resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==}
immer@10.2.0:
resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==}
immer@11.1.4:
resolution: {integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==}
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
@ -6565,6 +6594,18 @@ packages:
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
react-redux@9.2.0:
resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==}
peerDependencies:
'@types/react': ^18.2.25 || ^19
react: ^18.0 || ^19
redux: ^5.0.0
peerDependenciesMeta:
'@types/react':
optional: true
redux:
optional: true
react-refresh@0.18.0:
resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
engines: {node: '>=0.10.0'}
@ -6630,10 +6671,26 @@ packages:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
recharts@3.8.0:
resolution: {integrity: sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ==}
engines: {node: '>=18'}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
rechoir@0.8.0:
resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==}
engines: {node: '>= 10.13.0'}
redux-thunk@3.1.0:
resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
peerDependencies:
redux: ^5.0.0
redux@5.0.1:
resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==}
regex-recursion@6.0.2:
resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
@ -6706,6 +6763,9 @@ packages:
resolution: {integrity: sha512-oTeemxwoMuxxTYxXUwjkrOPfngTQehlv0/HoYFNkB4uzsP1Un1A9nI8JQKGOFkxpqkC7qkMs0lUsGrvUlbLNUA==}
engines: {node: '>=14', npm: '>=7'}
reselect@5.1.1:
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
resolve-alpn@1.2.1:
resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==}
@ -7114,6 +7174,9 @@ packages:
tiny-inflate@1.0.3:
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
tinyexec@1.0.2:
resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
engines: {node: '>=18'}
@ -7385,6 +7448,9 @@ packages:
vfile@6.0.3:
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
victory-vendor@37.3.6:
resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==}
virtual-dom@2.1.1:
resolution: {integrity: sha512-wb6Qc9Lbqug0kRqo/iuApfBpJJAq14Sk1faAnSmtqXiwahg7PVTvWMs9L02Z8nNIMqbwsxzBAA90bbtRLbw0zg==}
@ -10395,6 +10461,18 @@ snapshots:
'@react-pdf/primitives': 4.1.1
'@react-pdf/stylesheet': 6.1.2
'@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1))(react@19.2.3)':
dependencies:
'@standard-schema/spec': 1.1.0
'@standard-schema/utils': 0.3.0
immer: 11.1.4
redux: 5.0.1
redux-thunk: 3.1.0(redux@5.0.1)
reselect: 5.1.1
optionalDependencies:
react: 19.2.3
react-redux: 9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1)
'@remirror/core-constants@3.0.0': {}
'@rolldown/pluginutils@1.0.0-beta.53': {}
@ -10840,6 +10918,8 @@ snapshots:
'@standard-schema/spec@1.1.0': {}
'@standard-schema/utils@0.3.0': {}
'@swc/helpers@0.5.18':
dependencies:
tslib: 2.8.1
@ -12342,6 +12422,8 @@ snapshots:
dependencies:
ms: 2.1.3
decimal.js-light@2.5.1: {}
decode-named-character-reference@1.2.0:
dependencies:
character-entities: 2.0.2
@ -12625,6 +12707,8 @@ snapshots:
has-tostringtag: 1.0.2
hasown: 2.0.2
es-toolkit@1.45.1: {}
es6-error@4.1.1:
optional: true
@ -13601,6 +13685,10 @@ snapshots:
immediate@3.0.6: {}
immer@10.2.0: {}
immer@11.1.4: {}
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
@ -15270,6 +15358,15 @@ snapshots:
react-is@16.13.1: {}
react-redux@9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1):
dependencies:
'@types/use-sync-external-store': 0.0.6
react: 19.2.3
use-sync-external-store: 1.6.0(react@19.2.3)
optionalDependencies:
'@types/react': 19.2.7
redux: 5.0.1
react-refresh@0.18.0: {}
react-remove-scroll-bar@2.3.8(@types/react@19.2.7)(react@19.2.3):
@ -15344,10 +15441,36 @@ snapshots:
readdirp@4.1.2: {}
recharts@3.8.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react-is@16.13.1)(react@19.2.3)(redux@5.0.1):
dependencies:
'@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1))(react@19.2.3)
clsx: 2.1.1
decimal.js-light: 2.5.1
es-toolkit: 1.45.1
eventemitter3: 5.0.1
immer: 10.2.0
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
react-is: 16.13.1
react-redux: 9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1)
reselect: 5.1.1
tiny-invariant: 1.3.3
use-sync-external-store: 1.6.0(react@19.2.3)
victory-vendor: 37.3.6
transitivePeerDependencies:
- '@types/react'
- redux
rechoir@0.8.0:
dependencies:
resolve: 1.22.11
redux-thunk@3.1.0(redux@5.0.1):
dependencies:
redux: 5.0.1
redux@5.0.1: {}
regex-recursion@6.0.2:
dependencies:
regex-utilities: 2.3.0
@ -15454,6 +15577,8 @@ snapshots:
dependencies:
pe-library: 1.0.1
reselect@5.1.1: {}
resolve-alpn@1.2.1: {}
resolve-from@4.0.0: {}
@ -15947,6 +16072,8 @@ snapshots:
tiny-inflate@1.0.3: {}
tiny-invariant@1.3.3: {}
tinyexec@1.0.2: {}
tinyglobby@0.2.15:
@ -16218,6 +16345,23 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
victory-vendor@37.3.6:
dependencies:
'@types/d3-array': 3.2.2
'@types/d3-ease': 3.0.2
'@types/d3-interpolate': 3.0.4
'@types/d3-scale': 4.0.9
'@types/d3-shape': 3.1.7
'@types/d3-time': 3.0.4
'@types/d3-timer': 3.0.2
d3-array: 3.2.4
d3-ease: 3.0.1
d3-interpolate: 3.0.1
d3-scale: 4.0.2
d3-shape: 3.2.0
d3-time: 3.1.0
d3-timer: 3.0.1
virtual-dom@2.1.1:
dependencies:
browser-split: 0.0.1