diff --git a/package.json b/package.json index fe8d819..3b06516 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "cem": "custom-elements-manifest analyze --config 'lib/custom-elements-manifest.config.js'", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", - "preview-storybook": "storybook preview", + "preview-storybook": "storybook dev", "test-storybook": "test-storybook --coverage", "predist": "npm run cem && npm run build-storybook", "dist": "node lib/esbuild.config.js", diff --git a/src/devto/README.md b/src/devto/README.md index 9035798..7adda9e 100644 --- a/src/devto/README.md +++ b/src/devto/README.md @@ -23,46 +23,6 @@ see README at root of repo for usage details --- -## Members - -
-
DEV-Post-Declarative-Shadow-DOMstring
-
-
DEV-Declarative-Shadow-DOMstring
-
-
- -## Objects - -
-
DEVUtils : object
-

Utility functions for fetching and parsing dev.to api data, getting - styles and generating HTML for dev.to profile UIs

-
-
- - - -## DEV-Post-Declarative-Shadow-DOM ⇒ string -**Kind**: global variable -**Returns**: string - DEV post HTML wrapped in a `template` - -| Param | Type | Description | -| --- | --- | --- | -| content | ForemPostHTML | Content about one post by dev.to (or Forem) user | -| fetch | boolean | | - - - -## DEV-Declarative-Shadow-DOM ⇒ string -**Kind**: global variable -**Returns**: string - DEV HTML wrapped in a `template` - -| Param | Type | Description | -| --- | --- | --- | -| content | ForemUserHTML | a content object representing a DEV user | -| fetch | boolean | | - ## DEVUtils : object @@ -74,6 +34,7 @@ Utility functions for fetching and parsing dev.to api data, getting * [DEVUtils](#DEVUtils) : object * [.post](#DEVUtils.post) : object + * [.dsd](#DEVUtils.post.dsd) ⇒ string * [.html(content)](#DEVUtils.post.html) ⇒ string * [.generateContent(content, [fetch])](#DEVUtils.post.generateContent) ⇒ ForemPost \| ForemError * [.ForemPost](#DEVUtils.post.ForemPost) : Object @@ -82,6 +43,7 @@ Utility functions for fetching and parsing dev.to api data, getting * [.styles](#DEVUtils.user.styles) * [.html(content)](#DEVUtils.user.html) ⇒ string * [.generateContent(content, [fetch])](#DEVUtils.user.generateContent) ⇒ ForemUserHTML + * [.dsd(content, fetch)](#DEVUtils.user.dsd) ⇒ string * [.ForemUser](#DEVUtils.user.ForemUser) : Object * [.ForemUserHTML](#DEVUtils.user.ForemUserHTML) : ForemUser @@ -91,6 +53,27 @@ Utility functions for fetching and parsing dev.to api data, getting Utility functions for a post **Kind**: static namespace of [DEVUtils](#DEVUtils) + +* [.post](#DEVUtils.post) : object + * [.dsd](#DEVUtils.post.dsd) ⇒ string + * [.html(content)](#DEVUtils.post.html) ⇒ string + * [.generateContent(content, [fetch])](#DEVUtils.post.generateContent) ⇒ ForemPost \| ForemError + * [.ForemPost](#DEVUtils.post.ForemPost) : Object + * [.ForemPostHTML](#DEVUtils.post.ForemPostHTML) : ForemPost + + + +#### post.dsd ⇒ string +Generate a `template` element with a shadowdom with a Post in it + +**Kind**: static namespace of [post](#DEVUtils.post) +**Returns**: string - DEV post HTML wrapped in a `template` + +| Param | Type | Description | +| --- | --- | --- | +| content | ForemPostHTML | Content about one post by dev.to (or Forem) user | +| fetch | boolean | | + **Example** *(Server side rendering a post with Declarative Shadow Dom)* ```js @@ -101,13 +84,6 @@ const dsdHTML = post.dsd({id: '12345'}, true); document.querySelector('devto-post').innerHTML = dsdHTML; ``` - -* [.post](#DEVUtils.post) : object - * [.html(content)](#DEVUtils.post.html) ⇒ string - * [.generateContent(content, [fetch])](#DEVUtils.post.generateContent) ⇒ ForemPost \| ForemError - * [.ForemPost](#DEVUtils.post.ForemPost) : Object - * [.ForemPostHTML](#DEVUtils.post.ForemPostHTML) : ForemPost - #### post.html(content) ⇒ string @@ -168,21 +144,12 @@ Forem post content, ready for HTML Utility functions for a user **Kind**: static namespace of [DEVUtils](#DEVUtils) -**Example** *(Server side rendering with Declarative Shadow Dom)* -```js - - - -``` * [.user](#DEVUtils.user) : object * [.styles](#DEVUtils.user.styles) * [.html(content)](#DEVUtils.user.html) ⇒ string * [.generateContent(content, [fetch])](#DEVUtils.user.generateContent) ⇒ ForemUserHTML + * [.dsd(content, fetch)](#DEVUtils.user.dsd) ⇒ string * [.ForemUser](#DEVUtils.user.ForemUser) : Object * [.ForemUserHTML](#DEVUtils.user.ForemUserHTML) : ForemUser @@ -217,6 +184,29 @@ Generates an object of content for the user HTML | content | ForemUserHTML | | [fetch] | boolean | + + +#### user.dsd(content, fetch) ⇒ string +Generate a `template` element with shadowrootmode with a User in it + +**Kind**: static method of [user](#DEVUtils.user) +**Returns**: string - DEV HTML wrapped in a `template` + +| Param | Type | Description | +| --- | --- | --- | +| content | ForemUserHTML | a content object representing a DEV user | +| fetch | boolean | | + +**Example** *(Server side rendering with Declarative Shadow Dom)* +```js + + + +``` #### user.ForemUser : Object diff --git a/src/devto/dsd.docs.mdx b/src/devto/dsd.docs.mdx new file mode 100644 index 0000000..7b52a40 --- /dev/null +++ b/src/devto/dsd.docs.mdx @@ -0,0 +1,114 @@ +import { Meta, Title, Source } from '@storybook/blocks'; + + + + + +Both DEV components can be implemented via Declarative Shadow DOM using methods exported from the `devto-utils.js` file. + + +## Server Side Rendering HTML in Node.js + +<Source code={` +// import from npm module +import { dsd } from 'profile-components/devto-utils'; + +const generatedTemplate = await dsd({ + username: 'scottnath', +},true); + +/** +generatedTemplate contains: +<template shadowrootmode="open"> + <styles>(...css styles for DEV component)</styles> + <section (...rest of generated HTML)</section> +</template> +*/ + +const componentHTML = \`<devto-user>\${generatedTemplate}</devto-user>\`; +`} language='js' /> + +## Server side render in an Astro component + +<Source code={` +--- +import {dsd} from 'profile-components/devto-utils'; + +const declaredDOM = await dsd({ + username: 'scottnath', +},true) +--- + +<devto-user + data-theme="light_high_contrast" + set:html={declaredDOM}> +</devto-user> +`} language='jsx' /> + +## Client side rendering via unpkg + +<Source code={` + +<!-- add empty elements to HTML --> +<devto-post></devto-post> +<hr /> +<devto-user></devto-user> + +<script type="module"> + // import from unpkg + import { + user, + post, + } from 'https://unpkg.com/profile-components/dist/devto-utils.js'; + + // post has it's own DSD method: + const dsdPost = post.dsd; + + /** + * Polyfill for Declarative Shadow DOM which, when triggered, converts + * the template element into actual shadow DOM. + * This is only needed when injecting _after_ page is loaded + * @see https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#polyfill + */ + const triggerAttachShadowRoots = () => { + (function attachShadowRoots(root) { + root + .querySelectorAll('template[shadowrootmode]') + .forEach((template) => { + const mode = template.getAttribute('shadowrootmode'); + const shadowRoot = template.parentNode.attachShadow({ mode }); + shadowRoot.appendChild(template.content); + template.remove(); + attachShadowRoots(shadowRoot); + }); + })(document); + }; + + /** + * Uses the "dsd" method to generate DSD, add the string of DSD content + * to the element, then trigger the polyfill to convert the template + */ + const injectDSD = async () => { + const dsdHTML = await dsd({ username: 'scottnath' }, true); + document.querySelector('devto-user').innerHTML = dsdHTML; + // now that the HTML is async-created, the polyfill can convert it + triggerAttachShadowRoots(); + }; + injectDSD(); + + /** + * Uses the "dsdPost" method to generate DSD, add the string of DSD content + * to the element, then trigger the polyfill to convert the template + */ + const injectPostDSD = async () => { + const dsdHTML = await dsdPost( + { full_name: 'scottnath/profile-components' }, + true + ); + document.querySelector('devto-post').innerHTML = dsdHTML; + // now that the HTML is async-created, the polyfill can convert it + triggerAttachShadowRoots(); + }; + injectPostDSD(); +</script> +`} language='html' /> diff --git a/src/devto/dsd.stories.js b/src/devto/dsd.stories.js new file mode 100644 index 0000000..03f6692 --- /dev/null +++ b/src/devto/dsd.stories.js @@ -0,0 +1,70 @@ + +import { parseFetchedPost } from './post/content.js'; +import { parseFetchedUser } from './user/content.js'; +import { default as userScottnath } from './fixtures/generated/user--scottnath.json'; +import { default as postProfileComponents } from './fixtures/generated/post--profile-components.json'; +import { default as postDependabot } from './fixtures/generated/post--dependabot.json'; +import { default as postBugfix } from './fixtures/generated/post--bugfix-multi-vite.json'; +import { post, dsd } from './index.js'; +import docs from './dsd.docs.mdx'; + + +export default { + title: 'DevTo/Declarative Shadow DOM', + parameters: { + docs: { + page: docs + }, + }, + tags: ['autodocs'], + decorators: [(story) => `${story()} + <script> + + (function attachShadowRoots(root) { + root.querySelectorAll("template[shadowrootmode]").forEach(template => { + const mode = template.getAttribute("shadowrootmode"); + const shadowRoot = template.parentNode.attachShadow({ mode }); + shadowRoot.appendChild(template.content); + template.remove(); + attachShadowRoots(shadowRoot); + }); + })(document); + </script> + `], +}; + +export const Post = { + loaders: [ + async ({args}) => ({ + dsdOutput: await (await post.dsd(args)), + }), + ], + render: (args, { loaded: { dsdOutput } }) => { + return ` + <devto-post-dsd>${dsdOutput}</devto-post-dsd> + + `; + }, + args: { + ...parseFetchedPost(postProfileComponents) + }, +} + +export const User = { + loaders: [ + async ({args}) => ({ + dsdOutput: await (await dsd(args)), + }), + ], + render: (args, { loaded: { dsdOutput } }) => { + return ` + <devto-user-dsd data-theme="dark">${dsdOutput}</devto-user-dsd> + + `; + }, + args: { + ...parseFetchedUser(userScottnath), + latest_post: stringify(parseFetchedPost(postDependabot)), + popular_post: stringify(parseFetchedPost(postBugfix)), + }, +} diff --git a/src/devto/fixtures/generated/post--bugfix-multi-vite.json b/src/devto/fixtures/generated/post--bugfix-multi-vite.json index aaaa076..5d3a285 100644 --- a/src/devto/fixtures/generated/post--bugfix-multi-vite.json +++ b/src/devto/fixtures/generated/post--bugfix-multi-vite.json @@ -3,7 +3,7 @@ "id": 1466138, "title": "Bugfix: Multiple Vite Storybooks from Same node_modules", "description": "tl;dr Encountering Failed to fetch dynamically imported module or ENOTEMPTY: directory not...", - "readable_publish_date": "May 12 2023", + "readable_publish_date": "May 12 '23", "slug": "bugfix-multiple-vite-storybooks-from-same-nodemodules-4mg1", "path": "/scottnath/bugfix-multiple-vite-storybooks-from-same-nodemodules-4mg1", "url": "https://dev.to/scottnath/bugfix-multiple-vite-storybooks-from-same-nodemodules-4mg1", @@ -40,4 +40,4 @@ "profile_image": "https://media.dev.to/cdn-cgi/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1055555%2F4d0bf90a-bec7-4228-b1ca-d663fa40adeb.jpeg", "profile_image_90": "https://media.dev.to/cdn-cgi/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1055555%2F4d0bf90a-bec7-4228-b1ca-d663fa40adeb.jpeg" } -} +} \ No newline at end of file diff --git a/src/devto/fixtures/generated/post--dependabot.json b/src/devto/fixtures/generated/post--dependabot.json index 72f5f68..e4d70a2 100644 --- a/src/devto/fixtures/generated/post--dependabot.json +++ b/src/devto/fixtures/generated/post--dependabot.json @@ -3,7 +3,7 @@ "id": 1568661, "title": "A crazy-simple way to bulk-update NPM dependencies with GitHub's Dependabot", "description": "This is the simplest way I've found to keep your NPM dependencies up-to-date. This will update all...", - "readable_publish_date": "Aug 15 2023", + "readable_publish_date": "Aug 15 '23", "slug": "a-crazy-simple-way-to-bulk-update-npm-dependencies-with-githubs-dependabot-3e2o", "path": "/scottnath/a-crazy-simple-way-to-bulk-update-npm-dependencies-with-githubs-dependabot-3e2o", "url": "https://dev.to/scottnath/a-crazy-simple-way-to-bulk-update-npm-dependencies-with-githubs-dependabot-3e2o", @@ -40,4 +40,4 @@ "profile_image": "https://media.dev.to/cdn-cgi/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1055555%2F4d0bf90a-bec7-4228-b1ca-d663fa40adeb.jpeg", "profile_image_90": "https://media.dev.to/cdn-cgi/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1055555%2F4d0bf90a-bec7-4228-b1ca-d663fa40adeb.jpeg" } -} +} \ No newline at end of file diff --git a/src/devto/fixtures/generated/post--profile-components-github.json b/src/devto/fixtures/generated/post--profile-components-github.json index 5e911d8..84eb3a6 100644 --- a/src/devto/fixtures/generated/post--profile-components-github.json +++ b/src/devto/fixtures/generated/post--profile-components-github.json @@ -3,7 +3,7 @@ "id": 1631825, "title": "GitHub Profile Native Web Components", "description": "Learn about native web components that showcase GitHub profiles and repositories.", - "readable_publish_date": "Oct 11 2023", + "readable_publish_date": "Oct 11 '23", "slug": "github-profile-native-web-components-4ed7", "path": "/scottnath/github-profile-native-web-components-4ed7", "url": "https://dev.to/scottnath/github-profile-native-web-components-4ed7", @@ -35,4 +35,4 @@ "profile_image": "https://media.dev.to/cdn-cgi/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1055555%2F4d0bf90a-bec7-4228-b1ca-d663fa40adeb.jpeg", "profile_image_90": "https://media.dev.to/cdn-cgi/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1055555%2F4d0bf90a-bec7-4228-b1ca-d663fa40adeb.jpeg" } -} +} \ No newline at end of file diff --git a/src/devto/fixtures/generated/post--profile-components.json b/src/devto/fixtures/generated/post--profile-components.json index e558199..caf5f8f 100644 --- a/src/devto/fixtures/generated/post--profile-components.json +++ b/src/devto/fixtures/generated/post--profile-components.json @@ -3,7 +3,7 @@ "id": 1630208, "title": "Profile Components: display social profiles in native web components", "description": "Profile-components are a set of web components with zero dependencies that display publicly-available profile information from various social networks. Currently two: GitHub and dev.to.", - "readable_publish_date": "Oct 10 2023", + "readable_publish_date": "Oct 10 '23", "slug": "profile-components-display-social-profiles-in-native-web-components-49b2", "path": "/scottnath/profile-components-display-social-profiles-in-native-web-components-49b2", "url": "https://dev.to/scottnath/profile-components-display-social-profiles-in-native-web-components-49b2", @@ -40,4 +40,4 @@ "profile_image": "https://media.dev.to/cdn-cgi/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1055555%2F4d0bf90a-bec7-4228-b1ca-d663fa40adeb.jpeg", "profile_image_90": "https://media.dev.to/cdn-cgi/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1055555%2F4d0bf90a-bec7-4228-b1ca-d663fa40adeb.jpeg" } -} +} \ No newline at end of file diff --git a/src/devto/index.js b/src/devto/index.js index 1321b3e..ed36bd1 100644 --- a/src/devto/index.js +++ b/src/devto/index.js @@ -18,6 +18,19 @@ import postHTML from './post/html.js'; * @param {ForemPostHTML} content - Content about one post by dev.to (or Forem) user * @param {boolean} fetch * @returns {string} DEV post HTML wrapped in a `template` + * @function + * @memberof DEVUtils.post + * @namespace dsd + * @description Generate a `template` element with a shadowdom with a Post in it + * + * @example <caption>Server side rendering a post with Declarative Shadow Dom</caption> + * <devto-post></devto-post> + * + * <script type="module"> + * import {post} from 'profile-components/devto-utils'; + * const dsdHTML = post.dsd({id: '12345'}, true); + * document.querySelector('devto-post').innerHTML = dsdHTML; + * </script> */ const dsdPost = async (content, fetch = false) => { const generated = await generatePostContent(content, fetch); @@ -33,15 +46,6 @@ const dsdPost = async (content, fetch = false) => { * @namespace post * @memberof DEVUtils * @description Utility functions for a post - * - * @example <caption>Server side rendering a post with Declarative Shadow Dom</caption> - * <devto-post></devto-post> - * - * <script type="module"> - * import {post} from 'profile-components/devto-utils'; - * const dsdHTML = post.dsd({id: '12345'}, true); - * document.querySelector('devto-post').innerHTML = dsdHTML; - * </script> */ const post = { generateContent: generatePostContent, @@ -52,9 +56,22 @@ const post = { /** * @name DEV-Declarative-Shadow-DOM + * @namespace dsd * @param {ForemUserHTML} content - a content object representing a DEV user * @param {boolean} fetch * @returns {string} DEV HTML wrapped in a `template` + * @function + * @memberof DEVUtils.user + * @description Generate a `template` element with shadowrootmode with a User in it + * + * @example <caption>Server side rendering with Declarative Shadow Dom</caption> + * <devto-user></devto-user> + * + * <script type="module"> + * import {dsd} from 'profile-components/devto-utils'; + * const dsdHTML = dsd({username: 'scottnath'}, true); + * document.querySelector('devto-user').innerHTML = dsdHTML; + * </script> */ const dsd = async (content, fetch = false) => { const generated = await generateUserContent(content, fetch); @@ -70,15 +87,6 @@ const dsd = async (content, fetch = false) => { * @namespace user * @memberof DEVUtils * @description Utility functions for a user - * - * @example <caption>Server side rendering with Declarative Shadow Dom</caption> - * <devto-user></devto-user> - * - * <script type="module"> - * import {dsd} from 'profile-components/devto-utils'; - * const dsdHTML = dsd({username: 'scottnath'}, true); - * document.querySelector('devto-user').innerHTML = dsdHTML; - * </script> */ const user = { generateContent: generateUserContent, diff --git a/src/github/README.md b/src/github/README.md index 17dcc0d..521edea 100644 --- a/src/github/README.md +++ b/src/github/README.md @@ -19,46 +19,6 @@ see README at root of repo for usage details --- -## Members - -<dl> -<dt><a href="#GitHub-Repository-Declarative-Shadow-DOM">GitHub-Repository-Declarative-Shadow-DOM</a> ⇒ <code>string</code></dt> -<dd></dd> -<dt><a href="#GitHub-Declarative-Shadow-DOM">GitHub-Declarative-Shadow-DOM</a> ⇒ <code>string</code></dt> -<dd></dd> -</dl> - -## Objects - -<dl> -<dt><a href="#GitHubUtils">GitHubUtils</a> : <code>object</code></dt> -<dd><p>Utility functions for fetching and parsing GitHub api data, getting - styles and generating HTML for GitHub profile UIs</p> -</dd> -</dl> - -<a name="GitHub-Repository-Declarative-Shadow-DOM"></a> - -## GitHub-Repository-Declarative-Shadow-DOM ⇒ <code>string</code> -**Kind**: global variable -**Returns**: <code>string</code> - GitHub HTML wrapped in a `template` - -| Param | Type | Description | -| --- | --- | --- | -| content | <code>GitHubRepositoryHTML</code> | a content object representing a GitHub repository | -| fetch | <code>boolean</code> | | - -<a name="GitHub-Declarative-Shadow-DOM"></a> - -## GitHub-Declarative-Shadow-DOM ⇒ <code>string</code> -**Kind**: global variable -**Returns**: <code>string</code> - GitHub HTML wrapped in a `template` - -| Param | Type | Description | -| --- | --- | --- | -| content | <code>GitHubUserHTML</code> | a content object representing a GitHub user | -| fetch | <code>boolean</code> | | - <a name="GitHubUtils"></a> ## GitHubUtils : <code>object</code> @@ -71,11 +31,13 @@ Utility functions for fetching and parsing GitHub api data, getting * [GitHubUtils](#GitHubUtils) : <code>object</code> * [.repo](#GitHubUtils.repo) : <code>object</code> * [.styles](#GitHubUtils.repo.styles) + * [.dsd](#GitHubUtils.repo.dsd) ⇒ <code>string</code> * [.html(content)](#GitHubUtils.repo.html) ⇒ <code>string</code> * [.generateContent(content, [fetch], [no_org])](#GitHubUtils.repo.generateContent) ⇒ <code>GitHubRepositoryHTML</code> * [.GitHubRepositoryHTML](#GitHubUtils.repo.GitHubRepositoryHTML) : <code>Object</code> * [.user](#GitHubUtils.user) : <code>object</code> * [.styles](#GitHubUtils.user.styles) + * [.dsd](#GitHubUtils.user.dsd) ⇒ <code>string</code> * [.html(content)](#GitHubUtils.user.html) ⇒ <code>string</code> * [.generateContent(content, [fetch])](#GitHubUtils.user.generateContent) ⇒ <code>GitHubUserHTML</code> * [.GitHubUserHTML](#GitHubUtils.user.GitHubUserHTML) : <code>Object</code> @@ -86,19 +48,10 @@ Utility functions for fetching and parsing GitHub api data, getting Utility functions for a repository **Kind**: static namespace of [<code>GitHubUtils</code>](#GitHubUtils) -**Example** *(Server side rendering a Repository with Declarative Shadow Dom)* -```js -<github-repository id="github-repo-1"></github-repository> - -<script type="module"> -import {repo} from 'profile-components/github-utils'; -const dsdHTML = repo.dsd({full_name: 'scottnath/profile-components'}, true); -document.querySelector('#github-repo-1').innerHTML = dsdHTML; -</script> -``` * [.repo](#GitHubUtils.repo) : <code>object</code> * [.styles](#GitHubUtils.repo.styles) + * [.dsd](#GitHubUtils.repo.dsd) ⇒ <code>string</code> * [.html(content)](#GitHubUtils.repo.html) ⇒ <code>string</code> * [.generateContent(content, [fetch], [no_org])](#GitHubUtils.repo.generateContent) ⇒ <code>GitHubRepositoryHTML</code> * [.GitHubRepositoryHTML](#GitHubUtils.repo.GitHubRepositoryHTML) : <code>Object</code> @@ -109,6 +62,29 @@ document.querySelector('#github-repo-1').innerHTML = dsdHTML; GitHub repository styles **Kind**: static property of [<code>repo</code>](#GitHubUtils.repo) +<a name="GitHubUtils.repo.dsd"></a> + +#### repo.dsd ⇒ <code>string</code> +Generate a `template` element with shadowrootmode and a repository in it + +**Kind**: static namespace of [<code>repo</code>](#GitHubUtils.repo) +**Returns**: <code>string</code> - GitHub HTML wrapped in a `template` + +| Param | Type | Description | +| --- | --- | --- | +| content | <code>GitHubRepositoryHTML</code> | a content object representing a GitHub repository | +| fetch | <code>boolean</code> | | + +**Example** *(Server side rendering a Repository with Declarative Shadow Dom)* +```js +<github-repository id="github-repo-1"></github-repository> + +<script type="module"> +import {repo} from 'profile-components/github-utils'; +const dsdHTML = repo.dsd({full_name: 'scottnath/profile-components'}, true); +document.querySelector('#github-repo-1').innerHTML = dsdHTML; +</script> +``` <a name="GitHubUtils.repo.html"></a> #### repo.html(content) ⇒ <code>string</code> @@ -161,19 +137,10 @@ Content needed to render a GitHub repository. This is a subset of the `repos` en Utility functions for a user **Kind**: static namespace of [<code>GitHubUtils</code>](#GitHubUtils) -**Example** *(Server side rendering with Declarative Shadow Dom)* -```js -<github-user></github-user> - -<script type="module"> -import {dsd} from 'profile-components/github-utils'; -const dsdHTML = dsd({login: 'scottnath'}, true); -document.querySelector('github-user').innerHTML = dsdHTML; -</script> -``` * [.user](#GitHubUtils.user) : <code>object</code> * [.styles](#GitHubUtils.user.styles) + * [.dsd](#GitHubUtils.user.dsd) ⇒ <code>string</code> * [.html(content)](#GitHubUtils.user.html) ⇒ <code>string</code> * [.generateContent(content, [fetch])](#GitHubUtils.user.generateContent) ⇒ <code>GitHubUserHTML</code> * [.GitHubUserHTML](#GitHubUtils.user.GitHubUserHTML) : <code>Object</code> @@ -184,6 +151,29 @@ document.querySelector('github-user').innerHTML = dsdHTML; GitHub user styles **Kind**: static property of [<code>user</code>](#GitHubUtils.user) +<a name="GitHubUtils.user.dsd"></a> + +#### user.dsd ⇒ <code>string</code> +Generate a `template` element with shadowrootmode with a User in it + +**Kind**: static namespace of [<code>user</code>](#GitHubUtils.user) +**Returns**: <code>string</code> - GitHub HTML wrapped in a `template` + +| Param | Type | Description | +| --- | --- | --- | +| content | <code>GitHubUserHTML</code> | a content object representing a GitHub user | +| fetch | <code>boolean</code> | | + +**Example** *(Server side rendering with Declarative Shadow Dom)* +```js +<github-user></github-user> + +<script type="module"> +import {dsd} from 'profile-components/github-utils'; +const dsdHTML = dsd({login: 'scottnath'}, true); +document.querySelector('github-user').innerHTML = dsdHTML; +</script> +``` <a name="GitHubUtils.user.html"></a> #### user.html(content) ⇒ <code>string</code> diff --git a/src/github/dsd.docs.mdx b/src/github/dsd.docs.mdx new file mode 100644 index 0000000..ba35d4e --- /dev/null +++ b/src/github/dsd.docs.mdx @@ -0,0 +1,123 @@ +import { Meta, Title, Primary, Source, Stories } from '@storybook/blocks'; + +<Meta isTemplate /> + +<Title /> + +Both GitHub components can be implemented via Declarative Shadow DOM using methods exported from the `github-utils.js` file. + + +## Server Side Rendering HTML in Node.js + +<Source code={` +// import from npm module +import { dsd } from 'profile-components/github-utils'; + +const repos = JSON.stringify([ + 'scottnath/profile-components', + 'storydocker/storydocker' +]); + +const generatedTemplate = await dsd({ + login: 'scottnath', + avatar_url: profilePic.src, + repos +},true); + +/** +generatedTemplate contains: +<template shadowrootmode="open"> + <styles>(...css styles for GitHub component)</styles> + <section (...rest of generated HTML)</section> +</template> +*/ + +const componentHTML = \`<github-user>\${generatedTemplate}</github-user>\`; +`} language='js' /> + +## Server side render in an Astro component + +<Source code={` +--- +import {dsd} from 'profile-components/github-utils'; + +const repos = JSON.stringify(['scottnath/profile-components', 'storydocker/storydocker']); +const declaredDOM = await dsd({ + login: 'scottnath', + repos +},true) +--- + +<github-user + data-theme="light_high_contrast" + set:html={declaredDOM}> +</github-user> +`} language='jsx' /> + +## Client side rendering via unpkg + +<Source code={` + +<!-- add empty elements to HTML --> +<github-repository></github-repository> +<hr /> +<github-user></github-user> + +<script type="module"> + // import from unpkg + import { + user, + repo, + } from 'https://unpkg.com/profile-components/dist/github-utils.js'; + + // repo has it's own DSD method: + const dsdRepo = repo.dsd; + + /** + * Polyfill for Declarative Shadow DOM which, when triggered, converts + * the template element into actual shadow DOM. + * This is only needed when injecting _after_ page is loaded + * @see https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#polyfill + */ + const triggerAttachShadowRoots = () => { + (function attachShadowRoots(root) { + root + .querySelectorAll('template[shadowrootmode]') + .forEach((template) => { + const mode = template.getAttribute('shadowrootmode'); + const shadowRoot = template.parentNode.attachShadow({ mode }); + shadowRoot.appendChild(template.content); + template.remove(); + attachShadowRoots(shadowRoot); + }); + })(document); + }; + + /** + * Uses the "dsd" method to generate DSD, add the string of DSD content + * to the element, then trigger the polyfill to convert the template + */ + const injectDSD = async () => { + const dsdHTML = await dsd({ username: 'scottnath' }, true); + document.querySelector('github-user').innerHTML = dsdHTML; + // now that the HTML is async-created, the polyfill can convert it + triggerAttachShadowRoots(); + }; + injectDSD(); + + /** + * Uses the "dsdRepo" method to generate DSD, add the string of DSD content + * to the element, then trigger the polyfill to convert the template + */ + const injectRepoDSD = async () => { + const dsdHTML = await dsdRepo( + { full_name: 'scottnath/profile-components' }, + true + ); + document.querySelector('github-repository').innerHTML = dsdHTML; + // now that the HTML is async-created, the polyfill can convert it + triggerAttachShadowRoots(); + }; + injectRepoDSD(); +</script> +`} language='html' /> diff --git a/src/github/dsd.stories.js b/src/github/dsd.stories.js new file mode 100644 index 0000000..87c2f85 --- /dev/null +++ b/src/github/dsd.stories.js @@ -0,0 +1,71 @@ + +import { parseFetchedRepo } from './repository/content.js'; +import { parseFetchedUser } from './user/content.js'; +import { repoProfileComponents, repoStorydocker, userScottnath, repoFreeCodeCamp } from './fixtures'; +import { repo, dsd } from './index.js'; +import docs from './dsd.docs.mdx'; + + +export default { + title: 'GitHub/Declarative Shadow DOM', + parameters: { + docs: { + page: docs + }, + }, + tags: ['autodocs'], + decorators: [(story) => `${story()} + <script> + + (function attachShadowRoots(root) { + root.querySelectorAll("template[shadowrootmode]").forEach(template => { + const mode = template.getAttribute("shadowrootmode"); + const shadowRoot = template.parentNode.attachShadow({ mode }); + shadowRoot.appendChild(template.content); + template.remove(); + attachShadowRoots(shadowRoot); + }); + })(document); + </script> + `], +}; + +export const Repository = { + loaders: [ + async ({args}) => ({ + dsdOutput: await (await repo.dsd(args)), + }), + ], + render: (args, { loaded: { dsdOutput } }) => { + return ` + <github-repository-dsd>${dsdOutput}</github-repository-dsd> + + `; + }, + args: { + ...parseFetchedRepo(repoFreeCodeCamp), + }, +} + +export const User = { + loaders: [ + async ({args}) => ({ + dsdOutput: await (await dsd(args)), + }), + ], + render: (args, { loaded: { dsdOutput } }) => { + return ` + <github-user-dsd>${dsdOutput}</github-user-dsd> + + `; + }, + args: { + ...parseFetchedUser(userScottnath), + repos: stringify([ + { + ...parseFetchedRepo(repoProfileComponents), + user_login: userScottnath.login + }, + parseFetchedRepo(repoStorydocker)]), + }, +} diff --git a/src/github/fixtures/generated/repo--freeCodeCamp-freeCodeCamp.json b/src/github/fixtures/generated/repo--freeCodeCamp-freeCodeCamp.json index c46f084..1f1a9df 100644 --- a/src/github/fixtures/generated/repo--freeCodeCamp-freeCodeCamp.json +++ b/src/github/fixtures/generated/repo--freeCodeCamp-freeCodeCamp.json @@ -65,16 +65,16 @@ "releases_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/releases{/id}", "deployments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/deployments", "created_at": "2014-12-24T17:49:19Z", - "updated_at": "2024-02-13T19:26:21Z", - "pushed_at": "2024-02-13T18:34:49Z", + "updated_at": "2024-02-21T20:40:00Z", + "pushed_at": "2024-02-21T19:07:44Z", "git_url": "git://github.com/freeCodeCamp/freeCodeCamp.git", "ssh_url": "git@github.com:freeCodeCamp/freeCodeCamp.git", "clone_url": "https://github.com/freeCodeCamp/freeCodeCamp.git", "svn_url": "https://github.com/freeCodeCamp/freeCodeCamp", "homepage": "http://contribute.freecodecamp.org/", - "size": 412666, - "stargazers_count": 383225, - "watchers_count": 383225, + "size": 417124, + "stargazers_count": 383862, + "watchers_count": 383862, "language": "TypeScript", "has_issues": true, "has_projects": true, @@ -82,11 +82,11 @@ "has_wiki": false, "has_pages": true, "has_discussions": false, - "forks_count": 35816, + "forks_count": 34680, "mirror_url": null, "archived": false, "disabled": false, - "open_issues_count": 359, + "open_issues_count": 344, "license": { "key": "bsd-3-clause", "name": "BSD 3-Clause \"New\" or \"Revised\" License", @@ -116,9 +116,9 @@ "teachers" ], "visibility": "public", - "forks": 35816, - "open_issues": 359, - "watchers": 383225, + "forks": 34680, + "open_issues": 344, + "watchers": 383862, "default_branch": "main", "temp_clone_token": null, "custom_properties": {}, @@ -142,6 +142,6 @@ "type": "Organization", "site_admin": false }, - "network_count": 35816, - "subscribers_count": 8488 + "network_count": 34680, + "subscribers_count": 8499 } \ No newline at end of file diff --git a/src/github/fixtures/generated/repo--scottnath-profile-components.json b/src/github/fixtures/generated/repo--scottnath-profile-components.json index ffc580f..c274c2a 100644 --- a/src/github/fixtures/generated/repo--scottnath-profile-components.json +++ b/src/github/fixtures/generated/repo--scottnath-profile-components.json @@ -66,13 +66,13 @@ "deployments_url": "https://api.github.com/repos/scottnath/profile-components/deployments", "created_at": "2023-08-16T13:36:29Z", "updated_at": "2023-10-12T16:03:17Z", - "pushed_at": "2023-11-10T18:26:22Z", + "pushed_at": "2024-02-14T15:19:26Z", "git_url": "git://github.com/scottnath/profile-components.git", "ssh_url": "git@github.com:scottnath/profile-components.git", "clone_url": "https://github.com/scottnath/profile-components.git", "svn_url": "https://github.com/scottnath/profile-components", "homepage": "https://scottnath.com/profile-components/", - "size": 14935, + "size": 18742, "stargazers_count": 1, "watchers_count": 1, "language": "JavaScript", diff --git a/src/github/fixtures/generated/user--scottnath.json b/src/github/fixtures/generated/user--scottnath.json index 7294d4f..b0045d4 100644 --- a/src/github/fixtures/generated/user--scottnath.json +++ b/src/github/fixtures/generated/user--scottnath.json @@ -27,7 +27,7 @@ "twitter_username": null, "public_repos": 134, "public_gists": 24, - "followers": 10, + "followers": 9, "following": 18, "created_at": "2010-03-06T01:59:25Z", "updated_at": "2023-11-09T20:32:48Z" diff --git a/src/github/fixtures/generated/user--sindresorhus.json b/src/github/fixtures/generated/user--sindresorhus.json index c605749..825a56f 100644 --- a/src/github/fixtures/generated/user--sindresorhus.json +++ b/src/github/fixtures/generated/user--sindresorhus.json @@ -27,8 +27,8 @@ "twitter_username": "sindresorhus", "public_repos": 1100, "public_gists": 97, - "followers": 64444, - "following": 36, + "followers": 64767, + "following": 37, "created_at": "2009-12-20T22:57:02Z", "updated_at": "2024-02-08T17:59:12Z" } \ No newline at end of file diff --git a/src/github/index.js b/src/github/index.js index 4fa1c8a..39d5591 100644 --- a/src/github/index.js +++ b/src/github/index.js @@ -19,6 +19,19 @@ import repoHTML from './repository/html.js'; * @param {GitHubRepositoryHTML} content - a content object representing a GitHub repository * @param {boolean} fetch * @returns {string} GitHub HTML wrapped in a `template` + * @function + * @memberof GitHubUtils.repo + * @namespace dsd + * @description Generate a `template` element with shadowrootmode and a repository in it + * + * @example <caption>Server side rendering a Repository with Declarative Shadow Dom</caption> + * <github-repository id="github-repo-1"></github-repository> + * + * <script type="module"> + * import {repo} from 'profile-components/github-utils'; + * const dsdHTML = repo.dsd({full_name: 'scottnath/profile-components'}, true); + * document.querySelector('#github-repo-1').innerHTML = dsdHTML; + * </script> */ const dsdRepo = async (content, fetch = false) => { const generated = await generateRepoContent(content, fetch); @@ -34,15 +47,6 @@ const dsdRepo = async (content, fetch = false) => { * @namespace repo * @memberof GitHubUtils * @description Utility functions for a repository - * - * @example <caption>Server side rendering a Repository with Declarative Shadow Dom</caption> - * <github-repository id="github-repo-1"></github-repository> - * - * <script type="module"> - * import {repo} from 'profile-components/github-utils'; - * const dsdHTML = repo.dsd({full_name: 'scottnath/profile-components'}, true); - * document.querySelector('#github-repo-1').innerHTML = dsdHTML; - * </script> */ const repo = { generateContent: generateRepoContent, @@ -56,6 +60,19 @@ const repo = { * @param {GitHubUserHTML} content - a content object representing a GitHub user * @param {boolean} fetch * @returns {string} GitHub HTML wrapped in a `template` + * @function + * @memberof GitHubUtils.user + * @namespace dsd + * @description Generate a `template` element with shadowrootmode with a User in it + * + * @example <caption>Server side rendering with Declarative Shadow Dom</caption> + * <github-user></github-user> + * + * <script type="module"> + * import {dsd} from 'profile-components/github-utils'; + * const dsdHTML = dsd({login: 'scottnath'}, true); + * document.querySelector('github-user').innerHTML = dsdHTML; + * </script> */ const dsd = async (content, fetch = false) => { const generated = await generateUserContent(content, fetch); @@ -71,15 +88,6 @@ const dsd = async (content, fetch = false) => { * @namespace user * @memberof GitHubUtils * @description Utility functions for a user - * - * @example <caption>Server side rendering with Declarative Shadow Dom</caption> - * <github-user></github-user> - * - * <script type="module"> - * import {dsd} from 'profile-components/github-utils'; - * const dsdHTML = dsd({login: 'scottnath'}, true); - * document.querySelector('github-user').innerHTML = dsdHTML; - * </script> */ const user = { generateContent: generateUserContent,