tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
289
fork
atom
a tool for shared writing and social publishing
289
fork
atom
overview
issues
28
pulls
pipelines
process text and facets together for bsky post editor
awarm.space
5 months ago
3035d0c7
b75ca40f
+71
-64
2 changed files
expand all
collapse all
unified
split
app
[leaflet_id]
publish
BskyPostEditorProsemirror.tsx
PublishPost.tsx
+66
-59
app/[leaflet_id]/publish/BskyPostEditorProsemirror.tsx
···
12
12
import * as Popover from "@radix-ui/react-popover";
13
13
import { EditorState, TextSelection, Plugin } from "prosemirror-state";
14
14
import { EditorView } from "prosemirror-view";
15
15
-
import { Schema, MarkSpec } from "prosemirror-model";
15
15
+
import { Schema, MarkSpec, Mark } from "prosemirror-model";
16
16
import { baseKeymap } from "prosemirror-commands";
17
17
import { keymap } from "prosemirror-keymap";
18
18
import { history, undo, redo } from "prosemirror-history";
···
507
507
* Extracts mentions, links, and hashtags from the editor state and returns them
508
508
* as an array of Bluesky richtext facets with proper byte positions.
509
509
*/
510
510
-
export function editorStateToFacets(
510
510
+
export function editorStateToFacetedText(
511
511
state: EditorState,
512
512
-
): AppBskyRichtextFacet.Main[] {
513
513
-
const facets: AppBskyRichtextFacet.Main[] = [];
514
514
-
const fullText = state.doc.textContent;
515
515
-
const unicodeString = new UnicodeString(fullText);
516
516
-
512
512
+
): [string, AppBskyRichtextFacet.Main[]] {
513
513
+
let fullText = "";
514
514
+
let facets: AppBskyRichtextFacet.Main[] = [];
517
515
let byteOffset = 0;
518
516
519
519
-
// Walk through the document to extract marks with their positions
520
520
-
state.doc.descendants((node, pos) => {
521
521
-
if (node.isText && node.text) {
522
522
-
const text = node.text;
523
523
-
const textLength = new UnicodeString(text).length;
517
517
+
// Iterate through each paragraph in the document
518
518
+
state.doc.forEach((paragraph) => {
519
519
+
if (paragraph.type.name !== "paragraph") return;
524
520
525
525
-
// Check for mention mark
526
526
-
const mentionMark = node.marks.find((m) => m.type.name === "mention");
527
527
-
if (mentionMark) {
528
528
-
facets.push({
529
529
-
index: {
530
530
-
byteStart: byteOffset,
531
531
-
byteEnd: byteOffset + textLength,
532
532
-
},
533
533
-
features: [
534
534
-
{
535
535
-
$type: "app.bsky.richtext.facet#mention",
536
536
-
did: mentionMark.attrs.did,
521
521
+
// Process each inline node in the paragraph
522
522
+
paragraph.forEach((node) => {
523
523
+
if (node.isText) {
524
524
+
const text = node.text || "";
525
525
+
const unicodeString = new UnicodeString(text);
526
526
+
527
527
+
// If this text node has marks, create a facet
528
528
+
if (node.marks.length > 0) {
529
529
+
const facet: AppBskyRichtextFacet.Main = {
530
530
+
index: {
531
531
+
byteStart: byteOffset,
532
532
+
byteEnd: byteOffset + unicodeString.length,
537
533
},
538
538
-
],
539
539
-
});
534
534
+
features: marksToFeatures(node.marks),
535
535
+
};
536
536
+
537
537
+
if (facet.features.length > 0) {
538
538
+
facets.push(facet);
539
539
+
}
540
540
+
}
541
541
+
542
542
+
fullText += text;
543
543
+
byteOffset += unicodeString.length;
540
544
}
545
545
+
});
541
546
542
542
-
// Check for link mark
543
543
-
const linkMark = node.marks.find((m) => m.type.name === "link");
544
544
-
if (linkMark) {
545
545
-
facets.push({
546
546
-
index: {
547
547
-
byteStart: byteOffset,
548
548
-
byteEnd: byteOffset + textLength,
549
549
-
},
550
550
-
features: [
551
551
-
{
552
552
-
$type: "app.bsky.richtext.facet#link",
553
553
-
uri: linkMark.attrs.href,
554
554
-
},
555
555
-
],
547
547
+
// Add newline between paragraphs (except after the last one)
548
548
+
if (paragraph !== state.doc.lastChild) {
549
549
+
const newline = "\n";
550
550
+
const unicodeNewline = new UnicodeString(newline);
551
551
+
fullText += newline;
552
552
+
byteOffset += unicodeNewline.length;
553
553
+
}
554
554
+
});
555
555
+
556
556
+
return [fullText, facets];
557
557
+
}
558
558
+
559
559
+
function marksToFeatures(marks: readonly Mark[]) {
560
560
+
const features: AppBskyRichtextFacet.Main["features"] = [];
561
561
+
562
562
+
for (const mark of marks) {
563
563
+
switch (mark.type.name) {
564
564
+
case "mention": {
565
565
+
features.push({
566
566
+
$type: "app.bsky.richtext.facet#mention",
567
567
+
did: mark.attrs.did,
556
568
});
569
569
+
break;
557
570
}
558
558
-
559
559
-
// Check for hashtag mark
560
560
-
const hashtagMark = node.marks.find((m) => m.type.name === "hashtag");
561
561
-
if (hashtagMark) {
562
562
-
facets.push({
563
563
-
index: {
564
564
-
byteStart: byteOffset,
565
565
-
byteEnd: byteOffset + textLength,
566
566
-
},
567
567
-
features: [
568
568
-
{
569
569
-
$type: "app.bsky.richtext.facet#tag",
570
570
-
tag: hashtagMark.attrs.tag,
571
571
-
},
572
572
-
],
571
571
+
case "hashtag": {
572
572
+
features.push({
573
573
+
$type: "app.bsky.richtext.facet#tag",
574
574
+
tag: mark.attrs.tag,
573
575
});
576
576
+
break;
574
577
}
575
575
-
576
576
-
byteOffset += textLength;
578
578
+
case "link":
579
579
+
features.push({
580
580
+
$type: "app.bsky.richtext.facet#link",
581
581
+
uri: mark.attrs.href as string,
582
582
+
});
583
583
+
break;
577
584
}
578
578
-
});
585
585
+
}
579
586
580
580
-
return facets;
587
587
+
return features;
581
588
}
+5
-5
app/[leaflet_id]/publish/PublishPost.tsx
···
15
15
import { useReplicache } from "src/replicache";
16
16
import {
17
17
BlueskyPostEditorProsemirror,
18
18
-
editorStateToFacets,
18
18
+
editorStateToFacetedText,
19
19
} from "./BskyPostEditorProsemirror";
20
20
import { EditorState } from "prosemirror-state";
21
21
···
76
76
if (!doc) return;
77
77
78
78
let post_url = `https://${props.record?.base_path}/${doc.rkey}`;
79
79
-
let facets = editorStateRef.current
80
80
-
? editorStateToFacets(editorStateRef.current)
79
79
+
let [text, facets] = editorStateRef.current
80
80
+
? editorStateToFacetedText(editorStateRef.current)
81
81
: [];
82
82
if (shareOption === "bluesky")
83
83
await publishPostToBsky({
84
84
-
facets,
85
85
-
text: editorStateRef.current?.doc.textContent || "",
84
84
+
facets: facets || [],
85
85
+
text: text || "",
86
86
title: props.title,
87
87
url: post_url,
88
88
description: props.description,