Skip to content

Commit 716633c

Browse files
committed
Remove trailing empty paragraphs
1 parent 8a215a7 commit 716633c

2 files changed

Lines changed: 72 additions & 25 deletions

File tree

common/src/util/parse.ts

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
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'
127
import 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 */
1813
export 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[] {
4843
export 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

8075
export 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+
}

web/pages/messages/[channelId].tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ import {useGroupedMessages, usePaginatedScrollingMessages,} from 'web/lib/supaba
3232
import {PrivateMessageChannel} from 'common/supabase/private-messages'
3333
import {ChatMessage} from 'common/chat-message'
3434
import {BackButton} from 'web/components/back-button'
35-
import {SEO} from "web/components/SEO";
35+
import {SEO} from "web/components/SEO"
36+
import {cleanDoc} from "common/util/parse";
3637

3738
export default function PrivateMessagesPage() {
3839
const router = useRouter()
@@ -183,8 +184,8 @@ export const PrivateChat = (props: {
183184
setIsSubmitting(true)
184185

185186
try {
186-
const content = editor.getJSON();
187-
// console.log('editingMessage submitting message', {editingMessage}, JSON.stringify(content))
187+
const content = cleanDoc(editor.getJSON())
188+
// console.log('submitting message', JSON.stringify(content))
188189
if (editingMessage) {
189190
// console.log('editingMessage edit-message', editingMessage)
190191
setMessages((prevMessages) =>

0 commit comments

Comments
 (0)