aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2022-03-16 21:49:49 -0400
committerBobby <[email protected]>2022-03-16 21:49:49 -0400
commit339e86edb5c8ad729cf0f4cfac3c3cde081d374e (patch)
tree1258e217559533695e6d04608f004f9535115bea
parentb8e33bf3f0e5df9c5c9b5e35d98aa446e5203d87 (diff)
downloadluciferreeves.github.io-339e86edb5c8ad729cf0f4cfac3c3cde081d374e.tar.xz
luciferreeves.github.io-339e86edb5c8ad729cf0f4cfac3c3cde081d374e.zip
Function to create posts
-rw-r--r--public/views/admin.html5
-rw-r--r--public/views/createPost.html185
-rw-r--r--public/views/dashboard.html32
-rw-r--r--routes/admin.js4
-rw-r--r--routes/posts.js17
-rw-r--r--static/assets/css/custom.css1
-rw-r--r--static/assets/js/marked.js1401
-rw-r--r--static/assets/js/pages/admin.js10
-rw-r--r--static/assets/js/pages/authCheck.js10
-rw-r--r--static/assets/js/pages/config.js10
10 files changed, 1639 insertions, 36 deletions
diff --git a/public/views/admin.html b/public/views/admin.html
index 2e9e485..489a736 100644
--- a/public/views/admin.html
+++ b/public/views/admin.html
@@ -95,8 +95,9 @@
<script src="/static/assets/js/jquery.js"></script>
<script src="/static/assets/js/bootstrap-transition.js"></script>
<script src="/static/assets/js/bootstrap-collapse.js"></script>
- <script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-app.js" defer></script>
- <script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-auth.js" defer></script>
+ <script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-app.js"></script>
+ <script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-auth.js"></script>
+ <script src="/static/assets/js/pages/config.js"></script>
<script src="/static/assets/js/pages/admin.js"></script>
</body>
diff --git a/public/views/createPost.html b/public/views/createPost.html
new file mode 100644
index 0000000..e01ad55
--- /dev/null
+++ b/public/views/createPost.html
@@ -0,0 +1,185 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+ <meta charset="UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <link href="/static/assets/css/bootstrap.css" rel="stylesheet">
+ <link href="/static/assets/css/bootstrap-responsive.css" rel="stylesheet">
+ <link rel="stylesheet"
+ href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/styles/base16/seti-ui.min.css">
+ <link rel="preconnect" href="https://fonts.googleapis.com">
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+ <link href="https://fonts.googleapis.com/css2?family=Fira+Code&display=swap" rel="stylesheet">
+ <link href="/static/assets/css/custom.css" rel="stylesheet">
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css"
+ integrity="sha384-KiWOvVjnN8qwAZbuQyWDIbfCLFhLXNETzBQjA/92pIowpC0d2O3nppDGQVgwd2nB" crossorigin="anonymous">
+ <link rel="shortcut icon" href="/static/images/favicon.png">
+ <title>Dashboard - Add New Post</title>
+ <style type="text/css">
+ input,
+ textarea {
+ width: 100%;
+ }
+
+ code,
+ pre {
+ font-family: 'Fira Code', monospace !important;
+ font-size: 14px;
+ background-color: #1c1c1c;
+ color: #D4D4D5;
+ }
+
+ pre {
+ padding: 10px;
+ }
+ </style>
+</head>
+
+<body>
+ <div class="navbar navbar-inverse navbar-fixed-top">
+ <div class="navbar-inner">
+ <div class="container">
+ <button type="button" class="btn btn-navbar" data-toggle="collapse"
+ data-target=".nav-collapse"></button>
+ <a class="brand" href="../">That Computer Scientist</a>
+ <div class="nav-collapse collapse">
+ <ul class="nav">
+ <li><a href="../">Home</a></li>
+ <li><a href="../about">About</a></li>
+ <li><a href="../repos">Repositories</a></li>
+ <li class="active"><a href="./">Administration</a></li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="container margin-top-375" style="margin-top: 3.75rem;">
+ <div class="container">
+ <div class="row">
+ <div class="span3 bs-docs-sidebar">
+ <ul class="nav nav-list bs-docs-sidenav affix-top">
+ <li><a href="../admin/dashboard">All Posts</a></li>
+ <li class="active"><a href="/admin/dashboard/new">Add Post</a></li>
+ <li><a href="#">Manage Comments</a></li>
+ <li><a href="#">Manage Users</a></li>
+ <li><a id="logout">Logout</a></li>
+ </ul>
+ </div>
+ <div class="span9">
+ <header class="page-header">
+ <h1>Create New Post</h1>
+ </header>
+ <div class="span9" style="padding: 0; margin: 0;">
+ <div id="createPost">
+ <div class="form-group">
+ <label for="title">Title</label>
+ <input type="text" class="form-control" id="title" name="title" placeholder="Title"
+ style=" margin-left: 0;">
+ </div>
+ <div class="form-group">
+ <label for="content">Content</label>
+ <textarea class="form-control" id="content" name="content" rows="10"
+ placeholder="Content"></textarea>
+ </div>
+ <div class="container">
+ <h1 style="margin-left: 0; padding-left: 0; transform: translate(-20px, 0px);">Render
+ Preview</h1>
+ <div class="span9" id="renderPreview"
+ style="padding: 10px 0px; margin: 20px 0px 20px -20px"></div>
+ </div>
+ <div class="form-group">
+ <label for="tags">Tags</label>
+ <input type="text" class="form-control" id="tags" name="tags" placeholder="Tags"
+ style=" margin-left: 0;">
+ </div>
+ <div class="form-group">
+ <label for="publishDate">Publish Date</label>
+ <input type="date" class="form-control" id="publishDate" name="publishDate"
+ placeholder="Publish Date" style=" margin-left: 0;">
+ </div>
+ <div id="error" class="alert alert-error hidden">
+ <strong>Error!</strong> Please fill out all fields.
+ </div>
+ <button class="btn btn-primary" id="publishPost">Create New Post</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-app.js"></script>
+ <script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-auth.js"></script>
+ <script src="/static/assets/js/jquery.js"></script>
+ <script src="/static/assets/js/bootstrap-transition.js"></script>
+ <script src="/static/assets/js/bootstrap-collapse.js"></script>
+ <script src="/static/assets/js/pages/config.js"></script>
+ <script src="/static/assets/js/pages/authCheck.js"></script>
+ <script src="/static/assets/js/marked.js"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/highlight.min.js"></script>
+ <script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js"
+ integrity="sha384-0fdwu/T/EQMsQlrHCCHoH10pkPLlKA1jL5dFyUOvB3lfeT2540/2g6YgSi2BL14p"
+ crossorigin="anonymous"></script>
+ <script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/contrib/auto-render.min.js"
+ integrity="sha384-+XBljXPPiv+OzfbB3cVmLHf4hdUFHlWNZN5spNQ7rmHTXpd7WvJum6fIACpNNfIR"
+ crossorigin="anonymous"></script>
+ <script>
+ window.addEventListener("DOMContentLoaded", () => {
+ const content = document.getElementById('content');
+ const renderPreview = document.getElementById('renderPreview');
+ marked.setOptions({
+ highlight: function (code) {
+ return hljs.highlightAuto(code).value;
+ }
+ });
+ content.addEventListener('input', () => {
+ renderPreview.innerHTML = marked.parse(content.value);
+ renderMathInElement(renderPreview, {
+ // customised options
+ // • auto-render specific keys, e.g.:
+ delimiters: [
+ { left: '$$', right: '$$', display: true },
+ { left: '$', right: '$', display: false },
+ { left: '\\(', right: '\\)', display: false },
+ { left: '\\[', right: '\\]', display: true }
+ ],
+ // • rendering keys, e.g.:
+ throwOnError: false
+ });
+ });
+ });
+ $(document).on('click', '#publishPost', () => {
+ document.getElementById('error').classList.add('hidden');
+ const htmlRender = $('#renderPreview').html();
+ const title = $('#title').val();
+ const publishDate = $('#publishDate').val();
+ const tags = $('#tags').val();
+ if (title === '' || publishDate === '') {
+ document.getElementById('error').classList.remove('hidden');
+ return;
+ } else {
+ // Publish post to api/blog/new
+ const body = {
+ title: title,
+ publishDate: publishDate,
+ tags: tags,
+ content: htmlRender
+ };
+ $.ajax({
+ url: '/api/blog/new',
+ type: 'POST',
+ data: body,
+ success: (data) => {
+ window.location.href = '/admin/dashboard';
+ },
+ error: (err) => {
+ console.log(err);
+ }
+ });
+ }
+ });
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/public/views/dashboard.html b/public/views/dashboard.html
index 58ac028..3791678 100644
--- a/public/views/dashboard.html
+++ b/public/views/dashboard.html
@@ -9,7 +9,7 @@
<link href="/static/assets/css/bootstrap-responsive.css" rel="stylesheet">
<link href="/static/assets/css/custom.css" rel="stylesheet">
<link rel="shortcut icon" href="/static/images/favicon.png">
- <title>Document</title>
+ <title>Dashboard - All Posts</title>
</head>
<body>
@@ -36,7 +36,7 @@
<div class="span3 bs-docs-sidebar">
<ul class="nav nav-list bs-docs-sidenav affix-top">
<li class="active"><a href="../admin/dashboard">All Posts</a></li>
- <li><a href="#">Add Post</a></li>
+ <li><a href="/admin/dashboard/new">Add Post</a></li>
<li><a href="#">Manage Comments</a></li>
<li><a href="#">Manage Users</a></li>
<li><a id="logout">Logout</a></li>
@@ -45,6 +45,7 @@
<div class="span9">
<header class="page-header">
<h1>All Posts</h1>
+ <div id="posts"></div>
</header>
</div>
</div>
@@ -52,28 +53,11 @@
</div>
<script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-auth.js"></script>
- <script>
- const firebaseConfig = {
- apiKey: "AIzaSyBCmKUnEmm8hLR9ZcFWPYbYiplbP6yUzfU",
- authDomain: "thatcomputerscientist-e9cf2.firebaseapp.com",
- projectId: "thatcomputerscientist-e9cf2",
- storageBucket: "thatcomputerscientist-e9cf2.appspot.com",
- messagingSenderId: "178402875544",
- appId: "1:178402875544:web:8c9d8880d3ef495a5658ed",
- measurementId: "G-JECWWZG5J6"
- };
- firebase.initializeApp(firebaseConfig);
- firebase.auth().onAuthStateChanged((user) => {
- if (!user) {
- window.location.assign("/admin/");
- }
- });
- document.getElementById("logout").addEventListener("click", () => {
- firebase.auth().signOut().then(() => {
- window.location.assign("/admin/");
- });
- });
- </script>
+ <script src="/static/assets/js/jquery.js"></script>
+ <script src="/static/assets/js/bootstrap-transition.js"></script>
+ <script src="/static/assets/js/bootstrap-collapse.js"></script>
+ <script src="/static/assets/js/pages/config.js"></script>
+ <script src="/static/assets/js/pages/authCheck.js"></script>
</body>
</html> \ No newline at end of file
diff --git a/routes/admin.js b/routes/admin.js
index d7b79a5..b6afab7 100644
--- a/routes/admin.js
+++ b/routes/admin.js
@@ -5,6 +5,10 @@ router.get("/dashboard", function (req, res) {
res.render("dashboard.html");
});
+router.get("/dashboard/new", function (req, res) {
+ res.render("createPost.html");
+})
+
router.get("/", (req, res) => {
// Send admin.html from public folder
res.render("admin.html");
diff --git a/routes/posts.js b/routes/posts.js
index e862680..d81327f 100644
--- a/routes/posts.js
+++ b/routes/posts.js
@@ -20,4 +20,21 @@ router.get("/posts", (req, res) => {
});
});
+router.post("/new", (req, res) => {
+ const { title, content, tags, publishDate } = req.body;
+ const store = firebase.firestore();
+ const post = {
+ title,
+ content,
+ tags: String(tags).split(",").length > 0 ? String(tags).split(",") : [],
+ publishDate,
+ };
+ let query = store.collection("posts");
+ query.add(post).then(() => {
+ res.json({ success: true });
+ }).catch((err) => {
+ res.json({ success: false, err });
+ });
+});
+
module.exports = router;
diff --git a/static/assets/css/custom.css b/static/assets/css/custom.css
index 446d894..709d6b0 100644
--- a/static/assets/css/custom.css
+++ b/static/assets/css/custom.css
@@ -7,6 +7,7 @@ button {
cursor: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAzElEQVRYR+2X0Q6AIAhF5f8/2jYXZkwEjNSVvVUjDpcrGgT7FUkI2D9xRfQETwNIiWO85wfINfQUEyxBG2ArsLwC0jioGt5zFcwF4OYDPi/mBYKm4t0U8ATgRm3ThFoAqkhNgWkA0jJLvaOVSs7j3qMnSgXWBMiWPXe94QqMBMBc1VZIvaTu5u5pQewq0EqNZvIEMCmxAawK0DNkay9QmfFNAJUXfgGgUkLaE7j/h8fnASkxHTz0DGIBMCnBeeM7AArpUd3mz2x3C7wADglA8BcWMZhZAAAAAElFTkSuQmCC)
14 0,
pointer !important;
+ outline: none !important;
}
.cx {
diff --git a/static/assets/js/marked.js b/static/assets/js/marked.js
new file mode 100644
index 0000000..2985cd0
--- /dev/null
+++ b/static/assets/js/marked.js
@@ -0,0 +1,1401 @@
+/**
+ * marked - a markdown parser
+ * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
+ * https://github.com/chjj/marked
+ */
+
+(function () {
+ /**
+ * Block-Level Grammar
+ */
+
+ var block = {
+ newline: /^\n+/,
+ code: /^\n( {4}[^\n]+\n*)+/,
+ fences: noop,
+ hr: /^( *[-*_]){3,} *(?:\n+|$)/,
+ heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
+ nptable: noop,
+ lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
+ blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,
+ list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
+ html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,
+ def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
+ table: noop,
+ paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
+ text: /^[^\n]+/,
+ };
+
+ block.bullet = /(?:[*+-]|\d+\.)/;
+ block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
+ block.item = replace(block.item, "gm")(/bull/g, block.bullet)();
+
+ block.list = replace(block.list)(/bull/g, block.bullet)(
+ "hr",
+ "\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))"
+ )("def", "\\n+(?=" + block.def.source + ")")();
+
+ block.blockquote = replace(block.blockquote)("def", block.def)();
+
+ block._tag =
+ "(?!(?:" +
+ "a|em|strong|small|s|cite|q|dfn|abbr|data|time|code" +
+ "|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo" +
+ "|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";
+
+ block.html = replace(block.html)("comment", /<!--[\s\S]*?-->/)(
+ "closed",
+ /<(tag)[\s\S]+?<\/\1>/
+ )("closing", /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)(/tag/g, block._tag)();
+
+ block.paragraph = replace(block.paragraph)("hr", block.hr)(
+ "heading",
+ block.heading
+ )("lheading", block.lheading)("blockquote", block.blockquote)(
+ "tag",
+ "<" + block._tag
+ )("def", block.def)();
+
+ /**
+ * Normal Block Grammar
+ */
+
+ block.normal = merge({}, block);
+
+ /**
+ * GFM Block Grammar
+ */
+
+ block.gfm = merge({}, block.normal, {
+ fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,
+ paragraph: /^/,
+ heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/,
+ });
+
+ block.gfm.paragraph = replace(block.paragraph)(
+ "(?!",
+ "(?!" +
+ block.gfm.fences.source.replace("\\1", "\\2") +
+ "|" +
+ block.list.source.replace("\\1", "\\3") +
+ "|"
+ )();
+
+ /**
+ * GFM + Tables Block Grammar
+ */
+
+ block.tables = merge({}, block.gfm, {
+ nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
+ table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/,
+ });
+
+ /**
+ * Block Lexer
+ */
+
+ function Lexer(options) {
+ this.tokens = [];
+ this.tokens.links = {};
+ this.options = options || marked.defaults;
+ // Sort math delimiters, because parsing of $$..$$ must come before $..$ etc.
+ if (Array.isArray(this.options.mathDelimiters)) {
+ this.options.mathDelimiters.sort(function (a, b) {
+ return b[0].length - a[0].length;
+ });
+ }
+ this.rules = block.normal;
+
+ if (this.options.gfm) {
+ if (this.options.tables) {
+ this.rules = block.tables;
+ } else {
+ this.rules = block.gfm;
+ }
+ }
+ }
+
+ /**
+ * Expose Block Rules
+ */
+
+ Lexer.rules = block;
+
+ /**
+ * Static Lex Method
+ */
+
+ Lexer.lex = function (src, options) {
+ var lexer = new Lexer(options);
+ return lexer.lex(src);
+ };
+
+ /**
+ * Preprocessing
+ */
+
+ Lexer.prototype.lex = function (src) {
+ src = src
+ .replace(/\r\n|\r/g, "\n")
+ .replace(/\t/g, " ")
+ .replace(/\u00a0/g, " ")
+ .replace(/\u2424/g, "\n");
+
+ return this.token(src, true);
+ };
+
+ /**
+ * Lexing
+ */
+
+ Lexer.prototype.token = function (src, top, bq) {
+ var src = src.replace(/^ +$/gm, ""),
+ next,
+ loose,
+ cap,
+ bull,
+ b,
+ item,
+ space,
+ i,
+ l;
+
+ while (src) {
+ // newline
+ if ((cap = this.rules.newline.exec(src))) {
+ src = src.substring(cap[0].length);
+ if (cap[0].length > 1) {
+ this.tokens.push({
+ type: "space",
+ });
+ }
+ }
+
+ // code
+ if ((cap = this.rules.code.exec(src))) {
+ src = src.substring(cap[0].length);
+ cap = cap[0].replace(/^ {4}/gm, "");
+ this.tokens.push({
+ type: "code",
+ text: !this.options.pedantic ? cap.replace(/\n+$/, "") : cap,
+ });
+ continue;
+ }
+
+ // fences (gfm)
+ if ((cap = this.rules.fences.exec(src))) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: "code",
+ lang: cap[2],
+ text: cap[3] || "",
+ });
+ continue;
+ }
+
+ // heading
+ if ((cap = this.rules.heading.exec(src))) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: "heading",
+ depth: cap[1].length,
+ text: cap[2],
+ });
+ continue;
+ }
+
+ // table no leading pipe (gfm)
+ if (top && (cap = this.rules.nptable.exec(src))) {
+ src = src.substring(cap[0].length);
+
+ item = {
+ type: "table",
+ header: cap[1].replace(/^ *| *\| *$/g, "").split(/ *\| */),
+ align: cap[2].replace(/^ *|\| *$/g, "").split(/ *\| */),
+ cells: cap[3].replace(/\n$/, "").split("\n"),
+ };
+
+ for (i = 0; i < item.align.length; i++) {
+ if (/^ *-+: *$/.test(item.align[i])) {
+ item.align[i] = "right";
+ } else if (/^ *:-+: *$/.test(item.align[i])) {
+ item.align[i] = "center";
+ } else if (/^ *:-+ *$/.test(item.align[i])) {
+ item.align[i] = "left";
+ } else {
+ item.align[i] = null;
+ }
+ }
+
+ for (i = 0; i < item.cells.length; i++) {
+ item.cells[i] = item.cells[i].split(/ *\| */);
+ }
+
+ this.tokens.push(item);
+
+ continue;
+ }
+
+ // lheading
+ if ((cap = this.rules.lheading.exec(src))) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: "heading",
+ depth: cap[2] === "=" ? 1 : 2,
+ text: cap[1],
+ });
+ continue;
+ }
+
+ // hr
+ if ((cap = this.rules.hr.exec(src))) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: "hr",
+ });
+ continue;
+ }
+
+ // blockquote
+ if ((cap = this.rules.blockquote.exec(src))) {
+ src = src.substring(cap[0].length);
+
+ this.tokens.push({
+ type: "blockquote_start",
+ });
+
+ cap = cap[0].replace(/^ *> ?/gm, "");
+
+ // Pass `top` to keep the current
+ // "toplevel" state. This is exactly
+ // how markdown.pl works.
+ this.token(cap, top, true);
+
+ this.tokens.push({
+ type: "blockquote_end",
+ });
+
+ continue;
+ }
+
+ // list
+ if ((cap = this.rules.list.exec(src))) {
+ src = src.substring(cap[0].length);
+ bull = cap[2];
+
+ this.tokens.push({
+ type: "list_start",
+ ordered: bull.length > 1,
+ });
+
+ // Get each top-level item.
+ cap = cap[0].match(this.rules.item);
+
+ next = false;
+ l = cap.length;
+ i = 0;
+
+ for (; i < l; i++) {
+ item = cap[i];
+
+ // Remove the list item's bullet
+ // so it is seen as the next token.
+ space = item.length;
+ item = item.replace(/^ *([*+-]|\d+\.) +/, "");
+
+ // Outdent whatever the
+ // list item contains. Hacky.
+ if (~item.indexOf("\n ")) {
+ space -= item.length;
+ item = !this.options.pedantic
+ ? item.replace(new RegExp("^ {1," + space + "}", "gm"), "")
+ : item.replace(/^ {1,4}/gm, "");
+ }
+
+ // Determine whether the next list item belongs here.
+ // Backpedal if it does not belong in this list.
+ if (this.options.smartLists && i !== l - 1) {
+ b = block.bullet.exec(cap[i + 1])[0];
+ if (bull !== b && !(bull.length > 1 && b.length > 1)) {
+ src = cap.slice(i + 1).join("\n") + src;
+ i = l - 1;
+ }
+ }
+
+ // Determine whether item is loose or not.
+ // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
+ // for discount behavior.
+ loose = next || /\n\n(?!\s*$)/.test(item);
+ if (i !== l - 1) {
+ next = item.charAt(item.length - 1) === "\n";
+ if (!loose) loose = next;
+ }
+
+ this.tokens.push({
+ type: loose ? "loose_item_start" : "list_item_start",
+ });
+
+ // Recurse.
+ this.token(item, false, bq);
+
+ this.tokens.push({
+ type: "list_item_end",
+ });
+ }
+
+ this.tokens.push({
+ type: "list_end",
+ });
+
+ continue;
+ }
+
+ // html
+ if ((cap = this.rules.html.exec(src))) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: this.options.sanitize ? "paragraph" : "html",
+ pre:
+ !this.options.sanitizer &&
+ (cap[1] === "pre" || cap[1] === "script" || cap[1] === "style"),
+ text: cap[0],
+ });
+ continue;
+ }
+
+ // def
+ if (!bq && top && (cap = this.rules.def.exec(src))) {
+ src = src.substring(cap[0].length);
+ this.tokens.links[cap[1].toLowerCase()] = {
+ href: cap[2],
+ title: cap[3],
+ };
+ continue;
+ }
+
+ // table (gfm)
+ if (top && (cap = this.rules.table.exec(src))) {
+ src = src.substring(cap[0].length);
+
+ item = {
+ type: "table",
+ header: cap[1].replace(/^ *| *\| *$/g, "").split(/ *\| */),
+ align: cap[2].replace(/^ *|\| *$/g, "").split(/ *\| */),
+ cells: cap[3].replace(/(?: *\| *)?\n$/, "").split("\n"),
+ };
+
+ for (i = 0; i < item.align.length; i++) {
+ if (/^ *-+: *$/.test(item.align[i])) {
+ item.align[i] = "right";
+ } else if (/^ *:-+: *$/.test(item.align[i])) {
+ item.align[i] = "center";
+ } else if (/^ *:-+ *$/.test(item.align[i])) {
+ item.align[i] = "left";
+ } else {
+ item.align[i] = null;
+ }
+ }
+
+ for (i = 0; i < item.cells.length; i++) {
+ item.cells[i] = item.cells[i]
+ .replace(/^ *\| *| *\| *$/g, "")
+ .split(/ *\| */);
+ }
+
+ this.tokens.push(item);
+
+ continue;
+ }
+
+ // top-level paragraph
+ if (top && (cap = this.rules.paragraph.exec(src))) {
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: "paragraph",
+ text:
+ cap[1].charAt(cap[1].length - 1) === "\n"
+ ? cap[1].slice(0, -1)
+ : cap[1],
+ });
+ continue;
+ }
+
+ // text
+ if ((cap = this.rules.text.exec(src))) {
+ // Top-level should never reach here.
+ src = src.substring(cap[0].length);
+ this.tokens.push({
+ type: "text",
+ text: cap[0],
+ });
+ continue;
+ }
+
+ if (src) {
+ throw new Error("Infinite loop on byte: " + src.charCodeAt(0));
+ }
+ }
+
+ return this.tokens;
+ };
+
+ /**
+ * Inline-Level Grammar
+ */
+
+ var inline = {
+ escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
+ autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
+ url: noop,
+ tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
+ link: /^!?\[(inside)\]\(href\)/,
+ reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
+ nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
+ strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
+ em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
+ code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
+ br: /^ {2,}\n(?!\s*$)/,
+ del: noop,
+ text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/,
+ };
+
+ inline._inside = /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;
+ inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;
+
+ inline.link = replace(inline.link)("inside", inline._inside)(
+ "href",
+ inline._href
+ )();
+
+ inline.reflink = replace(inline.reflink)("inside", inline._inside)();
+
+ /**
+ * Normal Inline Grammar
+ */
+
+ inline.normal = merge({}, inline);
+
+ /**
+ * Pedantic Inline Grammar
+ */
+
+ inline.pedantic = merge({}, inline.normal, {
+ strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
+ em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/,
+ });
+
+ /**
+ * GFM Inline Grammar
+ */
+
+ inline.gfm = merge({}, inline.normal, {
+ escape: replace(inline.escape)("])", "~|])")(),
+ url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
+ del: /^~~(?=\S)([\s\S]*?\S)~~/,
+ text: replace(inline.text)("]|", "~]|")("|", "|https?://|")(),
+ });
+
+ /**
+ * GFM + Line Breaks Inline Grammar
+ */
+
+ inline.breaks = merge({}, inline.gfm, {
+ br: replace(inline.br)("{2,}", "*")(),
+ text: replace(inline.gfm.text)("{2,}", "*")(),
+ });
+
+ /**
+ * Inline Lexer & Compiler
+ */
+
+ function InlineLexer(links, options) {
+ this.options = options || marked.defaults;
+ this.links = links;
+ this.rules = inline.normal;
+ this.renderer = this.options.renderer || new Renderer();
+ this.renderer.options = this.options;
+
+ if (!this.links) {
+ throw new Error("Tokens array requires a `links` property.");
+ }
+
+ if (this.options.gfm) {
+ if (this.options.breaks) {
+ this.rules = inline.breaks;
+ } else {
+ this.rules = inline.gfm;
+ }
+ } else if (this.options.pedantic) {
+ this.rules = inline.pedantic;
+ }
+
+ // Make rules compatible with specified math delimiters
+ if (
+ this.options.mathDelimiters &&
+ Array.isArray(this.options.mathDelimiters)
+ ) {
+ var regexEscape = function (s) {
+ return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
+ };
+ var openingMathDelimiters = [];
+ for (var i = 0; i < this.options.mathDelimiters.length; i++) {
+ if (
+ Array.isArray(this.options.mathDelimiters[i]) &&
+ this.options.mathDelimiters[i].length === 2
+ ) {
+ openingMathDelimiters.push(
+ regexEscape(this.options.mathDelimiters[i][0])
+ );
+ }
+ }
+ this.rules.text = replace(this.rules.text)(
+ "]|",
+ "]|(" + openingMathDelimiters.join("|") + ")|"
+ )();
+ }
+ }
+
+ /**
+ * Expose Inline Rules
+ */
+
+ InlineLexer.rules = inline;
+
+ /**
+ * Static Lexing/Compiling Method
+ */
+
+ InlineLexer.output = function (src, links, options) {
+ var inline = new InlineLexer(links, options);
+ return inline.output(src);
+ };
+
+ /**
+ * Lexing/Compiling
+ */
+
+ InlineLexer.prototype.output = function (src) {
+ var out = "",
+ link,
+ text,
+ href,
+ cap;
+
+ while (src) {
+ // Math wrapped in specified math delimiters is passed through unprocessed,
+ // For use with MathJax / KaTeX etc.
+ if (this.options.mathDelimiters) {
+ for (var i = 0; i < this.options.mathDelimiters.length; i++) {
+ var delim = this.options.mathDelimiters[i];
+ if (delim === "beginend") {
+ // \begin{}..\end{}
+ if (src.substr(0, 7) === "\\begin{") {
+ var idx = 7;
+ // traverse nested \begin{}..\end{}
+ var traverse = function () {
+ while (src.substr(idx, 5) !== "\\end{") {
+ if (src.substr(idx, 7) === "\\begin{") {
+ idx += 7;
+ traverse();
+ } else {
+ idx++;
+ }
+ }
+ idx += 5;
+ while (src[idx] !== "}") {
+ idx++;
+ }
+ idx++;
+ };
+ traverse();
+ out += this.renderer.math(src.substring(0, idx), "begin", "end"); // TODO, extract begin and end as \begin{foo} and \end{foo}.
+ src = src.substring(idx);
+ continue;
+ }
+ } else if (
+ Array.isArray(delim) &&
+ delim.length === 2 &&
+ src.substr(0, delim[0].length) === delim[0]
+ ) {
+ var idx = delim[0].length;
+ while (true) {
+ if (idx > src.length) {
+ break;
+ }
+ // Allow escaping closing delimiter
+ if (
+ delim[1].length === 1 &&
+ src.substr(idx, 2) === "\\" + delim[1]
+ ) {
+ idx += 2;
+ } else if (src.substr(idx, delim[1].length) !== delim[1]) {
+ idx++;
+ } else {
+ break;
+ }
+ }
+ idx += delim[1].length;
+ out += this.renderer.math(
+ src.substring(0, idx),
+ delim[0],
+ delim[1]
+ );
+ src = src.substring(idx);
+ continue;
+ }
+ }
+ }
+
+ // escape
+ if ((cap = this.rules.escape.exec(src))) {
+ src = src.substring(cap[0].length);
+ out += cap[1];
+ continue;
+ }
+
+ // autolink
+ if ((cap = this.rules.autolink.exec(src))) {
+ src = src.substring(cap[0].length);
+ if (cap[2] === "@") {
+ text =
+ cap[1].charAt(6) === ":"
+ ? this.mangle(cap[1].substring(7))
+ : this.mangle(cap[1]);
+ href = this.mangle("mailto:") + text;
+ } else {
+ text = escape(cap[1]);
+ href = text;
+ }
+ out += this.renderer.link(href, null, text);
+ continue;
+ }
+
+ // url (gfm)
+ if (!this.inLink && (cap = this.rules.url.exec(src))) {
+ src = src.substring(cap[0].length);
+ text = escape(cap[1]);
+ href = text;
+ out += this.renderer.link(href, null, text);
+ continue;
+ }
+
+ // tag
+ if ((cap = this.rules.tag.exec(src))) {
+ if (!this.inLink && /^<a /i.test(cap[0])) {
+ this.inLink = true;
+ } else if (this.inLink && /^<\/a>/i.test(cap[0])) {
+ this.inLink = false;
+ }
+ src = src.substring(cap[0].length);
+ out += this.options.sanitize
+ ? this.options.sanitizer
+ ? this.options.sanitizer(cap[0])
+ : escape(cap[0])
+ : cap[0];
+ continue;
+ }
+
+ // link
+ if ((cap = this.rules.link.exec(src))) {
+ src = src.substring(cap[0].length);
+ this.inLink = true;
+ out += this.outputLink(cap, {
+ href: cap[2],
+ title: cap[3],
+ });
+ this.inLink = false;
+ continue;
+ }
+
+ // reflink, nolink
+ if (
+ (cap = this.rules.reflink.exec(src)) ||
+ (cap = this.rules.nolink.exec(src))
+ ) {
+ src = src.substring(cap[0].length);
+ link = (cap[2] || cap[1]).replace(/\s+/g, " ");
+ link = this.links[link.toLowerCase()];
+ if (!link || !link.href) {
+ out += cap[0].charAt(0);
+ src = cap[0].substring(1) + src;
+ continue;
+ }
+ this.inLink = true;
+ out += this.outputLink(cap, link);
+ this.inLink = false;
+ continue;
+ }
+
+ // strong
+ if ((cap = this.rules.strong.exec(src))) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.strong(this.output(cap[2] || cap[1]));
+ continue;
+ }
+
+ // em
+ if ((cap = this.rules.em.exec(src))) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.em(this.output(cap[2] || cap[1]));
+ continue;
+ }
+
+ // code
+ if ((cap = this.rules.code.exec(src))) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.codespan(escape(cap[2], true));
+ continue;
+ }
+
+ // br
+ if ((cap = this.rules.br.exec(src))) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.br();
+ continue;
+ }
+
+ // del (gfm)
+ if ((cap = this.rules.del.exec(src))) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.del(this.output(cap[1]));
+ continue;
+ }
+
+ // text
+ if ((cap = this.rules.text.exec(src))) {
+ src = src.substring(cap[0].length);
+ out += this.renderer.text(escape(this.smartypants(cap[0])));
+ continue;
+ }
+
+ if (src) {
+ throw new Error("Infinite loop on byte: " + src.charCodeAt(0));
+ }
+ }
+
+ return out;
+ };
+
+ /**
+ * Compile Link
+ */
+
+ InlineLexer.prototype.outputLink = function (cap, link) {
+ var href = escape(link.href),
+ title = link.title ? escape(link.title) : null;
+
+ return cap[0].charAt(0) !== "!"
+ ? this.renderer.link(href, title, this.output(cap[1]))
+ : this.renderer.image(href, title, escape(cap[1]));
+ };
+
+ /**
+ * Smartypants Transformations
+ */
+
+ InlineLexer.prototype.smartypants = function (text) {
+ if (!this.options.smartypants) return text;
+ return (
+ text
+ // em-dashes
+ .replace(/---/g, "\u2014")
+ // en-dashes
+ .replace(/--/g, "\u2013")
+ // opening singles
+ .replace(/(^|[-\u2014/(\[{"\s])'/g, "$1\u2018")
+ // closing singles & apostrophes
+ .replace(/'/g, "\u2019")
+ // opening doubles
+ .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, "$1\u201c")
+ // closing doubles
+ .replace(/"/g, "\u201d")
+ // ellipses
+ .replace(/\.{3}/g, "\u2026")
+ );
+ };
+
+ /**
+ * Mangle Links
+ */
+
+ InlineLexer.prototype.mangle = function (text) {
+ if (!this.options.mangle) return text;
+ var out = "",
+ l = text.length,
+ i = 0,
+ ch;
+
+ for (; i < l; i++) {
+ ch = text.charCodeAt(i);
+ if (Math.random() > 0.5) {
+ ch = "x" + ch.toString(16);
+ }
+ out += "&#" + ch + ";";
+ }
+
+ return out;
+ };
+
+ /**
+ * Renderer
+ */
+
+ function Renderer(options) {
+ this.options = options || {};
+ }
+
+ Renderer.prototype.code = function (code, lang, escaped) {
+ if (this.options.highlight) {
+ var out = this.options.highlight(code, lang);
+ if (out != null && out !== code) {
+ escaped = true;
+ code = out;
+ }
+ }
+
+ if (!lang) {
+ return (
+ "<pre><code>" +
+ (escaped ? code : escape(code, true)) +
+ "\n</code></pre>"
+ );
+ }
+
+ return (
+ '<pre><code class="' +
+ this.options.langPrefix +
+ escape(lang, true) +
+ '">' +
+ (escaped ? code : escape(code, true)) +
+ "\n</code></pre>\n"
+ );
+ };
+
+ Renderer.prototype.blockquote = function (quote) {
+ return "<blockquote>\n" + quote + "</blockquote>\n";
+ };
+
+ Renderer.prototype.html = function (html) {
+ return html;
+ };
+
+ Renderer.prototype.heading = function (text, level, raw) {
+ return (
+ "<h" +
+ level +
+ ' id="' +
+ this.options.headerPrefix +
+ raw.toLowerCase().replace(/[^\w]+/g, "-") +
+ '">' +
+ text +
+ "</h" +
+ level +
+ ">\n"
+ );
+ };
+
+ Renderer.prototype.hr = function () {
+ return this.options.xhtml ? "<hr/>\n" : "<hr>\n";
+ };
+
+ Renderer.prototype.list = function (body, ordered) {
+ var type = ordered ? "ol" : "ul";
+ return "<" + type + ">\n" + body + "</" + type + ">\n";
+ };
+
+ Renderer.prototype.listitem = function (text) {
+ return "<li>" + text + "</li>\n";
+ };
+
+ Renderer.prototype.paragraph = function (text) {
+ return "<p>" + text + "</p>\n";
+ };
+
+ Renderer.prototype.table = function (header, body) {
+ return (
+ "<table>\n" +
+ "<thead>\n" +
+ header +
+ "</thead>\n" +
+ "<tbody>\n" +
+ body +
+ "</tbody>\n" +
+ "</table>\n"
+ );
+ };
+
+ Renderer.prototype.tablerow = function (content) {
+ return "<tr>\n" + content + "</tr>\n";
+ };
+
+ Renderer.prototype.tablecell = function (content, flags) {
+ var type = flags.header ? "th" : "td";
+ var tag = flags.align
+ ? "<" + type + ' style="text-align:' + flags.align + '">'
+ : "<" + type + ">";
+ return tag + content + "</" + type + ">\n";
+ };
+
+ // span level renderer
+ Renderer.prototype.strong = function (text) {
+ return "<strong>" + text + "</strong>";
+ };
+
+ Renderer.prototype.em = function (text) {
+ return "<em>" + text + "</em>";
+ };
+
+ Renderer.prototype.codespan = function (text) {
+ return "<code>" + text + "</code>";
+ };
+
+ Renderer.prototype.br = function () {
+ return this.options.xhtml ? "<br/>" : "<br>";
+ };
+
+ Renderer.prototype.del = function (text) {
+ return "<del>" + text + "</del>";
+ };
+
+ Renderer.prototype.link = function (href, title, text) {
+ if (this.options.sanitize) {
+ try {
+ var prot = decodeURIComponent(unescape(href))
+ .replace(/[^\w:]/g, "")
+ .toLowerCase();
+ } catch (e) {
+ return "";
+ }
+ if (
+ prot.indexOf("javascript:") === 0 ||
+ prot.indexOf("vbscript:") === 0 ||
+ prot.indexOf("data:") === 0
+ ) {
+ return "";
+ }
+ }
+ var out = '<a href="' + href + '"';
+ if (title) {
+ out += ' title="' + title + '"';
+ }
+ out += ">" + text + "</a>";
+ return out;
+ };
+
+ Renderer.prototype.image = function (href, title, text) {
+ var out = '<img src="' + href + '" alt="' + text + '"';
+ if (title) {
+ out += ' title="' + title + '"';
+ }
+ out += this.options.xhtml ? "/>" : ">";
+ return out;
+ };
+
+ Renderer.prototype.text = function (text) {
+ return text;
+ };
+
+ Renderer.prototype.math = function (text, begin, end) {
+ return text;
+
+ //// Example usage for inline TeX-processing:
+ // if ( begin === "$" || begin === "\\(" ) {
+ // return katex.renderToString(text, { displayMode: false });
+ // } else if ( begin === "$$" || begin === "\\[" ) {
+ // return katex.renderToString(text, { displayMode: true });
+ // } else {
+ // return katex.renderToString(begin + text + end, { displayMode: true });
+ // }
+ };
+
+ /**
+ * Parsing & Compiling
+ */
+
+ function Parser(options) {
+ this.tokens = [];
+ this.token = null;
+ this.options = options || marked.defaults;
+ this.options.renderer = this.options.renderer || new Renderer();
+ this.renderer = this.options.renderer;
+ this.renderer.options = this.options;
+ }
+
+ /**
+ * Static Parse Method
+ */
+
+ Parser.parse = function (src, options, renderer) {
+ var parser = new Parser(options, renderer);
+ return parser.parse(src);
+ };
+
+ /**
+ * Parse Loop
+ */
+
+ Parser.prototype.parse = function (src) {
+ this.inline = new InlineLexer(src.links, this.options, this.renderer);
+ this.tokens = src.reverse();
+
+ var out = "";
+ while (this.next()) {
+ out += this.tok();
+ }
+
+ return out;
+ };
+
+ /**
+ * Next Token
+ */
+
+ Parser.prototype.next = function () {
+ return (this.token = this.tokens.pop());
+ };
+
+ /**
+ * Preview Next Token
+ */
+
+ Parser.prototype.peek = function () {
+ return this.tokens[this.tokens.length - 1] || 0;
+ };
+
+ /**
+ * Parse Text Tokens
+ */
+
+ Parser.prototype.parseText = function () {
+ var body = this.token.text;
+
+ while (this.peek().type === "text") {
+ body += "\n" + this.next().text;
+ }
+
+ return this.inline.output(body);
+ };
+
+ /**
+ * Parse Current Token
+ */
+
+ Parser.prototype.tok = function () {
+ switch (this.token.type) {
+ case "space": {
+ return "";
+ }
+ case "hr": {
+ return this.renderer.hr();
+ }
+ case "heading": {
+ return this.renderer.heading(
+ this.inline.output(this.token.text),
+ this.token.depth,
+ this.token.text
+ );
+ }
+ case "code": {
+ return this.renderer.code(
+ this.token.text,
+ this.token.lang,
+ this.token.escaped
+ );
+ }
+ case "table": {
+ var header = "",
+ body = "",
+ i,
+ row,
+ cell,
+ flags,
+ j;
+
+ // header
+ cell = "";
+ for (i = 0; i < this.token.header.length; i++) {
+ flags = { header: true, align: this.token.align[i] };
+ cell += this.renderer.tablecell(
+ this.inline.output(this.token.header[i]),
+ { header: true, align: this.token.align[i] }
+ );
+ }
+ header += this.renderer.tablerow(cell);
+
+ for (i = 0; i < this.token.cells.length; i++) {
+ row = this.token.cells[i];
+
+ cell = "";
+ for (j = 0; j < row.length; j++) {
+ cell += this.renderer.tablecell(this.inline.output(row[j]), {
+ header: false,
+ align: this.token.align[j],
+ });
+ }
+
+ body += this.renderer.tablerow(cell);
+ }
+ return this.renderer.table(header, body);
+ }
+ case "blockquote_start": {
+ var body = "";
+
+ while (this.next().type !== "blockquote_end") {
+ body += this.tok();
+ }
+
+ return this.renderer.blockquote(body);
+ }
+ case "list_start": {
+ var body = "",
+ ordered = this.token.ordered;
+
+ while (this.next().type !== "list_end") {
+ body += this.tok();
+ }
+
+ return this.renderer.list(body, ordered);
+ }
+ case "list_item_start": {
+ var body = "";
+
+ while (this.next().type !== "list_item_end") {
+ body += this.token.type === "text" ? this.parseText() : this.tok();
+ }
+
+ return this.renderer.listitem(body);
+ }
+ case "loose_item_start": {
+ var body = "";
+
+ while (this.next().type !== "list_item_end") {
+ body += this.tok();
+ }
+
+ return this.renderer.listitem(body);
+ }
+ case "html": {
+ var html =
+ !this.token.pre && !this.options.pedantic
+ ? this.inline.output(this.token.text)
+ : this.token.text;
+ return this.renderer.html(html);
+ }
+ case "paragraph": {
+ return this.renderer.paragraph(this.inline.output(this.token.text));
+ }
+ case "text": {
+ return this.renderer.paragraph(this.parseText());
+ }
+ }
+ };
+
+ /**
+ * Helpers
+ */
+
+ function escape(html, encode) {
+ return html
+ .replace(!encode ? /&(?!#?\w+;)/g : /&/g, "&amp;")
+ .replace(/</g, "&lt;")
+ .replace(/>/g, "&gt;")
+ .replace(/"/g, "&quot;")
+ .replace(/'/g, "&#39;");
+ }
+
+ function unescape(html) {
+ // explicitly match decimal, hex, and named HTML entities
+ return html.replace(
+ /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/g,
+ function (_, n) {
+ n = n.toLowerCase();
+ if (n === "colon") return ":";
+ if (n.charAt(0) === "#") {
+ return n.charAt(1) === "x"
+ ? String.fromCharCode(parseInt(n.substring(2), 16))
+ : String.fromCharCode(+n.substring(1));
+ }
+ return "";
+ }
+ );
+ }
+
+ function replace(regex, opt) {
+ regex = regex.source;
+ opt = opt || "";
+ return function self(name, val) {
+ if (!name) return new RegExp(regex, opt);
+ val = val.source || val;
+ val = val.replace(/(^|[^\[])\^/g, "$1");
+ regex = regex.replace(name, val);
+ return self;
+ };
+ }
+
+ function noop() {}
+ noop.exec = noop;
+
+ function merge(obj) {
+ var i = 1,
+ target,
+ key;
+
+ for (; i < arguments.length; i++) {
+ target = arguments[i];
+ for (key in target) {
+ if (Object.prototype.hasOwnProperty.call(target, key)) {
+ obj[key] = target[key];
+ }
+ }
+ }
+
+ return obj;
+ }
+
+ /**
+ * Marked
+ */
+
+ function marked(src, opt, callback) {
+ if (callback || typeof opt === "function") {
+ if (!callback) {
+ callback = opt;
+ opt = null;
+ }
+
+ opt = merge({}, marked.defaults, opt || {});
+
+ var highlight = opt.highlight,
+ tokens,
+ pending,
+ i = 0;
+
+ try {
+ tokens = Lexer.lex(src, opt);
+ } catch (e) {
+ return callback(e);
+ }
+
+ pending = tokens.length;
+
+ var done = function (err) {
+ if (err) {
+ opt.highlight = highlight;
+ return callback(err);
+ }
+
+ var out;
+
+ try {
+ out = Parser.parse(tokens, opt);
+ } catch (e) {
+ err = e;
+ }
+
+ opt.highlight = highlight;
+
+ return err ? callback(err) : callback(null, out);
+ };
+
+ if (!highlight || highlight.length < 3) {
+ return done();
+ }
+
+ delete opt.highlight;
+
+ if (!pending) return done();
+
+ for (; i < tokens.length; i++) {
+ (function (token) {
+ if (token.type !== "code") {
+ return --pending || done();
+ }
+ return highlight(token.text, token.lang, function (err, code) {
+ if (err) return done(err);
+ if (code == null || code === token.text) {
+ return --pending || done();
+ }
+ token.text = code;
+ token.escaped = true;
+ --pending || done();
+ });
+ })(tokens[i]);
+ }
+
+ return;
+ }
+ try {
+ if (opt) opt = merge({}, marked.defaults, opt);
+ return Parser.parse(Lexer.lex(src, opt), opt);
+ } catch (e) {
+ e.message += "\nPlease report this to https://github.com/chjj/marked.";
+ if ((opt || marked.defaults).silent) {
+ return (
+ "<p>An error occured:</p><pre>" +
+ escape(e.message + "", true) +
+ "</pre>"
+ );
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Options
+ */
+
+ marked.options = marked.setOptions = function (opt) {
+ merge(marked.defaults, opt);
+ return marked;
+ };
+
+ marked.defaults = {
+ gfm: true,
+ tables: true,
+ breaks: false,
+ pedantic: false,
+ sanitize: false,
+ sanitizer: null,
+ mangle: true,
+ smartLists: false,
+ silent: false,
+ highlight: null,
+ langPrefix: "lang-",
+ smartypants: false,
+ headerPrefix: "",
+ mathDelimiters: null,
+ renderer: new Renderer(),
+ xhtml: false,
+ };
+
+ /**
+ * Expose
+ */
+
+ marked.Parser = Parser;
+ marked.parser = Parser.parse;
+
+ marked.Renderer = Renderer;
+
+ marked.Lexer = Lexer;
+ marked.lexer = Lexer.lex;
+
+ marked.InlineLexer = InlineLexer;
+ marked.inlineLexer = InlineLexer.output;
+
+ marked.parse = marked;
+
+ if (typeof module !== "undefined" && typeof exports === "object") {
+ module.exports = marked;
+ } else if (typeof define === "function" && define.amd) {
+ define(function () {
+ return marked;
+ });
+ } else {
+ this.marked = marked;
+ }
+}.call(
+ (function () {
+ return this || (typeof window !== "undefined" ? window : global);
+ })()
+));
diff --git a/static/assets/js/pages/admin.js b/static/assets/js/pages/admin.js
index 8dc8578..028601a 100644
--- a/static/assets/js/pages/admin.js
+++ b/static/assets/js/pages/admin.js
@@ -2,16 +2,6 @@ const loginInfo = document.getElementById("login-info");
const loginForm = document.getElementById("login-page");
window.addEventListener("DOMContentLoaded", () => {
- const firebaseConfig = {
- apiKey: "AIzaSyBCmKUnEmm8hLR9ZcFWPYbYiplbP6yUzfU",
- authDomain: "thatcomputerscientist-e9cf2.firebaseapp.com",
- projectId: "thatcomputerscientist-e9cf2",
- storageBucket: "thatcomputerscientist-e9cf2.appspot.com",
- messagingSenderId: "178402875544",
- appId: "1:178402875544:web:8c9d8880d3ef495a5658ed",
- measurementId: "G-JECWWZG5J6",
- };
- firebase.initializeApp(firebaseConfig);
const signInButton = document.getElementById("signInButton");
signInButton.addEventListener("click", () => {
const email = document.getElementById("inputEmail").value;
diff --git a/static/assets/js/pages/authCheck.js b/static/assets/js/pages/authCheck.js
new file mode 100644
index 0000000..722fe0c
--- /dev/null
+++ b/static/assets/js/pages/authCheck.js
@@ -0,0 +1,10 @@
+firebase.auth().onAuthStateChanged((user) => {
+ if (!user) {
+ window.location.assign("/admin/");
+ }
+});
+document.getElementById("logout").addEventListener("click", () => {
+ firebase.auth().signOut().then(() => {
+ window.location.assign("/admin/");
+ });
+});
diff --git a/static/assets/js/pages/config.js b/static/assets/js/pages/config.js
new file mode 100644
index 0000000..256173a
--- /dev/null
+++ b/static/assets/js/pages/config.js
@@ -0,0 +1,10 @@
+const firebaseConfig = {
+ apiKey: "AIzaSyBCmKUnEmm8hLR9ZcFWPYbYiplbP6yUzfU",
+ authDomain: "thatcomputerscientist-e9cf2.firebaseapp.com",
+ projectId: "thatcomputerscientist-e9cf2",
+ storageBucket: "thatcomputerscientist-e9cf2.appspot.com",
+ messagingSenderId: "178402875544",
+ appId: "1:178402875544:web:8c9d8880d3ef495a5658ed",
+ measurementId: "G-JECWWZG5J6",
+};
+firebase.initializeApp(firebaseConfig);