1- import {
2- getText ,
3- getSchema ,
4- getTextSerializersFromSchema ,
5- JSONContent ,
6- } from '@tiptap/core'
7- import { Node as ProseMirrorNode } from '@tiptap/pm/model'
8- import { StarterKit } from '@tiptap/starter-kit'
9- import { Image } from '@tiptap/extension-image'
10- import { Link } from '@tiptap/extension-link'
11- import { Mention } from '@tiptap/extension-mention'
1+ import { getSchema , getText , getTextSerializersFromSchema , JSONContent , } from '@tiptap/core'
2+ import { Node as ProseMirrorNode } from '@tiptap/pm/model'
3+ import { StarterKit } from '@tiptap/starter-kit'
4+ import { Image } from '@tiptap/extension-image'
5+ import { Link } from '@tiptap/extension-link'
6+ import { Mention } from '@tiptap/extension-mention'
127import Iframe from './tiptap-iframe'
13- import { find } from 'linkifyjs'
14- import { uniq } from 'lodash'
15- import { compareTwoStrings } from 'string-similarity'
8+ import { find } from 'linkifyjs'
9+ import { uniq } from 'lodash'
10+ import { compareTwoStrings } from 'string-similarity'
1611
17- /** get first url in text. like "notion.so " -> "http://notion.so"; "notion" -> null */
12+ /** get first url in text. like "notion.so " -> "http://notion.so" "notion" -> null */
1813export function getUrl ( text : string ) {
1914 const results = find ( text , 'url' )
2015 return results . length ? results [ 0 ] . href : null
@@ -48,10 +43,10 @@ export function parseMentions(data: JSONContent): string[] {
4843export const extensions = [
4944 StarterKit ,
5045 Link ,
51- Image . extend ( { renderText : ( ) => '[image]' } ) ,
46+ Image . extend ( { renderText : ( ) => '[image]' } ) ,
5247 Mention , // user @mention
5348 Iframe . extend ( {
54- renderText : ( { node } ) =>
49+ renderText : ( { node} ) =>
5550 '[embed]' + node . attrs . src ? `(${ node . attrs . src } )` : '' ,
5651 } ) ,
5752]
@@ -78,8 +73,59 @@ export function parseJsonContentToText(content: JSONContent | string) {
7873}
7974
8075export function urlBase64ToUint8Array ( base64String : string ) {
81- const padding = '=' . repeat ( ( 4 - ( base64String . length % 4 ) ) % 4 ) ;
82- const base64 = ( base64String + padding ) . replace ( / \- / g, '+' ) . replace ( / _ / g, '/' ) ;
83- const rawData = window . atob ( base64 ) ;
84- return new Uint8Array ( [ ...rawData ] . map ( c => c . charCodeAt ( 0 ) ) ) ;
85- }
76+ const padding = '=' . repeat ( ( 4 - ( base64String . length % 4 ) ) % 4 )
77+ const base64 = ( base64String + padding ) . replace ( / \- / g, '+' ) . replace ( / _ / g, '/' )
78+ const rawData = window . atob ( base64 )
79+ return new Uint8Array ( [ ...rawData ] . map ( c => c . charCodeAt ( 0 ) ) )
80+ }
81+
82+ export function cleanDoc ( doc : JSONContent ) {
83+ if ( ! doc || ! Array . isArray ( doc . content ) ) return doc ;
84+
85+ let content = [ ...doc . content ] ;
86+
87+ const isEmptyParagraph = ( node : JSONContent ) =>
88+ node . type === "paragraph" &&
89+ ( ! node . content || node . content . length === 0 ) ;
90+
91+ // Remove empty paragraphs at the start
92+ while ( content . length > 0 && isEmptyParagraph ( content [ 0 ] ) ) {
93+ content . shift ( ) ;
94+ }
95+
96+ // Remove empty paragraphs at the end
97+ while ( content . length > 0 && isEmptyParagraph ( content [ content . length - 1 ] ) ) {
98+ content . pop ( ) ;
99+ }
100+
101+ // Trim leading/trailing hardBreaks within first and last paragraphs
102+ const trimHardBreaks = ( paragraph : JSONContent ) => {
103+ if ( ! paragraph . content ) return paragraph ;
104+
105+ let nodes = [ ...paragraph . content ] ;
106+
107+ // Remove hardBreaks at the start
108+ while ( nodes . length > 0 && nodes [ 0 ] . type === "hardBreak" ) {
109+ nodes . shift ( ) ;
110+ }
111+
112+ // Remove hardBreaks at the end
113+ while ( nodes . length > 0 && nodes [ nodes . length - 1 ] . type === "hardBreak" ) {
114+ nodes . pop ( ) ;
115+ }
116+
117+ return { ...paragraph , content : nodes } ;
118+ } ;
119+
120+ if ( content . length > 0 ) {
121+ content [ 0 ] = trimHardBreaks ( content [ 0 ] ) ;
122+ if ( content . length > 1 ) {
123+ content [ content . length - 1 ] = trimHardBreaks ( content [ content . length - 1 ] ) ;
124+ }
125+ }
126+
127+ // Remove any now-empty paragraphs created by hardBreak trimming
128+ content = content . filter ( node => ! ( node . type === "paragraph" && ( ! node . content || node . content . length === 0 ) ) ) ;
129+
130+ return { ...doc , content } ;
131+ }
0 commit comments