diff --git a/Gemfile.lock b/Gemfile.lock index d03ec58..fdce2c2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -63,6 +63,7 @@ GEM faraday-net_http (3.1.0) net-http ffi (1.16.3) + ffi (1.16.3-x64-mingw-ucrt) hash_with_dot_access (1.2.0) activesupport (>= 5.0.0, < 8.0) i18n (1.14.1) @@ -107,6 +108,7 @@ GEM PLATFORMS arm64-darwin-22 arm64-darwin-23 + x64-mingw-ucrt x86_64-linux DEPENDENCIES diff --git a/frontend/javascript/code_snippets.js b/frontend/javascript/code_snippets.js index f6cc28b..4ce8e5e 100644 --- a/frontend/javascript/code_snippets.js +++ b/frontend/javascript/code_snippets.js @@ -1,16 +1,57 @@ +import JSONEditor from "jsoneditor"; + export function enableCodeHighlighting() { document.querySelectorAll('pre code').forEach(block => block.textContent = block.textContent.trim()); hljs.highlightAll(); } +export function copyHeadingDirectLinks() { + const headings = document.querySelectorAll('article h2, article h3, article h4, article h5, article h6, main h2, main h3, main h4, main h5, main h6'); + + headings.forEach(function (heading) { + const linkIcon = document.createElement('span'); + linkIcon.innerHTML = `` + linkIcon.style.cursor = 'pointer'; + linkIcon.style.position = 'relative'; + linkIcon.style.left = '10px'; + linkIcon.style.display = 'none'; + + heading.appendChild(linkIcon); + + linkIcon.addEventListener('click', function (event) { + event.stopPropagation(); + const id = heading.getAttribute('id'); + const url = window.location.href.split('#')[0] + '#' + id; + navigator.clipboard.writeText(url); + + // Replace the link icon with a checkmark icon + linkIcon.innerHTML = ` + ` + + // Automatically revert the checkmark to the link icon after 2 seconds + setTimeout(function () { + linkIcon.innerHTML = `` + }, 2000); + }); + + heading.addEventListener('mouseover', function () { + linkIcon.style.display = 'inline-block'; // Show the link icon on hover + }); + + heading.addEventListener('mouseout', function () { + linkIcon.style.display = 'none'; // Hide the link icon when not hovering + }); + }); +} + export function insertCodeSnippetCopyButtons() { const codeBlocks = document.querySelectorAll('pre code'); - codeBlocks.forEach(function(block) { + codeBlocks.forEach(function (block) { const pre = block.parentNode; const copyButton = document.createElement('button'); - const svgIcon = ` - - + const svgIcon = ` + + ` copyButton.innerHTML = svgIcon; @@ -19,15 +60,18 @@ export function insertCodeSnippetCopyButtons() { pre.appendChild(copyButton); pre.style.position = 'relative'; copyButton.style.position = 'absolute'; - copyButton.style.right = '5px'; - copyButton.style.top = '5px'; + copyButton.style.right = '7px'; + copyButton.style.top = '10px'; copyButton.style.zIndex = '1'; - copyButton.style.border = 'none'; - copyButton.style.padding = '5px'; + copyButton.style.border = '1px solid var(--tw-prose-code-bg)'; // Add border style + copyButton.style.padding = '10px'; + copyButton.style.backdropFilter = 'blur(20px)'; copyButton.style.cursor = 'pointer'; copyButton.style.fontSize = '14px'; + copyButton.style.borderRadius='10px'; + copyButton.style.boxShadow= 'inset 0 0 0 1.5px var(--tw-prose-code-ring)'; - copyButton.addEventListener('click', function() { + copyButton.addEventListener('click', function () { const contentToCopy = block.innerText; const tempTextarea = document.createElement('textarea'); tempTextarea.value = contentToCopy; @@ -35,14 +79,37 @@ export function insertCodeSnippetCopyButtons() { tempTextarea.select(); document.execCommand('copy'); document.body.removeChild(tempTextarea); - const copiedIcon = ` - + const copiedIcon = ` + fill="#B4B4B8"/> `; - copyButton.innerHTML = copiedIcon; - setTimeout(function() { + copyButton.innerHTML = copiedIcon; + setTimeout(function () { copyButton.innerHTML = svgIcon; }, 2000); }); }); -} \ No newline at end of file +} + +export function initializeJSONEditor() { + // Does the page have JSON data? + const jsonTextElt = document.getElementById("json-data"); + if (!jsonTextElt) { + return; + } + + // Can we turn it into a JS object? + const jsonData = JSON.parse(jsonTextElt.innerText); + if (!jsonData) { + return; + } + + // create the editor + const container = document.getElementById("json-editor"); + const options = { + mode: 'code', + modes: ['code', 'view'], // allowed modes + } + const editor = new JSONEditor(container, options); + editor.set(jsonData); +} diff --git a/frontend/javascript/index.js b/frontend/javascript/index.js index 8a7bf82..f543098 100644 --- a/frontend/javascript/index.js +++ b/frontend/javascript/index.js @@ -1,14 +1,18 @@ -import "$styles/index.css" -import "$styles/syntax-highlighting.css" +import "$styles/index.css"; +import "$styles/syntax-highlighting.css"; +import "jsoneditor/dist/jsoneditor.min.css"; import { enableCodeHighlighting, - insertCodeSnippetCopyButtons + insertCodeSnippetCopyButtons, + copyHeadingDirectLinks, + initializeJSONEditor, } from "./code_snippets"; import { enableScrollToTop, - saveAndRestoreNavigationPosition + saveAndRestoreNavigationPosition, + setupSidebar } from "./page_navigation"; import { enableDocSearch } from "./search"; @@ -19,10 +23,13 @@ import components from "$components/**/*.{js,jsx,js.rb,css}" console.info("Bridgetown is loaded!") document.addEventListener("DOMContentLoaded", function(event) { + copyHeadingDirectLinks(); enableCodeHighlighting(); insertCodeSnippetCopyButtons(); + initializeJSONEditor(); enableDocSearch('#oba-docs-search-container--desktop'); enableDocSearch('#oba-docs-search-container--mobile'); enableScrollToTop(); saveAndRestoreNavigationPosition(); + setupSidebar(); }); diff --git a/frontend/javascript/page_navigation.js b/frontend/javascript/page_navigation.js index 53ba504..5d8e473 100644 --- a/frontend/javascript/page_navigation.js +++ b/frontend/javascript/page_navigation.js @@ -16,6 +16,46 @@ export function enableScrollToTop() { }); } +export function setupSidebar() { + const h1Elements = document.querySelectorAll('h1'); + const h2Elements = document.querySelectorAll('article h2'); + const sidebar = document.querySelector('.sidebar'); + + function appendSidebarItem(textContent, tagName) { + const newItem = document.createElement('a'); + newItem.textContent = textContent; + if (tagName === 'h1') { + newItem.classList.add('sidebar-item', 'text-green-500'); + } + else if (tagName === 'h2') { + newItem.classList.add('sidebar-item-h2', 'text-gray-500', 'hover:text-green-400', 'ml-4', 'cursor-pointer'); + } + sidebar.appendChild(newItem); + + newItem.addEventListener('click', function() { + const currentVersion = newItem.textContent; + const headings = document.querySelectorAll('h2'); + let targetElement = null; + headings.forEach(function(heading) { + if (heading.textContent.trim() === currentVersion.trim()) { + targetElement = heading; + } + }); + if (targetElement) { + window.scrollTo(0, targetElement.offsetTop - 100); + } + }); + } + + h1Elements.forEach(function (element) { + appendSidebarItem(element.textContent, 'h1'); + }); + + h2Elements.forEach(function (element) { + appendSidebarItem(element.textContent, 'h2'); + }); +} + export function saveAndRestoreNavigationPosition() { var scrollPosition = sessionStorage.getItem('scrollPosition'); if (scrollPosition !== undefined) { diff --git a/frontend/styles/index.css b/frontend/styles/index.css index b60954f..af5c284 100644 --- a/frontend/styles/index.css +++ b/frontend/styles/index.css @@ -35,7 +35,7 @@ button.DocSearch.DocSearch-Button { } .DocSearch-Search-Icon { - @apply !h-5 !w-5; + @apply !h-5 !w-5 !text-zinc-400 dark:text-zinc-500; } .DocSearch-Button-Placeholder { @@ -87,6 +87,14 @@ pre.highlight { ::-webkit-scrollbar-thumb:hover { background: #333; } + +.jsoneditor { + @apply !border-zinc-300; +} + +.jsoneditor-menu { + @apply bg-gradient-to-br; + @apply from-green-600 to-green-700; } @tailwind utilities; diff --git a/package.json b/package.json index 3640e11..a06b129 100644 --- a/package.json +++ b/package.json @@ -17,5 +17,8 @@ "postcss-preset-env": "^9.1.2", "read-cache": "^1.0.0", "tailwindcss": "^3.4.1" + }, + "dependencies": { + "jsoneditor": "^10.0.2" } } diff --git a/src/_layouts/default.erb b/src/_layouts/default.erb index 9dc9d2c..3c808dd 100644 --- a/src/_layouts/default.erb +++ b/src/_layouts/default.erb @@ -1,5 +1,9 @@ - - + + <%= render "head", locals: { metadata: site.metadata, title: data.title } %> @@ -7,8 +11,7 @@
+ class="contents lg:pointer-events-none lg:fixed lg:inset-0 lg:z-40 lg:flex">