"matrix-js-sdk": "^15.4.0",
"micromark": "^3.0.3",
"micromark-extension-gfm": "^1.0.0",
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-resolve-all": "^1.0.0",
+ "micromark-util-symbol": "^1.0.1",
"prop-types": "^15.8.1",
"react": "^17.0.2",
"react-autosize-textarea": "^7.1.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
"integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==",
"dev": true,
+ "optional": true,
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true
+ "dev": true,
+ "optional": true,
+ "peer": true
},
"node_modules/ajv-keywords": {
"version": "3.5.2",
}
},
"node_modules/micromark-util-symbol": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.0.tgz",
- "integrity": "sha512-NZA01jHRNCt4KlOROn8/bGi6vvpEmlXld7EHcRH+aYWUfL3Wc8JLUNNlqUMKa0hhz6GrpUWsHtzPmKof57v0gQ==",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz",
+ "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==",
"funding": [
{
"type": "GitHub Sponsors",
"type": "OpenCollective",
"url": "https://opencollective.com/unified"
}
- ]
+ ],
+ "license": "MIT"
},
"node_modules/micromark-util-types": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"dev": true,
- "requires": {
- "ajv": "^8.0.0"
- },
+ "requires": {},
"dependencies": {
"ajv": {
- "version": "8.9.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
+ "version": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
"integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==",
"dev": true,
+ "optional": true,
+ "peer": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true
+ "dev": true,
+ "optional": true,
+ "peer": true
}
}
},
}
},
"micromark-util-symbol": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.0.tgz",
- "integrity": "sha512-NZA01jHRNCt4KlOROn8/bGi6vvpEmlXld7EHcRH+aYWUfL3Wc8JLUNNlqUMKa0hhz6GrpUWsHtzPmKof57v0gQ=="
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz",
+ "integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ=="
},
"micromark-util-types": {
"version": "1.0.1",
"matrix-js-sdk": "^15.4.0",
"micromark": "^3.0.3",
"micromark-extension-gfm": "^1.0.0",
+ "micromark-util-chunked": "^1.0.0",
+ "micromark-util-resolve-all": "^1.0.0",
+ "micromark-util-symbol": "^1.0.1",
"prop-types": "^15.8.1",
"react": "^17.0.2",
"react-autosize-textarea": "^7.1.0",
import { gfm, gfmHtml } from 'micromark-extension-gfm';
import encrypt from 'browser-encrypt-attachment';
import { getShortcodeToEmoji } from '../../app/organisms/emoji-board/custom-emoji';
+import { spoilerExtension, spoilerExtensionHtml } from '../../util/markdown';
import cons from './cons';
import settings from './settings';
function getFormattedBody(markdown) {
const result = micromark(markdown, {
- extensions: [gfm()],
- htmlExtensions: [gfmHtml],
+ extensions: [gfm(), spoilerExtension()],
+ htmlExtensions: [gfmHtml, spoilerExtensionHtml],
});
const bodyParts = result.match(/^(<p>)(.*)(<\/p>)$/);
if (bodyParts === null) return result;
// Apply formatting if relevant
const formattedBody = formatAndEmojifyText(
this.matrixClient.getRoom(roomId),
- editedBody
+ editedBody,
);
if (formattedBody !== editedBody) {
content.formatted_body = ` * ${formattedBody}`;
--- /dev/null
+/* eslint-disable no-param-reassign */
+/* eslint-disable no-plusplus */
+/* eslint-disable no-continue */
+
+import { codes } from 'micromark-util-symbol/codes';
+import { types } from 'micromark-util-symbol/types';
+import { resolveAll } from 'micromark-util-resolve-all';
+import { splice } from 'micromark-util-chunked';
+
+function inlineExtension(marker, len, key) {
+ const keySeq = `${key}Sequence`;
+ const keySeqTmp = `${keySeq}Temporary`;
+
+ return () => {
+ function tokenize(effects, ok, nok) {
+ const { previous, events } = this;
+
+ let size = 0;
+
+ function more(code) {
+ // consume more markers if the maximum length hasn't been reached yet
+ if (code === marker && size < len) {
+ effects.consume(code);
+ size += 1;
+ return more;
+ }
+
+ // check for minimum length
+ if (size < len) return nok(code);
+
+ effects.exit(keySeqTmp);
+ return ok(code);
+ }
+
+ function start(code) {
+ // ignore code if it's not a marker
+ if (code !== marker) return nok(code);
+
+ if (previous === marker
+ && events[events.length - 1][1].type !== types.characterEscape) return nok(code);
+
+ effects.enter(keySeqTmp);
+ return more(code);
+ }
+
+ return start;
+ }
+
+ function resolve(events, context) {
+ let i = -1;
+
+ while (++i < events.length) {
+ if (events[i][0] !== 'enter' || events[i][1].type !== keySeqTmp) continue;
+
+ let open = i;
+ while (open--) {
+ if (events[open][0] !== 'exit' || events[open][1].type !== keySeqTmp) continue;
+
+ events[i][1].type = keySeq;
+ events[open][1].type = keySeq;
+
+ const border = {
+ type: key,
+ start: { ...events[open][1].start },
+ end: { ...events[i][1].end },
+ };
+
+ const text = {
+ type: `${key}Text`,
+ start: { ...events[open][1].end },
+ end: { ...events[i][1].start },
+ };
+
+ const nextEvents = [
+ ['enter', border, context],
+ ['enter', events[open][1], context],
+ ['exit', events[open][1], context],
+ ['enter', text, context],
+ ];
+
+ splice(
+ nextEvents,
+ nextEvents.length,
+ 0,
+ resolveAll(
+ context.parser.constructs.insideSpan.null,
+ events.slice(open + 1, i),
+ context,
+ ),
+ );
+
+ splice(nextEvents, nextEvents.length, 0, [
+ ['exit', text, context],
+ ['enter', events[i][1], context],
+ ['exit', events[i][1], context],
+ ['exit', border, context],
+ ]);
+
+ splice(events, open - 1, i - open + 3, nextEvents);
+
+ i = open + nextEvents.length - 2;
+ break;
+ }
+ }
+
+ events.forEach((event) => {
+ if (event[1].type === keySeqTmp) {
+ event[1].type = types.data;
+ }
+ });
+
+ return events;
+ }
+
+ const tokenizer = {
+ tokenize,
+ resolveAll: resolve,
+ };
+
+ return {
+ text: { [marker]: tokenizer },
+ insideSpan: { null: [tokenizer] },
+ attentionMarkers: { null: [marker] },
+ };
+ };
+}
+
+const spoilerExtension = inlineExtension(codes.verticalBar, 2, 'spoiler');
+
+const spoilerExtensionHtml = {
+ enter: {
+ spoiler() {
+ this.tag('<span data-mx-spoiler>');
+ },
+ },
+ exit: {
+ spoiler() {
+ this.tag('</span>');
+ },
+ },
+};
+
+export { inlineExtension, spoilerExtension, spoilerExtensionHtml };