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
27
pulls
pipelines
implement single line blockquote
awarm.space
7 months ago
1252e96f
fcbe14e4
+41
-73
9 changed files
expand all
collapse all
unified
split
components
Blocks
Block.tsx
BlockCommands.tsx
TextBlock
RenderYJSFragment.tsx
index.tsx
inputRules.ts
useHandlePaste.ts
src
utils
focusBlock.ts
getBlocksAsHTML.tsx
isTextBlock.ts
+1
components/Blocks/Block.tsx
···
104
? "pb-0"
105
: "pb-2"
106
}
0
107
${
108
!props.previousBlock
109
? props.type === "heading" || props.type === "text"
···
104
? "pb-0"
105
: "pb-2"
106
}
107
+
${props.type === "blockquote" && props.previousBlock?.type === "blockquote" ? "-mt-3" : ""}
108
${
109
!props.previousBlock
110
? props.type === "heading" || props.type === "text"
+10
-11
components/Blocks/BlockCommands.tsx
···
31
import { ListUnorderedSmall } from "components/Toolbar/ListToolbar";
32
import { BlockMathSmall } from "components/Icons/BlockMathSmall";
33
import { BlockCodeSmall } from "components/Icons/BlockCodeSmall";
34
-
import { QuoteTiny } from "components/Icons/QuoteTiny";
35
import { QuoteSmall } from "components/Icons/QuoteSmall";
36
37
type Props = {
···
160
clearCommandSearchText(entity);
161
},
162
},
163
-
// {
164
-
// name: "Block Quote",
165
-
// icon: <QuoteSmall />,
166
-
// type: "text",
167
-
// onSelect: async (rep, props, um) => {
168
-
// if (props.entityID) clearCommandSearchText(props.entityID);
169
-
// let entity = await createBlockWithType(rep, props, "blockquote");
170
-
// clearCommandSearchText(entity);
171
-
// },
172
-
// },
173
174
{
175
name: "Image",
···
31
import { ListUnorderedSmall } from "components/Toolbar/ListToolbar";
32
import { BlockMathSmall } from "components/Icons/BlockMathSmall";
33
import { BlockCodeSmall } from "components/Icons/BlockCodeSmall";
0
34
import { QuoteSmall } from "components/Icons/QuoteSmall";
35
36
type Props = {
···
159
clearCommandSearchText(entity);
160
},
161
},
162
+
{
163
+
name: "Block Quote",
164
+
icon: <QuoteSmall />,
165
+
type: "text",
166
+
onSelect: async (rep, props, um) => {
167
+
if (props.entityID) clearCommandSearchText(props.entityID);
168
+
let entity = await createBlockWithType(rep, props, "blockquote");
169
+
clearCommandSearchText(entity);
170
+
},
171
+
},
172
173
{
174
name: "Image",
+5
-2
components/Blocks/TextBlock/RenderYJSFragment.tsx
···
9
attrs,
10
}: {
11
node: XmlElement | XmlText | XmlHook;
12
-
wrapper?: "h1" | "h2" | "h3" | null;
13
attrs?: { [k: string]: any };
14
}) {
15
if (node.constructor === XmlElement) {
···
63
}
64
65
const BlockWrapper = (props: {
66
-
wrapper?: "h1" | "h2" | "h3" | null;
67
children: React.ReactNode;
68
attrs?: { [k: string]: any };
69
}) => {
70
if (props.wrapper === null) return <>{props.children}</>;
71
if (!props.wrapper) return <p {...props.attrs}>{props.children}</p>;
72
switch (props.wrapper) {
0
0
0
73
case "h1":
74
return <h1 {...props.attrs}>{props.children}</h1>;
75
case "h2":
···
9
attrs,
10
}: {
11
node: XmlElement | XmlText | XmlHook;
12
+
wrapper?: "h1" | "h2" | "h3" | null | "blockquote";
13
attrs?: { [k: string]: any };
14
}) {
15
if (node.constructor === XmlElement) {
···
63
}
64
65
const BlockWrapper = (props: {
66
+
wrapper?: "h1" | "h2" | "h3" | null | "blockquote";
67
children: React.ReactNode;
68
attrs?: { [k: string]: any };
69
}) => {
70
if (props.wrapper === null) return <>{props.children}</>;
71
if (!props.wrapper) return <p {...props.attrs}>{props.children}</p>;
72
switch (props.wrapper) {
73
+
case "blockquote":
74
+
return <blockquote {...props.attrs}>{props.children}</blockquote>;
75
+
76
case "h1":
77
return <h1 {...props.attrs}>{props.children}</h1>;
78
case "h2":
+3
-8
components/Blocks/TextBlock/index.tsx
···
218
let handlePaste = useHandlePaste(props.entityID, propsRef);
219
useLayoutEffect(() => {
220
if (!mountRef.current) return;
221
-
let km = TextBlockKeymap(
222
-
propsRef,
223
-
repRef,
224
-
rep.undoManager,
225
-
props.type === "blockquote",
226
-
);
227
let editor = EditorState.create({
228
-
schema: props.type === "blockquote" ? multiBlockSchema : schema,
229
plugins: [
230
ySyncPlugin(value),
231
keymap(km),
···
342
},
343
}));
344
};
345
-
}, [props.entityID, props.parent, value, handlePaste, rep, props.type]);
346
347
return (
348
<>
···
218
let handlePaste = useHandlePaste(props.entityID, propsRef);
219
useLayoutEffect(() => {
220
if (!mountRef.current) return;
221
+
let km = TextBlockKeymap(propsRef, repRef, rep.undoManager);
0
0
0
0
0
222
let editor = EditorState.create({
223
+
schema: schema,
224
plugins: [
225
ySyncPlugin(value),
226
keymap(km),
···
337
},
338
}));
339
};
340
+
}, [props.entityID, props.parent, value, handlePaste, rep]);
341
342
return (
343
<>
+11
-11
components/Blocks/TextBlock/inputRules.ts
···
152
return tr;
153
}),
154
155
-
// //Blockquote
156
-
// new InputRule(/^([>]{1})\s$/, (state, match) => {
157
-
// let tr = state.tr;
158
-
// tr.delete(0, 2);
159
-
// repRef.current?.mutate.assertFact({
160
-
// entity: propsRef.current.entityID,
161
-
// attribute: "block/type",
162
-
// data: { type: "block-type-union", value: "blockquote" },
163
-
// });
164
-
// return tr;
165
-
// }),
166
167
//Header
168
new InputRule(/^([#]{1,3})\s$/, (state, match) => {
···
152
return tr;
153
}),
154
155
+
//Blockquote
156
+
new InputRule(/^([>]{1})\s$/, (state, match) => {
157
+
let tr = state.tr;
158
+
tr.delete(0, 2);
159
+
repRef.current?.mutate.assertFact({
160
+
entity: propsRef.current.entityID,
161
+
attribute: "block/type",
162
+
data: { type: "block-type-union", value: "blockquote" },
163
+
});
164
+
return tr;
165
+
}),
166
167
//Header
168
new InputRule(/^([#]{1,3})\s$/, (state, match) => {
+2
-7
components/Blocks/TextBlock/useHandlePaste.ts
···
254
default:
255
type = null;
256
}
257
-
let content =
258
-
type === "blockquote" ? multilineParser.parse(child) : parser.parse(child);
259
if (!type) return;
260
261
let entityID: string;
···
498
block.editor.selection.to !== undefined
499
)
500
tr.delete(block.editor.selection.from, block.editor.selection.to);
501
-
if (type === "blockquote") {
502
-
tr.replaceWith(0, tr.doc.content.size, content.content);
503
-
} else {
504
-
tr.replaceSelectionWith(content);
505
-
}
506
let newState = block.editor.apply(tr);
507
setEditorState(entityID, {
508
editor: newState,
···
254
default:
255
type = null;
256
}
257
+
let content = parser.parse(child);
0
258
if (!type) return;
259
260
let entityID: string;
···
497
block.editor.selection.to !== undefined
498
)
499
tr.delete(block.editor.selection.from, block.editor.selection.to);
500
+
tr.replaceSelectionWith(content);
0
0
0
0
501
let newState = block.editor.apply(tr);
502
setEditorState(entityID, {
503
editor: newState,
+3
-8
src/utils/focusBlock.ts
···
104
}
105
}
106
107
-
if (block.type === "blockquote" && position.type === "start") {
108
-
let sel = NodeSelection.create(tr.doc, 0);
109
-
nextBlock.view.dispatch(tr.setSelection(sel));
110
-
} else {
111
-
nextBlock.view.dispatch(
112
-
tr.setSelection(TextSelection.create(tr.doc, pos?.pos || 1)),
113
-
);
114
-
}
115
nextBlock.view.focus();
116
}
117
···
104
}
105
}
106
107
+
nextBlock.view.dispatch(
108
+
tr.setSelection(TextSelection.create(tr.doc, pos?.pos || 1)),
109
+
);
0
0
0
0
0
110
nextBlock.view.focus();
111
}
112
+5
-26
src/utils/getBlocksAsHTML.tsx
···
74
tx: ReadTransaction,
75
ignoreWrapper?: boolean,
76
) {
77
-
let wrapper: undefined | "h1" | "h2" | "h3";
78
let [alignment] = await scanIndex(tx).eav(b.value, "block/text-alignment");
79
if (b.type === "horizontal-rule") {
80
return "<hr />";
···
125
</a>,
126
);
127
}
0
0
0
128
if (b.type === "heading") {
129
let headingLevel =
130
(await scanIndex(tx).eav(b.value, "block/heading-level"))[0]?.data
···
162
</div>,
163
);
164
}
165
-
if (b.type === "blockquote") {
166
-
let value = (await scanIndex(tx).eav(b.value, "block/text"))[0];
167
-
if (!value) return "<blockquote></blockquote>";
168
-
let doc = new Y.Doc();
169
-
const update = base64.toByteArray(value.data.value);
170
-
Y.applyUpdate(doc, update);
171
-
let nodes = doc.getXmlElement("prosemirror").toArray();
172
-
//Have to handle this specially because it's a multi-line block
173
-
return `<blockquote>${nodes
174
-
.map((node) => {
175
-
if (node.constructor === Y.XmlElement) {
176
-
let children = node.toArray();
177
-
if (children.length === 0) return "<p></p>";
178
-
return renderToStaticMarkup(
179
-
<RenderYJSFragment
180
-
attrs={{
181
-
"data-alignment": alignment?.data.value,
182
-
}}
183
-
node={node}
184
-
/>,
185
-
);
186
-
}
187
-
})
188
-
.join("\n")}</blockquote>`;
189
-
}
190
let value = (await scanIndex(tx).eav(b.value, "block/text"))[0];
0
191
if (!value)
192
return ignoreWrapper ? "" : `<${wrapper || "p"}></${wrapper || "p"}>`;
193
let doc = new Y.Doc();
···
74
tx: ReadTransaction,
75
ignoreWrapper?: boolean,
76
) {
77
+
let wrapper: undefined | "h1" | "h2" | "h3" | "blockquote";
78
let [alignment] = await scanIndex(tx).eav(b.value, "block/text-alignment");
79
if (b.type === "horizontal-rule") {
80
return "<hr />";
···
125
</a>,
126
);
127
}
128
+
if (b.type === "blockquote") {
129
+
wrapper = "blockquote";
130
+
}
131
if (b.type === "heading") {
132
let headingLevel =
133
(await scanIndex(tx).eav(b.value, "block/heading-level"))[0]?.data
···
165
</div>,
166
);
167
}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
168
let value = (await scanIndex(tx).eav(b.value, "block/text"))[0];
169
+
console.log("getBlockasHTML", value);
170
if (!value)
171
return ignoreWrapper ? "" : `<${wrapper || "p"}></${wrapper || "p"}>`;
172
let doc = new Y.Doc();
+1
src/utils/isTextBlock.ts
···
5
} = {
6
text: true,
7
heading: true,
0
8
};
···
5
} = {
6
text: true,
7
heading: true,
8
+
blockquote: true,
9
};