mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-11 00:02:38 +02:00
feat: Gmail-style email block with inbox container layout (#531)
* feat: restyle email block with Gmail-style layout and avatar * style: apply Google Sans/Roboto font to email block * feat: add Gmail inbox-style multi-email block with accordion rows * style: fix sender name casing, weight, and email display in expanded view * feat: emails inbox block with container layout, two-line rows, Gmail title style
This commit is contained in:
parent
3630032d21
commit
0e3d058c29
7 changed files with 899 additions and 363 deletions
|
|
@ -20,7 +20,7 @@ import { IframeBlockExtension } from '@/extensions/iframe-block'
|
|||
import { ChartBlockExtension } from '@/extensions/chart-block'
|
||||
import { TableBlockExtension } from '@/extensions/table-block'
|
||||
import { CalendarBlockExtension } from '@/extensions/calendar-block'
|
||||
import { EmailBlockExtension } from '@/extensions/email-block'
|
||||
import { EmailBlockExtension, EmailsBlockExtension } from '@/extensions/email-block'
|
||||
import { TranscriptBlockExtension } from '@/extensions/transcript-block'
|
||||
import { MermaidBlockExtension } from '@/extensions/mermaid-block'
|
||||
import { Markdown } from 'tiptap-markdown'
|
||||
|
|
@ -707,6 +707,7 @@ export const MarkdownEditor = forwardRef<MarkdownEditorHandle, MarkdownEditorPro
|
|||
ChartBlockExtension,
|
||||
TableBlockExtension,
|
||||
CalendarBlockExtension,
|
||||
EmailsBlockExtension,
|
||||
EmailBlockExtension,
|
||||
TranscriptBlockExtension,
|
||||
MermaidBlockExtension,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { mergeAttributes, Node } from '@tiptap/react'
|
||||
import { ReactNodeViewRenderer, NodeViewWrapper } from '@tiptap/react'
|
||||
import { X, Mail, ChevronDown, ExternalLink, Copy, Check, MessageSquare } from 'lucide-react'
|
||||
import { X, ExternalLink, Copy, Check, MessageSquare, ChevronDown } from 'lucide-react'
|
||||
import { blocks } from '@x/shared'
|
||||
import { useState, useEffect, useRef, useCallback } from 'react'
|
||||
import { useTheme } from '@/contexts/theme-context'
|
||||
|
|
@ -11,17 +11,47 @@ function formatEmailDate(dateStr: string): string {
|
|||
try {
|
||||
const d = new Date(dateStr)
|
||||
if (isNaN(d.getTime())) return dateStr
|
||||
return d.toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric' }) +
|
||||
' ' + d.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })
|
||||
const now = new Date()
|
||||
const isToday = d.getDate() === now.getDate() && d.getMonth() === now.getMonth() && d.getFullYear() === now.getFullYear()
|
||||
if (isToday) return d.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })
|
||||
return d.toLocaleDateString([], { month: 'short', day: 'numeric' })
|
||||
} catch {
|
||||
return dateStr
|
||||
}
|
||||
}
|
||||
|
||||
/** Extract just the name part from "Name <email>" format */
|
||||
function senderFirstName(from: string): string {
|
||||
const name = from.replace(/<.*>/, '').trim()
|
||||
return name.split(/\s+/)[0] || name
|
||||
function formatFullDate(dateStr: string): string {
|
||||
try {
|
||||
const d = new Date(dateStr)
|
||||
if (isNaN(d.getTime())) return dateStr
|
||||
return d.toLocaleDateString([], { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' }) +
|
||||
', ' + d.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })
|
||||
} catch {
|
||||
return dateStr
|
||||
}
|
||||
}
|
||||
|
||||
function extractName(from: string): string {
|
||||
const match = from.match(/^([^<]+)</)
|
||||
if (match) return match[1].trim()
|
||||
const username = from.replace(/@.*/, '').replace(/[._+]/g, ' ').trim()
|
||||
return username.replace(/\b\w/g, c => c.toUpperCase())
|
||||
}
|
||||
|
||||
function getInitial(from: string): string {
|
||||
const name = extractName(from)
|
||||
return (name[0] || '?').toUpperCase()
|
||||
}
|
||||
|
||||
const GMAIL_AVATAR_COLORS = [
|
||||
'#1a73e8', '#e8453c', '#34a853', '#8430ce', '#f29900',
|
||||
'#00796b', '#c62828', '#1565c0', '#6a1b9a', '#2e7d32',
|
||||
]
|
||||
|
||||
function avatarColor(from: string): string {
|
||||
let hash = 0
|
||||
for (let i = 0; i < from.length; i++) hash = (hash * 31 + from.charCodeAt(i)) >>> 0
|
||||
return GMAIL_AVATAR_COLORS[hash % GMAIL_AVATAR_COLORS.length]
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
|
@ -30,7 +60,308 @@ declare global {
|
|||
}
|
||||
}
|
||||
|
||||
// --- Email Block ---
|
||||
// --- Shared: expanded email body used by both block types ---
|
||||
|
||||
function EmailExpandedBody({
|
||||
config,
|
||||
resolvedTheme,
|
||||
}: {
|
||||
config: blocks.EmailBlock
|
||||
resolvedTheme: string
|
||||
}) {
|
||||
const [draftBody, setDraftBody] = useState(config.draft_response || '')
|
||||
const [copied, setCopied] = useState(false)
|
||||
const bodyRef = useRef<HTMLTextAreaElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setDraftBody(config.draft_response || '')
|
||||
}, [config.draft_response])
|
||||
|
||||
useEffect(() => {
|
||||
if (bodyRef.current) {
|
||||
bodyRef.current.style.height = 'auto'
|
||||
bodyRef.current.style.height = bodyRef.current.scrollHeight + 'px'
|
||||
}
|
||||
}, [draftBody])
|
||||
|
||||
const draftWithAssistant = useCallback(() => {
|
||||
let prompt = draftBody
|
||||
? `Help me refine this draft response to an email`
|
||||
: `Help me draft a response to this email`
|
||||
if (config.threadId) {
|
||||
prompt += `. Read the full thread at gmail_sync/${config.threadId}.md for context`
|
||||
}
|
||||
prompt += `.\n\n**From:** ${config.from || 'Unknown'}\n**Subject:** ${config.subject || 'No subject'}\n`
|
||||
if (draftBody) prompt += `\n**Current draft:**\n${draftBody}\n`
|
||||
window.__pendingEmailDraft = { prompt }
|
||||
window.dispatchEvent(new Event('email-block:draft-with-assistant'))
|
||||
}, [config, draftBody])
|
||||
|
||||
const copyDraft = useCallback(() => {
|
||||
navigator.clipboard.writeText(draftBody).then(() => {
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}).catch(() => {
|
||||
const el = document.createElement('textarea')
|
||||
el.value = draftBody
|
||||
document.body.appendChild(el)
|
||||
el.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(el)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
})
|
||||
}, [draftBody])
|
||||
|
||||
const gmailUrl = config.threadId
|
||||
? `https://mail.google.com/mail/u/0/#all/${config.threadId}`
|
||||
: null
|
||||
|
||||
const senderName = config.from ? extractName(config.from) : 'Unknown'
|
||||
const initial = config.from ? getInitial(config.from) : '?'
|
||||
const color = config.from ? avatarColor(config.from) : '#5f6368'
|
||||
const hasDraft = !!config.draft_response
|
||||
|
||||
return (
|
||||
<div className="email-gmail-expanded">
|
||||
{config.subject && (
|
||||
<div className="email-gmail-exp-subject">{config.subject}</div>
|
||||
)}
|
||||
|
||||
<div className="email-gmail-exp-meta">
|
||||
<div className="email-gmail-exp-avatar" style={{ backgroundColor: color }}>{initial}</div>
|
||||
<div className="email-gmail-exp-meta-right">
|
||||
<div className="email-gmail-exp-sender">{config.from || 'Unknown'}</div>
|
||||
<div className="email-gmail-exp-to-date">
|
||||
{config.to && <span>to {config.to}</span>}
|
||||
{config.date && <span className="email-gmail-exp-fulldate">{formatFullDate(config.date)}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="email-gmail-exp-body">{config.latest_email}</div>
|
||||
|
||||
{config.past_summary && (
|
||||
<div className="email-gmail-exp-history">
|
||||
<div className="email-gmail-exp-history-label">Earlier conversation</div>
|
||||
<div className="email-gmail-exp-history-body">{config.past_summary}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!hasDraft && (
|
||||
<div className="email-gmail-reply-row">
|
||||
{gmailUrl && (
|
||||
<button
|
||||
className="email-gmail-btn"
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => { e.stopPropagation(); window.open(gmailUrl, '_blank') }}
|
||||
>
|
||||
<ExternalLink size={13} />
|
||||
Open in Gmail
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="email-gmail-btn email-gmail-btn-primary email-gmail-reply-row-end"
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => { e.stopPropagation(); draftWithAssistant() }}
|
||||
>
|
||||
<MessageSquare size={13} />
|
||||
Draft with Rowboat
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasDraft && (
|
||||
<div className="email-gmail-compose">
|
||||
<div className="email-gmail-compose-to">
|
||||
<span className="email-gmail-compose-to-label">Reply</span>
|
||||
{config.from && <span className="email-gmail-compose-to-addr">{config.from}</span>}
|
||||
</div>
|
||||
<textarea
|
||||
key={resolvedTheme}
|
||||
ref={bodyRef}
|
||||
className="email-gmail-compose-body"
|
||||
value={draftBody}
|
||||
onChange={(e) => setDraftBody(e.target.value)}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
placeholder="Write your reply..."
|
||||
rows={3}
|
||||
/>
|
||||
<div className="email-gmail-compose-footer">
|
||||
<button
|
||||
className="email-gmail-btn email-gmail-btn-primary"
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => { e.stopPropagation(); draftWithAssistant() }}
|
||||
>
|
||||
<MessageSquare size={13} />
|
||||
{hasDraft ? 'Refine with Rowboat' : 'Draft with Rowboat'}
|
||||
</button>
|
||||
<button
|
||||
className="email-gmail-btn"
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => { e.stopPropagation(); copyDraft() }}
|
||||
>
|
||||
{copied ? <Check size={13} /> : <Copy size={13} />}
|
||||
{copied ? 'Copied!' : 'Copy draft'}
|
||||
</button>
|
||||
{gmailUrl && (
|
||||
<button
|
||||
className="email-gmail-btn"
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onClick={(e) => { e.stopPropagation(); window.open(gmailUrl, '_blank') }}
|
||||
>
|
||||
<ExternalLink size={13} />
|
||||
Open in Gmail
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// --- Multi-email inbox block (language-emails) ---
|
||||
|
||||
function EmailsBlockView({ node, deleteNode }: {
|
||||
node: { attrs: Record<string, unknown> }
|
||||
deleteNode: () => void
|
||||
}) {
|
||||
const raw = node.attrs.data as string
|
||||
let config: blocks.EmailsBlock | null = null
|
||||
|
||||
try {
|
||||
config = blocks.EmailsBlockSchema.parse(JSON.parse(raw))
|
||||
} catch { /* fallback below */ }
|
||||
|
||||
const { resolvedTheme } = useTheme()
|
||||
const [expandedIndex, setExpandedIndex] = useState<number | null>(null)
|
||||
|
||||
if (!config || config.emails.length === 0) {
|
||||
return (
|
||||
<NodeViewWrapper className="email-block-wrapper" data-type="emails-block">
|
||||
<div className="email-block-card email-block-error"><span>Invalid emails block</span></div>
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<NodeViewWrapper className="email-block-wrapper" data-type="emails-block">
|
||||
<div className="email-block-card email-inbox-card" onMouseDown={(e) => e.stopPropagation()}>
|
||||
<button className="email-block-delete" onClick={deleteNode} aria-label="Remove block"><X size={14} /></button>
|
||||
|
||||
{config.title && (
|
||||
<div className="email-inbox-title">{config.title}</div>
|
||||
)}
|
||||
|
||||
<div className="email-inbox-list">
|
||||
{config.emails.map((email, i) => {
|
||||
const isExpanded = expandedIndex === i
|
||||
const senderName = email.from ? extractName(email.from) : 'Unknown'
|
||||
const initial = email.from ? getInitial(email.from) : '?'
|
||||
const color = email.from ? avatarColor(email.from) : '#5f6368'
|
||||
const snippet = email.summary
|
||||
|| (email.latest_email ? email.latest_email.slice(0, 100).replace(/\s+/g, ' ').trim() : '')
|
||||
|
||||
return (
|
||||
<div key={i} className={`email-inbox-row${isExpanded ? ' email-inbox-row-expanded' : ''}`}>
|
||||
{/* Collapsed row */}
|
||||
<div
|
||||
className="email-inbox-row-header"
|
||||
onClick={(e) => { e.stopPropagation(); setExpandedIndex(isExpanded ? null : i) }}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="email-inbox-avatar" style={{ backgroundColor: color }}>{initial}</div>
|
||||
|
||||
<div className="email-inbox-content">
|
||||
<div className="email-inbox-top-row">
|
||||
<span className="email-inbox-sender">{senderName}</span>
|
||||
{email.date && <span className="email-inbox-date">{formatEmailDate(email.date)}</span>}
|
||||
</div>
|
||||
<div className="email-inbox-bottom-row">
|
||||
{email.subject && <span className="email-inbox-subject">{email.subject}</span>}
|
||||
{snippet && (
|
||||
<span className="email-inbox-snippet">
|
||||
{email.subject ? ` — ${snippet}` : snippet}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChevronDown
|
||||
size={14}
|
||||
className={`email-inbox-chevron${isExpanded ? ' email-inbox-chevron-open' : ''}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Expanded content */}
|
||||
{isExpanded && (
|
||||
<div className="email-inbox-expanded-wrap">
|
||||
<EmailExpandedBody
|
||||
config={email}
|
||||
resolvedTheme={resolvedTheme}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export const EmailsBlockExtension = Node.create({
|
||||
name: 'emailsBlock',
|
||||
group: 'block',
|
||||
atom: true,
|
||||
selectable: true,
|
||||
draggable: false,
|
||||
|
||||
addAttributes() {
|
||||
return { data: { default: '{}' } }
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [{
|
||||
tag: 'pre',
|
||||
priority: 61,
|
||||
getAttrs(element) {
|
||||
const code = element.querySelector('code')
|
||||
if (!code) return false
|
||||
if ((code.className || '').includes('language-emails')) {
|
||||
return { data: code.textContent || '{}' }
|
||||
}
|
||||
return false
|
||||
},
|
||||
}]
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }: { HTMLAttributes: Record<string, unknown> }) {
|
||||
return ['div', mergeAttributes(HTMLAttributes, { 'data-type': 'emails-block' })]
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return ReactNodeViewRenderer(EmailsBlockView)
|
||||
},
|
||||
|
||||
addStorage() {
|
||||
return {
|
||||
markdown: {
|
||||
serialize(state: { write: (text: string) => void; closeBlock: (node: unknown) => void }, node: { attrs: { data: string } }) {
|
||||
state.write('```emails\n' + node.attrs.data + '\n```')
|
||||
state.closeBlock(node)
|
||||
},
|
||||
parse: {},
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// --- Single email block (language-email, backward compat) ---
|
||||
|
||||
function EmailBlockView({ node, deleteNode, updateAttributes }: {
|
||||
node: { attrs: Record<string, unknown> }
|
||||
|
|
@ -42,194 +373,57 @@ function EmailBlockView({ node, deleteNode, updateAttributes }: {
|
|||
|
||||
try {
|
||||
config = blocks.EmailBlockSchema.parse(JSON.parse(raw))
|
||||
} catch {
|
||||
// fallback below
|
||||
}
|
||||
|
||||
const hasDraft = !!config?.draft_response
|
||||
const hasPastSummary = !!config?.past_summary
|
||||
} catch { /* fallback below */ }
|
||||
|
||||
const { resolvedTheme } = useTheme()
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
|
||||
// Local draft state for editing
|
||||
const [draftBody, setDraftBody] = useState(config?.draft_response || '')
|
||||
const [emailExpanded, setEmailExpanded] = useState(false)
|
||||
const [copied, setCopied] = useState(false)
|
||||
const bodyRef = useRef<HTMLTextAreaElement>(null)
|
||||
|
||||
// Sync draft from external changes
|
||||
useEffect(() => {
|
||||
try {
|
||||
const parsed = blocks.EmailBlockSchema.parse(JSON.parse(raw))
|
||||
setDraftBody(parsed.draft_response || '')
|
||||
} catch { /* ignore */ }
|
||||
}, [raw])
|
||||
|
||||
// Auto-resize textarea
|
||||
useEffect(() => {
|
||||
if (bodyRef.current) {
|
||||
bodyRef.current.style.height = 'auto'
|
||||
bodyRef.current.style.height = bodyRef.current.scrollHeight + 'px'
|
||||
}
|
||||
}, [draftBody])
|
||||
|
||||
const commitDraft = useCallback((newBody: string) => {
|
||||
try {
|
||||
const current = JSON.parse(raw) as Record<string, unknown>
|
||||
updateAttributes({ data: JSON.stringify({ ...current, draft_response: newBody }) })
|
||||
} catch { /* ignore */ }
|
||||
}, [raw, updateAttributes])
|
||||
|
||||
const draftWithAssistant = useCallback(() => {
|
||||
if (!config) return
|
||||
let prompt = draftBody
|
||||
? `Help me refine this draft response to an email`
|
||||
: `Help me draft a response to this email`
|
||||
if (config.threadId) {
|
||||
prompt += `. Read the full thread at gmail_sync/${config.threadId}.md for context`
|
||||
}
|
||||
prompt += `.\n\n`
|
||||
prompt += `**From:** ${config.from || 'Unknown'}\n`
|
||||
prompt += `**Subject:** ${config.subject || 'No subject'}\n`
|
||||
if (draftBody) {
|
||||
prompt += `\n**Current draft:**\n${draftBody}\n`
|
||||
}
|
||||
window.__pendingEmailDraft = { prompt }
|
||||
window.dispatchEvent(new Event('email-block:draft-with-assistant'))
|
||||
}, [config, draftBody])
|
||||
void updateAttributes // available for future per-email draft persistence
|
||||
|
||||
if (!config) {
|
||||
return (
|
||||
<NodeViewWrapper className="email-block-wrapper" data-type="email-block">
|
||||
<div className="email-block-card email-block-error">
|
||||
<Mail size={16} />
|
||||
<span>Invalid email block</span>
|
||||
</div>
|
||||
<div className="email-block-card email-block-error"><span>Invalid email block</span></div>
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
const gmailUrl = config.threadId
|
||||
? `https://mail.google.com/mail/u/0/#all/${config.threadId}`
|
||||
: null
|
||||
|
||||
// Build summary: use explicit summary, or auto-generate from sender + subject
|
||||
const summary = config.summary
|
||||
|| (config.from && config.subject
|
||||
? `${senderFirstName(config.from)} reached out about ${config.subject}`
|
||||
: config.subject || 'New email')
|
||||
const senderName = config.from ? extractName(config.from) : 'Unknown'
|
||||
const initial = config.from ? getInitial(config.from) : '?'
|
||||
const color = config.from ? avatarColor(config.from) : '#5f6368'
|
||||
const snippet = config.summary
|
||||
|| (config.latest_email ? config.latest_email.slice(0, 120).replace(/\s+/g, ' ').trim() : '')
|
||||
|
||||
return (
|
||||
<NodeViewWrapper className="email-block-wrapper" data-type="email-block">
|
||||
<div className="email-block-card email-block-card-gmail" onMouseDown={(e) => e.stopPropagation()}>
|
||||
<button className="email-block-delete" onClick={deleteNode} aria-label="Delete email block">
|
||||
<X size={14} />
|
||||
</button>
|
||||
<button className="email-block-delete" onClick={deleteNode} aria-label="Delete email block"><X size={14} /></button>
|
||||
|
||||
{/* Header: Email badge */}
|
||||
<div className="email-block-badge">
|
||||
<Mail size={13} />
|
||||
Email
|
||||
</div>
|
||||
|
||||
{/* Summary */}
|
||||
<div className="email-block-summary">{summary}</div>
|
||||
|
||||
{/* Expandable email details */}
|
||||
<button
|
||||
className="email-block-expand-btn"
|
||||
onClick={(e) => { e.stopPropagation(); setEmailExpanded(!emailExpanded) }}
|
||||
<div
|
||||
className={`email-gmail-row${expanded ? ' email-gmail-row-expanded' : ''}`}
|
||||
onClick={(e) => { e.stopPropagation(); setExpanded(!expanded) }}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<ChevronDown size={13} className={`email-block-toggle-chevron ${emailExpanded ? 'email-block-toggle-chevron-open' : ''}`} />
|
||||
{emailExpanded ? 'Hide email' : 'Show email'}
|
||||
{config.from && <span className="email-block-expand-meta">· From {senderFirstName(config.from)}</span>}
|
||||
{config.date && <span className="email-block-expand-meta">· {formatEmailDate(config.date)}</span>}
|
||||
</button>
|
||||
<div className="email-gmail-avatar" style={{ backgroundColor: color }} aria-hidden="true">{initial}</div>
|
||||
<div className="email-gmail-content">
|
||||
<div className="email-gmail-top-row">
|
||||
<span className="email-gmail-sender">{senderName}</span>
|
||||
{config.date && <span className="email-gmail-date">{formatEmailDate(config.date)}</span>}
|
||||
</div>
|
||||
<div className="email-gmail-bottom-row">
|
||||
{config.subject && <span className="email-gmail-subject">{config.subject}</span>}
|
||||
{snippet && <span className="email-gmail-snippet">{config.subject ? ` — ${snippet}` : snippet}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<ChevronDown size={15} className={`email-gmail-chevron${expanded ? ' email-gmail-chevron-open' : ''}`} />
|
||||
</div>
|
||||
|
||||
{emailExpanded && (
|
||||
<div className="email-block-email-details">
|
||||
<div className="email-block-message">
|
||||
<div className="email-block-message-header">
|
||||
<div className="email-block-sender-info">
|
||||
<div className="email-block-sender-row">
|
||||
<div className="email-block-sender-name">{config.from || 'Unknown'}</div>
|
||||
{config.date && <div className="email-block-sender-date">{formatEmailDate(config.date)}</div>}
|
||||
</div>
|
||||
{config.subject && <div className="email-block-subject-line">Subject: {config.subject}</div>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="email-block-message-body">{config.latest_email}</div>
|
||||
</div>
|
||||
{hasPastSummary && (
|
||||
<div className="email-block-context-section">
|
||||
<div className="email-block-context-label">Earlier conversation</div>
|
||||
<div className="email-block-context-summary">{config.past_summary}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Draft section */}
|
||||
{hasDraft && (
|
||||
<div className="email-block-draft-section">
|
||||
<div className="email-block-draft-label">Draft reply</div>
|
||||
<textarea
|
||||
key={resolvedTheme}
|
||||
ref={bodyRef}
|
||||
className="email-draft-block-body-input"
|
||||
value={draftBody}
|
||||
onChange={(e) => setDraftBody(e.target.value)}
|
||||
onBlur={() => commitDraft(draftBody)}
|
||||
placeholder="Write your reply..."
|
||||
rows={3}
|
||||
{expanded && (
|
||||
<EmailExpandedBody
|
||||
config={config}
|
||||
resolvedTheme={resolvedTheme}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Action buttons */}
|
||||
<div className="email-block-actions">
|
||||
<button
|
||||
className="email-block-gmail-btn email-block-gmail-btn-primary"
|
||||
onClick={draftWithAssistant}
|
||||
>
|
||||
<MessageSquare size={13} />
|
||||
{hasDraft ? 'Refine with Rowboat' : 'Draft with Rowboat'}
|
||||
</button>
|
||||
{hasDraft && (
|
||||
<button
|
||||
className="email-block-gmail-btn email-block-gmail-btn-primary"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(draftBody).then(() => {
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}).catch(() => {
|
||||
// Fallback for Electron contexts where clipboard API may fail
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = draftBody
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
})
|
||||
}}
|
||||
>
|
||||
{copied ? <Check size={13} /> : <Copy size={13} />}
|
||||
{copied ? 'Copied!' : 'Copy draft'}
|
||||
</button>
|
||||
)}
|
||||
{gmailUrl && (
|
||||
<button
|
||||
className="email-block-gmail-btn"
|
||||
onClick={() => window.open(gmailUrl, '_blank')}
|
||||
>
|
||||
<ExternalLink size={13} />
|
||||
Open in Gmail
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
|
|
@ -243,9 +437,7 @@ export const EmailBlockExtension = Node.create({
|
|||
draggable: false,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
data: { default: '{}' },
|
||||
}
|
||||
return { data: { default: '{}' } }
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
|
|
@ -256,7 +448,7 @@ export const EmailBlockExtension = Node.create({
|
|||
const code = element.querySelector('code')
|
||||
if (!code) return false
|
||||
const cls = code.className || ''
|
||||
if (cls.includes('language-email') && !cls.includes('language-emailDraft')) {
|
||||
if (cls.includes('language-email') && !cls.includes('language-emailDraft') && !cls.includes('language-emails')) {
|
||||
return { data: code.textContent || '{}' }
|
||||
}
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap');
|
||||
|
||||
/* Tiptap Editor Styles */
|
||||
|
||||
.tiptap-editor {
|
||||
|
|
@ -820,6 +822,49 @@
|
|||
margin: 8px 0;
|
||||
}
|
||||
|
||||
/* Consecutive email blocks — zero gap, shared outer border */
|
||||
|
||||
/* Kill margins between adjacent email wrappers */
|
||||
.tiptap-editor .ProseMirror .email-block-wrapper:has(+ .email-block-wrapper) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.tiptap-editor .ProseMirror .email-block-wrapper + .email-block-wrapper {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* Strip card border/radius from every card inside a sequence */
|
||||
.tiptap-editor .ProseMirror .email-block-wrapper:has(+ .email-block-wrapper) .email-block-card,
|
||||
.tiptap-editor .ProseMirror .email-block-wrapper + .email-block-wrapper .email-block-card {
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--foreground) 8%, transparent);
|
||||
}
|
||||
|
||||
/* First in group: restore top border + top radius */
|
||||
.tiptap-editor .ProseMirror .email-block-wrapper:not(.email-block-wrapper + .email-block-wrapper):has(+ .email-block-wrapper) .email-block-card {
|
||||
border-top: 1px solid var(--border);
|
||||
border-left: 1px solid var(--border);
|
||||
border-right: 1px solid var(--border);
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
}
|
||||
|
||||
/* Last in group: restore bottom border + bottom radius, remove hairline */
|
||||
.tiptap-editor .ProseMirror .email-block-wrapper + .email-block-wrapper:not(:has(+ .email-block-wrapper)) .email-block-card {
|
||||
border-bottom: 1px solid var(--border);
|
||||
border-left: 1px solid var(--border);
|
||||
border-right: 1px solid var(--border);
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
}
|
||||
|
||||
/* Middle cards: just left + right borders */
|
||||
.tiptap-editor .ProseMirror .email-block-wrapper + .email-block-wrapper:has(+ .email-block-wrapper) .email-block-card {
|
||||
border-left: 1px solid var(--border);
|
||||
border-right: 1px solid var(--border);
|
||||
}
|
||||
|
||||
|
||||
.tiptap-editor .ProseMirror .image-block-card,
|
||||
.tiptap-editor .ProseMirror .embed-block-card,
|
||||
.tiptap-editor .ProseMirror .iframe-block-card,
|
||||
|
|
@ -1422,141 +1467,209 @@
|
|||
|
||||
/* Email block – Gmail style */
|
||||
.tiptap-editor .ProseMirror .email-block-card-gmail {
|
||||
background-color: var(--background);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1), 0 1px 3px 1px rgba(0, 0, 0, 0.06);
|
||||
font-family: 'Google Sans', Roboto, RobotoDraft, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-card-gmail:hover {
|
||||
background-color: var(--background);
|
||||
}
|
||||
|
||||
/* Email badge */
|
||||
.tiptap-editor .ProseMirror .email-block-badge {
|
||||
display: inline-flex;
|
||||
/* Gmail-style two-column row */
|
||||
.tiptap-editor .ProseMirror .email-gmail-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: color-mix(in srgb, var(--foreground) 45%, transparent);
|
||||
margin-bottom: 8px;
|
||||
gap: 12px;
|
||||
cursor: pointer;
|
||||
padding: 2px 0;
|
||||
border-radius: 4px;
|
||||
transition: background 0.1s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Summary */
|
||||
.tiptap-editor .ProseMirror .email-block-summary {
|
||||
.tiptap-editor .ProseMirror .email-gmail-row:hover {
|
||||
background: color-mix(in srgb, var(--foreground) 4%, transparent);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-row.email-gmail-row-expanded {
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--foreground) 8%, transparent);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* Sender avatar circle */
|
||||
.tiptap-editor .ProseMirror .email-gmail-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: var(--foreground);
|
||||
line-height: 1.4;
|
||||
margin-bottom: 10px;
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
/* Expand button */
|
||||
.tiptap-editor .ProseMirror .email-block-expand-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
color: color-mix(in srgb, var(--foreground) 50%, transparent);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: color 0.12s ease;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-expand-btn:hover {
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-expand-meta {
|
||||
color: color-mix(in srgb, var(--foreground) 35%, transparent);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-toggle-chevron {
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-toggle-chevron-open {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* Email details (expanded) */
|
||||
.tiptap-editor .ProseMirror .email-block-email-details {
|
||||
margin-top: 10px;
|
||||
padding: 12px;
|
||||
background: color-mix(in srgb, var(--foreground) 4%, transparent);
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-message {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-message-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-sender-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
/* Content column */
|
||||
.tiptap-editor .ProseMirror .email-gmail-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-sender-row {
|
||||
.tiptap-editor .ProseMirror .email-gmail-top-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-sender-name {
|
||||
.tiptap-editor .ProseMirror .email-gmail-sender {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-family: 'Google Sans', Roboto, RobotoDraft, Helvetica, Arial, sans-serif;
|
||||
color: var(--foreground);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-sender-date {
|
||||
.tiptap-editor .ProseMirror .email-gmail-date {
|
||||
font-size: 12px;
|
||||
color: color-mix(in srgb, var(--foreground) 50%, transparent);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-subject-line {
|
||||
font-size: 12px;
|
||||
.tiptap-editor .ProseMirror .email-gmail-bottom-row {
|
||||
font-size: 13px;
|
||||
color: color-mix(in srgb, var(--foreground) 55%, transparent);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-subject {
|
||||
color: color-mix(in srgb, var(--foreground) 80%, transparent);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-snippet {
|
||||
color: color-mix(in srgb, var(--foreground) 45%, transparent);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-message-body {
|
||||
/* Chevron */
|
||||
.tiptap-editor .ProseMirror .email-gmail-chevron {
|
||||
flex-shrink: 0;
|
||||
color: color-mix(in srgb, var(--foreground) 40%, transparent);
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-chevron.email-gmail-chevron-open {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* Expanded area */
|
||||
.tiptap-editor .ProseMirror .email-gmail-expanded {
|
||||
padding-top: 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-exp-subject {
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
color: var(--foreground);
|
||||
line-height: 1.35;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* Metadata strip (avatar + from/to/date + open button) */
|
||||
.tiptap-editor .ProseMirror .email-gmail-exp-meta {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-exp-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-exp-meta-right {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-exp-sender {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-exp-to-date {
|
||||
font-size: 12px;
|
||||
color: color-mix(in srgb, var(--foreground) 50%, transparent);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-exp-fulldate {
|
||||
color: color-mix(in srgb, var(--foreground) 40%, transparent);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-open-btn {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: none;
|
||||
color: color-mix(in srgb, var(--foreground) 50%, transparent);
|
||||
cursor: pointer;
|
||||
transition: background 0.12s ease, color 0.12s ease;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-open-btn:hover {
|
||||
background: color-mix(in srgb, var(--foreground) 8%, transparent);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
/* Email body */
|
||||
.tiptap-editor .ProseMirror .email-gmail-exp-body {
|
||||
font-size: 14px;
|
||||
color: color-mix(in srgb, var(--foreground) 80%, transparent);
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.58;
|
||||
line-height: 1.6;
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-context-section {
|
||||
/* Earlier conversation */
|
||||
.tiptap-editor .ProseMirror .email-gmail-exp-history {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent);
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-context-label {
|
||||
.tiptap-editor .ProseMirror .email-gmail-exp-history-label {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
|
|
@ -1564,68 +1677,88 @@
|
|||
color: color-mix(in srgb, var(--foreground) 40%, transparent);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-context-summary {
|
||||
font-size: 14px;
|
||||
color: color-mix(in srgb, var(--foreground) 65%, transparent);
|
||||
line-height: 1.58;
|
||||
.tiptap-editor .ProseMirror .email-gmail-exp-history-body {
|
||||
font-size: 13px;
|
||||
color: color-mix(in srgb, var(--foreground) 55%, transparent);
|
||||
line-height: 1.55;
|
||||
white-space: pre-wrap;
|
||||
padding-left: 12px;
|
||||
border-left: 3px solid color-mix(in srgb, var(--foreground) 12%, transparent);
|
||||
}
|
||||
|
||||
/* Draft section */
|
||||
.tiptap-editor .ProseMirror .email-block-draft-section {
|
||||
margin-top: 10px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid color-mix(in srgb, var(--foreground) 12%, transparent);
|
||||
border-radius: 6px;
|
||||
/* Compose / draft box */
|
||||
.tiptap-editor .ProseMirror .email-gmail-compose {
|
||||
margin-top: 4px;
|
||||
border: 1px solid color-mix(in srgb, var(--foreground) 15%, transparent);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-draft-label {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: color-mix(in srgb, var(--foreground) 40%, transparent);
|
||||
margin-bottom: 4px;
|
||||
.tiptap-editor .ProseMirror .email-gmail-compose-to {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 12px 6px;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--foreground) 8%, transparent);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-draft-block-body-input {
|
||||
.tiptap-editor .ProseMirror .email-gmail-compose-to-label {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: color-mix(in srgb, var(--foreground) 45%, transparent);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-compose-to-addr {
|
||||
font-size: 13px;
|
||||
color: color-mix(in srgb, var(--foreground) 70%, transparent);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-compose-body {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
color: var(--foreground);
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 4px 0;
|
||||
padding: 10px 12px;
|
||||
font-family: inherit;
|
||||
line-height: 1.58;
|
||||
resize: none;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-draft-block-body-input::placeholder {
|
||||
.tiptap-editor .ProseMirror .email-gmail-compose-body::placeholder {
|
||||
color: color-mix(in srgb, var(--foreground) 35%, transparent);
|
||||
}
|
||||
|
||||
/* Action buttons */
|
||||
.tiptap-editor .ProseMirror .email-block-actions {
|
||||
.tiptap-editor .ProseMirror .email-gmail-compose-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding: 8px 12px;
|
||||
border-top: 1px solid color-mix(in srgb, var(--foreground) 8%, transparent);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-gmail-btn {
|
||||
/* Action buttons */
|
||||
.tiptap-editor .ProseMirror .email-gmail-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 7px 16px;
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: color-mix(in srgb, var(--foreground) 60%, transparent);
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
border: 1px solid color-mix(in srgb, var(--foreground) 20%, transparent);
|
||||
border-radius: 18px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease, box-shadow 0.15s ease;
|
||||
|
|
@ -1633,24 +1766,19 @@
|
|||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-gmail-btn:hover {
|
||||
background: color-mix(in srgb, var(--foreground) 8%, transparent);
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1), 0 1px 3px 1px rgba(0, 0, 0, 0.06);
|
||||
.tiptap-editor .ProseMirror .email-gmail-btn:hover {
|
||||
background: color-mix(in srgb, var(--foreground) 6%, transparent);
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.1);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-gmail-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-gmail-btn-primary {
|
||||
.tiptap-editor .ProseMirror .email-gmail-btn-primary {
|
||||
color: #fff;
|
||||
background: #1a73e8;
|
||||
border-color: #1a73e8;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-block-gmail-btn-primary:hover:not(:disabled) {
|
||||
.tiptap-editor .ProseMirror .email-gmail-btn-primary:hover {
|
||||
background: #1765cc;
|
||||
box-shadow: 0 1px 2px 0 rgba(26, 115, 232, 0.45), 0 1px 3px 1px rgba(26, 115, 232, 0.3);
|
||||
color: #fff;
|
||||
|
|
@ -1665,6 +1793,167 @@
|
|||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Reply / Forward pill buttons (in expanded view) */
|
||||
.tiptap-editor .ProseMirror .email-gmail-reply-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding-left: 50px;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-reply-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 20px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: color-mix(in srgb, var(--foreground) 65%, transparent);
|
||||
background: transparent;
|
||||
border: 1px solid color-mix(in srgb, var(--foreground) 22%, transparent);
|
||||
border-radius: 18px;
|
||||
cursor: pointer;
|
||||
transition: background 0.12s ease, box-shadow 0.12s ease;
|
||||
font-family: 'Google Sans', Roboto, RobotoDraft, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-reply-btn:hover {
|
||||
background: color-mix(in srgb, var(--foreground) 6%, transparent);
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-gmail-reply-row-end {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* ---- Emails inbox block (language-emails) ---- */
|
||||
|
||||
.tiptap-editor .ProseMirror .email-inbox-card {
|
||||
font-family: 'Google Sans', Roboto, RobotoDraft, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-inbox-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
color: color-mix(in srgb, var(--foreground) 70%, transparent);
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-inbox-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Each email row — hairline separator only, no card */
|
||||
.tiptap-editor .ProseMirror .email-inbox-row {
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--foreground) 8%, transparent);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-inbox-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-inbox-row-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 7px 4px 7px 0;
|
||||
cursor: pointer;
|
||||
transition: background 0.1s ease;
|
||||
user-select: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-inbox-row-header:hover {
|
||||
background: color-mix(in srgb, var(--foreground) 5%, transparent);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-inbox-row.email-inbox-row-expanded .email-inbox-row-header {
|
||||
background: color-mix(in srgb, var(--foreground) 3%, transparent);
|
||||
}
|
||||
|
||||
/* Avatar */
|
||||
.tiptap-editor .ProseMirror .email-inbox-avatar {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Content column — two-line layout */
|
||||
.tiptap-editor .ProseMirror .email-inbox-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-inbox-top-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-inbox-sender {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-family: 'Google Sans', Roboto, RobotoDraft, Helvetica, Arial, sans-serif;
|
||||
color: var(--foreground);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-inbox-date {
|
||||
font-size: 12px;
|
||||
color: color-mix(in srgb, var(--foreground) 50%, transparent);
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-inbox-bottom-row {
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-inbox-subject {
|
||||
font-weight: 500;
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-inbox-snippet {
|
||||
color: color-mix(in srgb, var(--foreground) 45%, transparent);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* Expand chevron */
|
||||
.tiptap-editor .ProseMirror .email-inbox-chevron {
|
||||
flex-shrink: 0;
|
||||
color: color-mix(in srgb, var(--foreground) 35%, transparent);
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
|
||||
.tiptap-editor .ProseMirror .email-inbox-chevron.email-inbox-chevron-open {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* Expanded content padding */
|
||||
.tiptap-editor .ProseMirror .email-inbox-expanded-wrap {
|
||||
padding: 8px 0 12px 0;
|
||||
border-top: 1px solid color-mix(in srgb, var(--foreground) 6%, transparent);
|
||||
}
|
||||
|
||||
/* Transcript block */
|
||||
.tiptap-editor .ProseMirror .transcript-block-toggle {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -67,14 +67,12 @@ After the block, you MAY add one short markdown line per event giving useful pre
|
|||
trackId: 'emails',
|
||||
icon: 'mail',
|
||||
instruction:
|
||||
`Maintain a digest of email threads worth the user's attention today, rendered as zero or more email blocks (one per thread).
|
||||
`Maintain a digest of email threads worth the user's attention today. Output everything as a single fenced code block with language "emails" (plural) — never individual "email" (singular) blocks. The content must be a JSON object: {"title":"Today's Emails","emails":[...]} where each entry has threadId, subject, from, date, summary, and latest_email. For threads that need a reply, add draft_response written in the user's voice — direct, informal, no fluff. For FYI threads, omit draft_response.
|
||||
|
||||
Event-driven path (primary): the agent message will include a "Gmail sync update" digest payload describing one or more freshly-synced threads from a single sync run. The digest lists each thread with its subject, sender, date, threadId, and body. Iterate over every thread in the payload and decide per thread whether it warrants surfacing. Skip marketing, auto-notifications, closed-out threads, and other low-signal mail. For threads that are attention-worthy, integrate them into the existing digest: add a new email block for a new threadId, or update the existing block if the threadId is already shown. If NONE of the threads in the payload are attention-worthy, skip the update — do NOT call update-track-content. Emit at most one update-track-content call that covers the full set of changes from this event.
|
||||
Event-driven path (primary): the agent message will include a "Gmail sync update" digest payload describing one or more freshly-synced threads from a single sync run. The digest lists each thread with its subject, sender, date, threadId, and body. Iterate over every thread in the payload and decide per thread whether it warrants surfacing. Skip marketing, auto-notifications, closed-out threads, and other low-signal mail. For threads that are attention-worthy, integrate them into the existing digest: add a new entry for a new threadId, or update the existing entry if the threadId is already shown. If NONE of the threads in the payload are attention-worthy, skip the update — do NOT call update-track-content. Emit at most one update-track-content call that covers the full set of changes from this event.
|
||||
|
||||
Manual path (fallback): with no event payload, scan gmail_sync/ via workspace-readdir (skip sync_state.json and attachments/). Read threads with workspace-readFile. Prioritize threads whose frontmatter action field is "reply" or "respond", plus other high-signal recent threads.
|
||||
|
||||
Each email block should include threadId, subject, from, date, summary, and latest_email. For threads that need a reply, add a draft_response written in the user's voice — direct, informal, no fluff. For FYI threads, omit draft_response.
|
||||
|
||||
If there is genuinely nothing to surface, output the single line: No new emails.
|
||||
|
||||
Do NOT re-list threads the user has already seen unless their state changed (new reply, status flip).`,
|
||||
|
|
|
|||
|
|
@ -163,15 +163,15 @@ If there are events, include them:
|
|||
1. Use \`workspace-readdir\` with path \`gmail_sync\` to list files (skip \`sync_state.json\` and \`attachments/\`)
|
||||
2. Use \`workspace-readFile\` to read the email markdown files (e.g. \`gmail_sync/threadid123.md\`)
|
||||
3. Check the frontmatter \`action\` field — emails with \`action: reply\` or \`action: respond\` need a response
|
||||
4. For emails needing a response, output \\\`\\\`\\\`email blocks with a \`draft_response\`. Write the draft in the user's voice — direct, informal, no fluff. Example:
|
||||
4. Output ALL emails (both action items and FYI) in a single \\\`\\\`\\\`emails block as a JSON array. Emails needing a response get a \`draft_response\`. Write drafts in the user's voice — direct, informal, no fluff. Example:
|
||||
|
||||
\`\`\`
|
||||
\\\`\\\`\\\`email
|
||||
{"threadId":"abc123","summary":"Payment confirmation","subject":"Google services payment","from":"Sender <sender@example.com>","date":"2026-04-01T11:28:39+05:30","latest_email":"Hi, I've made the payment...","draft_response":"Thanks for confirming. I'll update our records."}
|
||||
\\\`\\\`\\\`emails
|
||||
{"title":"Today's Emails","emails":[{"threadId":"abc123","summary":"Payment confirmation","subject":"Google services payment","from":"Sender <sender@example.com>","date":"2026-04-01T11:28:39+05:30","latest_email":"Hi, I've made the payment...","draft_response":"Thanks for confirming. I'll update our records."},{"threadId":"def456","summary":"Security alert","subject":"New sign-in from Chrome","from":"Google <no-reply@accounts.google.com>","date":"2026-04-01T09:15:00+05:30","latest_email":"A new sign-in to your account was detected."}]}
|
||||
\\\`\\\`\\\`
|
||||
\`\`\`
|
||||
|
||||
5. For other important/recent emails, output \\\`\\\`\\\`email blocks without \`draft_response\` as FYI items
|
||||
5. FYI emails go in the same \`emails\` array without a \`draft_response\`
|
||||
6. **Recency matters.** Since this refreshes every 15 minutes, prioritize emails that arrived since the last refresh. On the first run of the day (morning), include notable emails from the last 24 hours. On subsequent runs, focus on what's new — don't re-list emails the user has already seen unless their status changed (e.g., a thread got a new reply).
|
||||
7. Add a brief take on emails where it's helpful — flag what's worth reading vs. what's noise. Be direct: "This is a cold pitch, probably skip" or "Worth reading — they're asking about pricing for a team of 50."
|
||||
8. If no new emails have come in since the last refresh, just say "No new emails" or omit the section entirely. Don't re-surface stale items.
|
||||
|
|
@ -200,7 +200,7 @@ This is NOT a generic task list. These are the things the user should actually f
|
|||
## Output format
|
||||
- Start with the date heading as described above
|
||||
- Use clean markdown with the section headers (## Up Next, ## Calendar, ## Emails, ## What You Missed, ## Today's Priorities)
|
||||
- Use \\\`\\\`\\\`calendar and \\\`\\\`\\\`email code blocks where specified — these render as interactive UI blocks
|
||||
- Use \\\`\\\`\\\`calendar and \\\`\\\`\\\`emails (plural) code blocks where specified — these render as interactive UI blocks. Never use \\\`\\\`\\\`email (singular)
|
||||
- Keep the overall brief **scannable and concise** — this should take under 30 seconds to read on a refresh, under 60 seconds for the morning brief
|
||||
- Write in a natural, conversational tone throughout — you're briefing a person, not generating a report
|
||||
- **Sections can be omitted** if they have nothing to show. Don't include empty sections with filler text. The brief should get shorter as the day goes on and things get resolved.
|
||||
|
|
|
|||
|
|
@ -101,6 +101,13 @@ export const EmailBlockSchema = z.object({
|
|||
|
||||
export type EmailBlock = z.infer<typeof EmailBlockSchema>;
|
||||
|
||||
export const EmailsBlockSchema = z.object({
|
||||
title: z.string().optional(),
|
||||
emails: z.array(EmailBlockSchema),
|
||||
});
|
||||
|
||||
export type EmailsBlock = z.infer<typeof EmailsBlockSchema>;
|
||||
|
||||
export const TranscriptBlockSchema = z.object({
|
||||
transcript: z.string(),
|
||||
});
|
||||
|
|
|
|||
141
apps/x/pnpm-lock.yaml
generated
141
apps/x/pnpm-lock.yaml
generated
|
|
@ -177,28 +177,28 @@ importers:
|
|||
version: 4.1.18(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.46.0)(yaml@2.8.2))
|
||||
'@tiptap/extension-image':
|
||||
specifier: ^3.16.0
|
||||
version: 3.16.0(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))
|
||||
version: 3.16.0(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-link':
|
||||
specifier: ^3.15.3
|
||||
version: 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
version: 3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extension-placeholder':
|
||||
specifier: ^3.15.3
|
||||
version: 3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
version: 3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-table':
|
||||
specifier: ^3.22.4
|
||||
version: 3.22.4(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
version: 3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extension-task-item':
|
||||
specifier: ^3.15.3
|
||||
version: 3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
version: 3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-task-list':
|
||||
specifier: ^3.15.3
|
||||
version: 3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
version: 3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
'@tiptap/pm':
|
||||
specifier: ^3.15.3
|
||||
version: 3.15.3
|
||||
'@tiptap/react':
|
||||
specifier: ^3.15.3
|
||||
version: 3.15.3(@floating-ui/dom@1.7.4)(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
version: 3.15.3(@floating-ui/dom@1.7.4)(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@tiptap/starter-kit':
|
||||
specifier: ^3.15.3
|
||||
version: 3.15.3
|
||||
|
|
@ -264,7 +264,7 @@ importers:
|
|||
version: 4.1.18
|
||||
tiptap-markdown:
|
||||
specifier: ^0.9.0
|
||||
version: 0.9.0(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))
|
||||
version: 0.9.0(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))
|
||||
tokenlens:
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1
|
||||
|
|
@ -1479,30 +1479,35 @@ packages:
|
|||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-musl@0.1.80':
|
||||
resolution: {integrity: sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.80':
|
||||
resolution: {integrity: sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/canvas-linux-x64-gnu@0.1.80':
|
||||
resolution: {integrity: sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/canvas-linux-x64-musl@0.1.80':
|
||||
resolution: {integrity: sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@napi-rs/canvas-win32-x64-msvc@0.1.80':
|
||||
resolution: {integrity: sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==}
|
||||
|
|
@ -2628,56 +2633,67 @@ packages:
|
|||
resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.54.0':
|
||||
resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.54.0':
|
||||
resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.54.0':
|
||||
resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.54.0':
|
||||
resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.54.0':
|
||||
resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.54.0':
|
||||
resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.54.0':
|
||||
resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.54.0':
|
||||
resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.54.0':
|
||||
resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.54.0':
|
||||
resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openharmony-arm64@4.54.0':
|
||||
resolution: {integrity: sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==}
|
||||
|
|
@ -2996,24 +3012,28 @@ packages:
|
|||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.18':
|
||||
resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.18':
|
||||
resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.1.18':
|
||||
resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.1.18':
|
||||
resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
|
||||
|
|
@ -3053,6 +3073,11 @@ packages:
|
|||
peerDependencies:
|
||||
'@tiptap/pm': ^3.15.3
|
||||
|
||||
'@tiptap/core@3.22.4':
|
||||
resolution: {integrity: sha512-vGIGm/HpqLg8EAAQXQ+koV+/S828OEpzocfWcPOwo1u2QUVf9dQG47Yy6JJ8zFFaJwfv4dBcOXli+7BrJwsxDQ==}
|
||||
peerDependencies:
|
||||
'@tiptap/pm': 3.22.4
|
||||
|
||||
'@tiptap/extension-blockquote@3.15.3':
|
||||
resolution: {integrity: sha512-13x5UsQXtttFpoS/n1q173OeurNxppsdWgP3JfsshzyxIghhC141uL3H6SGYQLPU31AizgDs2OEzt6cSUevaZg==}
|
||||
peerDependencies:
|
||||
|
|
@ -5602,24 +5627,28 @@ packages:
|
|||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.30.2:
|
||||
resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
|
||||
|
|
@ -11073,6 +11102,10 @@ snapshots:
|
|||
dependencies:
|
||||
'@tiptap/pm': 3.15.3
|
||||
|
||||
'@tiptap/core@3.22.4(@tiptap/pm@3.15.3)':
|
||||
dependencies:
|
||||
'@tiptap/pm': 3.15.3
|
||||
|
||||
'@tiptap/extension-blockquote@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.15.3(@tiptap/pm@3.15.3)
|
||||
|
|
@ -11081,16 +11114,16 @@ snapshots:
|
|||
dependencies:
|
||||
'@tiptap/core': 3.15.3(@tiptap/pm@3.15.3)
|
||||
|
||||
'@tiptap/extension-bubble-menu@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)':
|
||||
'@tiptap/extension-bubble-menu@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.7.4
|
||||
'@tiptap/core': 3.15.3(@tiptap/pm@3.15.3)
|
||||
'@tiptap/core': 3.22.4(@tiptap/pm@3.15.3)
|
||||
'@tiptap/pm': 3.15.3
|
||||
optional: true
|
||||
|
||||
'@tiptap/extension-bullet-list@3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
'@tiptap/extension-bullet-list@3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
dependencies:
|
||||
'@tiptap/extension-list': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extension-list': 3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
|
||||
'@tiptap/extension-code-block@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)':
|
||||
dependencies:
|
||||
|
|
@ -11105,20 +11138,20 @@ snapshots:
|
|||
dependencies:
|
||||
'@tiptap/core': 3.15.3(@tiptap/pm@3.15.3)
|
||||
|
||||
'@tiptap/extension-dropcursor@3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
'@tiptap/extension-dropcursor@3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
dependencies:
|
||||
'@tiptap/extensions': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extensions': 3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
|
||||
'@tiptap/extension-floating-menu@3.15.3(@floating-ui/dom@1.7.4)(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)':
|
||||
'@tiptap/extension-floating-menu@3.15.3(@floating-ui/dom@1.7.4)(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.7.4
|
||||
'@tiptap/core': 3.15.3(@tiptap/pm@3.15.3)
|
||||
'@tiptap/core': 3.22.4(@tiptap/pm@3.15.3)
|
||||
'@tiptap/pm': 3.15.3
|
||||
optional: true
|
||||
|
||||
'@tiptap/extension-gapcursor@3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
'@tiptap/extension-gapcursor@3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
dependencies:
|
||||
'@tiptap/extensions': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extensions': 3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
|
||||
'@tiptap/extension-hard-break@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))':
|
||||
dependencies:
|
||||
|
|
@ -11133,9 +11166,9 @@ snapshots:
|
|||
'@tiptap/core': 3.15.3(@tiptap/pm@3.15.3)
|
||||
'@tiptap/pm': 3.15.3
|
||||
|
||||
'@tiptap/extension-image@3.16.0(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))':
|
||||
'@tiptap/extension-image@3.16.0(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.15.3(@tiptap/pm@3.15.3)
|
||||
'@tiptap/core': 3.22.4(@tiptap/pm@3.15.3)
|
||||
|
||||
'@tiptap/extension-italic@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))':
|
||||
dependencies:
|
||||
|
|
@ -11147,47 +11180,58 @@ snapshots:
|
|||
'@tiptap/pm': 3.15.3
|
||||
linkifyjs: 4.3.2
|
||||
|
||||
'@tiptap/extension-list-item@3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
'@tiptap/extension-link@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)':
|
||||
dependencies:
|
||||
'@tiptap/extension-list': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/core': 3.22.4(@tiptap/pm@3.15.3)
|
||||
'@tiptap/pm': 3.15.3
|
||||
linkifyjs: 4.3.2
|
||||
|
||||
'@tiptap/extension-list-keymap@3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
'@tiptap/extension-list-item@3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
dependencies:
|
||||
'@tiptap/extension-list': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extension-list': 3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
|
||||
'@tiptap/extension-list-keymap@3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
dependencies:
|
||||
'@tiptap/extension-list': 3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
|
||||
'@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.15.3(@tiptap/pm@3.15.3)
|
||||
'@tiptap/pm': 3.15.3
|
||||
|
||||
'@tiptap/extension-ordered-list@3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
'@tiptap/extension-list@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)':
|
||||
dependencies:
|
||||
'@tiptap/extension-list': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/core': 3.22.4(@tiptap/pm@3.15.3)
|
||||
'@tiptap/pm': 3.15.3
|
||||
|
||||
'@tiptap/extension-ordered-list@3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
dependencies:
|
||||
'@tiptap/extension-list': 3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
|
||||
'@tiptap/extension-paragraph@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.15.3(@tiptap/pm@3.15.3)
|
||||
|
||||
'@tiptap/extension-placeholder@3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
'@tiptap/extension-placeholder@3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
dependencies:
|
||||
'@tiptap/extensions': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extensions': 3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
|
||||
'@tiptap/extension-strike@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.15.3(@tiptap/pm@3.15.3)
|
||||
|
||||
'@tiptap/extension-table@3.22.4(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)':
|
||||
'@tiptap/extension-table@3.22.4(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.15.3(@tiptap/pm@3.15.3)
|
||||
'@tiptap/core': 3.22.4(@tiptap/pm@3.15.3)
|
||||
'@tiptap/pm': 3.15.3
|
||||
|
||||
'@tiptap/extension-task-item@3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
'@tiptap/extension-task-item@3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
dependencies:
|
||||
'@tiptap/extension-list': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extension-list': 3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
|
||||
'@tiptap/extension-task-list@3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
'@tiptap/extension-task-list@3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))':
|
||||
dependencies:
|
||||
'@tiptap/extension-list': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extension-list': 3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
|
||||
'@tiptap/extension-text@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))':
|
||||
dependencies:
|
||||
|
|
@ -11202,6 +11246,11 @@ snapshots:
|
|||
'@tiptap/core': 3.15.3(@tiptap/pm@3.15.3)
|
||||
'@tiptap/pm': 3.15.3
|
||||
|
||||
'@tiptap/extensions@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.22.4(@tiptap/pm@3.15.3)
|
||||
'@tiptap/pm': 3.15.3
|
||||
|
||||
'@tiptap/pm@3.15.3':
|
||||
dependencies:
|
||||
prosemirror-changeset: 2.3.1
|
||||
|
|
@ -11223,9 +11272,9 @@ snapshots:
|
|||
prosemirror-transform: 1.10.5
|
||||
prosemirror-view: 1.41.4
|
||||
|
||||
'@tiptap/react@3.15.3(@floating-ui/dom@1.7.4)(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
'@tiptap/react@3.15.3(@floating-ui/dom@1.7.4)(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.15.3(@tiptap/pm@3.15.3)
|
||||
'@tiptap/core': 3.22.4(@tiptap/pm@3.15.3)
|
||||
'@tiptap/pm': 3.15.3
|
||||
'@types/react': 19.2.7
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.7)
|
||||
|
|
@ -11235,8 +11284,8 @@ snapshots:
|
|||
react-dom: 19.2.3(react@19.2.3)
|
||||
use-sync-external-store: 1.6.0(react@19.2.3)
|
||||
optionalDependencies:
|
||||
'@tiptap/extension-bubble-menu': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extension-floating-menu': 3.15.3(@floating-ui/dom@1.7.4)(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extension-bubble-menu': 3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extension-floating-menu': 3.15.3(@floating-ui/dom@1.7.4)(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
transitivePeerDependencies:
|
||||
- '@floating-ui/dom'
|
||||
|
||||
|
|
@ -11245,21 +11294,21 @@ snapshots:
|
|||
'@tiptap/core': 3.15.3(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extension-blockquote': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-bold': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-bullet-list': 3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-bullet-list': 3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-code': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-code-block': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extension-document': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-dropcursor': 3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-gapcursor': 3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-dropcursor': 3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-gapcursor': 3.15.3(@tiptap/extensions@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-hard-break': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-heading': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-horizontal-rule': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extension-italic': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-link': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extension-list': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3)
|
||||
'@tiptap/extension-list-item': 3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-list-keymap': 3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-ordered-list': 3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-list-item': 3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-list-keymap': 3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-ordered-list': 3.15.3(@tiptap/extension-list@3.15.3(@tiptap/core@3.22.4(@tiptap/pm@3.15.3))(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-paragraph': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-strike': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))
|
||||
'@tiptap/extension-text': 3.15.3(@tiptap/core@3.15.3(@tiptap/pm@3.15.3))
|
||||
|
|
@ -16221,9 +16270,9 @@ snapshots:
|
|||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
|
||||
tiptap-markdown@0.9.0(@tiptap/core@3.15.3(@tiptap/pm@3.15.3)):
|
||||
tiptap-markdown@0.9.0(@tiptap/core@3.22.4(@tiptap/pm@3.15.3)):
|
||||
dependencies:
|
||||
'@tiptap/core': 3.15.3(@tiptap/pm@3.15.3)
|
||||
'@tiptap/core': 3.22.4(@tiptap/pm@3.15.3)
|
||||
'@types/markdown-it': 13.0.9
|
||||
markdown-it: 14.1.0
|
||||
markdown-it-task-lists: 2.1.1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue