From c1b976572c7512dfa7d7407706891f511883c0ad Mon Sep 17 00:00:00 2001 From: Tarun Chauhan Date: Thu, 12 Jan 2023 21:31:07 +0530 Subject: [PATCH 1/7] feat: add copy to clipboard in code blocks (#14153) * feat: add copy to clipboard in code blocks * Use the same style as docusaurus v2 * Remove style from siteConfig.js * Fix typo * Disable button on copied Co-authored-by: Tarun Chauhan Co-authored-by: fisker Cheung --- website/siteConfig.js | 6 ++- website/static/css/code-block-buttons.css | 63 +++++++++++++++++++++++ website/static/js/code-block-buttons.js | 57 ++++++++++++++++++++ website/yarn.lock | 6 +-- 4 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 website/static/css/code-block-buttons.css create mode 100644 website/static/js/code-block-buttons.js diff --git a/website/siteConfig.js b/website/siteConfig.js index b4d93ab11..a8046d438 100644 --- a/website/siteConfig.js +++ b/website/siteConfig.js @@ -56,7 +56,11 @@ const siteConfig = { }, usePrism: ["javascript", "jsx", "typescript", "ts", "js", "html", "css"], useEnglishUrl: true, - scripts: ["https://buttons.github.io/buttons.js"], + scripts: [ + "https://buttons.github.io/buttons.js", + "https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js", + "/js/code-block-buttons.js", + ], stylesheets: [ "//unpkg.com/@sandhose/prettier-animated-logo@1.0.3/dist/wide.css", ], diff --git a/website/static/css/code-block-buttons.css b/website/static/css/code-block-buttons.css new file mode 100644 index 000000000..73207981c --- /dev/null +++ b/website/static/css/code-block-buttons.css @@ -0,0 +1,63 @@ +/* "Copy" code block button, style from docusaurus v2 */ + +.code-block-with-actions { + position: relative; +} + +.code-block-actions { + position: absolute; + top: 10px; + right: 10px; +} + +.code-block-copy-button { + width: 32px; + height: 32px; + border: 1px solid #dadde1; + border-radius: 6px; + display: flex; + transition: opacity 0.2s ease-in-out; + opacity: 0; + position: relative; + cursor: pointer; + color: #393a34; + background-color: #f6f8fa; +} + +.code-block-with-actions:hover .code-block-copy-button { + opacity: 0.4; +} + +.code-block-with-actions:hover .code-block-copy-button:hover { + opacity: 1; +} + +.code-block-copy-button__icon { + fill: currentColor; + position: absolute; + width: 18px; + height: 18px; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; + transition: 0.15s; +} + +.code-block-copy-button__icon--copied { + color: #00d600; + opacity: 0; + transform: scale(0.33); +} + +.code-block-copy-button--copied .code-block-copy-button__icon--copy { + opacity: 0; + transform: scale(0.33); +} + +.code-block-copy-button--copied .code-block-copy-button__icon--copied { + transition-delay: 75ms; + transform: scale(1); + opacity: 1; +} diff --git a/website/static/js/code-block-buttons.js b/website/static/js/code-block-buttons.js new file mode 100644 index 000000000..dd09911f3 --- /dev/null +++ b/website/static/js/code-block-buttons.js @@ -0,0 +1,57 @@ +/* global ClipboardJS */ + +"use strict"; + +(function () { + const CONTAINER_CLASS_NAME = "code-block-with-actions"; + const ACTIONS_CONTAINER_CLASS_NAME = "code-block-actions"; + const COPY_BUTTON_CLASS_NAME = "code-block-copy-button"; + const COPY_BUTTON_COPIED_CLASS_NAME = `${COPY_BUTTON_CLASS_NAME}--copied`; + const COPY_BUTTON_ICON_CLASS_NAME = `${COPY_BUTTON_CLASS_NAME}__icon`; + const COPY_BUTTON_COPY_ICON_CLASS_NAME = `${COPY_BUTTON_ICON_CLASS_NAME}--copy`; + const COPY_BUTTON_COPIED_ICON_CLASS_NAME = `${COPY_BUTTON_ICON_CLASS_NAME}--copied`; + const ARIA_LABEL = "Copy code to clipboard"; + const ARIA_LABEL_COPIED = "Copied"; + + function init(codeBlock) { + const container = codeBlock.parentNode; + container.classList.add(CONTAINER_CLASS_NAME); + + const actionsContainer = Object.assign(document.createElement("div"), { + className: ACTIONS_CONTAINER_CLASS_NAME, + }); + const copyButton = Object.assign(document.createElement("button"), { + className: COPY_BUTTON_CLASS_NAME, + type: "button", + innerHTML: + `` + + ``, + }); + copyButton.setAttribute("aria-label", ARIA_LABEL); + + new ClipboardJS(copyButton, { target: () => codeBlock }).on( + "success", + (event) => { + event.clearSelection(); + copyButton.classList.add(COPY_BUTTON_COPIED_CLASS_NAME); + copyButton.setAttribute("aria-label", ARIA_LABEL_COPIED); + copyButton.disabled = true; + + setTimeout(() => { + copyButton.classList.remove(COPY_BUTTON_COPIED_CLASS_NAME); + copyButton.setAttribute("aria-label", ARIA_LABEL); + copyButton.disabled = false; + }, 2000); + } + ); + + actionsContainer.appendChild(copyButton); + container.appendChild(actionsContainer); + } + + window.addEventListener("load", () => { + for (const codeBlock of document.querySelectorAll("pre > code.hljs")) { + init(codeBlock); + } + }); +})(); diff --git a/website/yarn.lock b/website/yarn.lock index 3abc0b3ae..99a7ef961 100644 --- a/website/yarn.lock +++ b/website/yarn.lock @@ -2558,9 +2558,9 @@ dashdash@^1.12.0: assert-plus "^1.0.0" date-fns@^2.16.1: - version "2.28.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" - integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== + version "2.29.3" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" + integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0: version "2.6.9" From 06ea1ac0d05a7b7d463bdbb0b662b22f3d340be0 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Sat, 14 Jan 2023 01:07:42 +0800 Subject: [PATCH 2/7] Stricter node type check (#14165) --- src/language-js/print/jsx.js | 9 ++++----- src/language-js/print/typescript.js | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/language-js/print/jsx.js b/src/language-js/print/jsx.js index db4a68497..6f9161218 100644 --- a/src/language-js/print/jsx.js +++ b/src/language-js/print/jsx.js @@ -25,7 +25,6 @@ const { getLast, getPreferredQuote } = require("../../common/util.js"); const { isJsxNode, rawText, - isLiteral, isCallExpression, isStringLiteral, isBinaryish, @@ -279,7 +278,7 @@ function printJsxChildren( const parts = []; path.each((childPath, i, children) => { const child = childPath.getValue(); - if (isLiteral(child)) { + if (child.type === "JSXText") { const text = rawText(child); // Contains a non-whitespace character @@ -812,7 +811,7 @@ function isEmptyJsxElement(node) { // if there is one text child and does not contain any meaningful text // we can treat the element as empty. const child = node.children[0]; - return isLiteral(child) && !isMeaningfulJsxText(child); + return child.type === "JSXText" && !isMeaningfulJsxText(child); } // Meaningful if it contains non-whitespace characters, @@ -823,7 +822,7 @@ function isEmptyJsxElement(node) { */ function isMeaningfulJsxText(node) { return ( - isLiteral(node) && + node.type === "JSXText" && (containsNonJsxWhitespaceRegex.test(rawText(node)) || !/\n/.test(rawText(node))) ); @@ -833,7 +832,7 @@ function isMeaningfulJsxText(node) { function isJsxWhitespaceExpression(node) { return ( node.type === "JSXExpressionContainer" && - isLiteral(node.expression) && + isStringLiteral(node.expression) && node.expression.value === " " && !hasComment(node.expression) ); diff --git a/src/language-js/print/typescript.js b/src/language-js/print/typescript.js index 63e3e109b..68defeffe 100644 --- a/src/language-js/print/typescript.js +++ b/src/language-js/print/typescript.js @@ -15,7 +15,7 @@ const { }, } = require("../../document/index.js"); const { - isLiteral, + isStringLiteral, getTypeScriptMappedTypeModifier, shouldPrintComma, isCallExpression, @@ -449,7 +449,7 @@ function printTypescript(path, options, print) { return ["require(", print("expression"), ")"]; case "TSModuleDeclaration": { const parent = path.getParentNode(); - const isExternalModule = isLiteral(node.id); + const isExternalModule = isStringLiteral(node.id); const parentIsDeclaration = parent.type === "TSModuleDeclaration"; const bodyIsDeclaration = node.body && node.body.type === "TSModuleDeclaration"; From 4ad9160891c7209118ce8bd11f8a0f4a23b41417 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Sat, 14 Jan 2023 10:57:39 +0800 Subject: [PATCH 3/7] Angular: allow self-closing tags on custom elements (#14170) Fixes https://github.com/prettier/prettier/issues/14168 --- changelog_unreleased/angular/14170.md | 18 ++++++++++ src/language-html/parser-html.js | 22 ++++++------ .../__snapshots__/jsfmt.spec.js.snap | 36 +++++++++++++++++++ .../format/angular/self-closing/jsfmt.spec.js | 1 + .../self-closing/self-closing.component.html | 13 +++++++ 5 files changed, 79 insertions(+), 11 deletions(-) create mode 100644 changelog_unreleased/angular/14170.md create mode 100644 tests/format/angular/self-closing/__snapshots__/jsfmt.spec.js.snap create mode 100644 tests/format/angular/self-closing/jsfmt.spec.js create mode 100644 tests/format/angular/self-closing/self-closing.component.html diff --git a/changelog_unreleased/angular/14170.md b/changelog_unreleased/angular/14170.md new file mode 100644 index 000000000..471169c7d --- /dev/null +++ b/changelog_unreleased/angular/14170.md @@ -0,0 +1,18 @@ +#### Allow self-closing tags on custom elements (#14170 by @fisker) + +See [Angular v15.1.0 release note](https://github.com/angular/angular/releases/tag/15.1.0) for details. + + +```html +// Input + + +// Prettier stable +SyntaxError: Only void and foreign elements can be self closed "app-test" (1:1) +> 1 | + | ^^^^^^^^^ + 2 | + +// Prettier main + +``` diff --git a/src/language-html/parser-html.js b/src/language-html/parser-html.js index 5763fe7c7..a959d6e11 100644 --- a/src/language-html/parser-html.js +++ b/src/language-html/parser-html.js @@ -24,7 +24,7 @@ const { locStart, locEnd } = require("./loc.js"); * @typedef {import('angular-html-parser/lib/compiler/src/ml_parser/parser').ParseTreeResult} ParserTreeResult * @typedef {Omit & { * name?: 'html' | 'angular' | 'vue' | 'lwc'; - * recognizeSelfClosing?: boolean; + * canSelfClose?: boolean; * normalizeTagName?: boolean; * normalizeAttributeName?: boolean; * }} ParserOptions @@ -42,7 +42,7 @@ const { locStart, locEnd } = require("./loc.js"); function ngHtmlParser( input, { - recognizeSelfClosing, + canSelfClose, normalizeTagName, normalizeAttributeName, allowHtmComponentClosingTags, @@ -64,7 +64,7 @@ function ngHtmlParser( } = require("angular-html-parser/lib/compiler/src/ml_parser/html_tags"); let { rootNodes, errors } = parser.parse(input, { - canSelfClose: recognizeSelfClosing, + canSelfClose, allowHtmComponentClosingTags, isTagNameCaseSensitive, getTagContentType, @@ -97,7 +97,7 @@ function ngHtmlParser( let secondParseResult; const doSecondParse = () => parser.parse(input, { - canSelfClose: recognizeSelfClosing, + canSelfClose, allowHtmComponentClosingTags, isTagNameCaseSensitive, }); @@ -135,13 +135,13 @@ function ngHtmlParser( } } else { // If not Vue SFC, treat as html - recognizeSelfClosing = true; + canSelfClose = true; normalizeTagName = true; normalizeAttributeName = true; allowHtmComponentClosingTags = true; isTagNameCaseSensitive = false; const htmlParseResult = parser.parse(input, { - canSelfClose: recognizeSelfClosing, + canSelfClose, allowHtmComponentClosingTags, isTagNameCaseSensitive, }); @@ -373,7 +373,7 @@ function _parse(text, options, parserOptions, shouldParseFrontMatter = true) { */ function createParser({ name, - recognizeSelfClosing = false, + canSelfClose = false, normalizeTagName = false, normalizeAttributeName = false, allowHtmComponentClosingTags = false, @@ -386,7 +386,7 @@ function createParser({ text, { parser: name, ...options }, { - recognizeSelfClosing, + canSelfClose, normalizeTagName, normalizeAttributeName, allowHtmComponentClosingTags, @@ -405,15 +405,15 @@ module.exports = { parsers: { html: createParser({ name: "html", - recognizeSelfClosing: true, + canSelfClose: true, normalizeTagName: true, normalizeAttributeName: true, allowHtmComponentClosingTags: true, }), - angular: createParser({ name: "angular" }), + angular: createParser({ name: "angular", canSelfClose: true }), vue: createParser({ name: "vue", - recognizeSelfClosing: true, + canSelfClose: true, isTagNameCaseSensitive: true, getTagContentType: (tagName, prefix, hasParent, attrs) => { if ( diff --git a/tests/format/angular/self-closing/__snapshots__/jsfmt.spec.js.snap b/tests/format/angular/self-closing/__snapshots__/jsfmt.spec.js.snap new file mode 100644 index 000000000..f4a624bed --- /dev/null +++ b/tests/format/angular/self-closing/__snapshots__/jsfmt.spec.js.snap @@ -0,0 +1,36 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`self-closing.component.html format 1`] = ` +====================================options===================================== +parsers: ["angular"] +printWidth: 80 + | printWidth +=====================================input====================================== + + + + + + + +
+
+
+ +=====================================output===================================== + + + + + + + +
+
+
+ +================================================================================ +`; diff --git a/tests/format/angular/self-closing/jsfmt.spec.js b/tests/format/angular/self-closing/jsfmt.spec.js new file mode 100644 index 000000000..b802ad867 --- /dev/null +++ b/tests/format/angular/self-closing/jsfmt.spec.js @@ -0,0 +1 @@ +run_spec(__dirname, ["angular"]); diff --git a/tests/format/angular/self-closing/self-closing.component.html b/tests/format/angular/self-closing/self-closing.component.html new file mode 100644 index 000000000..06e1d53f7 --- /dev/null +++ b/tests/format/angular/self-closing/self-closing.component.html @@ -0,0 +1,13 @@ + + + + + + + +
+
+
From c990e522ce81712e2e70527a6ce3d7a87b3f1114 Mon Sep 17 00:00:00 2001 From: fisker Date: Sat, 14 Jan 2023 11:33:26 +0800 Subject: [PATCH 4/7] Release 2.8.3 --- .github/ISSUE_TEMPLATE/formatting.md | 2 +- .github/ISSUE_TEMPLATE/integration.md | 2 +- CHANGELOG.md | 23 ++++++++++++++ docs/browser.md | 30 +++++++++---------- package.json | 2 +- .../versioned_docs/version-stable/browser.md | 30 +++++++++---------- 6 files changed, 56 insertions(+), 33 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/formatting.md b/.github/ISSUE_TEMPLATE/formatting.md index 340fab2a4..8e49b6b6f 100644 --- a/.github/ISSUE_TEMPLATE/formatting.md +++ b/.github/ISSUE_TEMPLATE/formatting.md @@ -26,7 +26,7 @@ Don't fill the form below manually! Let a program create a report for you: --> -**Prettier 2.8.2** +**Prettier 2.8.3** [Playground link](https://prettier.io/playground/#.....) ```sh diff --git a/.github/ISSUE_TEMPLATE/integration.md b/.github/ISSUE_TEMPLATE/integration.md index b397119d2..239aba6e9 100644 --- a/.github/ISSUE_TEMPLATE/integration.md +++ b/.github/ISSUE_TEMPLATE/integration.md @@ -20,7 +20,7 @@ BEFORE SUBMITTING AN ISSUE: **Environments:** -- Prettier Version: 2.8.2 +- Prettier Version: 2.8.3 - Running Prettier via: - Runtime: - Operating System: diff --git a/CHANGELOG.md b/CHANGELOG.md index df1b8030f..2a47b3eff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +# 2.8.3 + +[diff](https://github.com/prettier/prettier/compare/2.8.2...2.8.3) + +#### Allow self-closing tags on custom elements ([#14170](https://github.com/prettier/prettier/pull/14170) by [@fisker](https://github.com/fisker)) + +See [Angular v15.1.0 release note](https://github.com/angular/angular/releases/tag/15.1.0) for details. + + +```html +// Input + + +// Prettier 2.8.2 +SyntaxError: Only void and foreign elements can be self closed "app-test" (1:1) +> 1 | + | ^^^^^^^^^ + 2 | + +// Prettier 2.8.3 + +``` + # 2.8.2 [diff](https://github.com/prettier/prettier/compare/2.8.1...2.8.2) diff --git a/docs/browser.md b/docs/browser.md index 1db441def..f55a8a773 100644 --- a/docs/browser.md +++ b/docs/browser.md @@ -20,8 +20,8 @@ Required options: - **`plugins`**: Unlike the `format` function from the [Node.js-based API](api.md#prettierformatsource--options), this function doesn’t load plugins automatically. The `plugins` option is required because all the parsers included in the Prettier package come as plugins (for reasons of file size). These plugins are files named - - `parser-*.js` in and - - `parser-*.mjs` in + - `parser-*.js` in and + - `parser-*.mjs` in You need to load the ones that you’re going to use and pass them to `prettier.format` using the `plugins` option. @@ -32,8 +32,8 @@ See below for examples. ### Global ```html - - + + - + +