Remove trailing empty paragraphs

This commit is contained in:
MartinBraquet
2025-11-13 13:53:11 +01:00
parent 8a215a765f
commit 716633c6df
2 changed files with 72 additions and 25 deletions

View File

@@ -1,20 +1,15 @@
import {
getText,
getSchema,
getTextSerializersFromSchema,
JSONContent,
} from '@tiptap/core'
import { Node as ProseMirrorNode } from '@tiptap/pm/model'
import { StarterKit } from '@tiptap/starter-kit'
import { Image } from '@tiptap/extension-image'
import { Link } from '@tiptap/extension-link'
import { Mention } from '@tiptap/extension-mention'
import {getSchema, getText, getTextSerializersFromSchema, JSONContent,} from '@tiptap/core'
import {Node as ProseMirrorNode} from '@tiptap/pm/model'
import {StarterKit} from '@tiptap/starter-kit'
import {Image} from '@tiptap/extension-image'
import {Link} from '@tiptap/extension-link'
import {Mention} from '@tiptap/extension-mention'
import Iframe from './tiptap-iframe'
import { find } from 'linkifyjs'
import { uniq } from 'lodash'
import { compareTwoStrings } from 'string-similarity'
import {find} from 'linkifyjs'
import {uniq} from 'lodash'
import {compareTwoStrings} from 'string-similarity'
/** get first url in text. like "notion.so " -> "http://notion.so"; "notion" -> null */
/** get first url in text. like "notion.so " -> "http://notion.so" "notion" -> null */
export function getUrl(text: string) {
const results = find(text, 'url')
return results.length ? results[0].href : null
@@ -48,10 +43,10 @@ export function parseMentions(data: JSONContent): string[] {
export const extensions = [
StarterKit,
Link,
Image.extend({ renderText: () => '[image]' }),
Image.extend({renderText: () => '[image]'}),
Mention, // user @mention
Iframe.extend({
renderText: ({ node }) =>
renderText: ({node}) =>
'[embed]' + node.attrs.src ? `(${node.attrs.src})` : '',
}),
]
@@ -78,8 +73,59 @@ export function parseJsonContentToText(content: JSONContent | string) {
}
export function urlBase64ToUint8Array(base64String: string) {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/');
const rawData = window.atob(base64);
return new Uint8Array([...rawData].map(c => c.charCodeAt(0)));
}
const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/')
const rawData = window.atob(base64)
return new Uint8Array([...rawData].map(c => c.charCodeAt(0)))
}
export function cleanDoc(doc: JSONContent) {
if (!doc || !Array.isArray(doc.content)) return doc;
let content = [...doc.content];
const isEmptyParagraph = (node: JSONContent) =>
node.type === "paragraph" &&
(!node.content || node.content.length === 0);
// Remove empty paragraphs at the start
while (content.length > 0 && isEmptyParagraph(content[0])) {
content.shift();
}
// Remove empty paragraphs at the end
while (content.length > 0 && isEmptyParagraph(content[content.length - 1])) {
content.pop();
}
// Trim leading/trailing hardBreaks within first and last paragraphs
const trimHardBreaks = (paragraph: JSONContent) => {
if (!paragraph.content) return paragraph;
let nodes = [...paragraph.content];
// Remove hardBreaks at the start
while (nodes.length > 0 && nodes[0].type === "hardBreak") {
nodes.shift();
}
// Remove hardBreaks at the end
while (nodes.length > 0 && nodes[nodes.length - 1].type === "hardBreak") {
nodes.pop();
}
return { ...paragraph, content: nodes };
};
if (content.length > 0) {
content[0] = trimHardBreaks(content[0]);
if (content.length > 1) {
content[content.length - 1] = trimHardBreaks(content[content.length - 1]);
}
}
// Remove any now-empty paragraphs created by hardBreak trimming
content = content.filter(node => !(node.type === "paragraph" && (!node.content || node.content.length === 0)));
return { ...doc, content };
}

View File

@@ -32,7 +32,8 @@ import {useGroupedMessages, usePaginatedScrollingMessages,} from 'web/lib/supaba
import {PrivateMessageChannel} from 'common/supabase/private-messages'
import {ChatMessage} from 'common/chat-message'
import {BackButton} from 'web/components/back-button'
import {SEO} from "web/components/SEO";
import {SEO} from "web/components/SEO"
import {cleanDoc} from "common/util/parse";
export default function PrivateMessagesPage() {
const router = useRouter()
@@ -183,8 +184,8 @@ export const PrivateChat = (props: {
setIsSubmitting(true)
try {
const content = editor.getJSON();
// console.log('editingMessage submitting message', {editingMessage}, JSON.stringify(content))
const content = cleanDoc(editor.getJSON())
// console.log('submitting message', JSON.stringify(content))
if (editingMessage) {
// console.log('editingMessage edit-message', editingMessage)
setMessages((prevMessages) =>