Add support for sending spoiler markdown (#267)
* Basic spoiler markdown plugin * Remove console.log statement
This commit is contained in:
parent
5f7fa0557f
commit
0e8219b200
4 changed files with 173 additions and 17 deletions
37
package-lock.json
generated
37
package-lock.json
generated
|
@ -23,6 +23,9 @@
|
||||||
"matrix-js-sdk": "^15.4.0",
|
"matrix-js-sdk": "^15.4.0",
|
||||||
"micromark": "^3.0.3",
|
"micromark": "^3.0.3",
|
||||||
"micromark-extension-gfm": "^1.0.0",
|
"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",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-autosize-textarea": "^7.1.0",
|
"react-autosize-textarea": "^7.1.0",
|
||||||
|
@ -3003,6 +3006,8 @@
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
|
||||||
"integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==",
|
"integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"json-schema-traverse": "^1.0.0",
|
"json-schema-traverse": "^1.0.0",
|
||||||
|
@ -3018,7 +3023,9 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/ajv-keywords": {
|
"node_modules/ajv-keywords": {
|
||||||
"version": "3.5.2",
|
"version": "3.5.2",
|
||||||
|
@ -9989,9 +9996,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/micromark-util-symbol": {
|
"node_modules/micromark-util-symbol": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz",
|
||||||
"integrity": "sha512-NZA01jHRNCt4KlOROn8/bGi6vvpEmlXld7EHcRH+aYWUfL3Wc8JLUNNlqUMKa0hhz6GrpUWsHtzPmKof57v0gQ==",
|
"integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "GitHub Sponsors",
|
"type": "GitHub Sponsors",
|
||||||
|
@ -10001,7 +10008,8 @@
|
||||||
"type": "OpenCollective",
|
"type": "OpenCollective",
|
||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/micromark-util-types": {
|
"node_modules/micromark-util-types": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
@ -18336,15 +18344,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
|
||||||
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
|
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {},
|
||||||
"ajv": "^8.0.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "8.9.0",
|
"version": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz",
|
|
||||||
"integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==",
|
"integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"json-schema-traverse": "^1.0.0",
|
"json-schema-traverse": "^1.0.0",
|
||||||
|
@ -18356,7 +18363,9 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -23789,9 +23798,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"micromark-util-symbol": {
|
"micromark-util-symbol": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz",
|
||||||
"integrity": "sha512-NZA01jHRNCt4KlOROn8/bGi6vvpEmlXld7EHcRH+aYWUfL3Wc8JLUNNlqUMKa0hhz6GrpUWsHtzPmKof57v0gQ=="
|
"integrity": "sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ=="
|
||||||
},
|
},
|
||||||
"micromark-util-types": {
|
"micromark-util-types": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
|
|
@ -29,6 +29,9 @@
|
||||||
"matrix-js-sdk": "^15.4.0",
|
"matrix-js-sdk": "^15.4.0",
|
||||||
"micromark": "^3.0.3",
|
"micromark": "^3.0.3",
|
||||||
"micromark-extension-gfm": "^1.0.0",
|
"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",
|
"prop-types": "^15.8.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-autosize-textarea": "^7.1.0",
|
"react-autosize-textarea": "^7.1.0",
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { micromark } from 'micromark';
|
||||||
import { gfm, gfmHtml } from 'micromark-extension-gfm';
|
import { gfm, gfmHtml } from 'micromark-extension-gfm';
|
||||||
import encrypt from 'browser-encrypt-attachment';
|
import encrypt from 'browser-encrypt-attachment';
|
||||||
import { getShortcodeToEmoji } from '../../app/organisms/emoji-board/custom-emoji';
|
import { getShortcodeToEmoji } from '../../app/organisms/emoji-board/custom-emoji';
|
||||||
|
import { spoilerExtension, spoilerExtensionHtml } from '../../util/markdown';
|
||||||
import cons from './cons';
|
import cons from './cons';
|
||||||
import settings from './settings';
|
import settings from './settings';
|
||||||
|
|
||||||
|
@ -84,8 +85,8 @@ function getVideoThumbnail(video, width, height, mimeType) {
|
||||||
|
|
||||||
function getFormattedBody(markdown) {
|
function getFormattedBody(markdown) {
|
||||||
const result = micromark(markdown, {
|
const result = micromark(markdown, {
|
||||||
extensions: [gfm()],
|
extensions: [gfm(), spoilerExtension()],
|
||||||
htmlExtensions: [gfmHtml],
|
htmlExtensions: [gfmHtml, spoilerExtensionHtml],
|
||||||
});
|
});
|
||||||
const bodyParts = result.match(/^(<p>)(.*)(<\/p>)$/);
|
const bodyParts = result.match(/^(<p>)(.*)(<\/p>)$/);
|
||||||
if (bodyParts === null) return result;
|
if (bodyParts === null) return result;
|
||||||
|
@ -406,7 +407,7 @@ class RoomsInput extends EventEmitter {
|
||||||
// Apply formatting if relevant
|
// Apply formatting if relevant
|
||||||
const formattedBody = formatAndEmojifyText(
|
const formattedBody = formatAndEmojifyText(
|
||||||
this.matrixClient.getRoom(roomId),
|
this.matrixClient.getRoom(roomId),
|
||||||
editedBody
|
editedBody,
|
||||||
);
|
);
|
||||||
if (formattedBody !== editedBody) {
|
if (formattedBody !== editedBody) {
|
||||||
content.formatted_body = ` * ${formattedBody}`;
|
content.formatted_body = ` * ${formattedBody}`;
|
||||||
|
|
143
src/util/markdown.js
Normal file
143
src/util/markdown.js
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
/* 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 };
|
Loading…
Reference in a new issue