From 5cd6b786962253323b6081e0dbf990ec084625f9 Mon Sep 17 00:00:00 2001 From: DarkenLM Date: Mon, 23 May 2022 23:53:44 +0100 Subject: [PATCH 1/5] Added WebComponent Universal Method. Added Documentation Added Local Script Copy Fallbacks --- docs/docs.html | 109 +++++++ docs/landing.html | 182 +++++++++++ docs/scripts/global.js | 144 +++++++++ docs/stylesheets/docs.css | 125 ++++++++ docs/stylesheets/global.css | 333 ++++++++++++++++++++ docs/stylesheets/landing.css | 193 ++++++++++++ docs/svg/logo.svg | 1 + markdown-tag-wc.js | 433 ++++++++++++++++++++++++++ parsers/DOMPurify/purify.min.js | 3 + parsers/DOMPurify/purify.min.js.map | 1 + parsers/commonmark/markdown-it.min.js | 2 + parsers/marked/marked.min.js | 6 + parsers/showdown/showdown.min.js | 3 + parsers/showdown/showdown.min.js.map | 1 + to-do.md | 2 +- 15 files changed, 1537 insertions(+), 1 deletion(-) create mode 100644 docs/docs.html create mode 100644 docs/landing.html create mode 100644 docs/scripts/global.js create mode 100644 docs/stylesheets/docs.css create mode 100644 docs/stylesheets/global.css create mode 100644 docs/stylesheets/landing.css create mode 100644 docs/svg/logo.svg create mode 100644 markdown-tag-wc.js create mode 100644 parsers/DOMPurify/purify.min.js create mode 100644 parsers/DOMPurify/purify.min.js.map create mode 100644 parsers/commonmark/markdown-it.min.js create mode 100644 parsers/marked/marked.min.js create mode 100644 parsers/showdown/showdown.min.js create mode 100644 parsers/showdown/showdown.min.js.map diff --git a/docs/docs.html b/docs/docs.html new file mode 100644 index 0000000..ac04a41 --- /dev/null +++ b/docs/docs.html @@ -0,0 +1,109 @@ + + + + + + + MarkdownTag Documentation + + + + + + + +
+ +
+ +
+
+

Get Started

+

Here's how to quickly install and setup MarkdownTag, no chit-chat involved.

+

Installation

+

Add this line of code anywhere in your HTML page:

+
+
+              
+				<script src="https://cdn.jsdelivr.net/gh/MarketingPipeline/Markdown-Tag/markdown-tag-wc.js"></script>
+              
+            
+
+

And MarkdownTag should be ready to be used.

+
+
+

Configuration

+

To configure MarkdownTag, edit the attributes on every md-tag you wish to configure.

+ + + + + + + + + + + + + + + + +
AttributeMeaningDefault
flavorThe parser to render the markdown with. Can be one of showdown, github, commonmark or markedshowdown
parser_rootThe root of the remote directory where the parsers are located, if any. Refer to Parser Directory Structure for more information../
+

Editing any md-tag attribute will cause the md-tag to update the render.

+
+
+
+

Parsers

+

Currently, MarkdownTag supports three markdown parsers.

+

Showdown - Markdown parser following the original rules as defined by John Gruber.

+

MarkdownIt - Markdown parser following the rules as defined by the Commonmark spec.

+

Marked - Fast, Lightweight Markdown parser with minimalist parsing.

+
+
+
+

Parser Directory Structure

+

In order for a custom parser version to be used, the following directory structure must be used:

+
+
+              
+				parsers
+				├── showdown
+				│   └── showdown.min.js
+				├── commonmark
+				│   └── markdown-it.min.js
+				└── marked
+					└── marked.min.js
+              
+            
+
+
+
+
+

Issues

+

Any issue you may find while using this package, please refer to the Issues Panel on Github.

+
+
+
+ + + + + + \ No newline at end of file diff --git a/docs/landing.html b/docs/landing.html new file mode 100644 index 0000000..1fa473f --- /dev/null +++ b/docs/landing.html @@ -0,0 +1,182 @@ + + + + + + + MarkdownTag + + + + + + + + + + + +
+

MarkdownTag

+

The easiest way to add Markdown support to your website!

+
+
+ + +
+
+
+			
+
    +
  • default
  • +
  • github
  • +
  • commonmark
  • +
  • marked
  • +
+
+
+ +### Heading + +# H1 +## H2 +### H3 + +### Bold + +**bold text** + +### Italic + +*italicized text* + +### Blockquote + +> blockquote + +### Ordered List + +1. First item +2. Second item +3. Third item + +### Unordered List + +- First item +- Second item +- Third item + +### Code + +`code` + +### Horizontal Rule + +--- + +### Link + +[Markdown Guide](https://www.markdownguide.org) + +### Image + +![alt text](https://www.markdownguide.org/assets/images/tux.png) + +## Extended Syntax + +These elements extend the basic syntax by adding additional features. Not all Markdown applications support these elements. + +### Table + +| Syntax | Description | +| ----------- | ----------- | +| Header | Title | +| Paragraph | Text | + +### Fenced Code Block + +``` +{ + "firstName": "John", + "lastName": "Smith", + "age": 25 +} +``` + +### Footnote + +Here's a sentence with a footnote. [^1] + +[^1]: This is the footnote. + +### Heading ID + +### My Great Heading {#custom-id} + +### Definition List + +term +: definition + +### Strikethrough + +~~The world is flat.~~ + +### Task List + +- [x] Write the press release +- [ ] Update the website +- [ ] Contact the media + +
+ +
+
+
+
+

Installation

+ Add this line of code anywhere in your HTML page: +
+				<script src="https://cdn.jsdelivr.net/gh/MarketingPipeline/Markdown-Tag/markdown-tag-wc.js"></script>
+			
+
+
+
+

Fast & Light

+

Based on the WebComponents API, each md-tag is guarenteed to be isolated from the DOM and only affects properties inside itself.

+
+
+

Dependency Backup

+

You can use your use your own version of the supported markdown parsers, or use the predefined version, downloaded straight from the web.

+
+
+

Easy to Configure

+

Simply edit the md-tag's contents and it will automatically edit the displayed markdown!

+
+
+

Pure HTML Configuration

+

No Javascript required! Just edit the attributes on the md-tag and it will automatically update.

+
+
+
+

Read our documentation for advanced keybindings and customization

+ Documentation +
+
+ + + + + \ No newline at end of file diff --git a/docs/scripts/global.js b/docs/scripts/global.js new file mode 100644 index 0000000..4599321 --- /dev/null +++ b/docs/scripts/global.js @@ -0,0 +1,144 @@ +// utilities +let get = function (selector, scope) { + scope = scope ? scope : document; + return scope.querySelector(selector); +}; + +let getAll = function (selector, scope) { + scope = scope ? scope : document; + return scope.querySelectorAll(selector); +}; + +// toggle tabs on codeblock +window.addEventListener("load", function () { + // get all tab_containers in the document + let tabContainers = getAll(".tab__container"); + + // bind click event to each tab container + for (let i = 0; i < tabContainers.length; i++) { + const tab_menu = get('.tab__menu', tabContainers[i]) + get('.tab__menu', tabContainers[i]).addEventListener("click", tabClick); + // console.log(tab_menu, tab_menu.classList, tab_menu.classList.contains("function")) + + // if (tab_menu.classList.contains("function")) tab_menu.addEventListener("click", tabFunctionClick); + // else tab_menu.addEventListener("click", tabClick); + } + + // each click event is scoped to the tab_container + function tabClick(event) { + let scope = event.currentTarget.parentNode; + let clickedTab = event.target; + let tabs = getAll('.tab', scope); + let panes = getAll('.tab__pane', scope); + let activePane = get(`.${clickedTab.getAttribute('data-tab')}`, scope); + + // remove all active tab classes + for (let i = 0; i < tabs.length; i++) { + tabs[i].classList.remove('active'); + } + + // remove all active pane classes + for (let i = 0; i < panes.length; i++) { + panes[i].classList.remove('active'); + } + + // apply active classes on desired tab and pane + clickedTab.classList.add('active'); + + if (clickedTab.classList.contains("function")) { + let functionString = clickedTab.getAttribute('data-function') + let functionArgsString = clickedTab.getAttribute('data-function-arguments') + + if (window[functionString] && typeof(window[functionString]) === "function") window[functionString].apply({}, JSON.parse(functionArgsString || "[]")) + } else { + activePane.classList.add('active'); + } + } +}); + +//in page scrolling for documentaiton page +let btns = getAll('.js-btn'); +let sections = getAll('.js-section'); + +function setActiveLink(event) { + // remove all active tab classes + for (let i = 0; i < btns.length; i++) { + btns[i].classList.remove('selected'); + } + + event.target.classList.add('selected'); +} + +function smoothScrollTo(i, event) { + let element = sections[i]; + setActiveLink(event); + + window.scrollTo({ + 'behavior': 'smooth', + 'top': element.offsetTop - 20, + 'left': 0 + }); +} + +function triggerClick(elemID) { + const element = document.getElementById(elemID) + if (element) element.click() +} + +if (btns.length && sections.length > 0) { + for (let i = 0; i < btns.length; i++) { + btns[i].addEventListener('click', smoothScrollTo.bind(this, i)); + } +} + +// fix menu to page-top once user starts scrolling +window.addEventListener('scroll', function () { + let docNav = get('.doc__nav > ul'); + + if (docNav) { + if (window.pageYOffset > 63) { + docNav.classList.add('fixed'); + } else { + docNav.classList.remove('fixed'); + } + } +}); + +// responsive navigation +let topNav = get('.menu'); +let icon = get('.toggle'); + +window.addEventListener('load', function () { + function showNav() { + if (topNav.className === 'menu') { + topNav.className += ' responsive'; + icon.className += ' open'; + } else { + topNav.className = 'menu'; + icon.classList.remove('open'); + } + } + icon.addEventListener('click', showNav); +}); + +function changeMdFormatter(id, formatter) { + const mdTag = document.getElementById(id) + const term = mdTag.parentElement + + switch (formatter) { + case "github": { + term.style.backgroundColor = "var(--bg-color)" + break; + } + // case "marked": + // case "commonmark": { + // term.style.backgroundColor = "#605F5F" + // break; + // } + default: { + term.style.backgroundColor = "var(--medium-gray-color)"//"#605F5F" //"#232323" + } + } + + if (mdTag) mdTag.setAttribute("flavor", formatter) +} \ No newline at end of file diff --git a/docs/stylesheets/docs.css b/docs/stylesheets/docs.css new file mode 100644 index 0000000..0d22525 --- /dev/null +++ b/docs/stylesheets/docs.css @@ -0,0 +1,125 @@ +html, +body { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +a { + color: var(--accent-color); +} + +/* layout */ +.header { + border-bottom: 1px solid var(--code-bg-color); + grid-template-columns: 1fr 150px 60% 1fr; +} + +.wrapper { + display: flex; + flex-grow: 1; +} + +/* logo */ +.logo { + font-weight: 900; + color: var(--primary-color); + font-size: 1.4em; + grid-column: 2; +} + +.logo__thin { + font-weight: 300; +} + +/* menu */ +.menu { + grid-template-columns: 1fr 180px 60% 1fr; +} + +.menu__item { + padding: 1.5rem 1rem; +} + +/* doc */ +.doc__bg { + position: fixed; + top: 0; + left: 0; + bottom: 0; + width: 28%; + background-color: var(--bg-color); + z-index: -1; +} + +.doc__nav { + flex-basis: 20%; + font-weight: 200; +} + +.doc__nav ul { + list-style: none; + padding-left: 0; + line-height: 1.8; +} + +.doc__nav ul.fixed { + position: fixed; + top: 2rem; +} + +.doc__nav li:hover { + color: var(--primary-color-light); + cursor: pointer; + transition: color .3s ease-in-out; +} + +.doc__nav .selected { + color: var(--accent-color); + position: relative; +} + +.doc__nav .selected:after { + position: absolute; + content: ""; + width: 1rem; + height: 1rem; + background-color: var(--accent-color); + left: -1.5rem; + top: 0.3rem; +} + +.doc__content { + flex-basis: 80%; + padding: 0 0 5rem 1rem; +} + +@media (max-width: 750px) { + .wrapper { + flex-direction: column; + } + + .doc__content { + padding-left: 0; + } + + .doc__nav ul { + border-bottom: 1px solid var(--code-bg-color); + padding-bottom: 0.5rem; + } + + .doc__nav ul.fixed { + /* nutralized the fixed menu for mobile*/ + position: relative; + top: 0; + } + + .doc__nav li { + display: inline-block; + padding-right: 1rem; + } + + .doc__nav .selected:after { + display: none; + } +} \ No newline at end of file diff --git a/docs/stylesheets/global.css b/docs/stylesheets/global.css new file mode 100644 index 0000000..1be3d14 --- /dev/null +++ b/docs/stylesheets/global.css @@ -0,0 +1,333 @@ +/* css variables*/ +:root { + --primary-color: #432E30; + --primary-color-light: #8E7474; + --accent-color: #FE6A6B; + --accent-color-light: #FFE4E4; + --accent-color-dark: #B94B4C; + --white-color: #FAFBFC; + --light-gray-color: #C6CBD1; + --medium-gray-color: #959DA5; + --dark-gray-color: #444D56; + --bg-color: #F8F8FA; + --code-bg-color: #F0E8E8; + --scrollbar-bg: rgb(201 201 201); + --scrollbar-thumb: rgb(133 133 133); +} + +/* Scrollbar */ +*::-webkit-scrollbar { + width: 12px; /* width of the entire scrollbar */ +} +*::-webkit-scrollbar-track { + background: var(--scrollbar-bg); /* color of the tracking area */ +} +*::-webkit-scrollbar-thumb { + background-color: var(--scrollbar-thumb); /* color of the scroll thumb */ + border-radius: 20px; /* roundness of the scroll thumb */ + border: 3px solid var(--scrollbar-bg); /* creates padding around scroll thumb */ +} + + +/* normalized */ +html, +body { + padding: 0; + margin: 0; + font-family: 'Nunito Sans', sans-serif; + background-color: white; +} + +p { + font-weight: 300; + color: #4A4A4A; +} + +a, +a:hover { + text-decoration: none; + color: var(--primary-color); +} + +hr { + padding: 1rem 0; + border: 0; + border-bottom: 1px solid var(--bg-color); +} + +* { + box-sizing: border-box; +} + +/* global components */ + +/* typography */ +.section__title { + color: var(--primary-color); +} + +/* tabs */ +.tab__container { + position: relative; +} + +.tab__container>ul { + position: absolute; + list-style: none; + margin: 0; + right: 1rem; + top: -2rem; + padding-left: 0; +} + +.tab__container .code { + white-space: normal; + padding: 1rem 1.5rem; +} + +.tab { + display: inline-block; + padding: 0.3rem 0.5rem; + font-weight: 200; + cursor: pointer; +} + +.tab.active { + border-bottom: 1px solid var(--primary-color); + font-weight: 700; + display: inline-block; +} + +.tab__pane { + display: none; +} + +.tab__pane.active { + display: block; +} + +/* code */ +.code { + border-radius: 3px; + font-family: Space Mono, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace; + background: var(--bg-color); + border: 1px solid var(--code-bg-color); + color: var(--primary-color-light); +} + +.code--block { + white-space: pre-line; + padding: 0 1.5rem; +} + +.code--inline { + padding: 3px 6px; + font-size: 80%; +} + +/* buttons */ +.button--primary { + padding: 10px 22px; + background-color: var(--accent-color); + color: white; + position: relative; + text-decoration: none; + border: 0; + transition: all .3s ease-out; +} + +.button--primary:after { + position: absolute; + content: ""; + width: 1rem; + height: 1rem; + background-color: var(--accent-color-light); + right: -0.4rem; + top: -0.4rem; + transition: all 0.3s ease-out; +} + +.button--primary:hover { + text-shadow: 0px 1px 1px var(--accent-color-dark); + color: white; + transform: translate3D(0, -3px, 0); +} + +.button--primary:hover::after { + transform: rotate(90deg); +} + +.button--secondary { + padding: 10px 22px; + border: 2px solid var(--primary-color); + transition: all 0.5s ease-out; +} + +.button--secondary:hover { + border-color: var(--accent-color); + color: var(--accent-color); +} + +/* links */ +.link { + text-decoration: none; + transition: all 0.3s ease-out; +} + +.link:hover { + color: var(--accent-color); +} + +.link--dark { + color: var(--primary-color); +} + +.link--light { + color: var(--accent-color); +} + +/* menu */ +nav { + display: grid; + grid-template-columns: 70px auto; +} + +.menu { + margin: 0; + text-align: right; + overflow: hidden; + list-style: none; +} + +.toggle { + display: none; + position: relative; +} + +.toggle span, +.toggle span:before, +.toggle span:after { + content: ''; + position: absolute; + height: 2px; + width: 18px; + border-radius: 2px; + background: var(--primary-color); + display: block; + cursor: pointer; + transition: all 0.3s ease-in-out; + right: 0; +} + +.toggle span:before { + top: -6px; +} + +.toggle span:after { + bottom: -6px; +} + +.toggle.open span { + background-color: transparent; +} + +.toggle.open span:before, +.toggle.open span:after { + top: 0; +} + +.toggle.open span:before { + transform: rotate(45deg); +} + +.toggle.open span:after { + transform: rotate(-45deg); +} + +.menu__item { + padding: 1rem; + display: inline-block; +} + +.menu__item.toggle { + display: none; +} + +/* table */ +table { + border-collapse: collapse; + width: 100%; + transition: color .3s ease-out; + margin-bottom: 2rem; +} + +table td, +table th { + border: 1px solid var(--code-bg-color); + padding: 0.8rem; + font-weight: 300; +} + +table th { + text-align: left; + background-color: white; + border-color: white; + border-bottom-color: var(--code-bg-color); +} + +table td:first-child { + background-color: var(--bg-color); + font-weight: 600; +} + +@media screen and (max-width: 600px) { + nav { + grid-template-columns: 70px auto; + } + + .menu__item { + display: none; + } + + .menu__item.toggle { + display: inline-block; + } + + .menu { + text-align: right; + padding: 0.5rem 1rem; + } + + .toggle { + display: block; + } + + .menu.responsive .menu__item:not(:first-child) { + display: block; + padding: 0 0 0.5rem 0; + } +} + +/* layout */ +.wrapper { + margin: 0 auto; + width: 70%; +} + +.footer { + text-align: center; + background-color: var(--primary-color); + padding: 2rem; + color: white; +} + +@keyframes fadeUp { + 0% { + opacity: 0; + transform: translate3d(0, 30px, 0); + } + + 100% { + transform: translate3d(0, 0, 0); + } +} \ No newline at end of file diff --git a/docs/stylesheets/landing.css b/docs/stylesheets/landing.css new file mode 100644 index 0000000..472e6c1 --- /dev/null +++ b/docs/stylesheets/landing.css @@ -0,0 +1,193 @@ +/* nav specialized to landing page */ +.logo { + background: url('../svg/logo.svg') no-repeat; + background-size: contain; + margin: 1rem 0 0 1rem; +} + +nav { + background-color: var(--bg-color); +} + +/* hero section */ +.hero { + text-align: center; + background-color: var(--bg-color); + padding: 2rem 0 10rem 0; +} + +.hero__title { + font-weight: 900; + color: var(--primary-color); +} + +.hero__description { + margin: -1rem auto 2rem auto; +} + +/* @keyframes fadeUp { from { opacity: 0; } to { opacity: 1; }} */ +@keyframes fadeUp { + from { + transform: translate3d(0,40px,0); + opacity: 0; + } + + to { + transform: translate3d(0,0,0); + opacity: 1 + } +} +.hero__terminal { + margin-top: 1px !important; + width: 60%; + margin: -11rem auto 3rem auto; + text-align: left; + color: white; + padding: 0 1rem; + border-radius: 4px; + background-color: #605F5F; + min-height: 285px; + animation: fadeUp 2s; + box-shadow: 0px 12px 36.8px 9.2px rgba(0, 0, 0, 0.1); + opacity: 0; + animation-fill-mode: forwards; +} + +.hero__image { + width: 60%; + margin: -11rem auto 3rem auto; + text-align: center; + color: white; + padding: 0 1rem; + border-radius: 4px; + background-color: transparent; + min-height: 285px; + animation: fadeUp 2s; + box-shadow: 0px 12px 36.8px 9.2px rgba(0, 0, 0, 0.1); + display: flex; + justify-content: center; +} + +.hero__image img { + display: block; + /* max-width:230px; + max-height:95px; */ + width: auto; + height: auto; +} + +.hero__image pre { + background-color: transparent; + white-space: pre-line; + padding-top: 1rem; +} + +.hero__terminal pre { + white-space: pre-line; + padding-top: 1rem; +} + +/* feature section */ +.feature { + display: flex; + flex-wrap: wrap; + flex-direction: row; + justify-content: center; +} + +.feature__item { + max-width: calc(33% - 20px); + margin: 0 20px 0 0; +} + +.feature__item .section__title { + margin-bottom: 0; +} + +.feature__item p { + margin-top: 0.5rem; +} + +/* callout section */ +.callout { + text-align: center; + padding: 1rem 0 3rem 0; +} + +.callout .button--primary { + display: inline-block; + margin-top: 0.5rem; +} + +/* changelog section */ +.changelog { + background-color: var(--bg-color); + padding: 2rem 0; +} + +.changelog__item { + display: flex; +} + +.changelog__meta { + flex-basis: 25%; +} + +.changelog__meta small { + color: var(--primary-color-light); + font-weight: 200; + letter-spacing: 1px; +} + +.changelog__title { + margin-bottom: 0; +} + +.changelog__callout { + margin: 3rem auto 2rem auto; + text-align: center; +} + +@media (max-width: 750px) { + .hero__terminal { + width: 70%; + } + + .hero__terminal pre .hero__terminal { + margin-left: 0; + margin-right: 0; + } + + .hero__terminal .tab__container { + left: 70% !important; + } + + .hero__image { + width: 70%; + } + + .hero__image img { + /* width: 70%; */ + transform: scale(0.5, 0.5); + } + + .tab__container>ul { + right: auto; + left: 0; + padding-left: 0; + } + + .tab__container .code { + margin-top: 2rem; + } + + .feature, + .changelog__item { + flex-direction: column; + } + + .feature__item { + max-width: 100%; + margin: 0; + } +} \ No newline at end of file diff --git a/docs/svg/logo.svg b/docs/svg/logo.svg new file mode 100644 index 0000000..171ea25 --- /dev/null +++ b/docs/svg/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/markdown-tag-wc.js b/markdown-tag-wc.js new file mode 100644 index 0000000..2ac9dae --- /dev/null +++ b/markdown-tag-wc.js @@ -0,0 +1,433 @@ +/*! (c) Andrea Giammarchi (https://github.com/WebReflection/html-parsed-element) - ISC */ +const HTMLParsedElement = (() => { + const DCL = 'DOMContentLoaded'; + const init = new WeakMap; + const queue = []; + const isParsed = el => { + do { + if (el.nextSibling) + return true; + } while (el = el.parentNode); + return false; + }; + const upgrade = () => { + queue.splice(0).forEach(info => { + if (init.get(info[0]) !== true) { + init.set(info[0], true); + info[0][info[1]](); + } + }); + }; + document.addEventListener(DCL, upgrade); + class HTMLParsedElement extends HTMLElement {//HTMLTextAreaElement + static withParsedCallback(Class, name = 'parsed') { + const { + prototype + } = Class; + const { + connectedCallback + } = prototype; + const method = name + 'Callback'; + const cleanUp = (el, observer, ownerDocument, onDCL) => { + observer.disconnect(); + ownerDocument.removeEventListener(DCL, onDCL); + parsedCallback(el); + }; + const parsedCallback = el => { + if (!queue.length) + requestAnimationFrame(upgrade); + queue.push([el, method]); + }; + Object.defineProperties( + prototype, { + connectedCallback: { + configurable: true, + writable: true, + value() { + if (connectedCallback) + connectedCallback.apply(this, arguments); + if (method in this && !init.has(this)) { + const self = this; + const { + ownerDocument + } = self; + init.set(self, false); + if (ownerDocument.readyState === 'complete' || isParsed(self)) + parsedCallback(self); + else { + const onDCL = () => cleanUp(self, observer, ownerDocument, onDCL); + ownerDocument.addEventListener(DCL, onDCL); + const observer = new MutationObserver(() => { + /* istanbul ignore else */ + if (isParsed(self)) + cleanUp(self, observer, ownerDocument, onDCL); + }); + observer.observe(self.parentNode, { + childList: true, + subtree: true + }); + } + } + } + }, + [name]: { + configurable: true, + get() { + return init.get(this) === true; + } + } + } + ); + return Class; + } + } + return HTMLParsedElement.withParsedCallback(HTMLParsedElement); +})(); + +// WIP: Extend BuiltIn Element to prevent script injection +// const ClassMixin = (baseClass, ...mixins) => { +// class base extends baseClass { +// constructor (...args) { +// super(...args); +// mixins.forEach((mixin) => { +// copyProps(this,(new mixin)); +// }); +// } +// } +// let copyProps = (target, source) => { // this function copies all properties and symbols, filtering out some special ones +// Object.getOwnPropertyNames(source) +// .concat(Object.getOwnPropertySymbols(source)) +// .forEach((prop) => { +// if (!prop.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/)) +// Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop)); +// }) +// } +// mixins.forEach((mixin) => { // outside contructor() to allow aggregation(A,B,C).staticFunction() to be called etc. +// copyProps(base.prototype, mixin.prototype); +// copyProps(base, mixin); +// }); +// return base; +// } + +/** + * Markdown Tag Web Component Script + * By: DarkenLM + */ + +function mdt_wc_init() { + class MDTag extends HTMLParsedElement {// ClassMixin(HTMLTextAreaElement, HTMLParsedElement) WIP: Extend BuiltIn Element to prevent script injection + constructor() { + super(); + this.generated = false + this.defaultFlavor = "showdown" + this.defaultParserRoot = "./" + this.flavors = [ + "commonmark", + "github", + "marked", + "showdown" + ] + + this.shadow = this.attachShadow({mode: 'open'}); + this.headID = `md-tag-head-${Date.now()}${Math.floor(Math.random() * 999) + 1}` + + const shadowHead = document.createElement("head") + shadowHead.id = this.headID + this.shadow.appendChild(shadowHead) + this.contentObserver = null + } + + get parserRoot() { + return this.hasAttribute("parser_root") ? this.getAttribute("parser_root") : this.defaultParserRoot; + } + + set parserRoot(val) { + if (val && typeof(val) === "string") { + this.setAttribute("parser_root", val); + } else this.setAttribute("parser_root", this.defaultParserRoot) + } + + get flavor() { + return this.hasAttribute("flavor") ? this.flavors.includes(this.getAttribute("flavor")) ? this.getAttribute("flavor") : this.defaultFlavor: this.defaultFlavor; + } + + set flavor(val) { + if (val && typeof(val) === "string") { + if (this.flavors.includes(val)) { + this.setAttribute("flavor", val); + } else this.setAttribute("flavor", this.defaultFlavor); + } else this.setAttribute("flavor", this.defaultFlavor); + } + + get raw() { + const contentDiv = this.shadow.querySelector("div[md-tag-role='raw']") + if (contentDiv) { + return contentDiv.innerHTML + } + } + + set raw(val) { + const contentDiv = this.shadow.querySelector("div[md-tag-role='raw']") + if (contentDiv) { + contentDiv.innerHTML = val + this.generateMarkdown() + return; + } + } + + addCss(url) { + const head = this.shadow.querySelector(`#${this.headID}`) + const link = document.createElement("link"); + + link.id = `md-style-${Date.now()}${Math.floor(Math.random() * 999) + 1}` + link.type = "text/css"; + link.rel = "stylesheet"; + link.href = url; + + head.appendChild(link); + } + + assertStyleSheet(url) { + const ss = this.shadow.styleSheets + for (let i = 0, max = ss.length; i < max; i++) { + if (ss[i].href === url) return true; + } + + return false + } + + addJs(url) { + return new Promise((resolve, reject) => { + try { + const head = document.head; + const script = document.createElement("script"); + + script.id = `md-script-${Date.now()}${Math.floor(Math.random() * 999) + 1}` + script.type = "text/javascript"; + script.src = url; + + script.addEventListener("load", () => { + resolve() + }) + + head.appendChild(script); + } catch(e) { + reject(e) + } + }) + } + + removeScriptTagWithURL(url) { + const head = document.head; + const script = document.querySelector(`script[src="${url}"]`) + + if (script) { + head.removeChild(script) + return true + } else return false + } + + async ensureMarkdownParser(parser) { + if (parser) { + switch (parser) { + case "showdown": { + if (!window.hasOwnProperty("showdown")) { + try { + await this.addJs(`${this.parserRoot.endsWith("/") ? this.parserRoot.slice(0, -1) : this.parserRoot}/showdown/showdown.min.js`).catch(e => { throw e }) + } catch(e) { + if (!window.hasOwnProperty("showdown")) { + await this.addJs("https://cdn.jsdelivr.net/npm/showdown@2.1.0/dist/showdown.min.js") + } else return true + + if (!window.hasOwnProperty("showdown")) { + console.error(`Unable to load markdown parser "showdown": ${e}`) + return false + } + } + } + break; + } + case "commonmark": { + if (!window.hasOwnProperty("md")) { + try { + await this.addJs(`${this.parserRoot.endsWith("/") ? this.parserRoot.slice(0, -1) : this.parserRoot}/commonmark/markdown-it.min.js`).catch(e => { throw e }) + } catch(e) { + if (!window.hasOwnProperty("md")) { + await this.addJs("https://cdn.jsdelivr.net/npm/markdown-it@13.0.1/dist/markdown-it.min.js") + } else return true + + if (!window.hasOwnProperty("md")) { + console.error(`Unable to load markdown parser "markdown-it": ${e}`) + return false + } + } + } + break; + } + case "marked": { + if (!window.hasOwnProperty("marked")) { + try { + await this.addJs(`${this.parserRoot.endsWith("/") ? this.parserRoot.slice(0, -1) : this.parserRoot}/marked/marked.min.js`).catch(e => { throw e }) + } catch(e) { + if (!window.hasOwnProperty("marked")) { + await this.addJs("https://cdn.jsdelivr.net/npm/marked/marked.min.js") + } else return true + + if (!window.hasOwnProperty("marked")) { + console.error(`Unable to load markdown parser "marked": ${e}`) + return false + } + } + } + break; + } + case "DOMPurify": { + if (!window.hasOwnProperty("DOMPurify")) { + try { + await this.addJs(`${this.parserRoot.endsWith("/") ? this.parserRoot.slice(0, -1) : this.parserRoot}/DOMPurify/purify.min.js`).catch(e => { throw e }) + } catch(e) { + if (!window.hasOwnProperty("DOMPurify")) { + await this.addJs("https://cdn.jsdelivr.net/npm/dompurify@2.3.8/dist/purify.min.js") + } else return true + + if (!window.hasOwnProperty("DOMPurify")) { + console.error(`Unable to load markdown parser addon "DOMPurify": ${e}`) + return false + } + } + } + break; + } + } + } + } + + sanitizeHTML(html) { + let sanitized = DOMPurify.sanitize(html) + return sanitized + } + + async generateMarkdown() { + const flavor = this.flavor + let contentDiv = this.shadow.querySelector("div[md-tag-role='raw']") + let displayDiv = this.shadow.querySelector("div[md-tag-role='display']") + + if (!contentDiv || !displayDiv) this.generateFields() + contentDiv = this.shadow.querySelector("div[md-tag-role='raw']") + displayDiv = this.shadow.querySelector("div[md-tag-role='display']") + + await this.ensureMarkdownParser("DOMPurify") + + switch (flavor) { + case "commonmark": { + await this.ensureMarkdownParser("commonmark") + const md = window.markdownit() + displayDiv.innerHTML = md.render(this.sanitizeHTML(contentDiv.innerHTML)) + break; + } + case "marked": { + await this.ensureMarkdownParser("marked") + displayDiv.innerHTML = window.marked.parse(this.sanitizeHTML(contentDiv.innerText)) + break; + } + case "github": { + const css = "https://cdn.jsdelivr.net/gh/MarketingPipeline/Markdown-Elements/stylesheets/github_md.css" + if (!this.assertStyleSheet(css)) this.addCss(css) + await this.ensureMarkdownParser("showdown") + + const converter = new window.showdown.Converter() + converter.setOption('tables', 'on') + converter.setOption('emoji', 'on') + converter.setOption('strikethrough', 'on'); + converter.setOption('tasklists', 'true'); + converter.setOption('ghMentions', 'true'); + converter.setOption('simplifiedAutoLink', 'true'); + + const githubMDTag = document.createElement("github-md") + githubMDTag.innerHTML = converter.makeHtml(this.sanitizeHTML(contentDiv.innerText)) + + displayDiv.innerHTML = "" + displayDiv.appendChild(githubMDTag) + break; + } + case "showdown": + default: { + await this.ensureMarkdownParser("showdown") + + const converter = new window.showdown.Converter() + converter.setOption('tables', 'on') + converter.setOption('emoji', 'on') + converter.setOption('strikethrough', 'on'); + converter.setOption('tasklists', 'true'); + converter.setOption('ghMentions', 'true'); + converter.setOption('simplifiedAutoLink', 'true'); + + displayDiv.innerHTML = converter.makeHtml(this.sanitizeHTML(contentDiv.innerText)) + } + } + } + + generateFields() { + const _contentDiv = this.shadow.querySelector("div[md-tag-role='raw']") + const _displayDiv = this.shadow.querySelector("div[md-tag-role='display']") + + if (_contentDiv && _displayDiv) return; + + let contentDiv = document.createElement("div") + if (!_contentDiv) { + contentDiv.setAttribute("md-tag-role", "raw") + contentDiv.style.display = "none" + contentDiv.innerHTML = _contentDiv?.innerHTML || this.innerHTML + } + + let displayDiv = document.createElement("div") + if (!_displayDiv) { + displayDiv.setAttribute("md-tag-role", "display") + displayDiv.style.display = "block" + } + + if (!_contentDiv) this.shadow.appendChild(contentDiv) + if (!_displayDiv) this.shadow.appendChild(displayDiv) + } + + // Spec-defined Methods + + parsedCallback() { + this.contentObserver = new MutationObserver((mutations, observer) => { + this.raw = this.innerHTML + }); + + this.contentObserver.observe(this, { + characterData: true, + subtree: true + }); + + this.generateFields() + this.generateMarkdown() + this.generated = true + } + + attributeChangedCallback(attrName, oldVal, newVal) { + if (!this.generated) return; + if (oldVal == newVal) return; + + switch (attrName) { + case "flavor": { + this.flavor = newVal + this.generateMarkdown() + break; + } + } + } + + static get observedAttributes() { + return [ + "flavor" + ]; + } + } + + window.customElements.define("md-tag", MDTag, { extends: "textarea" }); +} + +mdt_wc_init() \ No newline at end of file diff --git a/parsers/DOMPurify/purify.min.js b/parsers/DOMPurify/purify.min.js new file mode 100644 index 0000000..2790f5b --- /dev/null +++ b/parsers/DOMPurify/purify.min.js @@ -0,0 +1,3 @@ +/*! @license DOMPurify 2.3.8 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.8/LICENSE */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).DOMPurify=t()}(this,(function(){"use strict";function e(t){return(e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(t)}function t(e,n){return(t=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,n)}function n(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(e){return!1}}function r(e,o,a){return(r=n()?Reflect.construct:function(e,n,r){var o=[null];o.push.apply(o,n);var a=new(Function.bind.apply(e,o));return r&&t(a,r.prototype),a}).apply(null,arguments)}function o(e){return function(e){if(Array.isArray(e))return a(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return a(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return a(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function a(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n1?n-1:0),o=1;o/gm),q=f(/^data-[\-\w.\u00B7-\uFFFF]/),Y=f(/^aria-[\-\w]+$/),K=f(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),V=f(/^(?:\w+script|data):/i),$=f(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),X=f(/^html$/i),Z=function(){return"undefined"==typeof window?null:window},J=function(t,n){if("object"!==e(t)||"function"!=typeof t.createPolicy)return null;var r=null,o="data-tt-policy-suffix";n.currentScript&&n.currentScript.hasAttribute(o)&&(r=n.currentScript.getAttribute(o));var a="dompurify"+(r?"#"+r:"");try{return t.createPolicy(a,{createHTML:function(e){return e}})}catch(e){return console.warn("TrustedTypes policy "+a+" could not be created."),null}};return function t(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:Z(),r=function(e){return t(e)};if(r.version="2.3.8",r.removed=[],!n||!n.document||9!==n.document.nodeType)return r.isSupported=!1,r;var a=n.document,i=n.document,l=n.DocumentFragment,c=n.HTMLTemplateElement,u=n.Node,s=n.Element,f=n.NodeFilter,p=n.NamedNodeMap,d=void 0===p?n.NamedNodeMap||n.MozNamedAttrMap:p,h=n.HTMLFormElement,g=n.DOMParser,y=n.trustedTypes,_=s.prototype,Q=C(_,"cloneNode"),ee=C(_,"nextSibling"),te=C(_,"childNodes"),ne=C(_,"parentNode");if("function"==typeof c){var re=i.createElement("template");re.content&&re.content.ownerDocument&&(i=re.content.ownerDocument)}var oe=J(y,a),ae=oe?oe.createHTML(""):"",ie=i,le=ie.implementation,ce=ie.createNodeIterator,ue=ie.createDocumentFragment,se=ie.getElementsByTagName,me=a.importNode,fe={};try{fe=D(i).documentMode?i.documentMode:{}}catch(e){}var pe={};r.isSupported="function"==typeof ne&&le&&void 0!==le.createHTMLDocument&&9!==fe;var de,he,ge=G,ye=W,be=q,ve=Y,Te=V,Ne=$,Ee=K,Ae=null,we=O({},[].concat(o(M),o(R),o(L),o(F),o(U))),xe=null,ke=O({},[].concat(o(z),o(B),o(j),o(P))),Se=Object.seal(Object.create(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),_e=null,Oe=null,De=!0,Ce=!0,Me=!1,Re=!1,Le=!1,Ie=!1,Fe=!1,He=!1,Ue=!1,ze=!1,Be=!0,je=!0,Pe=!1,Ge={},We=null,qe=O({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),Ye=null,Ke=O({},["audio","video","img","source","image","track"]),Ve=null,$e=O({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Xe="http://www.w3.org/1998/Math/MathML",Ze="http://www.w3.org/2000/svg",Je="http://www.w3.org/1999/xhtml",Qe=Je,et=!1,tt=["application/xhtml+xml","text/html"],nt="text/html",rt=null,ot=i.createElement("form"),at=function(e){return e instanceof RegExp||e instanceof Function},it=function(t){rt&&rt===t||(t&&"object"===e(t)||(t={}),t=D(t),Ae="ALLOWED_TAGS"in t?O({},t.ALLOWED_TAGS):we,xe="ALLOWED_ATTR"in t?O({},t.ALLOWED_ATTR):ke,Ve="ADD_URI_SAFE_ATTR"in t?O(D($e),t.ADD_URI_SAFE_ATTR):$e,Ye="ADD_DATA_URI_TAGS"in t?O(D(Ke),t.ADD_DATA_URI_TAGS):Ke,We="FORBID_CONTENTS"in t?O({},t.FORBID_CONTENTS):qe,_e="FORBID_TAGS"in t?O({},t.FORBID_TAGS):{},Oe="FORBID_ATTR"in t?O({},t.FORBID_ATTR):{},Ge="USE_PROFILES"in t&&t.USE_PROFILES,De=!1!==t.ALLOW_ARIA_ATTR,Ce=!1!==t.ALLOW_DATA_ATTR,Me=t.ALLOW_UNKNOWN_PROTOCOLS||!1,Re=t.SAFE_FOR_TEMPLATES||!1,Le=t.WHOLE_DOCUMENT||!1,He=t.RETURN_DOM||!1,Ue=t.RETURN_DOM_FRAGMENT||!1,ze=t.RETURN_TRUSTED_TYPE||!1,Fe=t.FORCE_BODY||!1,Be=!1!==t.SANITIZE_DOM,je=!1!==t.KEEP_CONTENT,Pe=t.IN_PLACE||!1,Ee=t.ALLOWED_URI_REGEXP||Ee,Qe=t.NAMESPACE||Je,t.CUSTOM_ELEMENT_HANDLING&&at(t.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(Se.tagNameCheck=t.CUSTOM_ELEMENT_HANDLING.tagNameCheck),t.CUSTOM_ELEMENT_HANDLING&&at(t.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(Se.attributeNameCheck=t.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),t.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof t.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(Se.allowCustomizedBuiltInElements=t.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),de=de=-1===tt.indexOf(t.PARSER_MEDIA_TYPE)?nt:t.PARSER_MEDIA_TYPE,he="application/xhtml+xml"===de?function(e){return e}:N,Re&&(Ce=!1),Ue&&(He=!0),Ge&&(Ae=O({},o(U)),xe=[],!0===Ge.html&&(O(Ae,M),O(xe,z)),!0===Ge.svg&&(O(Ae,R),O(xe,B),O(xe,P)),!0===Ge.svgFilters&&(O(Ae,L),O(xe,B),O(xe,P)),!0===Ge.mathMl&&(O(Ae,F),O(xe,j),O(xe,P))),t.ADD_TAGS&&(Ae===we&&(Ae=D(Ae)),O(Ae,t.ADD_TAGS)),t.ADD_ATTR&&(xe===ke&&(xe=D(xe)),O(xe,t.ADD_ATTR)),t.ADD_URI_SAFE_ATTR&&O(Ve,t.ADD_URI_SAFE_ATTR),t.FORBID_CONTENTS&&(We===qe&&(We=D(We)),O(We,t.FORBID_CONTENTS)),je&&(Ae["#text"]=!0),Le&&O(Ae,["html","head","body"]),Ae.table&&(O(Ae,["tbody"]),delete _e.tbody),m&&m(t),rt=t)},lt=O({},["mi","mo","mn","ms","mtext"]),ct=O({},["foreignobject","desc","title","annotation-xml"]),ut=O({},["title","style","font","a","script"]),st=O({},R);O(st,L),O(st,I);var mt=O({},F);O(mt,H);var ft=function(e){var t=ne(e);t&&t.tagName||(t={namespaceURI:Je,tagName:"template"});var n=N(e.tagName),r=N(t.tagName);return e.namespaceURI===Ze?t.namespaceURI===Je?"svg"===n:t.namespaceURI===Xe?"svg"===n&&("annotation-xml"===r||lt[r]):Boolean(st[n]):e.namespaceURI===Xe?t.namespaceURI===Je?"math"===n:t.namespaceURI===Ze?"math"===n&&ct[r]:Boolean(mt[n]):e.namespaceURI===Je&&(!(t.namespaceURI===Ze&&!ct[r])&&(!(t.namespaceURI===Xe&&!lt[r])&&(!mt[n]&&(ut[n]||!st[n]))))},pt=function(e){T(r.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){try{e.outerHTML=ae}catch(t){e.remove()}}},dt=function(e,t){try{T(r.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){T(r.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!xe[e])if(He||Ue)try{pt(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},ht=function(e){var t,n;if(Fe)e=""+e;else{var r=E(e,/^[\r\n\t ]+/);n=r&&r[0]}"application/xhtml+xml"===de&&(e=''+e+"");var o=oe?oe.createHTML(e):e;if(Qe===Je)try{t=(new g).parseFromString(o,de)}catch(e){}if(!t||!t.documentElement){t=le.createDocument(Qe,"template",null);try{t.documentElement.innerHTML=et?"":o}catch(e){}}var a=t.body||t.documentElement;return e&&n&&a.insertBefore(i.createTextNode(n),a.childNodes[0]||null),Qe===Je?se.call(t,Le?"html":"body")[0]:Le?t.documentElement:a},gt=function(e){return ce.call(e.ownerDocument||e,e,f.SHOW_ELEMENT|f.SHOW_COMMENT|f.SHOW_TEXT,null,!1)},yt=function(e){return e instanceof h&&("string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof d)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore)},bt=function(t){return"object"===e(u)?t instanceof u:t&&"object"===e(t)&&"number"==typeof t.nodeType&&"string"==typeof t.nodeName},vt=function(e,t,n){pe[e]&&b(pe[e],(function(e){e.call(r,t,n,rt)}))},Tt=function(e){var t;if(vt("beforeSanitizeElements",e,null),yt(e))return pt(e),!0;if(k(/[\u0080-\uFFFF]/,e.nodeName))return pt(e),!0;var n=he(e.nodeName);if(vt("uponSanitizeElement",e,{tagName:n,allowedTags:Ae}),e.hasChildNodes()&&!bt(e.firstElementChild)&&(!bt(e.content)||!bt(e.content.firstElementChild))&&k(/<[/\w]/g,e.innerHTML)&&k(/<[/\w]/g,e.textContent))return pt(e),!0;if("select"===n&&k(/