diff --git a/static/css/markdown.css b/static/css/markdown.css
index ee9a8fd..151c6f7 100644
--- a/static/css/markdown.css
+++ b/static/css/markdown.css
@@ -91,10 +91,26 @@
border-radius: 4px;
}
+.markdown .table-wrapper {
+ width: 100%;
+ overflow-x: auto;
+ margin: 16px 0;
+ cursor: grab;
+ touch-action: pan-y;
+}
+
+.markdown .table-wrapper:not(.overflowing) {
+ cursor: default;
+ overflow-x: hidden;
+}
+
+.markdown .table-wrapper.dragging {
+ cursor: grabbing;
+}
+
.markdown table {
width: 100%;
border-collapse: collapse;
- margin: 16px 0;
font-size: 14px;
}
diff --git a/static/js/markdown.js b/static/js/markdown.js
index 9f41cfa..5760bbf 100644
--- a/static/js/markdown.js
+++ b/static/js/markdown.js
@@ -1,5 +1,12 @@
(() => {
- const timeouts = new WeakMap();
+ const timeouts = new WeakMap(),
+ scrollState = {
+ el: null,
+ startX: 0,
+ scrollLeft: 0,
+ pointerId: null,
+ moved: false,
+ };
marked.use({
async: false,
@@ -7,13 +14,13 @@
gfm: true,
pedantic: false,
- walkTokens: (token) => {
+ walkTokens: token => {
const { type, text } = token;
if (type === "html") {
- token.text = token.text.replace(/&/g, "&")
- token.text = token.text.replace(//g, ">")
+ token.text = token.text.replace(/&/g, "&");
+ token.text = token.text.replace(//g, ">");
return;
} else if (type !== "code") {
@@ -48,9 +55,18 @@
return `${escapeHtml(link.text || link.href)}`;
},
},
+
+ hooks: {
+ postprocess: html => {
+ html = html.replace(/
/g, ``);
+ html = html.replace(/<\/ ?table>/g, `
`);
+
+ return html;
+ },
+ },
});
- document.body.addEventListener("click", (event) => {
+ addEventListener("click", event => {
const button = event.target,
header = button.closest(".pre-header"),
pre = header?.closest("pre"),
@@ -70,11 +86,76 @@
pre,
setTimeout(() => {
button.classList.remove("copied");
- }, 1000),
+ }, 1000)
);
});
- window.render = (markdown) => {
+ addEventListener("pointerover", event => {
+ if (event.pointerType !== "mouse") {
+ return;
+ }
+
+ const el = event.target.closest(".table-wrapper");
+
+ if (!el) {
+ return;
+ }
+
+ el.classList.toggle("overflowing", el.scrollWidth - el.clientWidth > 1);
+ });
+
+ addEventListener("pointerdown", event => {
+ if (event.button !== 0) {
+ return;
+ }
+
+ const el = event.target.closest(".table-wrapper");
+
+ if (!el) {
+ return;
+ }
+
+ scrollState.el = el;
+ scrollState.pointerId = event.pointerId;
+ scrollState.startX = event.clientX;
+ scrollState.scrollLeft = el.scrollLeft;
+ scrollState.moved = false;
+
+ el.classList.add("dragging");
+ el.setPointerCapture?.(event.pointerId);
+
+ event.preventDefault();
+ });
+
+ addEventListener("pointermove", event => {
+ if (!scrollState.el || event.pointerId !== scrollState.pointerId) {
+ return;
+ }
+
+ const dx = event.clientX - scrollState.startX;
+
+ if (Math.abs(dx) > 3) {
+ scrollState.moved = true;
+ }
+
+ scrollState.el.scrollLeft = scrollState.scrollLeft - dx;
+ });
+
+ function endScroll(event) {
+ if (!scrollState.el || (event && event.pointerId !== scrollState.pointerId)) {
+ return;
+ }
+
+ scrollState.el.classList.remove("dragging");
+ scrollState.el.releasePointerCapture?.(scrollState.pointerId);
+ scrollState.el = null;
+ scrollState.pointerId = null;
+ }
+
+ addEventListener("pointerup", endScroll);
+ addEventListener("pointercancel", endScroll);
+
+ window.render = markdown => {
return marked.parse(markdown);
};
})();