Tweaked discord markdown

This commit is contained in:
Spectralitree
2020-12-09 22:40:14 +01:00
parent bc13cced34
commit 63eecd3f94
9 changed files with 484 additions and 22 deletions

40
src/404.html Normal file
View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Single Page Apps for GitHub Pages</title>
<script type="text/javascript">
// Single Page Apps for GitHub Pages
// MIT License
// https://github.com/rafgraph/spa-github-pages
// This script takes the current url and converts the path and query
// string into just a query string, and then redirects the browser
// to the new url with only a query string and hash fragment,
// e.g. https://www.foo.tld/one/two?a=b&c=d#qwe, becomes
// https://www.foo.tld/?/one/two&a=b~and~c=d#qwe
// Note: this 404.html file must be at least 512 bytes for it to work
// with Internet Explorer (it is currently > 512 bytes)
// If you're creating a Project Pages site and NOT using a custom domain,
// then set pathSegmentsToKeep to 1 (enterprise users may need to set it to > 1).
// This way the code will only replace the route part of the path, and not
// the real directory in which the app resides, for example:
// https://username.github.io/repo-name/one/two?a=b&c=d#qwe becomes
// https://username.github.io/repo-name/?/one/two&a=b~and~c=d#qwe
// Otherwise, leave pathSegmentsToKeep as 0.
var pathSegmentsToKeep = 1;
var l = window.location;
l.replace(
l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') +
l.pathname.split('/').slice(0, 1 + pathSegmentsToKeep).join('/') + '/?/' +
l.pathname.slice(1).split('/').slice(pathSegmentsToKeep).join('/').replace(/&/g, '~and~') +
(l.search ? '&' + l.search.slice(1).replace(/&/g, '~and~') : '') +
l.hash
);
</script>
</head>
<body>
</body>
</html>

View File

@@ -6,7 +6,7 @@ import { FaUser } from "react-icons/fa";
export default function MemberCard(props) {
const { toHTML } = require('discord-markdown');
const { toHTML } = require('../Functions/discord-parser.js');
const [ desc, setDesc ] = useState("");
const [ color, setColor ] = useState("");
@@ -33,7 +33,7 @@ export default function MemberCard(props) {
<BS.Image src={defaultAvatar} style={{width: 50, height: 50}} className="float-right" roundedCircle />}
</BS.Accordion.Toggle>
<BS.Accordion.Collapse eventKey={member.id}>
<BS.Card.Body style={{'border-left': `5px solid #${color}` }}>
<BS.Card.Body style={{borderLeft: `5px solid #${color}` }}>
<BS.Row>
<BS.Col className="mb-lg-3" xs={12} lg={3}><b>ID:</b> {member.id}</BS.Col>
{ member.display_name ? <BS.Col className="mb-lg-3" xs={12} lg={3}><b>AKA: </b>{member.display_name}</BS.Col> : "" }

View File

@@ -56,7 +56,7 @@ export default function Memberlist() {
<BS.Alert variant="danger">Error fetching members.</BS.Alert> :
<>
<BS.Row className="justify-content-md-center">
<BS.Col xs={12} md={4}>
<BS.Col xs={12} lg={4}>
<BS.Form inline>
<BS.Form.Control className="w-100" value={value} onChange={e => setValue(e.target.value)} placeholder="Search"/>
</BS.Form>

View File

@@ -17,11 +17,11 @@ export default function Navigation(props) {
}
return (
<BS.Navbar className="mb-3 d-flex align-items-center justify-content-between light" expand="md">
<BS.Navbar className="mb-3 align-items-center justify-content-between">
<BS.Navbar.Brand>
pk-web
</BS.Navbar.Brand>
<BS.Nav className="mr-md-2">
<BS.Nav className="mr-lg-2 d-flex align-items-center row">
<Toggle
defaultChecked={true}
icons={false}
@@ -29,7 +29,7 @@ export default function Navigation(props) {
{darkMode.value ? <FaMoon className="m-1"/> : <FaSun className="m-1"/>}
</BS.Nav>
<BS.Form inline>
{ localStorage.getItem('token') ? <BS.Button className=" mr-md-2" variant="primary" onClick={logOut}>Log Out</BS.Button> : "" }
{ localStorage.getItem('token') ? <BS.Button className=" mr-lg-2" variant="primary" onClick={logOut}>Log Out</BS.Button> : "" }
</BS.Form>
</BS.Navbar>
)

View File

@@ -6,7 +6,7 @@ import defaultAvatar from '../default_discord_avatar.png'
export default function System(props) {
const { toHTML } = require('discord-markdown');
const { toHTML } = require('../Functions/discord-parser.js');
const [ desc, setDesc ] = useState("");
const user = JSON.parse(localStorage.getItem("user"));

View File

@@ -1,3 +1,4 @@
.dark-mode {
background-color: $gray-800;
transition: background-color 0.3s ease;
@@ -6,17 +7,17 @@
.dark-mode .card {
color: $white;
background-color: $gray-800;
border: 1px solid $gray-900 !important;
border: 1px solid #191c1f !important;
transition: background-color 0.3s ease;
}
.dark-mode .card-body {
color: $white;
background-color: $gray-800;
transition: background-color 0.3s ease;
}
.dark-mode .card .card-header {
.dark-mode .card-header {
color: $gray-100;
background-color: $gray-900;
transition: background-color 0.3s ease;
@@ -36,25 +37,96 @@
color: $gray-100 !important;
}
.dark-mode .form-control {
color: $gray-100;
border-color: #000000;
background-color: $gray-900;
transition: background-color 0.3s ease;
}
.dark-mode .form-control:focus {
.dark-mode .card .form-control {
color: $gray-200;
border-color: #000000;
border-color: #191c1f;
background-color: $gray-900;
transition: background-color 0.3s ease;
}
.dark-mode .alert-danger {
color: $white;
background-color: $red;
background-color: $danger;
border: $gray-900;
transition: background-color 0.3s ease;
}
code {
color: black !important;
background-color: $gray-300;
padding: 5px 7px;
border-radius: 3px;
margin: 3px;
display: inline-block;
}
.dark-mode code{
color: white !important;
background-color: $gray-900;
transition: background-color 0.3s ease;
}
pre {
margin: 2px !important;
}
code.hljs {
display: block;
border: 1px solid rgba(0, 0, 0, 0.125);
margin: 3px 3px 7px 3px;
}
.d-spoiler {
color: $gray-300 !important;
background-color: $gray-300;
padding: 3px 5px;
border-radius: 3px;
margin: 3px;
display: inline-block;
transition-delay: 6000s;
}
.d-spoiler::selection {
color: $gray-300;
}
.dark-mode .d-spoiler {
color: $gray-900 !important;
background-color: $gray-900;
padding: 3px 5px;
border-radius: 3px;
margin: 3px;
display: inline-block;
transition-delay: 6000s;
}
.dark-mode .d-spoiler::selection {
color: $gray-900;
}
.d-spoiler:active {
color: $gray-900 !important;
background-color: $gray-200;
transition-delay: 0s;
transition: background-color 0.2s ease;
}
.dark-mode .d-spoiler:active {
color: $gray-200 !important;
background-color: $gray-700;
transition-delay: 0s;
transition: background-color 0.2s ease;
}
blockquote {
border-left: 6px solid $gray-200;
padding: 2px 5px 2px 15px;
margin: 7px !important;
margin-left: 9px !important;
}
.dark-mode blockquote {
border-left: 6px solid $gray-700;
}
@import "~bootstrap/scss/bootstrap";

View File

@@ -0,0 +1,323 @@
const markdown = require('simple-markdown');
const highlight = require('highlight.js');
function htmlTag(tagName, content, attributes, isClosed = true, state = { }) {
if (typeof isClosed === 'object') {
state = isClosed;
isClosed = true;
}
if (!attributes)
attributes = { };
if (attributes.class && state.cssModuleNames)
attributes.class = attributes.class.split(' ').map(cl => state.cssModuleNames[cl] || cl).join(' ');
let attributeString = '';
for (let attr in attributes) {
// Removes falsy attributes
if (Object.prototype.hasOwnProperty.call(attributes, attr) && attributes[attr])
attributeString += ` ${markdown.sanitizeText(attr)}="${markdown.sanitizeText(attributes[attr])}"`;
}
let unclosedTag = `<${tagName}${attributeString}>`;
if (isClosed)
return unclosedTag + content + `</${tagName}>`;
return unclosedTag;
}
markdown.htmlTag = htmlTag;
const rules = {
blockQuote: Object.assign({ }, markdown.defaultRules.blockQuote, {
match: function(source, state, prevSource) {
return !/^$|\n *$/.test(prevSource) || state.inQuote ? null : /^( *>>> ([\s\S]*))|^( *> [^\n]*(\n *> [^\n]*)*\n?)/.exec(source);
},
parse: function(capture, parse, state) {
const all = capture[0];
const isBlock = Boolean(/^ *>>> ?/.exec(all));
const removeSyntaxRegex = isBlock ? /^ *>>> ?/ : /^ *> ?/gm;
const content = all.replace(removeSyntaxRegex, '');
return {
content: parse(content, Object.assign({ }, state, { inQuote: true })),
type: 'blockQuote'
}
}
}),
codeBlock: Object.assign({ }, markdown.defaultRules.codeBlock, {
match: markdown.inlineRegex(/^```(([a-z0-9-]+?)\n+)?\n*([^]+?)\n*```/i),
parse: function(capture, parse, state) {
return {
lang: (capture[2] || '').trim(),
content: capture[3] || '',
inQuote: state.inQuote || false
};
},
html: (node, output, state) => {
let code;
if (node.lang && highlight.getLanguage(node.lang))
code = highlight.highlight(node.lang, node.content, true); // Discord seems to set ignoreIllegals: true
if (code && state.cssModuleNames) // Replace classes in hljs output
code.value = code.value.replace(/<span class="([a-z0-9-_ ]+)">/gi, (str, m) =>
str.replace(m, m.split(' ').map(cl => state.cssModuleNames[cl] || cl).join(' ')));
return htmlTag('pre', htmlTag(
'code', code ? code.value : markdown.sanitizeText(node.content), { class: `hljs${code ? ' ' + code.language : ''}` }, state
), null, state);
}
}),
newline: markdown.defaultRules.newline,
escape: markdown.defaultRules.escape,
autolink: Object.assign({ }, markdown.defaultRules.autolink, {
parse: capture => {
return {
content: [{
type: 'text',
content: capture[1]
}],
target: capture[1]
};
},
html: (node, output, state) => {
return htmlTag('a', output(node.content, state), { href: markdown.sanitizeUrl(node.target) }, state);
}
}),
url: Object.assign({ }, markdown.defaultRules.url, {
parse: capture => {
return {
content: [{
type: 'text',
content: capture[1]
}],
target: capture[1]
}
},
html: (node, output, state) => {
return htmlTag('a', output(node.content, state), { href: markdown.sanitizeUrl(node.target) }, state);
}
}),
em: Object.assign({ }, markdown.defaultRules.em, {
parse: function(capture, parse, state) {
const parsed = markdown.defaultRules.em.parse(capture, parse, Object.assign({ }, state, { inEmphasis: true }));
return state.inEmphasis ? parsed.content : parsed;
},
}),
strong: markdown.defaultRules.strong,
u: markdown.defaultRules.u,
strike: Object.assign({ }, markdown.defaultRules.del, {
match: markdown.inlineRegex(/^~~([\s\S]+?)~~(?!_)/),
}),
inlineCode: Object.assign({ }, markdown.defaultRules.inlineCode, {
match: source => markdown.defaultRules.inlineCode.match.regex.exec(source),
html: function(node, output, state) {
return htmlTag('code', markdown.sanitizeText(node.content.trim()), null, state);
}
}),
text: Object.assign({ }, markdown.defaultRules.text, {
match: source => /^[\s\S]+?(?=[^0-9A-Za-z\s\u00c0-\uffff-]|\n\n|\n|\w+:\S|$)/.exec(source),
html: function(node, output, state) {
if (state.escapeHTML)
return markdown.sanitizeText(node.content);
return node.content;
}
}),
emoticon: {
order: markdown.defaultRules.text.order,
match: source => /^(¯\\_\(\)_\/¯)/.exec(source),
parse: function(capture) {
return {
type: 'text',
content: capture[1]
};
},
html: function(node, output, state) {
return output(node.content, state);
},
},
br: Object.assign({ }, markdown.defaultRules.br, {
match: markdown.anyScopeRegex(/^\n/),
}),
spoiler: {
order: 0,
match: source => /^\|\|([\s\S]+?)\|\|/.exec(source),
parse: function(capture, parse, state) {
return {
content: parse(capture[1], state)
};
},
html: function(node, output, state) {
return htmlTag('a', output(node.content, state), { class: 'd-spoiler' }, state);
}
},
link: markdown.defaultRules.link
};
const discordCallbackDefaults = {
user: node => '@' + markdown.sanitizeText(node.id),
channel: node => '#' + markdown.sanitizeText(node.id),
role: node => '&' + markdown.sanitizeText(node.id),
everyone: () => '@everyone',
here: () => '@here'
};
const rulesDiscord = {
discordUser: {
order: markdown.defaultRules.strong.order,
match: source => /^<@!?([0-9]*)>/.exec(source),
parse: function(capture) {
return {
id: capture[1]
};
},
html: function(node, output, state) {
return htmlTag('span', state.discordCallback.user(node), { class: 'd-mention d-user' }, state);
}
},
discordChannel: {
order: markdown.defaultRules.strong.order,
match: source => /^<#?([0-9]*)>/.exec(source),
parse: function(capture) {
return {
id: capture[1]
};
},
html: function(node, output, state) {
return htmlTag('span', state.discordCallback.channel(node), { class: 'd-mention d-channel' }, state);
}
},
discordRole: {
order: markdown.defaultRules.strong.order,
match: source => /^<@&([0-9]*)>/.exec(source),
parse: function(capture) {
return {
id: capture[1]
};
},
html: function(node, output, state) {
return htmlTag('span', state.discordCallback.role(node), { class: 'd-mention d-role' }, state);
}
},
discordEmoji: {
order: markdown.defaultRules.strong.order,
match: source => /^<(a?):(\w+):(\d+)>/.exec(source),
parse: function(capture) {
return {
animated: capture[1] === "a",
name: capture[2],
id: capture[3],
};
},
html: function(node, output, state) {
return htmlTag('img', '', {
class: `d-emoji${node.animated ? ' d-emoji-animated' : ''}`,
src: `https://cdn.discordapp.com/emojis/${node.id}.${node.animated ? 'gif' : 'png'}`,
alt: `:${node.name}:`
}, false, state);
}
},
discordEveryone: {
order: markdown.defaultRules.strong.order,
match: source => /^@everyone/.exec(source),
parse: function() {
return { };
},
html: function(node, output, state) {
return htmlTag('span', state.discordCallback.everyone(node), { class: 'd-mention d-user' }, state);
}
},
discordHere: {
order: markdown.defaultRules.strong.order,
match: source => /^@here/.exec(source),
parse: function() {
return { };
},
html: function(node, output, state) {
return htmlTag('span', state.discordCallback.here(node), { class: 'd-mention d-user' }, state);
}
}
};
Object.assign(rules, rulesDiscord);
const rulesDiscordOnly = Object.assign({ }, rulesDiscord, {
text: Object.assign({ }, markdown.defaultRules.text, {
match: source => /^[\s\S]+?(?=[^0-9A-Za-z\s\u00c0-\uffff-]|\n\n|\n|\w+:\S|$)/.exec(source),
html: function(node, output, state) {
if (state.escapeHTML)
return markdown.sanitizeText(node.content);
return node.content;
}
})
});
const rulesEmbed = Object.assign({ }, rules, {
link: markdown.defaultRules.link
});
const parser = markdown.parserFor(rules);
const htmlOutput = markdown.htmlFor(markdown.ruleOutput(rules, 'html'));
const parserDiscord = markdown.parserFor(rulesDiscordOnly);
const htmlOutputDiscord = markdown.htmlFor(markdown.ruleOutput(rulesDiscordOnly, 'html'));
const parserEmbed = markdown.parserFor(rulesEmbed);
const htmlOutputEmbed = markdown.htmlFor(markdown.ruleOutput(rulesEmbed, 'html'));
/**
* Parse markdown and return the HTML output
* @param {String} source Source markdown content
* @param {Object} [options] Options for the parser
* @param {Boolean} [options.embed=false] Parse as embed content
* @param {Boolean} [options.escapeHTML=true] Escape HTML in the output
* @param {Boolean} [options.discordOnly=false] Only parse Discord-specific stuff (such as mentions)
* @param {Object} [options.discordCallback] Provide custom handling for mentions and emojis
* @param {Object} [options.cssModuleNames] An object mapping css classes to css module classes
*/
function toHTML(source, options, customParser, customHtmlOutput) {
if ((customParser || customHtmlOutput) && (!customParser || !customHtmlOutput))
throw new Error('You must pass both a custom parser and custom htmlOutput function, not just one');
options = Object.assign({
embed: false,
escapeHTML: true,
discordOnly: false,
discordCallback: { }
}, options || { });
let _parser = parser;
let _htmlOutput = htmlOutput;
if (customParser) {
_parser = customParser;
_htmlOutput = customHtmlOutput;
} else if (options.discordOnly) {
_parser = parserDiscord;
_htmlOutput = htmlOutputDiscord;
} else if (options.embed) {
_parser = parserEmbed;
_htmlOutput = htmlOutputEmbed;
}
const state = {
inline: true,
inQuote: false,
inEmphasis: false,
escapeHTML: options.escapeHTML,
cssModuleNames: options.cssModuleNames || null,
discordCallback: Object.assign({ }, discordCallbackDefaults, options.discordCallback)
};
return _htmlOutput(_parser(source, state), state);
}
module.exports = {
parser: source => parser(source, { inline: true }),
htmlOutput,
toHTML,
rules,
rulesDiscordOnly,
rulesEmbed,
markdownEngine: markdown,
htmlTag
};