diff --git a/cli/cli.ts b/cli/cli.ts index 7afeeb947605..ec57bee3fc94 100644 --- a/cli/cli.ts +++ b/cli/cli.ts @@ -56,6 +56,7 @@ pxt.docs.requireDOMSanitizer = () => { code: mergeClassAttribute("code"), pre: mergeClassAttribute("pre"), div: mergeClassAttribute("div", "data-youtube", "title"), + img: mergeClassAttribute("img"), }, }; @@ -3063,7 +3064,7 @@ function renderDocs(builtPackaged: string, localDir: string) { html = pxt.docs.renderMarkdown({ template: docsTemplate, - markdown: patchedMd, + markdown: pxt.docs.normalizeStaticMarkdown(patchedMd), theme: pxt.appTarget.appTheme, filepath: path.join("docs", pathUnderDocs), }); @@ -6278,6 +6279,23 @@ function readBlocksprjConfig(): pxt.PackageConfig { return config; } +function mergeCorePackage(corePackage: pxt.PackageConfig, dependencies = {} as pxt.Map): pxt.Map { + for (const dep of Object.keys(dependencies)) { + const verSpec = dependencies[dep]; + + if (pxt.appTarget.bundledpkgs[dep] && (verSpec === "*" || verSpec.startsWith("file:"))) { + const bundled = pxt.appTarget.bundledpkgs[dep]; + const config = pxt.U.jsonTryParse(bundled[pxt.CONFIG_NAME]); + + if (config?.core) { + return dependencies; + } + } + } + + return pxt.tutorial.mergeTutorialDependencies(corePackage.dependencies, dependencies); +} + function checkFileSize(files: string[]): number { if (!pxt.appTarget.cloud) return 0; @@ -6536,7 +6554,7 @@ function internalCheckDocsAsync(compileSnippets?: boolean, re?: string, fix?: bo continue; } const tutorial = pxt.tutorial.parseTutorial(tutorialMd); - const pkgs = pxt.tutorial.mergeTutorialDependencies(blocksprjConfig.dependencies, pxt.gallery.parsePackagesFromMarkdown(tutorialMd) || {}); + const pkgs = mergeCorePackage(blocksprjConfig, pxt.gallery.parsePackagesFromMarkdown(tutorialMd) || {}); let extraFiles: Map = null; @@ -6595,7 +6613,7 @@ function internalCheckDocsAsync(compileSnippets?: boolean, re?: string, fix?: bo continue; } const prj = pxt.gallery.parseExampleMarkdown(card.name, exMd); - const pkgs = pxt.tutorial.mergeTutorialDependencies(blocksprjConfig.dependencies, prj.dependencies); + const pkgs = mergeCorePackage(blocksprjConfig, prj.dependencies); let extraFiles: Map = undefined; @@ -6691,7 +6709,7 @@ function internalCacheUsedBlocksAsync(): Promise> { } const tutorial = pxt.tutorial.parseTutorial(md) as TutorialInfo; const tutorialDeps = pxt.gallery.parsePackagesFromMarkdown(md) || {}; - const pkgs: pxt.Map = pxt.tutorial.mergeTutorialDependencies(blocksprjConfig.dependencies, tutorialDeps); + const pkgs: pxt.Map = mergeCorePackage(blocksprjConfig, tutorialDeps); tutorial.pkgs = pkgs; tutorial.path = path; @@ -6849,7 +6867,7 @@ export function getCodeSnippets(fileName: string, md: string, baseConfig: pxt.Pa return acc; }, {} as pxt.Map); - const pkgs = pxt.tutorial.mergeTutorialDependencies(baseConfig.dependencies, tutorialDeps); + const pkgs = mergeCorePackage(baseConfig, tutorialDeps); const pkgName = fileName.replace(/\\/g, '-').replace(/.md$/i, ''); return codeSnippets.map((snip, i) => { diff --git a/package.json b/package.json index 4a113977ab5c..7ac84da69328 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pxt-core", - "version": "13.1.4", + "version": "13.1.5", "description": "Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors", "keywords": [ "TypeScript", diff --git a/pxtlib/docsrender.ts b/pxtlib/docsrender.ts index 2a6f18fafe37..21a1667cdd57 100644 --- a/pxtlib/docsrender.ts +++ b/pxtlib/docsrender.ts @@ -91,6 +91,39 @@ namespace pxt.docs { return s; } + export function normalizeStaticMarkdown(markdown: string): string { + const lines = markdown.split(/\r?\n/) + let codeFenceMarker = "" + + for (let i = 0; i < lines.length; i++) { + const fenceMatch = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(lines[i]) + if (fenceMatch) { + const marker = fenceMatch[1] + const trailingText = fenceMatch[2] + const closesCodeFence = !!codeFenceMarker + && marker[0] === codeFenceMarker[0] + && marker.length >= codeFenceMarker.length + && !trailingText.trim() + + if (closesCodeFence) { + codeFenceMarker = "" + } else if (!codeFenceMarker) { + codeFenceMarker = marker + } + + continue + } + + if (!codeFenceMarker && /^ {0,3}\s*$/i.test(lines[i])) { + // Marked treats a standalone break as inline content, which can extend a + // preceding table or keep the following Markdown in the same paragraph. + lines[i] = "\n


\n" + } + } + + return lines.join("\n") + } + // the input already should be HTML-quoted but we want to make sure, and also quote quotes export function html2Quote(s: string) { if (!s) return s; diff --git a/pxtrunner/runner.ts b/pxtrunner/runner.ts index e904e9227ba0..d542770a6813 100644 --- a/pxtrunner/runner.ts +++ b/pxtrunner/runner.ts @@ -316,7 +316,10 @@ function loadPackageAsync(id: string, code?: string, dependencies?: string[]) { epkg.files[pxt.MAIN_TS] = code; //set the custom doc name from the URL. let cfg = JSON.parse(epkg.files[pxt.CONFIG_NAME]) as pxt.PackageConfig; - cfg.name = window.location.href.split('/').pop().split(/[?#]/)[0];; + cfg.name = window.location.href.split('/').pop().split(/[?#]/)[0].replace(/\.html$/i, ""); + if (cfg.files.indexOf(pxt.MAIN_BLOCKS) == -1) { + cfg.files.push(pxt.MAIN_BLOCKS); + } epkg.files[pxt.CONFIG_NAME] = pxt.Package.stringifyConfig(cfg); //Propgate the change to main package diff --git a/react-common/components/controls/Modal.tsx b/react-common/components/controls/Modal.tsx index b40de569354c..d246cde15836 100644 --- a/react-common/components/controls/Modal.tsx +++ b/react-common/components/controls/Modal.tsx @@ -23,13 +23,13 @@ export interface ModalAction { export interface ModalProps extends ContainerProps { title: string; leftIcon?: string; - helpUrl?: string ariaDescribedBy?: string; actions?: ModalAction[]; onClose?: () => void; fullscreen?: boolean; parentElement?: Element; hideDismissButton?: boolean; + rightHeader?: React.ReactNode; } export const Modal = (props: ModalProps) => { @@ -43,12 +43,12 @@ export const Modal = (props: ModalProps) => { role, title, leftIcon, - helpUrl, actions, onClose, parentElement, fullscreen, hideDismissButton, + rightHeader } = props; const closeClickHandler = (e?: React.MouseEvent) => { @@ -110,18 +110,9 @@ export const Modal = (props: ModalProps) => { {leftIcon && } {title} - {fullscreen && helpUrl && -
- - - - - + {fullscreen && rightHeader && +
+ {rightHeader}
} {!fullscreen && !hideDismissButton && diff --git a/react-common/styles/controls/Modal.less b/react-common/styles/controls/Modal.less index fd90c10d023c..ee8334a6af95 100644 --- a/react-common/styles/controls/Modal.less +++ b/react-common/styles/controls/Modal.less @@ -112,21 +112,28 @@ align-items: center; font-size: 1.2rem; font-weight: 600; - - .common-button.men .fas { - - } } .common-modal-back:focus-within { border: 1px, solid var(--pxt-focus-border); } + .common-modal-right-menu { + display: flex; + flex-direction: row; + align-items: center; + } + .common-modal-help { display: flex; align-items: center; font-size: 1.2rem; font-weight: 600; + height: 100%; + + a:hover { + color: var(--pxt-primary-foreground); + } } .common-modal-title { diff --git a/webapp/src/extensionsBrowser.tsx b/webapp/src/extensionsBrowser.tsx index 8bb88da921d1..4b6369b0d47a 100644 --- a/webapp/src/extensionsBrowser.tsx +++ b/webapp/src/extensionsBrowser.tsx @@ -12,6 +12,7 @@ import { ExtensionCard } from "../../react-common/components/extensions/Extensio import { Modal } from "../../react-common/components/controls/Modal"; import { classList } from "../../react-common/components/util"; import { TabList, TabListProps } from "../../react-common/components/controls/TabList"; +import { Link } from "../../react-common/components/controls/Link"; type ExtensionMeta = pxtc.service.ExtensionMeta & { installed?: boolean }; const ExtensionType = pxtc.service.ExtensionType; @@ -618,7 +619,30 @@ export const ExtensionsBrowser = (props: ExtensionsProps) => { fullscreen={true} className={"extensions-browser"} onClose={props.hideExtensions} - helpUrl={"/extensions"} + rightHeader={ + <> +