diff --git a/docs/contribute/move.md b/docs/contribute/move.md index fdebd5784..4277227e5 100644 --- a/docs/contribute/move.md +++ b/docs/contribute/move.md @@ -6,7 +6,7 @@ When you move a source file or folder, you must also update all inbound and outb Move a file or folder from one location to another and update all links in the documentation. For example: -``` +```bash docs-builder mv ./old-location/ia.md ./new-location/ia.md ``` @@ -16,7 +16,7 @@ The `docset.yml` and `toc.yml` files are not automatically updated when using th ## `docs-builder mv --help` -``` +```bash Usage: mv [arguments...] [options...] [-h|--help] [--version] Move a file or folder from one location to another and update all links in the documentation @@ -28,4 +28,4 @@ Arguments: Options: --dry-run Dry run the move operation (Default: null) -p|--path Defaults to the`{pwd}` folder (Default: null) -``` \ No newline at end of file +``` diff --git a/docs/migration/guide/how-to-set-up-docs-previews.md b/docs/migration/guide/how-to-set-up-docs-previews.md index 167168eb3..fd5273038 100644 --- a/docs/migration/guide/how-to-set-up-docs-previews.md +++ b/docs/migration/guide/how-to-set-up-docs-previews.md @@ -16,8 +16,9 @@ If the `path-pattern` input does not match any changes in the PR, the workflow w This way you only build and deploy the docs when there are changes to the docs and you can still set it as a required status check. -::::{dropdown} .github/workflows/docs-build.yml -:open: +::::{tab-set} + +:::{tab-item} .github/workflows/docs-build.yml ```yaml --- @@ -45,6 +46,8 @@ jobs: 2. Reusable workflow: [elastic/docs-builder/.github/workflows/preview-build.yml](https://github.com/elastic/docs-builder/blob/main/.github/workflows/preview-build.yml) 3. his should be the path to your docs folder. +::: + :::: @@ -57,8 +60,10 @@ We are aware of the security implications of using `pull_request_target` as desc The workflow never checks out the code and doesn't use any user modifiable inputs (e.g. PR title). ::: -::::{dropdown} .github/workflows/docs-cleanup.yml -:open: +::::{tab-set} + +:::{tab-item} .github/workflows/docs-cleanup.yml + ```yaml --- name: docs-cleanup @@ -80,6 +85,8 @@ jobs: 1. Reusable workflow: [elastic/docs-builder/.github/workflows/preview-cleanup.yml](https://github.com/elastic/docs-builder/blob/main/.github/workflows/preview-cleanup.yml) 2. No permissions to read content +::: + :::: ## Required Status Checks diff --git a/docs/syntax/code.md b/docs/syntax/code.md index 8c3e2d920..eb88dfad3 100644 --- a/docs/syntax/code.md +++ b/docs/syntax/code.md @@ -1,24 +1,37 @@ -# Code blocks +# Code Code blocks can be used to display multiple lines of code. They preserve formatting and provide syntax highlighting when possible. -## Syntax +## Code block -Start and end a code block with a code fence. A code fence is a sequence of at least three consecutive backtick characters (~```~). You can optionally add a language identifier to enable syntax highlighting. +Start and end a code block with a code fence. A code fence is a sequence of at least three consecutive backtick characters `` ``` ``. You can optionally add a language identifier to enable syntax highlighting. + + +::::{tab-set} + +:::{tab-item} Output -````markdown ```yaml project: title: MyST Markdown github: https://github.com/jupyter-book/mystmd ``` -```` +::: + +:::{tab-item} Markdown + +````markdown ```yaml project: title: MyST Markdown github: https://github.com/jupyter-book/mystmd ``` +```` + +::: + +:::: ### Code callouts @@ -30,7 +43,11 @@ Add `<\d+>` to the end of a line to explicitly create a code callout. An ordered list with the same number of items as callouts must follow the code block. If the number of list items doesn’t match the callouts, docs-builder will throw an error. -````markdown + +::::{tab-set} + +:::{tab-item} Output + ```yaml project: license: @@ -38,8 +55,13 @@ project: ``` 1. The license -```` +::: + + +:::{tab-item} Markdown + +````markdown ```yaml project: license: @@ -47,42 +69,71 @@ project: ``` 1. The license +```` + +::: + +:::: #### Magic Callouts If a code block contains code comments in the form of `//` or `#`, callouts will be magically created 🪄. -````markdown + +::::{tab-set} + +:::{tab-item} Output + ```csharp var apiKey = new ApiKey(""); // Set up the api key var client = new ElasticsearchClient("", apiKey); ``` -```` +::: + +:::{tab-item} Markdown + +````markdown ```csharp var apiKey = new ApiKey(""); // Set up the api key var client = new ElasticsearchClient("", apiKey); ``` +```` +::: + + +:::: Code comments must follow code to be hoisted as a callout. For example: -````markdown +::::{tab-set} + +:::{tab-item} Output + ```csharp // THIS IS NOT A CALLOUT var apiKey = new ApiKey(""); // This is a callout var client = new ElasticsearchClient("", apiKey); ``` -```` +::: + +:::{tab-item} Markdown + +````markdown ```csharp // THIS IS NOT A CALLOUT var apiKey = new ApiKey(""); // This is a callout var client = new ElasticsearchClient("", apiKey); ``` +```` + +::: +:::: -## Console code blocks +### Console code blocks :::{note} This feature is still being developed. @@ -92,7 +143,10 @@ We document a lot of API endpoints at Elastic. For these endpoints, we support ` In a console code block, the first line is highlighted as a dev console string and the remainder as json: -````markdown +::::{tab-set} + +:::{tab-item} Output + ```console GET /mydocuments/_search { @@ -102,8 +156,12 @@ GET /mydocuments/_search } } ``` -```` +::: + +:::{tab-item} Markdown + +````markdown ```console GET /mydocuments/_search { @@ -113,3 +171,52 @@ GET /mydocuments/_search } } ``` +```` + +::: + +:::: + +## Inline code + +Use backticks to create an inline code span. +Inline code spans are useful for short code snippets or variable names. + + +### Inline code in a paragraph + +::::{tab-set} + +:::{tab-item} Output + +This `code` is inline. + +::: + +:::{tab-item} Markdown + +````markdown +This `code` is inline. +```` +::: + +:::: + +### Inline code in a heading + +::::{tab-set} + +:::{tab-item} Output + +## This `code` is in a heading. + +::: + +:::{tab-item} Markdown + +````markdown +## This `code` is in a heading. +```` +::: + +:::: diff --git a/src/Elastic.Markdown/Assets/copybutton.css b/src/Elastic.Markdown/Assets/copybutton.css new file mode 100644 index 000000000..316c769da --- /dev/null +++ b/src/Elastic.Markdown/Assets/copybutton.css @@ -0,0 +1,98 @@ +/* Copy buttons */ +button.copybtn { + position: absolute; + display: flex; + top: .3em; + right: .3em; + width: 1.7em; + height: 1.7em; + opacity: 0; + transition: opacity 0.3s, border .3s, background-color .3s; + user-select: none; + cursor: pointer; + padding: 0; + border: none; + outline: none; + border-radius: 0.4em; + /* The colors that GitHub uses */ + border: #1b1f2426 1px solid; + /*background-color: #f6f8fa;*/ + color: var(--color-gray-400); +} + +button.copybtn.success { + /*border-color: #22863a;*/ + color: #22863a; +} + +button.copybtn svg { + stroke: currentColor; + width: 1.5em; + height: 1.5em; + padding: 0.1em; +} + +div.highlight { + position: relative; +} + +/* Show the copybutton */ +.highlight:hover button.copybtn, button.copybtn.success { + opacity: 1; +} + +.highlight button.copybtn:hover { + /*background-color: rgb(235, 235, 235);*/ +} + +.highlight button.copybtn:active { + /*background-color: rgb(187, 187, 187);*/ +} + +/** + * A minimal CSS-only tooltip copied from: + * https://codepen.io/mildrenben/pen/rVBrpK + * + * To use, write HTML like the following: + * + *

Short

+ */ + .o-tooltip--left { + position: relative; + } + + .o-tooltip--left.success:after { + } + + .o-tooltip--left:after { + opacity: 0; + visibility: hidden; + position: absolute; + content: attr(data-tooltip); + padding: .2em; + font-size: .8em; + left: -.2em; + /*background: grey;*/ + color: var(--color-gray-400); + white-space: nowrap; + z-index: 2; + border-radius: 2px; + transform: translateX(-102%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); +} + +.o-tooltip--left:hover:after { + display: block; + opacity: 1; + visibility: visible; + transform: translateX(-100%) translateY(0); + transition: opacity 0.2s cubic-bezier(0.64, 0.09, 0.08, 1), transform 0.2s cubic-bezier(0.64, 0.09, 0.08, 1); + transition-delay: .5s; +} + +/* By default the copy button shouldn't show up when printing a page */ +@media print { + button.copybtn { + display: none; + } +} diff --git a/src/Elastic.Markdown/Assets/copybutton.ts b/src/Elastic.Markdown/Assets/copybutton.ts new file mode 100644 index 000000000..35cb8da22 --- /dev/null +++ b/src/Elastic.Markdown/Assets/copybutton.ts @@ -0,0 +1,261 @@ +// Localization support + +import * as ClipboardJS from 'clipboard' + +const DOCUMENTATION_OPTIONS = { + VERSION: '', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; + +const messages = { + 'en': { + 'copy': 'Copy', + 'copy_to_clipboard': 'Copy to clipboard', + 'copy_success': 'Copied!', + 'copy_failure': 'Failed to copy', + }, + 'es' : { + 'copy': 'Copiar', + 'copy_to_clipboard': 'Copiar al portapapeles', + 'copy_success': '¡Copiado!', + 'copy_failure': 'Error al copiar', + }, + 'de' : { + 'copy': 'Kopieren', + 'copy_to_clipboard': 'In die Zwischenablage kopieren', + 'copy_success': 'Kopiert!', + 'copy_failure': 'Fehler beim Kopieren', + }, + 'fr' : { + 'copy': 'Copier', + 'copy_to_clipboard': 'Copier dans le presse-papier', + 'copy_success': 'Copié !', + 'copy_failure': 'Échec de la copie', + }, + 'ru': { + 'copy': 'Скопировать', + 'copy_to_clipboard': 'Скопировать в буфер', + 'copy_success': 'Скопировано!', + 'copy_failure': 'Не удалось скопировать', + }, + 'zh-CN': { + 'copy': '复制', + 'copy_to_clipboard': '复制到剪贴板', + 'copy_success': '复制成功!', + 'copy_failure': '复制失败', + }, + 'it' : { + 'copy': 'Copiare', + 'copy_to_clipboard': 'Copiato negli appunti', + 'copy_success': 'Copiato!', + 'copy_failure': 'Errore durante la copia', + } +} + +let locale = 'en' +if( document.documentElement.lang !== undefined + && messages[document.documentElement.lang] !== undefined ) { + locale = document.documentElement.lang +} + +let doc_url_root = DOCUMENTATION_OPTIONS.URL_ROOT; +if (doc_url_root == '#') { + doc_url_root = ''; +} + +/** + * SVG files for our copy buttons + */ +let iconCheck = ` + ${messages[locale]['copy_success']} + + +` + +// If the user specified their own SVG use that, otherwise use the default +let iconCopy = ``; +if (!iconCopy) { + iconCopy = ` + ${messages[locale]['copy_to_clipboard']} + + + +` +} + +/** + * Set up copy/paste for code blocks + */ + +const runWhenDOMLoaded = cb => { + if (document.readyState != 'loading') { + cb() + } else if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', cb) + } else { + document.attachEvent('onreadystatechange', function() { + if (document.readyState == 'complete') cb() + }) + } +} + +const codeCellId = index => `codecell${index}` + +// Clears selected text since ClipboardJS will select the text when copying +const clearSelection = () => { + if (window.getSelection) { + window.getSelection().removeAllRanges() + } else if (document.selection) { + document.selection.empty() + } +} + +// Changes tooltip text for a moment, then changes it back +// We want the timeout of our `success` class to be a bit shorter than the +// tooltip and icon change, so that we can hide the icon before changing back. +var timeoutIcon = 1500; +var timeoutSuccessClass = 1500; + +const temporarilyChangeTooltip = (el, oldText, newText) => { + el.setAttribute('data-tooltip', newText) + el.classList.add('success') + // Remove success a little bit sooner than we change the tooltip + // So that we can use CSS to hide the copybutton first + setTimeout(() => el.classList.remove('success'), timeoutSuccessClass) + setTimeout(() => el.setAttribute('data-tooltip', oldText), timeoutIcon) +} + +// Changes the copy button icon for two seconds, then changes it back +const temporarilyChangeIcon = (el) => { + el.innerHTML = iconCheck; + setTimeout(() => {el.innerHTML = iconCopy}, timeoutIcon) +} + +const addCopyButtonToCodeCells = () => { + // If ClipboardJS hasn't loaded, wait a bit and try again. This + // happens because we load ClipboardJS asynchronously. + + // Add copybuttons to all of our code cells + const COPYBUTTON_SELECTOR = 'div.highlight pre'; + const codeCells = document.querySelectorAll(COPYBUTTON_SELECTOR) + codeCells.forEach((codeCell, index) => { + const id = codeCellId(index) + codeCell.setAttribute('id', id) + + const clipboardButton = id => + `` + codeCell.insertAdjacentHTML('afterend', clipboardButton(id)) + }) + +function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string +} + +/** + * Removes excluded text from a Node. + * + * @param {Node} target Node to filter. + * @param {string[]} excludes CSS selector of nodes to exclude. + * @returns {DOMString} Text from `target` with text removed. + */ +function filterText(target, excludes) { + const clone = target.cloneNode(true); // clone as to not modify the live DOM + excludes.forEach(exclude => { + clone.querySelectorAll(excludes).forEach(node => node.remove()); + }) + return clone.innerText; +} + +// Callback when a copy button is clicked. Will be passed the node that was clicked +// should then grab the text and replace pieces of text that shouldn't be used in output +function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") { + var regexp; + var match; + + // Do we check for line continuation characters and "HERE-documents"? + var useLineCont = !!lineContinuationChar + var useHereDoc = !!hereDocDelim + + // create regexp to capture prompt and remaining line + if (isRegexp) { + regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)') + } else { + regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)') + } + + const outputLines = []; + var promptFound = false; + var gotLineCont = false; + var gotHereDoc = false; + const lineGotPrompt = []; + for (const line of textContent.split('\n')) { + match = line.match(regexp) + if (match || gotLineCont || gotHereDoc) { + promptFound = regexp.test(line) + lineGotPrompt.push(promptFound) + if (removePrompts && promptFound) { + outputLines.push(match[2]) + } else { + outputLines.push(line) + } + gotLineCont = line.endsWith(lineContinuationChar) & useLineCont + if (line.includes(hereDocDelim) & useHereDoc) + gotHereDoc = !gotHereDoc + } else if (!onlyCopyPromptLines) { + outputLines.push(line) + } else if (copyEmptyLines && line.trim() === '') { + outputLines.push(line) + } + } + + // If no lines with the prompt were found then just use original lines + if (lineGotPrompt.some(v => v === true)) { + textContent = outputLines.join('\n'); + } + + // Remove a trailing newline to avoid auto-running when pasting + if (textContent.endsWith("\n")) { + textContent = textContent.slice(0, -1) + } + return textContent +} + + +var copyTargetText = (trigger) => { + var target = document.querySelector(trigger.attributes['data-clipboard-target'].value); + // get filtered text + let excludes = ['.code-callout', '.linenos', '.language-apiheader']; + let text = filterText(target, excludes); + return formatCopyText(text, '', false, true, true, true, '', '') +} + + // Initialize with a callback so we can modify the text before copy + const clipboard = new ClipboardJS('.copybtn', {text: copyTargetText}) + + // Update UI with error/success messages + clipboard.on('success', event => { + clearSelection() + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_success']) + temporarilyChangeIcon(event.trigger) + }) + + clipboard.on('error', event => { + temporarilyChangeTooltip(event.trigger, messages[locale]['copy'], messages[locale]['copy_failure']) + }) +} + +export function initCopyButton() { + console.log("initCopyButton"); + runWhenDOMLoaded(addCopyButtonToCodeCells) +} diff --git a/src/Elastic.Markdown/Assets/main.ts b/src/Elastic.Markdown/Assets/main.ts index b3078f6b7..610380ca1 100644 --- a/src/Elastic.Markdown/Assets/main.ts +++ b/src/Elastic.Markdown/Assets/main.ts @@ -2,8 +2,10 @@ import {initNav} from "./pages-nav"; import {initTocNav} from "./toc-nav"; import {initHighlight} from "./hljs"; import {initTabs} from "./tabs"; +import {initCopyButton} from "./copybutton"; initNav(); initTocNav(); initHighlight(); +initCopyButton(); initTabs(); diff --git a/src/Elastic.Markdown/Assets/markdown/code.css b/src/Elastic.Markdown/Assets/markdown/code.css new file mode 100644 index 000000000..14d9eed48 --- /dev/null +++ b/src/Elastic.Markdown/Assets/markdown/code.css @@ -0,0 +1,84 @@ +@layer components { + .markdown-content { + + + .highlight { + @apply mt-4; + } + + pre { + @apply grid; + code { + @apply text-sm + text-gray-300 + rounded-none + border-0 + overflow-x-auto + p-6! + ; + background-color: #22272e; + } + code:first-child { + @apply rounded-t-sm; + } + code:last-child { + @apply rounded-b-sm; + } + code.language-apiheader { + @apply border-b-1 border-b-gray-700; + } + } + + pre code .code-callout .hljs-number { + transform: translateY(-1px); + user-select: none; + } + } + + ol.code-callouts { + li { + @apply relative pl-1; + counter-increment: code-callout-counter; + list-style-type: none; + } + + li::before { + content: counter(code-callout-counter); + position: absolute; + top: 1px; + left: calc(-1 * var(--spacing) * 6); + } + } + + pre code .code-callout .hljs-number, + ol.code-callouts li::before { + @apply text-xs! + text-white! + font-mono + bg-blue-elastic + rounded-full + size-5 + inline-flex + justify-center + items-center + ; + } + + code { + @apply font-mono + bg-gray-100 + rounded-xs + border-1 + border-gray-200 + inline-block + text-nowrap + ; + font-size: 0.875em; + line-height: 1.4em; + padding-left: 0.2em; + padding-right: 0.2em; + letter-spacing: 0.02em; + text-decoration: inherit; + font-weight: inherit; + } +} diff --git a/src/Elastic.Markdown/Assets/markdown/list.css b/src/Elastic.Markdown/Assets/markdown/list.css index e993a59df..bdc4e39b3 100644 --- a/src/Elastic.Markdown/Assets/markdown/list.css +++ b/src/Elastic.Markdown/Assets/markdown/list.css @@ -1,7 +1,7 @@ .markdown-content { ol,ul { font-family: "Inter", sans-serif; - @apply text-base text-body mb-6; + @apply text-base text-body mt-6; line-height: 1.5em; letter-spacing: 0; margin-left: 1.5em; @@ -16,7 +16,7 @@ } li { - margin-bottom: calc(var(--spacing) * 3); + @apply first:mt-0 mt-2; p { margin-bottom: 0; diff --git a/src/Elastic.Markdown/Assets/markdown/tabs.css b/src/Elastic.Markdown/Assets/markdown/tabs.css index b35c71c93..8129f013f 100644 --- a/src/Elastic.Markdown/Assets/markdown/tabs.css +++ b/src/Elastic.Markdown/Assets/markdown/tabs.css @@ -1,6 +1,6 @@ @layer components { .tabs { - @apply flex flex-wrap relative overflow-hidden; + @apply flex flex-wrap relative overflow-hidden mt-4; .tabs-label { @apply cursor-pointer px-6 py-2 z-20 text-ink-light flex items-center; @@ -15,7 +15,7 @@ } .tabs-content { - @apply w-full order-99 border-t-1 border-gray-300 px-6 pt-4 z-0 hidden; + @apply w-full order-99 border-t-1 border-gray-300 px-6 z-0 hidden; transform: translateY(-1px); } diff --git a/src/Elastic.Markdown/Assets/markdown/typography.css b/src/Elastic.Markdown/Assets/markdown/typography.css index 806fc68d4..863ca365c 100644 --- a/src/Elastic.Markdown/Assets/markdown/typography.css +++ b/src/Elastic.Markdown/Assets/markdown/typography.css @@ -4,42 +4,42 @@ h1 { font-family: "Mier B", "Inter", sans-serif; - @apply text-5xl text-black mb-6 mt-5; + @apply text-4xl text-ink-dark font-semibold; line-height: 1.2em; letter-spacing: -0.04em; } h2 { font-family: "Mier B", "Inter", sans-serif; - @apply text-4xl text-black mb-6 mt-8; + @apply text-2xl text-ink-dark font-bold mt-10; line-height: 1.2em; - letter-spacing: -0.04em; + letter-spacing: -0.02em; } h3 { font-family: "Mier B", "Inter", sans-serif; - @apply text-2xl text-black mb-6 mt-6; + @apply text-xl text-ink-dark font-bold mt-8; line-height: 1.2em; letter-spacing: -0.02em; } h4 { font-family: "Mier B", "Inter", sans-serif; - @apply text-base text-black font-bold mb-5 mt-5; + @apply text-base text-ink-dark font-bold mt-8; line-height: 1.2em; letter-spacing: -0.02em; } h5 { font-family: "Mier B", "Inter", sans-serif; - @apply text-sm text-black font-bold mb-4 mt-5; + @apply text-sm text-ink-dark font-bold mt-8; line-height: 1.2em; letter-spacing: -0.02em; } h6 { font-family: "Mier B", "Inter", sans-serif; - @apply text-xs text-black font-bold mb-3 mt-3; + @apply text-xs text-ink-dark font-bold mt-8; line-height: 1.2em; letter-spacing: -0.02em; } @@ -47,19 +47,21 @@ h1, h2, h3, h4, h5, h6 { a { font-family: "Mier B", "Inter", sans-serif; - @apply text-black hover:text-black no-underline; + @apply text-ink-dark hover:text-ink-dark no-underline; } } p { font-family: "Inter", sans-serif; - @apply text-base text-ink text-body mb-6; - line-height: 1.5em; + @apply text-base text-ink mt-4; letter-spacing: 0; + line-height: 1.5em; + text-wrap: pretty; } a { font-family: "Inter", sans-serif; + text-wrap: nowrap; @apply text-blue-elastic hover:text-blue-800 underline; } } diff --git a/src/Elastic.Markdown/Assets/styles.css b/src/Elastic.Markdown/Assets/styles.css index ebf9d0200..bbe7ed99c 100644 --- a/src/Elastic.Markdown/Assets/styles.css +++ b/src/Elastic.Markdown/Assets/styles.css @@ -1,10 +1,12 @@ @import "tailwindcss"; @import "./fonts.css"; @import "./theme.css"; -@import "highlight.js/styles/atom-one-dark.css"; +@import "highlight.js/styles/github-dark-dimmed.css"; @import "./markdown/typography.css"; @import "./markdown/list.css"; @import "./markdown/tabs.css"; +@import "./markdown/code.css"; +@import "./copybutton.css"; #default-search::-webkit-search-cancel-button { padding-right: calc(var(--spacing) * 2); diff --git a/src/Elastic.Markdown/package-lock.json b/src/Elastic.Markdown/package-lock.json index ba2c40113..cace881b3 100644 --- a/src/Elastic.Markdown/package-lock.json +++ b/src/Elastic.Markdown/package-lock.json @@ -8,6 +8,7 @@ "name": "elastic-markdown", "version": "1.0.0", "dependencies": { + "clipboard": "^2.0.11", "highlight.js": "^11.11.1", "select-dom": "^9.3.0", "tailwindcss": "^4.0.3" @@ -2260,6 +2261,17 @@ "node": ">=6.0" } }, + "node_modules/clipboard": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.11.tgz", + "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==", + "license": "MIT", + "dependencies": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -2526,6 +2538,12 @@ "optional": true, "peer": true }, + "node_modules/delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", + "license": "MIT" + }, "node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -2723,6 +2741,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", + "license": "MIT", + "dependencies": { + "delegate": "^3.1.2" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -4291,6 +4318,12 @@ } ] }, + "node_modules/select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==", + "license": "MIT" + }, "node_modules/select-dom": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/select-dom/-/select-dom-9.3.0.tgz", @@ -4467,6 +4500,12 @@ "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==", "dev": true }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/src/Elastic.Markdown/package.json b/src/Elastic.Markdown/package.json index 4ac66c0d5..e517b0cd4 100644 --- a/src/Elastic.Markdown/package.json +++ b/src/Elastic.Markdown/package.json @@ -33,6 +33,7 @@ "defaults" ], "dependencies": { + "clipboard": "^2.0.11", "highlight.js": "^11.11.1", "select-dom": "^9.3.0", "tailwindcss": "^4.0.3"