UNPKG

pxt-core

Version:

Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors

1,149 lines (1,148 loc) • 60.7 kB
// Needs to be in its own file to avoid a circular dependency: util.ts -> main.ts -> util.ts var pxt; (function (pxt) { /** * Track an event. */ pxt.tickEvent = function (id) { }; })(pxt || (pxt = {})); var pxt; (function (pxt) { })(pxt || (pxt = {})); var pxt; (function (pxt) { let LogLevel; (function (LogLevel) { LogLevel[LogLevel["Debug"] = 0] = "Debug"; LogLevel[LogLevel["Info"] = 1] = "Info"; LogLevel[LogLevel["Log"] = 1] = "Log"; LogLevel[LogLevel["Warning"] = 2] = "Warning"; LogLevel[LogLevel["Error"] = 3] = "Error"; })(LogLevel = pxt.LogLevel || (pxt.LogLevel = {})); class ConsoleLogger { constructor() { this.setLogLevel(LogLevel.Info); } setLogLevel(level) { this.logLevel = level; } getLogLevel() { return this.logLevel; } info(...args) { if (!this.shouldLog(LogLevel.Info)) return; if (console === null || console === void 0 ? void 0 : console.info) { console.info.call(null, ...args); } } log(...args) { if (!this.shouldLog(LogLevel.Log)) return; if (console === null || console === void 0 ? void 0 : console.log) { console.log.call(null, ...args); } } debug(...args) { if (!this.shouldLog(LogLevel.Debug)) return; if (console === null || console === void 0 ? void 0 : console.debug) { console.debug.call(null, ...args); } } error(...args) { if (!this.shouldLog(LogLevel.Error)) return; if (console === null || console === void 0 ? void 0 : console.error) { console.error.call(null, ...args); } } warn(...args) { if (!this.shouldLog(LogLevel.Warning)) return; if (console === null || console === void 0 ? void 0 : console.warn) { console.warn.call(null, ...args); } } shouldLog(level) { return level >= this.logLevel; } } pxt.ConsoleLogger = ConsoleLogger; let logger = new ConsoleLogger(); function info(...args) { logger.info(...args); } pxt.info = info; function log(...args) { logger.log(...args); } pxt.log = log; function debug(...args) { logger.debug(...args); } pxt.debug = debug; function error(...args) { logger.error(...args); } pxt.error = error; function warn(...args) { logger.warn(...args); } pxt.warn = warn; function setLogger(impl) { const level = logger === null || logger === void 0 ? void 0 : logger.getLogLevel(); logger = impl; if (level !== undefined) { logger.setLogLevel(level); } } pxt.setLogger = setLogger; function setLogLevel(level) { logger.setLogLevel(level); } pxt.setLogLevel = setLogLevel; })(pxt || (pxt = {})); /// <reference path="./tickEvent.ts" /> /// <reference path="./apptarget.ts" /> /// <reference path="./logger.ts" /> var ts; (function (ts) { var pxtc; (function (pxtc) { pxtc.__dummy = 42; })(pxtc = ts.pxtc || (ts.pxtc = {})); })(ts || (ts = {})); var pxtc = ts.pxtc; (function (ts) { var pxtc; (function (pxtc) { var Util; (function (Util) { function assert(cond, msg = "Assertion failed") { if (!cond) { debugger; throw new Error(msg); } } Util.assert = assert; function flatClone(obj) { if (obj == null) return null; let r = {}; Object.keys(obj).forEach((k) => { r[k] = obj[k]; }); return r; } Util.flatClone = flatClone; function clone(v) { if (v == null) return null; return JSON.parse(JSON.stringify(v)); } Util.clone = clone; function htmlEscape(_input) { if (!_input) return _input; // null, undefined, empty string test return _input.replace(/([^\w .!?\-$])/ug, c => "&#" + c.codePointAt(0) + ";"); } Util.htmlEscape = htmlEscape; function htmlUnescape(_input) { if (!_input) return _input; // null, undefined, empty string test return _input.replace(/(&#\d+;)/g, c => String.fromCodePoint(Number(c.substr(2, c.length - 3)))); } Util.htmlUnescape = htmlUnescape; function jsStringQuote(s) { return s.replace(/[^\w .!?\-$]/g, (c) => { let h = c.charCodeAt(0).toString(16); return "\\u" + "0000".substr(0, 4 - h.length) + h; }); } Util.jsStringQuote = jsStringQuote; function jsStringLiteral(s) { return "\"" + jsStringQuote(s) + "\""; } Util.jsStringLiteral = jsStringLiteral; function initials(username) { if (/^\w+@/.test(username)) { // Looks like an email address. Return first two characters. const initials = username.match(/^\w\w/); return initials.shift().toUpperCase(); } else { // Parse the user name for user initials const initials = username.match(/\b\w/g) || []; return ((initials.shift() || '') + (initials.pop() || '')).toUpperCase(); } } Util.initials = initials; // Localization functions. Please port any modifications over to pxtsim/localization.ts let _localizeLang = "en"; let _localizeStrings = {}; let _translationsCache = {}; //let _didSetlocalizations = false; //let _didReportLocalizationsNotSet = false; let localizeLive = false; function enableLiveLocalizationUpdates() { localizeLive = true; } Util.enableLiveLocalizationUpdates = enableLiveLocalizationUpdates; function liveLocalizationEnabled() { return localizeLive; } Util.liveLocalizationEnabled = liveLocalizationEnabled; /** * Returns the current user language, prepended by "live-" if in live mode */ function localeInfo() { return `${localizeLive ? "live-" : ""}${userLanguage()}`; } Util.localeInfo = localeInfo; /** * Returns current user language iSO-code. Default is `en`. */ function userLanguage() { return _localizeLang; } Util.userLanguage = userLanguage; // This function returns normalized language code // For example: zh-CN this returns ["zh-CN", "zh", "zh-cn"] // First two are valid crowdin\makecode locale code, // Last all lowercase one is just for the backup when reading user defined extensions & tutorials. function normalizeLanguageCode(code) { const langParts = /^(\w{2,3})-(\w{2,4}$)/i.exec(code); if (langParts && langParts[1] && langParts[2]) { return [`${langParts[1].toLowerCase()}-${langParts[2].toUpperCase()}`, langParts[1].toLowerCase(), `${langParts[1].toLowerCase()}-${langParts[2].toLowerCase()}`]; } else { return [(code || "en").toLowerCase()]; } } Util.normalizeLanguageCode = normalizeLanguageCode; function setUserLanguage(localizeLang) { _localizeLang = normalizeLanguageCode(localizeLang)[0]; } Util.setUserLanguage = setUserLanguage; function isUserLanguageRtl() { return /^ar|dv|fa|ha|he|ks|ku|ps|ur|yi/i.test(_localizeLang); } Util.isUserLanguageRtl = isUserLanguageRtl; Util.TRANSLATION_LOCALE = "pxt"; function isTranslationMode() { return userLanguage() == Util.TRANSLATION_LOCALE; } Util.isTranslationMode = isTranslationMode; function _localize(s) { // Needs to be test in localhost / CLI /*if (!_didSetlocalizations && !_didReportLocalizationsNotSet) { _didReportLocalizationsNotSet = true; pxt.tickEvent("locale.localizationsnotset"); // pxt.reportError can't be used here because of order of file imports // Just use pxt.error instead, and use an Error so stacktrace is reported pxt.error(new Error("Attempted to translate a string before localizations were set")); }*/ return _localizeStrings[s] || s; } Util._localize = _localize; function getLocalizedStrings() { return _localizeStrings; } Util.getLocalizedStrings = getLocalizedStrings; function setLocalizedStrings(strs) { //_didSetlocalizations = true; _localizeStrings = strs; } Util.setLocalizedStrings = setLocalizedStrings; function translationsCache() { return _translationsCache; } Util.translationsCache = translationsCache; function fmt_va(f, args) { if (args.length == 0) return f; return f.replace(/\{([0-9]+)(\:[^\}]+)?\}/g, function (s, n, spec) { let v = args[parseInt(n)]; let r = ""; let fmtMatch = /^:f(\d*)\.(\d+)/.exec(spec); if (fmtMatch) { let precision = parseInt(fmtMatch[2]); let len = parseInt(fmtMatch[1]) || 0; let fillChar = /^0/.test(fmtMatch[1]) ? "0" : " "; let num = v.toFixed(precision); if (len > 0 && precision > 0) len += precision + 1; if (len > 0) { while (num.length < len) { num = fillChar + num; } } r = num; } else if (spec == ":x") { r = "0x" + v.toString(16); } else if (v === undefined) r = "(undef)"; else if (v === null) r = "(null)"; else if (v.toString) r = v.toString(); else r = v + ""; if (spec == ":a") { if (/^\s*[euioah]/.test(r.toLowerCase())) r = "an " + r; else if (/^\s*[bcdfgjklmnpqrstvwxz]/.test(r.toLowerCase())) r = "a " + r; } else if (spec == ":s") { if (v == 1) r = ""; else r = "s"; } else if (spec == ":q") { r = Util.htmlEscape(r); } else if (spec == ":jq") { r = Util.jsStringQuote(r); } else if (spec == ":uri") { r = encodeURIComponent(r).replace(/'/g, "%27").replace(/"/g, "%22"); } else if (spec == ":url") { r = encodeURI(r).replace(/'/g, "%27").replace(/"/g, "%22"); } else if (spec == ":%") { r = (v * 100).toFixed(1).toString() + '%'; } return r; }); } Util.fmt_va = fmt_va; function fmt(f, ...args) { return fmt_va(f, args); } Util.fmt = fmt; const locStats = {}; function dumpLocStats() { const r = {}; Object.keys(locStats).sort((a, b) => locStats[b] - locStats[a]) .forEach(k => r[k] = k); pxt.log('prioritized list of strings:'); pxt.log(JSON.stringify(r, null, 2)); } Util.dumpLocStats = dumpLocStats; let sForPlural = true; function lf_va(format, args) { if (!format) return format; locStats[format] = (locStats[format] || 0) + 1; let lfmt = Util._localize(format); if (!sForPlural && lfmt != format && /\d:s\}/.test(lfmt)) { lfmt = lfmt.replace(/\{\d+:s\}/g, ""); } lfmt = lfmt.replace(/^\{(id|loc):[^\}]+\}/g, ''); return fmt_va(lfmt, args); } Util.lf_va = lf_va; function lf(format, ...args) { return lf_va(format, args); // @ignorelf@ } Util.lf = lf; /** * Similar to lf but the string do not get extracted into the loc file. */ function rlf(format, ...args) { return lf_va(format, args); // @ignorelf@ } Util.rlf = rlf; function lookup(m, key) { if (m.hasOwnProperty(key)) return m[key]; return null; } Util.lookup = lookup; function isoTime(time) { let d = new Date(time * 1000); return Util.fmt("{0}-{1:f02.0}-{2:f02.0} {3:f02.0}:{4:f02.0}:{5:f02.0}", d.getFullYear(), d.getMonth() + 1, d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds()); } Util.isoTime = isoTime; function userError(msg) { let e = new Error(msg); e.isUserError = true; throw e; } Util.userError = userError; // small deep equals for primitives, objects, arrays. returns error message function deq(a, b) { if (a === b) return null; if (!a || !b) return "Null value"; if (typeof a == 'object' && typeof b == 'object') { if (Array.isArray(a)) { if (!Array.isArray(b)) { return "Expected array"; } if (a.length != b.length) { return "Expected array of length " + a.length + ", got " + b.length; } for (let i = 0; i < a.length; i++) { if (deq(a[i], b[i]) != null) { return "Expected array value " + a[i] + " got " + b[i]; } } return null; } let ak = Object.keys(a); let bk = Object.keys(a); if (ak.length != bk.length) { return "Expected " + ak.length + " keys, got " + bk.length; } for (let i = 0; i < ak.length; i++) { if (!Object.prototype.hasOwnProperty.call(b, ak[i])) { return "Missing key " + ak[i]; } else if (deq(a[ak[i]], b[ak[i]]) != null) { return "Expected value of " + ak[i] + " to be " + a[ak[i]] + ", got " + b[ak[i]]; } } return null; } return "Unable to compare " + a + ", " + b; } Util.deq = deq; function deepEqual(a, b) { if (a === b) { return true; } if (a && b && typeof a === 'object' && typeof b === 'object') { const arrA = Array.isArray(a); const arrB = Array.isArray(b); if (arrA && arrB) { if (a.length !== b.length) { return false; } for (let i = 0; i < a.length; ++i) { if (!deepEqual(a[i], b[i])) { return false; } } return true; } if (arrA !== arrB) { return false; } const keysA = Object.keys(a); if (keysA.length !== Object.keys(b).length) { return false; } for (const key of keysA) { if (!b.hasOwnProperty(key)) { return false; } if (!deepEqual(a[key], b[key])) { return false; } } return true; } // True if both are NaN, false otherwise return a !== a && b !== b; } Util.deepEqual = deepEqual; ; })(Util = pxtc.Util || (pxtc.Util = {})); })(pxtc = ts.pxtc || (ts.pxtc = {})); })(ts || (ts = {})); const lf = ts.pxtc.Util.lf; /// <reference path='../localtypings/pxtarget.d.ts' /> /// <reference path='../localtypings/dompurify.d.ts' /> /// <reference path="commonutil.ts"/> /// <reference path="./logger.ts" /> var pxt; (function (pxt) { var docs; (function (docs) { var U = pxtc.Util; let markedInstance; let stdboxes = {}; let stdmacros = {}; const stdSetting = "<!-- @CMD@ @ARGS@ -->"; let stdsettings = { "parent": stdSetting, "short": stdSetting, "description": "<!-- desc -->", "activities": "<!-- activities -->", "explicitHints": "<!-- hints -->", "flyoutOnly": "<!-- flyout -->", "hideToolbox": "<!-- hideToolbox -->", "hideIteration": "<!-- iter -->", "codeStart": "<!-- start -->", "codeStop": "<!-- stop -->", "autoOpen": "<!-- autoOpen -->", "autoexpandOff": "<!-- autoexpandOff -->", "preferredEditor": "<!-- preferredEditor -->" }; function replaceAll(replIn, x, y) { return replIn.split(x).join(y); } function htmlQuote(s) { s = replaceAll(s, "&", "&amp;"); s = replaceAll(s, "<", "&lt;"); s = replaceAll(s, ">", "&gt;"); s = replaceAll(s, "\"", "&quot;"); s = replaceAll(s, "\'", "&#39;"); return s; } docs.htmlQuote = htmlQuote; // the input already should be HTML-quoted but we want to make sure, and also quote quotes function html2Quote(s) { if (!s) return s; return htmlQuote(s.replace(/\&([#a-z0-9A-Z]+);/g, (f, ent) => { switch (ent) { case "amp": return "&"; case "lt": return "<"; case "gt": return ">"; case "quot": return "\""; default: if (ent[0] == "#") return String.fromCharCode(parseInt(ent.slice(1))); else return f; } })); } docs.html2Quote = html2Quote; //The extra YouTube macros are in case there is a timestamp on the YouTube URL. //TODO: Add equivalent support for youtu.be links const links = [ { rx: /^vimeo\.com\/(\d+)/i, cmd: "### @vimeo $1" }, { rx: /^(www\.youtube\.com\/watch\?v=|youtu\.be\/)([\w\-]+(\#t=([0-9]+m[0-9]+s|[0-9]+m|[0-9]+s))?)/i, cmd: "### @youtube $2" } ]; docs.requireMarked = () => { if (typeof marked !== "undefined") return marked; if (typeof require === "undefined") return undefined; return require("marked"); }; docs.requireDOMSanitizer = () => { if (typeof DOMPurify !== "undefined") return DOMPurify.sanitize; if (typeof require === "undefined") return undefined; return require("DOMPurify").sanitize; }; function parseHtmlAttrs(s) { let attrs = {}; while (s.trim()) { let m = /\s*([^=\s]+)=("([^"]*)"|'([^']*)'|(\S*))/.exec(s); if (m) { let v = m[3] || m[4] || m[5] || ""; attrs[m[1].toLowerCase()] = v; } else { m = /^\s*(\S+)/.exec(s); attrs[m[1]] = "true"; } s = s.slice(m[0].length); } return attrs; } const error = (s) => `<div class='ui negative message'>${htmlQuote(s)}</div>`; function prepTemplate(d) { let boxes = U.clone(stdboxes); let macros = U.clone(stdmacros); let settings = U.clone(stdsettings); let menus = {}; let toc = {}; let params = d.params; let theme = d.theme; d.boxes = boxes; d.macros = macros; d.settings = settings; d.html = d.html.replace(/<aside\s+([^<>]+)>([^]*?)<\/aside>/g, (full, attrsStr, body) => { let attrs = parseHtmlAttrs(attrsStr); let name = attrs["data-name"] || attrs["id"]; if (!name) return error("id or data-name missing on macro"); if (/box/.test(attrs["class"])) { boxes[name] = body; } else if (/aside/.test(attrs["class"])) { boxes[name] = `<!-- BEGIN-ASIDE ${name} -->${body}<!-- END-ASIDE -->`; } else if (/setting/.test(attrs["class"])) { settings[name] = body; } else if (/menu/.test(attrs["class"])) { menus[name] = body; } else if (/toc/.test(attrs["class"])) { toc[name] = body; } else { macros[name] = body; } return `<!-- macro ${name} -->`; }); let recMenu = (m, lev) => { let templ = menus["item"]; let mparams = { NAME: m.name, }; if (m.subitems) { if (!!menus["toc-dropdown"]) { templ = menus["toc-dropdown"]; } else { /** TODO: when all targets bumped to include https://github.com/microsoft/pxt/pull/6058, * swap templ assignments below with the commented out version, and remove * top-dropdown, top-dropdown-noheading, inner-dropdown, and nested-dropdown from * docfiles/macros.html **/ if (lev == 0) templ = menus["top-dropdown"]; else templ = menus["inner-dropdown"]; } mparams["ITEMS"] = m.subitems.map(e => recMenu(e, lev + 1)).join("\n"); } else { if (/^-+$/.test(m.name)) { templ = menus["divider"]; } if (m.path && !/^(https?:|\/)/.test(m.path)) return error("Invalid link: " + m.path); mparams["LINK"] = m.path; } return injectHtml(templ, mparams, ["ITEMS"]); }; let breadcrumb = [{ name: lf("Docs"), href: "/docs" }]; const TOC = d.TOC || theme.TOC || []; let tocPath = []; let isCurrentTOC = (m) => { for (let c of m.subitems || []) { if (isCurrentTOC(c)) { tocPath.push(m); return true; } } if (d.filepath && !!m.path && d.filepath == m.path) { tocPath.push(m); return true; } return false; }; TOC.forEach(isCurrentTOC); let recTOC = (m, lev) => { let templ = toc["item"]; let mparams = { NAME: m.name, }; if (m.path && !/^(https?:|\/)/.test(m.path)) return error("Invalid link: " + m.path); if (/^\//.test(m.path) && d.versionPath) m.path = `/${d.versionPath}${m.path}`; mparams["LINK"] = m.path; if (tocPath.indexOf(m) >= 0) { mparams["ACTIVE"] = 'active'; mparams["EXPANDED"] = 'true'; breadcrumb.push({ name: m.name, href: m.path }); } else { mparams["EXPANDED"] = 'false'; } if (m.subitems && m.subitems.length > 0) { if (!!toc["toc-dropdown"]) { // if macros support "toc-*", use them if (m.name !== "") { templ = toc["toc-dropdown"]; } else { templ = toc["toc-dropdown-noLink"]; } } else { // if macros don't support "toc-*" /** TODO: when all targets bumped to include https://github.com/microsoft/pxt/pull/6058, * delete this else branch, and remove * top-dropdown, top-dropdown-noheading, inner-dropdown, and nested-dropdown from * docfiles/macros.html **/ if (lev == 0) { if (m.name !== "") { templ = toc["top-dropdown"]; } else { templ = toc["top-dropdown-noHeading"]; } } else if (lev == 1) templ = toc["inner-dropdown"]; else templ = toc["nested-dropdown"]; } mparams["ITEMS"] = m.subitems.map(e => recTOC(e, lev + 1)).join("\n"); } else { if (/^-+$/.test(m.name)) { templ = toc["divider"]; } } return injectHtml(templ, mparams, ["ITEMS"]); }; params["menu"] = (theme.docMenu || []).map(e => recMenu(e, 0)).join("\n"); params["TOC"] = TOC.map(e => recTOC(e, 0)).join("\n"); if (theme.appStoreID) params["appstoremeta"] = `<meta name="apple-itunes-app" content="app-id=${U.htmlEscape(theme.appStoreID)}"/>`; let breadcrumbHtml = ''; if (breadcrumb.length > 1) { breadcrumbHtml = ` <nav class="ui breadcrumb" aria-label="${lf("Breadcrumb")}"> ${breadcrumb.map((b, i) => `<a class="${i == breadcrumb.length - 1 ? "active" : ""} section" href="${html2Quote(b.href)}" aria-current="${i == breadcrumb.length - 1 ? "page" : ""}">${html2Quote(b.name)}</a>`) .join('<i class="right chevron icon divider"></i>')} </nav>`; } params["breadcrumb"] = breadcrumbHtml; if (theme.boardName) params["boardname"] = html2Quote(theme.boardName); if (theme.boardNickname) params["boardnickname"] = html2Quote(theme.boardNickname); if (theme.driveDisplayName) params["drivename"] = html2Quote(theme.driveDisplayName); if (theme.homeUrl) params["homeurl"] = html2Quote(theme.homeUrl); params["targetid"] = theme.id || "???"; params["targetname"] = theme.name || "Microsoft MakeCode"; params["docsheader"] = theme.docsHeader || "Documentation"; params["orgtitle"] = "MakeCode"; const docsLogo = theme.docsLogo && U.htmlEscape(theme.docsLogo); const orgLogo = (theme.organizationWideLogo || theme.organizationLogo) && U.htmlEscape(theme.organizationWideLogo || theme.organizationLogo); const orglogomobile = theme.organizationLogo && U.htmlEscape(theme.organizationLogo); params["targetlogo"] = docsLogo ? `<img aria-hidden="true" role="presentation" class="ui ${theme.logoWide ? "small" : "mini"} image" src="${docsLogo}" />` : ""; params["orglogo"] = orgLogo ? `<img aria-hidden="true" role="presentation" class="ui image" src="${orgLogo}" />` : ""; params["orglogomobile"] = orglogomobile ? `<img aria-hidden="true" role="presentation" class="ui image" src="${orglogomobile}" />` : ""; let ghURLs = d.ghEditURLs || []; if (ghURLs.length) { let ghText = `<p style="margin-top:1em">\n`; let linkLabel = lf("Edit this page on GitHub"); for (let u of ghURLs) { ghText += `<a href="${u}"><i class="write icon"></i>${linkLabel}</a><br>\n`; linkLabel = lf("Edit template of this page on GitHub"); } ghText += `</p>\n`; params["github"] = ghText; } else { params["github"] = ""; } // Add accessiblity menu const accMenuHtml = ` <a href="#maincontent" class="ui item link" tabindex="0" role="menuitem">${lf("Skip to main content")}</a> `; params['accMenu'] = accMenuHtml; const printButtonTitleText = lf("Print this page"); // Add print button const printBtnHtml = ` <button id="printbtn" class="circular ui icon right floated button hideprint" title="${printButtonTitleText}" aria-label="${printButtonTitleText}"> <i class="icon print"></i> </button> `; params['printBtn'] = printBtnHtml; // Add sidebar toggle const sidebarToggleHtml = ` <a id="togglesidebar" class="launch icon item" tabindex="0" title="Side menu" aria-label="${lf("Side menu")}" role="menuitem" aria-expanded="false"> <i class="content icon"></i> </a> `; params['sidebarToggle'] = sidebarToggleHtml; // Add search bars const searchBarIds = ['tocsearch1', 'tocsearch2']; const searchBarsHtml = searchBarIds.map((searchBarId) => { return ` <input type="search" name="q" placeholder="${lf("Search...")}" aria-label="${lf("Search Documentation")}"> <i onclick="document.getElementById('${searchBarId}').submit();" tabindex="0" class="search link icon" aria-label="${lf("Search")}" role="button"></i> `; }); params["searchBar1"] = searchBarsHtml[0]; params["searchBar2"] = searchBarsHtml[1]; let style = ''; if (theme.accentColor) style += ` .ui.accent { color: ${theme.accentColor}; } .ui.inverted.accent { background: ${theme.accentColor}; } `; params["targetstyle"] = style; params["tocclass"] = theme.lightToc ? "lighttoc" : "inverted"; for (let k of Object.keys(theme)) { let v = theme[k]; if (params[k] === undefined && typeof v == "string") params[k] = v; } d.finish = () => injectHtml(d.html, params, [ "body", "menu", "accMenu", "TOC", "prev", "next", "printBtn", "breadcrumb", "targetlogo", "orglogo", "orglogomobile", "github", "JSON", "appstoremeta", "sidebarToggle", "searchBar1", "searchBar2" ]); // Normalize any path URL with any version path in the current URL function normalizeUrl(href) { if (!href) return href; const relative = href.indexOf('/') == 0; if (relative && d.versionPath) href = `/${d.versionPath}${href}`; return href; } } docs.prepTemplate = prepTemplate; function setupRenderer(renderer) { renderer.image = function (href, title, text) { var _a, _b; const endpointName = "makecodeprodmediaeastus-usea"; if (href.startsWith("youtube:")) { let out = '<div class="tutorial-video-embed"><iframe class="yt-embed" src="https://www.youtube.com/embed/' + href.split(":").pop() + '" title="' + text + '" frameborder="0" ' + 'allowFullScreen ' + 'allow="autoplay; picture-in-picture"></iframe></div>'; return out; } else if (href.startsWith("azuremedia:")) { let videoID = href.split(":")[1]; const flagsSplit = videoID.split("?"); let startTime; let endTime; if (flagsSplit[1]) { videoID = flagsSplit[0]; const passedParameters = flagsSplit[1]; startTime = (_a = /start(?:time)?=(\d+)/i.exec(passedParameters)) === null || _a === void 0 ? void 0 : _a[1]; endTime = (_b = /end(?:time)?=(\d+)/i.exec(passedParameters)) === null || _b === void 0 ? void 0 : _b[1]; } const url = new URL(`https://${endpointName}.streaming.media.azure.net/${videoID}/manifest(format=mpd-time-csf).mpd`); if (startTime) { url.hash = `t=${startTime}`; url.searchParams.append("startTime", startTime); } if (endTime) { url.searchParams.append("endTime", endTime); } let out = `<div class="tutorial-video-embed"><video class="ams-embed" controls src="${url.toString()}" /></div>`; return out; } else { let out = '<img class="ui image" src="' + href + '" alt="' + text + '"'; if (title) { out += ' title="' + title + '"'; } out += ' loading="lazy"'; out += this.options.xhtml ? '/>' : '>'; return out; } }; renderer.listitem = function (text) { const m = /^\s*\[( |x)\]/i.exec(text); if (m) return `<li class="${m[1] == ' ' ? 'unchecked' : 'checked'}">` + text.slice(m[0].length) + '</li>\n'; return '<li>' + text + '</li>\n'; }; renderer.heading = function (text, level, raw) { let m = /(.*)#([\w\-]+)\s*$/.exec(text); let id = ""; if (m) { text = m[1]; id = m[2]; } // remove tutorial macros if (text) text = text.replace(/@(fullscreen|unplugged|showdialog|showhint)/gi, ''); // remove brackets for hiding step title if (text.match(/\{([\s\S]+)\}/)) text = text.match(/\{([\s\S]+)\}/)[1].trim(); if (id === "") { id = text.toLowerCase().replace(/[^\w]+/g, '-'); } return `<h${level} id="${this.options.headerPrefix}${id}">${text}</h${level}>`; }; } docs.setupRenderer = setupRenderer; function renderConditionalMacros(template, pubinfo) { return template .replace(/<!--\s*@(ifn?def)\s+(\w+)\s*-->([^]*?)<!--\s*@endif\s*-->/g, (full, cond, sym, inner) => { if ((cond == "ifdef" && pubinfo[sym]) || (cond == "ifndef" && !pubinfo[sym])) return `<!-- ${cond} ${sym} -->${inner}<!-- endif -->`; else return `<!-- ${cond} ${sym} endif -->`; }); } docs.renderConditionalMacros = renderConditionalMacros; function renderMarkdown(opts) { let hasPubInfo = true; if (!opts.pubinfo) { hasPubInfo = false; opts.pubinfo = {}; } let pubinfo = opts.pubinfo; if (!opts.theme) opts.theme = {}; delete opts.pubinfo["private"]; // just in case if (pubinfo["time"]) { let tm = parseInt(pubinfo["time"]); if (!pubinfo["timems"]) pubinfo["timems"] = 1000 * tm + ""; if (!pubinfo["humantime"]) pubinfo["humantime"] = U.isoTime(tm); } if (pubinfo["name"]) { pubinfo["dirname"] = pubinfo["name"].replace(/[^A-Za-z0-9_]/g, "-"); pubinfo["title"] = pubinfo["name"]; } if (hasPubInfo) { pubinfo["JSON"] = JSON.stringify(pubinfo, null, 4).replace(/</g, "\\u003c"); } let template = opts.template; template = template .replace(/<!--\s*@include\s+(\S+)\s*-->/g, (full, fn) => { let cont = (opts.theme.htmlDocIncludes || {})[fn] || ""; return "<!-- include " + fn + " -->\n" + cont + "\n<!-- end include -->\n"; }); template = renderConditionalMacros(template, pubinfo); if (opts.locale) template = translate(template, opts.locale).text; let d = { html: template, theme: opts.theme, filepath: opts.filepath, versionPath: opts.versionPath, ghEditURLs: opts.ghEditURLs, params: pubinfo, TOC: opts.TOC }; prepTemplate(d); if (!markedInstance) { markedInstance = docs.requireMarked(); } // We have to re-create the renderer every time to avoid the link() function's closure capturing the opts let renderer = new markedInstance.Renderer(); setupRenderer(renderer); const linkRenderer = renderer.link; renderer.link = function (href, title, text) { const relative = new RegExp('^[/#]').test(href); const target = !relative ? '_blank' : ''; if (relative && d.versionPath) href = `/${d.versionPath}${href}`; const html = linkRenderer.call(renderer, href, title, text); return html.replace(/^<a /, `<a ${target ? `target="${target}"` : ''} rel="nofollow noopener" `); }; let sanitizer = docs.requireDOMSanitizer(); markedInstance.setOptions({ renderer: renderer, gfm: true, tables: true, breaks: false, pedantic: false, sanitize: true, sanitizer: sanitizer, smartLists: true, smartypants: true }); let markdown = opts.markdown; // append repo info if any if (opts.repo) markdown += ` \`\`\`package ${opts.repo.name.replace(/^pxt-/, '')}=github:${opts.repo.fullName}#${opts.repo.tag || "master"} \`\`\` `; //Uses the CmdLink definitions to replace links to YouTube and Vimeo (limited at the moment) markdown = markdown.replace(/^\s*https?:\/\/(\S+)\s*$/mg, (f, lnk) => { for (let ent of links) { let m = ent.rx.exec(lnk); if (m) { return ent.cmd.replace(/\$(\d+)/g, (f, k) => { return m[parseInt(k)] || ""; }) + "\n"; } } return f; }); // replace pre-template in markdown markdown = markdown.replace(/@([a-z]+)@/ig, (m, param) => { let macro = pubinfo[param]; if (!macro && opts.throwOnError) U.userError(`unknown macro ${param}`); return macro || 'unknown macro'; }); let html = markedInstance(markdown); // support for breaks which somehow don't work out of the box html = html.replace(/&lt;br\s*\/&gt;/ig, "<br/>"); // github will render images if referenced as ![](/docs/static/foo.png) // we require /static/foo.png html = html.replace(/(<img [^>]* src=")\/docs\/static\/([^">]+)"/g, (f, pref, addr) => pref + '/static/' + addr + '"'); let endBox = ""; let boxSize = 0; function appendEndBox(size, box, html) { let r = html; if (size <= boxSize) { r = endBox + r; endBox = ""; boxSize = 0; } return r; } html = html.replace(/<h(\d)[^>]+>\s*([~@])?\s*(.*?)<\/h\d>/g, (f, lvl, tp, body) => { let m = /^(\w+)\s+(.*)/.exec(body); let cmd = m ? m[1] : body; let args = m ? m[2] : ""; let rawArgs = args; args = html2Quote(args); cmd = html2Quote(cmd); lvl = parseInt(lvl); if (!tp) { return appendEndBox(lvl, endBox, f); } else if (tp == "@") { let expansion = U.lookup(d.settings, cmd); if (expansion != null) { pubinfo[cmd] = args; } else { expansion = U.lookup(d.macros, cmd); if (expansion == null) { if (opts.throwOnError) U.userError(`Unknown command: @${cmd}`); return error(`Unknown command: @${cmd}`); } } let ivars = { ARGS: args, CMD: cmd }; return appendEndBox(lvl, endBox, injectHtml(expansion, ivars, ["ARGS", "CMD"])); } else { if (!cmd) { let r = endBox; endBox = ""; return r; } let box = U.lookup(d.boxes, cmd); if (box) { let parts = box.split("@BODY@"); let r = appendEndBox(lvl, endBox, parts[0].replace("@ARGS@", args)); endBox = parts[1]; let attrs = box.match(/data-[^>\s]+/ig); if (attrs && attrs.indexOf('data-inferred') >= 0) { boxSize = lvl; } return r; } else { if (opts.throwOnError) U.userError(`Unknown box: ~ ${cmd}`); return error(`Unknown box: ~ ${cmd}`); } } }); if (endBox) html = html + endBox; if (!pubinfo["title"]) { let titleM = /<h1[^<>]*>([^<>]+)<\/h1>/.exec(html); if (titleM) pubinfo["title"] = html2Quote(titleM[1]); } if (!pubinfo["description"]) { let descM = /<p>([^]+?)<\/p>/.exec(html); if (descM) pubinfo["description"] = html2Quote(descM[1]); } // try getting a better custom image for twitter const imgM = /<div class="ui embed mdvid"[^<>]+?data-placeholder="([^"]+)"[^>]*\/?>/i.exec(html) || /<img class="ui [^"]*image" src="([^"]+)"[^>]*\/?>/i.exec(html); if (imgM) pubinfo["cardLogo"] = html2Quote(imgM[1]); pubinfo["twitter"] = html2Quote(opts.theme.twitter || "@msmakecode"); let registers = {}; registers["main"] = ""; // first html = html.replace(/<!-- BEGIN-ASIDE (\S+) -->([^]*?)<!-- END-ASIDE -->/g, (f, nam, cont) => { let s = U.lookup(registers, nam); registers[nam] = (s || "") + cont; return "<!-- aside -->"; }); // fix up spourious newlines at the end of code blocks html = html.replace(/\n<\/code>/g, "</code>"); registers["main"] = html; let injectBody = (tmpl, body) => injectHtml(d.boxes[tmpl] || "@BODY@", { BODY: body }, ["BODY"]); html = ""; for (let k of Object.keys(registers)) { html += injectBody(k + "-container", registers[k]); } pubinfo["body"] = html; // don't mangle target name in title, it is already in the sitename pubinfo["name"] = pubinfo["title"] || ""; for (let k of Object.keys(opts.theme)) { let v = opts.theme[k]; if (typeof v == "string") pubinfo["theme_" + k] = v; } return d.finish(); } docs.renderMarkdown = renderMarkdown; function injectHtml(template, vars, quoted = []) { if (!template) return ''; return template.replace(/@(\w+)@/g, (f, key) => { let res = U.lookup(vars, key) || ""; res += ""; // make sure it's a string if (quoted.indexOf(key) < 0) { res = html2Quote(res); } return res; }); } function embedUrl(rootUrl, tag, id, height) { const url = `${rootUrl}#${tag}:${id}`; let padding = '70%'; return `<div style="position:relative;height:0;padding-bottom:${padding};overflow:hidden;"><iframe style="position:absolute;top:0;left:0;width:100%;height:100%;" src="${url}" frameborder="0" sandbox="allow-popups allow-forms allow-scripts allow-same-origin"></iframe></div>`; } docs.embedUrl = embedUrl; function runUrl(url, padding, id) { let embed = `<div style="position:relative;height:0;padding-bottom:${padding};overflow:hidden;"><iframe style="position:absolute;top:0;left:0;width:100%;height:100%;" src="${url}?id=${encodeURIComponent(id)}" allowfullscreen="allowfullscreen" sandbox="allow-popups allow-forms allow-scripts allow-same-origin" frameborder="0"></iframe></div>`; return embed; } docs.runUrl = runUrl; function codeEmbedUrl(rootUrl, id, height) { const docurl = `${rootUrl}---codeembed#pub:${id}`; height = Math.ceil(height || 300); return `<div style="position:relative;height:calc(${height}px + 5em);width:100%;overflow:hidden;"><iframe style="position:absolute;top:0;left:0;width:100%;height:100%;" src="${docurl}" allowfullscreen="allowfullscreen" frameborder="0" sandbox="allow-scripts allow-same-origin"></iframe></div>`; } docs.codeEmbedUrl = codeEmbedUrl; const inlineTags = { b: 1, strong: 1, em: 1, };