Enhance Oliveboard UI and functionality.
// ==UserScript==
// @name Oliveboard Plus
// @namespace ob-enhancer
// @version 1.2.2
// @author quantavil
// @description Enhance Oliveboard UI and functionality.
// @license MIT
// @match *://*.oliveboard.in/*
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
const ICONS = {
idle: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 4v12m0 0l-4-4m4 4l4-4M4 20h16"/></svg>`,
loading: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle class="tb-sp" cx="12" cy="12" r="10" stroke-dasharray="32" stroke-linecap="round"/><path class="tb-x" d="M9 9l6 6M15 9l-6 6"/></svg>`,
success: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path class="tb-chk" d="M5 13l4 4L19 7"/></svg>`,
error: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6L6 18M6 6l12 12"/></svg>`
};
const STYLES = `
#tb-fab { position:fixed; bottom:24px; right:24px; width:44px; height:44px; border-radius:22px; background:#000; color:#fff; display:grid; place-items:center; cursor:pointer; transition:all .3s cubic-bezier(.25,.8,.25,1); box-shadow:0 4px 12px rgba(0,0,0,.15); z-index:99999; border:1px solid transparent; }
#tb-fab svg { width:20px; height:20px; transition:all .3s; }
#tb-fab[data-s="idle"] { opacity:0.3; }
#tb-fab[data-s="idle"]:hover { opacity:1; box-shadow:0 6px 16px rgba(0,0,0,.25); }
#tb-fab[data-s="idle"]:active { transform:scale(.92); }
#tb-fab[data-s="success"] { background:#fff; color:#000; border-color:#e5e5e5; pointer-events:none; transform:scale(1.1); }
#tb-fab[data-s="error"] { background:#fff; color:#000; border-color:#000; animation:sh .4s; }
#tb-fab::after { content:attr(data-t); position:absolute; right:56px; background:#000; color:#fff; padding:6px 12px; border-radius:6px; font:500 12px/1.2 -apple-system,sans-serif; opacity:0; pointer-events:none; transition:opacity .2s; white-space:nowrap; box-shadow:0 2px 8px rgba(0,0,0,.2); }
#tb-fab:hover::after, #tb-fab[data-s="loading"]::after, #tb-fab[data-s="error"]::after { opacity:1; }
#tb-fab[data-s="idle"]:not(:hover)::after { opacity:0; }
#tb-fab .tb-x { opacity:0; transition:opacity .2s; }
#tb-fab[data-s="loading"]:hover .tb-sp { opacity:0; }
#tb-fab[data-s="loading"]:hover .tb-x { opacity:1; }
#tb-fab[data-s="loading"]:hover::after { content:"Cancel"; }
@keyframes sh { 25%,75%{transform:translateX(-4px)} 50%{transform:translateX(4px)} }
#tb-fab .tb-chk { stroke-dasharray:24; stroke-dashoffset:24; animation:tb-draw .4s forwards .1s; }
@keyframes tb-draw { to { stroke-dashoffset:0; } }
`;
class DownloaderUI {
constructor(onStart, onCancel) {
__publicField(this, "el", document.createElement("div"));
__publicField(this, "state", "idle");
__publicField(this, "spinRAF", null);
__publicField(this, "timer", null);
this.onStart = onStart;
this.onCancel = onCancel;
this.el.id = "tb-fab";
this.el.onclick = () => {
if (this.state === "idle" || this.state === "error") {
this.setState("loading");
return this.onStart();
}
if (this.state === "loading") {
this.setState("idle");
return this.onCancel();
}
};
this.setState("idle");
}
startSpin() {
const el = this.el.querySelector(".tb-sp");
if (!el) return;
let last = null;
let angle = 0;
const tick = (now) => {
if (last !== null) angle = (angle + (now - last) * 0.36) % 360;
last = now;
el.setAttribute("transform", `rotate(${angle} 12 12)`);
this.spinRAF = requestAnimationFrame(tick);
};
this.spinRAF = requestAnimationFrame(tick);
}
stopSpin() {
if (this.spinRAF !== null) {
cancelAnimationFrame(this.spinRAF);
this.spinRAF = null;
}
}
setState(s) {
this.stopSpin();
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
this.state = s;
this.el.dataset.s = s;
this.el.innerHTML = ICONS[s];
this.el.dataset.t = { idle: "Download", loading: "Crawling…", success: "Done!", error: "Failed — retry" }[s];
if (s === "loading") this.startSpin();
}
mount() {
if (!document.getElementById("tb-css")) {
document.head.insertAdjacentHTML("beforeend", `<style id="tb-css">${STYLES}</style>`);
}
document.body.appendChild(this.el);
}
updateStatus(m) {
if (this.state === "loading") this.el.dataset.t = m;
}
error(m) {
this.setState("error");
if (m) this.el.dataset.t = `Error: ${m}`;
this.timer = window.setTimeout(() => {
if (this.state === "error") this.setState("idle");
this.timer = null;
}, 5e3);
}
finish() {
this.setState("success");
this.timer = window.setTimeout(() => {
if (this.state === "success") this.setState("idle");
this.timer = null;
}, 2500);
}
}
function extend(destination) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) destination[key] = source[key];
}
}
return destination;
}
function repeat(character, count) {
return Array(count + 1).join(character);
}
function trimLeadingNewlines(string) {
return string.replace(/^\n*/, "");
}
function trimTrailingNewlines(string) {
var indexEnd = string.length;
while (indexEnd > 0 && string[indexEnd - 1] === "\n") indexEnd--;
return string.substring(0, indexEnd);
}
function trimNewlines(string) {
return trimTrailingNewlines(trimLeadingNewlines(string));
}
var blockElements = ["ADDRESS", "ARTICLE", "ASIDE", "AUDIO", "BLOCKQUOTE", "BODY", "CANVAS", "CENTER", "DD", "DIR", "DIV", "DL", "DT", "FIELDSET", "FIGCAPTION", "FIGURE", "FOOTER", "FORM", "FRAMESET", "H1", "H2", "H3", "H4", "H5", "H6", "HEADER", "HGROUP", "HR", "HTML", "ISINDEX", "LI", "MAIN", "MENU", "NAV", "NOFRAMES", "NOSCRIPT", "OL", "OUTPUT", "P", "PRE", "SECTION", "TABLE", "TBODY", "TD", "TFOOT", "TH", "THEAD", "TR", "UL"];
function isBlock(node) {
return is(node, blockElements);
}
var voidElements = ["AREA", "BASE", "BR", "COL", "COMMAND", "EMBED", "HR", "IMG", "INPUT", "KEYGEN", "LINK", "META", "PARAM", "SOURCE", "TRACK", "WBR"];
function isVoid(node) {
return is(node, voidElements);
}
function hasVoid(node) {
return has(node, voidElements);
}
var meaningfulWhenBlankElements = ["A", "TABLE", "THEAD", "TBODY", "TFOOT", "TH", "TD", "IFRAME", "SCRIPT", "AUDIO", "VIDEO"];
function isMeaningfulWhenBlank(node) {
return is(node, meaningfulWhenBlankElements);
}
function hasMeaningfulWhenBlank(node) {
return has(node, meaningfulWhenBlankElements);
}
function is(node, tagNames) {
return tagNames.indexOf(node.nodeName) >= 0;
}
function has(node, tagNames) {
return node.getElementsByTagName && tagNames.some(function(tagName) {
return node.getElementsByTagName(tagName).length;
});
}
var markdownEscapes = [[/\\/g, "\\\\"], [/\*/g, "\\*"], [/^-/g, "\\-"], [/^\+ /g, "\\+ "], [/^(=+)/g, "\\$1"], [/^(#{1,6}) /g, "\\$1 "], [/`/g, "\\`"], [/^~~~/g, "\\~~~"], [/\[/g, "\\["], [/\]/g, "\\]"], [/^>/g, "\\>"], [/_/g, "\\_"], [/^(\d+)\. /g, "$1\\. "]];
function escapeMarkdown(string) {
return markdownEscapes.reduce(function(accumulator, escape) {
return accumulator.replace(escape[0], escape[1]);
}, string);
}
var rules$1 = {};
rules$1.paragraph = {
filter: "p",
replacement: function(content) {
return "\n\n" + content + "\n\n";
}
};
rules$1.lineBreak = {
filter: "br",
replacement: function(content, node, options) {
return options.br + "\n";
}
};
rules$1.heading = {
filter: ["h1", "h2", "h3", "h4", "h5", "h6"],
replacement: function(content, node, options) {
var hLevel = Number(node.nodeName.charAt(1));
if (options.headingStyle === "setext" && hLevel < 3) {
var underline = repeat(hLevel === 1 ? "=" : "-", content.length);
return "\n\n" + content + "\n" + underline + "\n\n";
} else {
return "\n\n" + repeat("#", hLevel) + " " + content + "\n\n";
}
}
};
rules$1.blockquote = {
filter: "blockquote",
replacement: function(content) {
content = trimNewlines(content).replace(/^/gm, "> ");
return "\n\n" + content + "\n\n";
}
};
rules$1.list = {
filter: ["ul", "ol"],
replacement: function(content, node) {
var parent = node.parentNode;
if (parent.nodeName === "LI" && parent.lastElementChild === node) {
return "\n" + content;
} else {
return "\n\n" + content + "\n\n";
}
}
};
rules$1.listItem = {
filter: "li",
replacement: function(content, node, options) {
var prefix = options.bulletListMarker + " ";
var parent = node.parentNode;
if (parent.nodeName === "OL") {
var start = parent.getAttribute("start");
var index = Array.prototype.indexOf.call(parent.children, node);
prefix = (start ? Number(start) + index : index + 1) + ". ";
}
var isParagraph = /\n$/.test(content);
content = trimNewlines(content) + (isParagraph ? "\n" : "");
content = content.replace(/\n/gm, "\n" + " ".repeat(prefix.length));
return prefix + content + (node.nextSibling ? "\n" : "");
}
};
rules$1.indentedCodeBlock = {
filter: function(node, options) {
return options.codeBlockStyle === "indented" && node.nodeName === "PRE" && node.firstChild && node.firstChild.nodeName === "CODE";
},
replacement: function(content, node, options) {
return "\n\n " + node.firstChild.textContent.replace(/\n/g, "\n ") + "\n\n";
}
};
rules$1.fencedCodeBlock = {
filter: function(node, options) {
return options.codeBlockStyle === "fenced" && node.nodeName === "PRE" && node.firstChild && node.firstChild.nodeName === "CODE";
},
replacement: function(content, node, options) {
var className = node.firstChild.getAttribute("class") || "";
var language = (className.match(/language-(\S+)/) || [null, ""])[1];
var code = node.firstChild.textContent;
var fenceChar = options.fence.charAt(0);
var fenceSize = 3;
var fenceInCodeRegex = new RegExp("^" + fenceChar + "{3,}", "gm");
var match;
while (match = fenceInCodeRegex.exec(code)) {
if (match[0].length >= fenceSize) {
fenceSize = match[0].length + 1;
}
}
var fence = repeat(fenceChar, fenceSize);
return "\n\n" + fence + language + "\n" + code.replace(/\n$/, "") + "\n" + fence + "\n\n";
}
};
rules$1.horizontalRule = {
filter: "hr",
replacement: function(content, node, options) {
return "\n\n" + options.hr + "\n\n";
}
};
rules$1.inlineLink = {
filter: function(node, options) {
return options.linkStyle === "inlined" && node.nodeName === "A" && node.getAttribute("href");
},
replacement: function(content, node) {
var href = escapeLinkDestination(node.getAttribute("href"));
var title = escapeLinkTitle(cleanAttribute(node.getAttribute("title")));
var titlePart = title ? ' "' + title + '"' : "";
return "[" + content + "](" + href + titlePart + ")";
}
};
rules$1.referenceLink = {
filter: function(node, options) {
return options.linkStyle === "referenced" && node.nodeName === "A" && node.getAttribute("href");
},
replacement: function(content, node, options) {
var href = escapeLinkDestination(node.getAttribute("href"));
var title = cleanAttribute(node.getAttribute("title"));
if (title) title = ' "' + escapeLinkTitle(title) + '"';
var replacement;
var reference;
switch (options.linkReferenceStyle) {
case "collapsed":
replacement = "[" + content + "][]";
reference = "[" + content + "]: " + href + title;
break;
case "shortcut":
replacement = "[" + content + "]";
reference = "[" + content + "]: " + href + title;
break;
default:
var id = this.references.length + 1;
replacement = "[" + content + "][" + id + "]";
reference = "[" + id + "]: " + href + title;
}
this.references.push(reference);
return replacement;
},
references: [],
append: function(options) {
var references = "";
if (this.references.length) {
references = "\n\n" + this.references.join("\n") + "\n\n";
this.references = [];
}
return references;
}
};
rules$1.emphasis = {
filter: ["em", "i"],
replacement: function(content, node, options) {
if (!content.trim()) return "";
return options.emDelimiter + content + options.emDelimiter;
}
};
rules$1.strong = {
filter: ["strong", "b"],
replacement: function(content, node, options) {
if (!content.trim()) return "";
return options.strongDelimiter + content + options.strongDelimiter;
}
};
rules$1.code = {
filter: function(node) {
var hasSiblings = node.previousSibling || node.nextSibling;
var isCodeBlock = node.parentNode.nodeName === "PRE" && !hasSiblings;
return node.nodeName === "CODE" && !isCodeBlock;
},
replacement: function(content) {
if (!content) return "";
content = content.replace(/\r?\n|\r/g, " ");
var extraSpace = /^`|^ .*?[^ ].* $|`$/.test(content) ? " " : "";
var delimiter = "`";
var matches = content.match(/`+/gm) || [];
while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + "`";
return delimiter + extraSpace + content + extraSpace + delimiter;
}
};
rules$1.image = {
filter: "img",
replacement: function(content, node) {
var alt = escapeMarkdown(cleanAttribute(node.getAttribute("alt")));
var src = escapeLinkDestination(node.getAttribute("src") || "");
var title = cleanAttribute(node.getAttribute("title"));
var titlePart = title ? ' "' + escapeLinkTitle(title) + '"' : "";
return src ? "" : "";
}
};
function cleanAttribute(attribute) {
return attribute ? attribute.replace(/(\n+\s*)+/g, "\n") : "";
}
function escapeLinkDestination(destination) {
var escaped = destination.replace(/([<>()])/g, "\\$1");
return escaped.indexOf(" ") >= 0 ? "<" + escaped + ">" : escaped;
}
function escapeLinkTitle(title) {
return title.replace(/"/g, '\\"');
}
function Rules(options) {
this.options = options;
this._keep = [];
this._remove = [];
this.blankRule = {
replacement: options.blankReplacement
};
this.keepReplacement = options.keepReplacement;
this.defaultRule = {
replacement: options.defaultReplacement
};
this.array = [];
for (var key in options.rules) this.array.push(options.rules[key]);
}
Rules.prototype = {
add: function(key, rule) {
this.array.unshift(rule);
},
keep: function(filter) {
this._keep.unshift({
filter,
replacement: this.keepReplacement
});
},
remove: function(filter) {
this._remove.unshift({
filter,
replacement: function() {
return "";
}
});
},
forNode: function(node) {
if (node.isBlank) return this.blankRule;
var rule;
if (rule = findRule(this.array, node, this.options)) return rule;
if (rule = findRule(this._keep, node, this.options)) return rule;
if (rule = findRule(this._remove, node, this.options)) return rule;
return this.defaultRule;
},
forEach: function(fn) {
for (var i = 0; i < this.array.length; i++) fn(this.array[i], i);
}
};
function findRule(rules2, node, options) {
for (var i = 0; i < rules2.length; i++) {
var rule = rules2[i];
if (filterValue(rule, node, options)) return rule;
}
return void 0;
}
function filterValue(rule, node, options) {
var filter = rule.filter;
if (typeof filter === "string") {
if (filter === node.nodeName.toLowerCase()) return true;
} else if (Array.isArray(filter)) {
if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true;
} else if (typeof filter === "function") {
if (filter.call(rule, node, options)) return true;
} else {
throw new TypeError("`filter` needs to be a string, array, or function");
}
}
function collapseWhitespace(options) {
var element = options.element;
var isBlock2 = options.isBlock;
var isVoid2 = options.isVoid;
var isPre = options.isPre || function(node2) {
return node2.nodeName === "PRE";
};
if (!element.firstChild || isPre(element)) return;
var prevText = null;
var keepLeadingWs = false;
var prev = null;
var node = next(prev, element, isPre);
while (node !== element) {
if (node.nodeType === 3 || node.nodeType === 4) {
var text = node.data.replace(/[ \r\n\t]+/g, " ");
if ((!prevText || / $/.test(prevText.data)) && !keepLeadingWs && text[0] === " ") {
text = text.substr(1);
}
if (!text) {
node = remove(node);
continue;
}
node.data = text;
prevText = node;
} else if (node.nodeType === 1) {
if (isBlock2(node) || node.nodeName === "BR") {
if (prevText) {
prevText.data = prevText.data.replace(/ $/, "");
}
prevText = null;
keepLeadingWs = false;
} else if (isVoid2(node) || isPre(node)) {
prevText = null;
keepLeadingWs = true;
} else if (prevText) {
keepLeadingWs = false;
}
} else {
node = remove(node);
continue;
}
var nextNode = next(prev, node, isPre);
prev = node;
node = nextNode;
}
if (prevText) {
prevText.data = prevText.data.replace(/ $/, "");
if (!prevText.data) {
remove(prevText);
}
}
}
function remove(node) {
var next2 = node.nextSibling || node.parentNode;
node.parentNode.removeChild(node);
return next2;
}
function next(prev, current, isPre) {
if (prev && prev.parentNode === current || isPre(current)) {
return current.nextSibling || current.parentNode;
}
return current.firstChild || current.nextSibling || current.parentNode;
}
var root = typeof window !== "undefined" ? window : {};
function canParseHTMLNatively() {
var Parser = root.DOMParser;
var canParse = false;
try {
if (new Parser().parseFromString("", "text/html")) {
canParse = true;
}
} catch (e) {
}
return canParse;
}
function createHTMLParser() {
var Parser = function() {
};
{
if (shouldUseActiveX()) {
Parser.prototype.parseFromString = function(string) {
var doc = new window.ActiveXObject("htmlfile");
doc.designMode = "on";
doc.open();
doc.write(string);
doc.close();
return doc;
};
} else {
Parser.prototype.parseFromString = function(string) {
var doc = document.implementation.createHTMLDocument("");
doc.open();
doc.write(string);
doc.close();
return doc;
};
}
}
return Parser;
}
function shouldUseActiveX() {
var useActiveX = false;
try {
document.implementation.createHTMLDocument("").open();
} catch (e) {
if (root.ActiveXObject) useActiveX = true;
}
return useActiveX;
}
var HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser();
function RootNode(input, options) {
var root2;
if (typeof input === "string") {
var doc = htmlParser().parseFromString(
// DOM parsers arrange elements in the <head> and <body>.
// Wrapping in a custom element ensures elements are reliably arranged in
// a single element.
'<x-turndown id="turndown-root">' + input + "</x-turndown>",
"text/html"
);
root2 = doc.getElementById("turndown-root");
} else {
root2 = input.cloneNode(true);
}
collapseWhitespace({
element: root2,
isBlock,
isVoid,
isPre: options.preformattedCode ? isPreOrCode : null
});
return root2;
}
var _htmlParser;
function htmlParser() {
_htmlParser = _htmlParser || new HTMLParser();
return _htmlParser;
}
function isPreOrCode(node) {
return node.nodeName === "PRE" || node.nodeName === "CODE";
}
function Node(node, options) {
node.isBlock = isBlock(node);
node.isCode = node.nodeName === "CODE" || node.parentNode.isCode;
node.isBlank = isBlank(node);
node.flankingWhitespace = flankingWhitespace(node, options);
return node;
}
function isBlank(node) {
return !isVoid(node) && !isMeaningfulWhenBlank(node) && /^\s*$/i.test(node.textContent) && !hasVoid(node) && !hasMeaningfulWhenBlank(node);
}
function flankingWhitespace(node, options) {
if (node.isBlock || options.preformattedCode && node.isCode) {
return {
leading: "",
trailing: ""
};
}
var edges = edgeWhitespace(node.textContent);
if (edges.leadingAscii && isFlankedByWhitespace("left", node, options)) {
edges.leading = edges.leadingNonAscii;
}
if (edges.trailingAscii && isFlankedByWhitespace("right", node, options)) {
edges.trailing = edges.trailingNonAscii;
}
return {
leading: edges.leading,
trailing: edges.trailing
};
}
function edgeWhitespace(string) {
var m = string.match(/^(([ \t\r\n]*)(\s*))(?:(?=\S)[\s\S]*\S)?((\s*?)([ \t\r\n]*))$/);
return {
leading: m[1],
// whole string for whitespace-only strings
leadingAscii: m[2],
leadingNonAscii: m[3],
trailing: m[4],
// empty for whitespace-only strings
trailingNonAscii: m[5],
trailingAscii: m[6]
};
}
function isFlankedByWhitespace(side, node, options) {
var sibling;
var regExp;
var isFlanked;
if (side === "left") {
sibling = node.previousSibling;
regExp = / $/;
} else {
sibling = node.nextSibling;
regExp = /^ /;
}
if (sibling) {
if (sibling.nodeType === 3) {
isFlanked = regExp.test(sibling.nodeValue);
} else if (options.preformattedCode && sibling.nodeName === "CODE") {
isFlanked = false;
} else if (sibling.nodeType === 1 && !isBlock(sibling)) {
isFlanked = regExp.test(sibling.textContent);
}
}
return isFlanked;
}
var reduce = Array.prototype.reduce;
function TurndownService(options) {
if (!(this instanceof TurndownService)) return new TurndownService(options);
var defaults = {
rules: rules$1,
headingStyle: "setext",
hr: "* * *",
bulletListMarker: "*",
codeBlockStyle: "indented",
fence: "```",
emDelimiter: "_",
strongDelimiter: "**",
linkStyle: "inlined",
linkReferenceStyle: "full",
br: " ",
preformattedCode: false,
blankReplacement: function(content, node) {
return node.isBlock ? "\n\n" : "";
},
keepReplacement: function(content, node) {
return node.isBlock ? "\n\n" + node.outerHTML + "\n\n" : node.outerHTML;
},
defaultReplacement: function(content, node) {
return node.isBlock ? "\n\n" + content + "\n\n" : content;
}
};
this.options = extend({}, defaults, options);
this.rules = new Rules(this.options);
}
TurndownService.prototype = {
/**
* The entry point for converting a string or DOM node to Markdown
* @public
* @param {String|HTMLElement} input The string or DOM node to convert
* @returns A Markdown representation of the input
* @type String
*/
turndown: function(input) {
if (!canConvert(input)) {
throw new TypeError(input + " is not a string, or an element/document/fragment node.");
}
if (input === "") return "";
var output = process.call(this, new RootNode(input, this.options));
return postProcess.call(this, output);
},
/**
* Add one or more plugins
* @public
* @param {Function|Array} plugin The plugin or array of plugins to add
* @returns The Turndown instance for chaining
* @type Object
*/
use: function(plugin) {
if (Array.isArray(plugin)) {
for (var i = 0; i < plugin.length; i++) this.use(plugin[i]);
} else if (typeof plugin === "function") {
plugin(this);
} else {
throw new TypeError("plugin must be a Function or an Array of Functions");
}
return this;
},
/**
* Adds a rule
* @public
* @param {String} key The unique key of the rule
* @param {Object} rule The rule
* @returns The Turndown instance for chaining
* @type Object
*/
addRule: function(key, rule) {
this.rules.add(key, rule);
return this;
},
/**
* Keep a node (as HTML) that matches the filter
* @public
* @param {String|Array|Function} filter The unique key of the rule
* @returns The Turndown instance for chaining
* @type Object
*/
keep: function(filter) {
this.rules.keep(filter);
return this;
},
/**
* Remove a node that matches the filter
* @public
* @param {String|Array|Function} filter The unique key of the rule
* @returns The Turndown instance for chaining
* @type Object
*/
remove: function(filter) {
this.rules.remove(filter);
return this;
},
/**
* Escapes Markdown syntax
* @public
* @param {String} string The string to escape
* @returns A string with Markdown syntax escaped
* @type String
*/
escape: function(string) {
return escapeMarkdown(string);
}
};
function process(parentNode) {
var self = this;
return reduce.call(parentNode.childNodes, function(output, node) {
node = new Node(node, self.options);
var replacement = "";
if (node.nodeType === 3) {
replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue);
} else if (node.nodeType === 1) {
replacement = replacementForNode.call(self, node);
}
return join(output, replacement);
}, "");
}
function postProcess(output) {
var self = this;
this.rules.forEach(function(rule) {
if (typeof rule.append === "function") {
output = join(output, rule.append(self.options));
}
});
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
}
function replacementForNode(node) {
var rule = this.rules.forNode(node);
var content = process.call(this, node);
var whitespace = node.flankingWhitespace;
if (whitespace.leading || whitespace.trailing) content = content.trim();
return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
}
function join(output, replacement) {
var s1 = trimTrailingNewlines(output);
var s2 = trimLeadingNewlines(replacement);
var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
var separator = "\n\n".substring(0, nls);
return s1 + separator + s2;
}
function canConvert(input) {
return input != null && (typeof input === "string" || input.nodeType && (input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11));
}
var highlightRegExp = /highlight-(?:text|source)-([a-z0-9]+)/;
function highlightedCodeBlock(turndownService) {
turndownService.addRule("highlightedCodeBlock", {
filter: function(node) {
var firstChild = node.firstChild;
return node.nodeName === "DIV" && highlightRegExp.test(node.className) && firstChild && firstChild.nodeName === "PRE";
},
replacement: function(content, node, options) {
var className = node.className || "";
var language = (className.match(highlightRegExp) || [null, ""])[1];
return "\n\n" + options.fence + language + "\n" + node.firstChild.textContent + "\n" + options.fence + "\n\n";
}
});
}
function strikethrough(turndownService) {
turndownService.addRule("strikethrough", {
filter: ["del", "s", "strike"],
replacement: function(content) {
return "~" + content + "~";
}
});
}
var indexOf = Array.prototype.indexOf;
var every = Array.prototype.every;
var rules = {};
rules.tableCell = {
filter: ["th", "td"],
replacement: function(content, node) {
return cell(content, node);
}
};
rules.tableRow = {
filter: "tr",
replacement: function(content, node) {
var borderCells = "";
var alignMap = { left: ":--", right: "--:", center: ":-:" };
if (isHeadingRow(node)) {
for (var i = 0; i < node.childNodes.length; i++) {
var border = "---";
var align = (node.childNodes[i].getAttribute("align") || "").toLowerCase();
if (align) border = alignMap[align] || border;
borderCells += cell(border, node.childNodes[i]);
}
}
return "\n" + content + (borderCells ? "\n" + borderCells : "");
}
};
rules.table = {
// Only convert tables with a heading row.
// Tables with no heading row are kept using `keep` (see below).
filter: function(node) {
return node.nodeName === "TABLE" && isHeadingRow(node.rows[0]);
},
replacement: function(content) {
content = content.replace("\n\n", "\n");
return "\n\n" + content + "\n\n";
}
};
rules.tableSection = {
filter: ["thead", "tbody", "tfoot"],
replacement: function(content) {
return content;
}
};
function isHeadingRow(tr) {
var parentNode = tr.parentNode;
return parentNode.nodeName === "THEAD" || parentNode.firstChild === tr && (parentNode.nodeName === "TABLE" || isFirstTbody(parentNode)) && every.call(tr.childNodes, function(n) {
return n.nodeName === "TH";
});
}
function isFirstTbody(element) {
var previousSibling = element.previousSibling;
return element.nodeName === "TBODY" && (!previousSibling || previousSibling.nodeName === "THEAD" && /^\s*$/i.test(previousSibling.textContent));
}
function cell(content, node) {
var index = indexOf.call(node.parentNode.childNodes, node);
var prefix = " ";
if (index === 0) prefix = "| ";
return prefix + content + " |";
}
function tables(turndownService) {
turndownService.keep(function(node) {
return node.nodeName === "TABLE" && !isHeadingRow(node.rows[0]);
});
for (var key in rules) turndownService.addRule(key, rules[key]);
}
function taskListItems(turndownService) {
turndownService.addRule("taskListItems", {
filter: function(node) {
return node.type === "checkbox" && node.parentNode.nodeName === "LI";
},
replacement: function(content, node) {
return (node.checked ? "[x]" : "[ ]") + " ";
}
});
}
function gfm(turndownService) {
turndownService.use([
highlightedCodeBlock,
strikethrough,
tables,
taskListItems
]);
}
let instance = null;
function getTurndownService() {
if (!instance) {
instance = new TurndownService({
headingStyle: "atx",
bulletListMarker: "-",
codeBlockStyle: "fenced"
});
instance.use(gfm);
instance.addRule("fractions", {
filter: function(node) {
if (node.nodeName === "DIV") {
const el = node;
const style = el.getAttribute("style") || "";
if (style.includes("inline-block") && style.includes("vertical-align: middle")) {
const spans = Array.from(el.children).filter((n) => n.nodeName === "SPAN");
if (spans.length >= 2) {
return true;
}
}
}
return false;
},
replacement: function(_content, node) {
var _a, _b;
const el = node;
const spans = Array.from(el.children).filter((n) => n.nodeName === "SPAN");
let num = ((_a = spans[0].textContent) == null ? void 0 : _a.trim()) || "";
let den = ((_b = spans[spans.length - 1].textContent) == null ? void 0 : _b.trim()) || "";
return ` (${num}/${den}) `;
}
});
}
return instance;
}
function fixImageUrls(html) {
return html.replace(/src=["'](?:\/)?(oliveimg\/[^"']+)["']/gi, 'src="https://u1.oliveboard.in/exams/solution/$1"');
}
function extractCurrentQuestion(fallbackIndex) {
var _a;
let activeBlock = null;
const blocks = Array.from(document.querySelectorAll(".singleqid, .paneqid"));
let domIndex = 0;
for (let i = 0; i < blocks.length; i++) {
const style = window.getComputedStyle(blocks[i]);
if (style.display !== "none") {
activeBlock = blocks[i];
domIndex = i + 1;
break;
}
}
if (!activeBlock) return null;
const sectionEl = document.querySelector(".ddn-select");
const sectionName = ((_a = sectionEl == null ? void 0 : sectionEl.textContent) == null ? void 0 : _a.trim()) || "Unknown Section";
try {
const paneTxt = activeBlock.querySelector(".panetxt .eqt") || activeBlock.querySelector(".panetxt");
const directionsHtml = paneTxt ? fixImageUrls(paneTxt.innerHTML.trim()) + "<br><br>" : "";
const qblock = activeBlock.querySelector(".qblock .eqt") || activeBlock.querySelector(".qblock");
const questionHtml = directionsHtml + (qblock ? fixImageUrls(qblock.innerHTML.trim()) : "");
const options = [];
const optionBlocks = activeBlock.querySelectorAll(".opt");
optionBlocks.forEach((optBlock) => {
var _a2;
const labelEl = optBlock.querySelector(".left");
const textEl = optBlock.querySelector(".rightopt .eqt") || optBlock.querySelector(".rightopt");
const label = labelEl ? ((_a2 = labelEl.textContent) == null ? void 0 : _a2.trim()) || "" : "";
const html = textEl ? fixImageUrls(textEl.innerHTML.trim()) : "";
const isCorrect = optBlock.classList.contains("correct");
if (label || html) {
options.push({ label, html, isCorrect });
}
});
const solblock = activeBlock.querySelector(".solutiontxt .eqt") || activeBlock.querySelector(".solutiontxt");
const solutionHtml = solblock ? fixImageUrls(solblock.innerHTML.trim()) : "";
return {
index: domIndex || fallbackIndex,
sectionName,
questionHtml,
options,
solutionHtml
};
} catch (err) {
console.error(`[OB+] Error parsing question block`, err);
return null;
}
}
function formatQuestion(q, displayIndex) {
const td = getTurndownService();
let markdown = `### Q${displayIndex}
`;
markdown += `${td.turndown(q.questionHtml)}
`;
q.options.forEach((opt) => {
const optionMark = opt.isCorrect ? `**[Correct]** ` : ``;
const optionMarkdown = td.turndown(opt.html).replace(/\n/g, " ");
markdown += `- **${opt.label}**: ${optionMark}${optionMarkdown}
`;
});
markdown += `
`;
if (q.solutionHtml) {
markdown += `**Solution:**
`;
markdown += `${td.turndown(q.solutionHtml)}
`;
}
return markdown;
}
class Crawler {
constructor(onUpdate, onComplete, onError) {
__publicField(this, "isCancelled", false);
__publicField(this, "questionsData", /* @__PURE__ */ new Map());
__publicField(this, "currentIndex", 1);
this.onUpdate = onUpdate;
this.onComplete = onComplete;
this.onError = onError;
}
sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
cancel() {
this.isCancelled = true;
}
hashString(str) {
let hash = 5381;
for (let i = 0; i < str.length; i++) {
hash = (hash << 5) + hash + str.charCodeAt(i) | 0;
}
return hash.toString(36);
}
parseSectionMap() {
const sections = [];
const sectionBoxes = document.querySelectorAll(".question-map .box");
sectionBoxes.forEach((box) => {
var _a;
const nameEl = box.querySelector("p b") || box.querySelector("p");
const name = ((_a = nameEl == null ? void 0 : nameEl.textContent) == null ? void 0 : _a.trim()) || "Unknown Section";
const qSpans = box.querySelectorAll(".map-qno");
const questionIndices = [];
qSpans.forEach((span) => {
const onclick = span.getAttribute("onclick") || "";
const match = onclick.match(/goToQuestion\((\d+)\)/);
if (match) {
questionIndices.push(parseInt(match[1], 10));
}
});
if (questionIndices.length > 0) {
sections.push({ name, questionIndices });
}
});
return sections;
}
navigateToQuestion(globalIndex) {
const span = document.querySelector(`.map-qno.q-${globalIndex}`);
if (span) {
span.click();
}
}
async start() {
this.isCancelled = false;
this.questionsData.clear();
this.currentIndex = 1;
const sections = this.parseSectionMap();
if (sections.length === 0) {
await this.linearCrawl();
return;
}
const totalQuestions = sections.reduce((sum, s) => sum + s.questionIndices.length, 0);
let processedCount = 0;
for (const section of sections) {
if (this.isCancelled) break;
this.onUpdate(`Section: ${section.name}`);
await this.sleep(50);
if (this.isCancelled) break;
for (const globalIdx of section.questionIndices) {
if (this.isCancelled) break;
processedCount++;
this.onUpdate(`${section.name} — Q${processedCount}/${totalQuestions}`);
this.navigateToQuestion(globalIdx);
await this.sleep(10);
if (this.isCancelled) break;
const qData = extractCurrentQuestion(this.currentIndex);
if (!qData) {
await this.sleep(50);
if (this.isCancelled) break;
const retry = extractCurrentQuestion(this.currentIndex);
if (!retry) continue;
this.addQuestion(retry, section.name);
} else {
this.addQuestion(qData, section.name);
}
}
}
if (this.isCancelled) {
this.onUpdate("Cancelled.");
return;
}
if (this.questionsData.size === 0) {
this.onError("No questions found.");
return;
}
this.onUpdate("Generating Markdown...");
const md = this.generateMarkdown(sections);
this.onComplete(md);
}
addQuestion(qData, sectionName) {
const textContent = qData.questionHtml.replace(/<[^>]*>/g, "").trim();
const signature = this.hashString(textContent);
if (!this.questionsData.has(signature)) {
qData.sectionName = sectionName;
this.questionsData.set(signature, qData);
this.currentIndex++;
}
}
async linearCrawl() {
let sameQuestionCount = 0;
let consecutiveNulls = 0;
let lastSignature = "";
while (!this.isCancelled) {
this.onUpdate(`Extracting Q${this.currentIndex}...`);
await this.sleep(10);
if (this.isCancelled) break;
const qData = extractCurrentQuestion(this.currentIndex);
if (!qData) {
consecutiveNulls++;
if (consecutiveNulls > 3) {
this.onUpdate("No questions found on page.");
break;
}
const nextBtn2 = Array.from(document.querySelectorAll("button.btn-prenext")).find((btn) => {
var _a;
return (_a = btn.textContent) == null ? void 0 : _a.includes("Next");
});
if (nextBtn2 && !nextBtn2.disabled && nextBtn2.style.display !== "none") {
nextBtn2.click();
await this.sleep(50);
} else {
break;
}
continue;
}
consecutiveNulls = 0;
const textContent = qData.questionHtml.replace(/<[^>]*>/g, "").trim();
const signature = this.hashString(textContent);
if (signature === lastSignature) {
sameQuestionCount++;
if (sameQuestionCount > 3) {
this.onUpdate(`Reached the end or stuck.`);
break;
}
} else {
sameQuestionCount = 0;
lastSignature = signature;
if (!this.questionsData.has(signature)) {
this.questionsData.set(signature, qData);
this.currentIndex++;
} else {
break;
}
}
const nextBtn = Array.from(document.querySelectorAll("button.btn-prenext")).find((btn) => {
var _a;
return (_a = btn.textContent) == null ? void 0 : _a.includes("Next");
});
if (nextBtn && !nextBtn.disabled && nextBtn.style.display !== "none") {
nextBtn.click();
await this.sleep(50);
} else {
break;
}
}
if (this.isCancelled) {
this.onUpdate("Cancelled.");
return;
}
if (this.questionsData.size === 0) {
this.onError("No questions found.");
return;
}
this.onUpdate("Generating Markdown...");
const md = this.generateMarkdown([]);
this.onComplete(md);
}
generateMarkdown(sections) {
let markdown = `# Oliveboard Exam
`;
if (sections.length > 0) {
const grouped = /* @__PURE__ */ new Map();
for (const section of sections) {
if (!grouped.has(section.name)) {
grouped.set(section.name, []);
}
}
for (const q of this.questionsData.values()) {
const sectionName = q.sectionName || "Unknown Section";
if (!grouped.has(sectionName)) {
grouped.set(sectionName, []);
}
grouped.get(sectionName).push(q);
}
for (const [sectionName, questions] of grouped) {
if (questions.length === 0) continue;
markdown += `## ${sectionName}
`;
let sectionQNo = 1;
for (const q of questions) {
markdown += formatQuestion(q, sectionQNo) + `---
`;
sectionQNo++;
}
}
} else {
let displayIndex = 1;
for (const q of this.questionsData.values()) {
markdown += formatQuestion(q, displayIndex) + `---
`;
displayIndex++;
}
}
return markdown;
}
}
function downloadFile(content, filename) {
const blob = new Blob([content], { type: "text/markdown;charset=utf-8" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function enableCopyAndRightClick() {
const style = document.createElement("style");
style.textContent = `
* {
-webkit-user-select: text !important;
-moz-user-select: text !important;
-ms-user-select: text !important;
user-select: text !important;
}
`;
(document.head || document.documentElement).appendChild(style);
const events = ["contextmenu", "copy", "cut", "paste", "selectstart"];
events.forEach((evt) => {
window.addEventListener(evt, (e) => {
e.stopPropagation();
}, true);
});
}
function onReady(fn) {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", fn);
} else {
fn();
}
}
const selectorsToHide = [
"#sec-update",
"#rightcol",
// Usually contains sidebars
".obadcard",
// Deals & Offers ads section
'.limenuitem:has(a[href*="refer"])',
'.limenuitem:has(a[href*="testimonials"])',
'.limenuitem:has(a[href*="edge/?c=1851"])',
'.limenuitem:has(a[href*="buypayu/validity"])',
// Zendesk chat widget
'[data-garden-id="buttons.icon_button"][aria-label="Open messaging window"]',
".sc-1w3tvxe-0",
// Chat widget wrapper
'iframe[title="Close message"]',
'iframe[title="Message from company"]',
'iframe[title="Number of unread messages"]',
'iframe[title="Messaging window"]',
"iframe#launcher",
'iframe[title^="Button to launch messaging window"]'
];
function hideElements() {
selectorsToHide.forEach((selector) => {
document.querySelectorAll(selector).forEach((el) => {
el.style.display = "none";
});
});
document.querySelectorAll('div[style*="z-index: 999999"]').forEach((el) => {
if (el.style.position === "fixed" && el.querySelector("iframe")) {
el.style.display = "none";
}
});
}
function modifyLinks() {
document.querySelectorAll('a[onclick^="openwin"]').forEach((a) => {
const onclick = a.getAttribute("onclick") || "";
const match = onclick.match(/openwin\(['"]([^'"]+)['"]/);
if (match && match[1]) {
a.setAttribute("href", match[1]);
a.setAttribute("target", "_blank");
a.removeAttribute("onclick");
}
});
}
function interceptViewSolutions() {
const script = document.createElement("script");
script.textContent = `
// Poison Zendesk widget globals so it never initializes
const noop = function() { return noop; };
noop.setLocale = noop; noop.identify = noop; noop.hide = noop;
noop.show = noop; noop.activate = noop; noop.on = noop;
['zE', '$zopim', 'zEACLoaded'].forEach(key => {
Object.defineProperty(window, key, {
get: () => noop,
set: () => {}, // silently ignore page's attempts to redefine
configurable: true
});
});
// Override openwin — getter always returns our version, setter silently absorbs
const openwinOverride = function(url, name) { window.open(url, '_blank'); };
Object.defineProperty(window, 'openwin', {
get: () => openwinOverride,
set: () => {}, // silently ignore page's attempts to redefine
configurable: true
});
`;
document.documentElement.appendChild(script);
script.remove();
}
const COPY_SVG = '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>';
const CHECK_SVG = '<path d="M20 6L9 17l-5-5"></path>';
function ensureCopyButton() {
var _a;
const headerCols = document.querySelectorAll(".solheader-col");
if (headerCols.length === 0) return;
const headerCol = headerCols[0];
if (headerCol.querySelector(".copy-md-btn")) return;
const btnWrapper = document.createElement("div");
btnWrapper.className = "copy-md-btn";
btnWrapper.title = "Copy Markdown";
btnWrapper.innerHTML = `
<button type="button" class="button btn-save-qn" style="display:flex;align-items:center;justify-content:center;margin-left:10px;">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
${COPY_SVG}
</svg>
</button>
`;
btnWrapper.onclick = async () => {
const q = extractCurrentQuestion(1);
if (!q) return;
const md = formatQuestion(q, q.index);
try {
await navigator.clipboard.writeText(md);
const svg = btnWrapper.querySelector("svg");
if (svg) {
svg.innerHTML = CHECK_SVG;
setTimeout(() => {
svg.innerHTML = COPY_SVG;
}, 2e3);
}
} catch (err) {
console.error("[OB+] Clipboard write failed:", err);
}
};
const saveQs = headerCol.querySelector(".save-qs");
if (saveQs) {
(_a = saveQs.parentNode) == null ? void 0 : _a.insertBefore(btnWrapper, saveQs);
} else {
headerCol.appendChild(btnWrapper);
}
}
function initDownloader() {
let activeCrawler = null;
const ui = new DownloaderUI(
() => {
activeCrawler = new Crawler(
(msg) => ui.updateStatus(msg),
(md) => {
ui.finish();
downloadFile(md, "Oliveboard_Questions.md");
},
(errMsg) => ui.error(errMsg)
);
activeCrawler.start();
},
() => activeCrawler == null ? void 0 : activeCrawler.cancel()
);
onReady(() => ui.mount());
}
function runMutationHandlers() {
hideElements();
modifyLinks();
ensureCopyButton();
}
function init() {
try {
enableCopyAndRightClick();
} catch (e) {
console.error("[OB+]", e);
}
try {
interceptViewSolutions();
} catch (e) {
console.error("[OB+]", e);
}
try {
initDownloader();
} catch (e) {
console.error("[OB+]", e);
}
onReady(runMutationHandlers);
let pending = false;
const observer = new MutationObserver(() => {
if (pending) return;
pending = true;
requestAnimationFrame(() => {
runMutationHandlers();
pending = false;
});
});
observer.observe(document.documentElement, { childList: true, subtree: true });
}
init();
})();