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