From 64ad925a50fdae30642da81e97d75252c55e5ad0 Mon Sep 17 00:00:00 2001 From: Scott Nath Date: Mon, 9 Oct 2023 17:24:09 -0400 Subject: [PATCH 1/8] :art: clean up github files --- README.hbs | 12 ++ README.md | 147 ++++++++++++++++- lib/esbuild.config.js | 29 ++++ lib/jsdoc.js | 37 +++-- package.json | 8 +- src/github/UTILITIES.md | 165 +++++++++++++++++++ src/github/index.js | 56 +++++++ src/github/repository/README.md | 146 +---------------- src/github/repository/content.js | 14 +- src/github/repository/html.js | 6 +- src/github/repository/index.js | 27 ++-- src/github/repository/repository.stories.js | 30 ++-- src/github/styles/index.js | 28 ++++ src/github/styles/user.css | 9 +- src/github/user/README.md | 168 -------------------- src/github/user/content.js | 17 +- src/github/user/html.js | 11 +- src/github/user/index.js | 31 ++-- src/github/user/user.stories.js | 44 +++-- 19 files changed, 565 insertions(+), 420 deletions(-) create mode 100644 README.hbs create mode 100644 src/github/UTILITIES.md create mode 100644 src/github/index.js create mode 100644 src/github/styles/index.js diff --git a/README.hbs b/README.hbs new file mode 100644 index 0000000..2e7f55e --- /dev/null +++ b/README.hbs @@ -0,0 +1,12 @@ +# profile-components + +Web components which display profile information from various websites + + +* install via npm: `npm i profile-components` +* play with the components in [Storybook](https://scottnath.github.io/profile-components) +* [See demos on stackblitz](https://stackblitz.com/edit/profile-components) + +## Components + +{{>main}} \ No newline at end of file diff --git a/README.md b/README.md index e156d9f..36f2813 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,146 @@ Web components which display profile information from various websites -## @todo -- [ ] docs for unpkg usage -- [ ] add stackblitz demo -- [ ] typescript types output from JSDoc +* install via npm: `npm i profile-components` +* play with the components in [Storybook](https://scottnath.github.io/profile-components) +* [See demos on stackblitz](https://stackblitz.com/edit/profile-components) -## Other profile sources +## Components -* Reddit: https://www.reddit.com/user/scottnath/about/.json -* LinkedIn: (3rd party) https://help.lix-it.com/en/articles/6674073-what-data-can-i-export-from-linkedin \ No newline at end of file +## Modules + +
+
GitHubUser
+

GitHub user profile web component

+
+
GitHubRepository
+

GitHub repository web component

+
+
DevtoUser
+

dev.to user profile web component

+
+
DevtoPost
+

dev.to post web component

+
+
+ + + +## GitHubUser +GitHub user profile web component + +**Summary**: Native web component which shows a GitHub user's profile content. Can use local data, + fetch data from the GitHub rest API, or use a combination of both. +**Element**: github-user +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| login | string | User's GitHub login | +| avatar_url | string | URL to user's avatar | +| name | string | User's name | +| [fetch] | boolean | when true, fetches user from [GitHub api](https://docs.github.com/en/rest/users/users#get-a-user) | +| [username] | string | alias for `login` | +| [bio] | string | User's biography content | +| [following] | string | number of people user is following | +| [followers] | string | number of followers | +| [repos] | string | JSON stringified array of repositories | + +**Example** +```js + + + + + +``` + + +## GitHubRepository +GitHub repository web component + +**Summary**: Native web component which shows a GitHub repository's content. Can use local data, + fetch data from the GitHub rest API, or use a combination of both. +**Element**: github-repository +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| full_name | string | repository org and name, as in `scottnath/profile-components` | +| [name] | string | repo name | +| [org] | string | repo owner organization's login, found at `.organization.login` | +| [description] | string | repo description | +| [language] | string | programming language used in repo | +| [stargazers_count] | string | number of stars | +| [forks_count] | string | number of forks | +| [subscribers_count] | string | number of watchers | +| [fetch] | boolean | when true, fetches repo from [GitHub api](https://docs.github.com/en/rest/repos/repos#get-a-repository) | +| [itemprop] | string | Itemprop content to go with a containing component's itemscope | +| [no_org] | string | Do not include the repo owner or organization | + +**Example** +```js + + + + + +``` + + +## DevtoUser +dev.to user profile web component + +**Summary**: Native web component which shows a dev.to user's profile content. + Can use local data, or fetch data from the dev.to API, or use a combination of both. +**Element**: devto-user +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| username | string | User's dev.to username | +| [name] | string | The name of the user | +| [summary] | string | The user's bio | +| [joined_at] | string | The date the user joined | +| [profile_image] | string | The URL of the user's profile image | +| [fetch] | boolean | when true, fetches user and posts from api | +| [post_count] | number | The number of posts the user has published | +| [latest_post] | string | User's latest post content, JSON stringified | +| [popular_post] | string | User's most popular post content, JSON stringified | + +**Example** +```js + + + + + +``` + + +## DevtoPost +dev.to post web component + +**Summary**: Native web component which shows a dev.to (or forem.dev) post. Can use local data, + fetch data from the dev.to API, or use a combination of both. +**Element**: devto-post +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| id | number | Post ID | +| title | string | Post title | +| url | string | Post URL | +| cover_image | string | Post cover image URL | +| social_image | string | Post social image URL | +| [fetch] | boolean | when true, fetches post from api | + +**Example** +```js + + + + + +``` diff --git a/lib/esbuild.config.js b/lib/esbuild.config.js index 4a2d2a6..5e335f8 100644 --- a/lib/esbuild.config.js +++ b/lib/esbuild.config.js @@ -11,6 +11,17 @@ import cem from '../custom-elements.json' assert { type: 'json' }; const { src, dist } = getModulePaths(); +const componentUtils = [ + { + name: 'github-utils', + entry: './src/github/index.js', + }, + { + name: 'devto-utils', + entry: './src/devto/index.js', + } +] + /** * Entrypoints for esbuild generated from custom elements manifest * which _must_ be generated before this script is triggered @@ -32,5 +43,23 @@ cem.modules.forEach(elm => { }), ], }); +}); +/** + * Entrypoints for esbuild generated from component utils + */ +componentUtils.forEach(elm => { + esbuild.build({ + entryPoints: [pathFromRoot(elm.entry)], + bundle: true, + format: 'esm', + entryNames: elm.name, + outbase: src, + outdir: dist, + plugins: [ + inlineImportPlugin({ + filter: /\?inline$/, + }), + ], + }); }); diff --git a/lib/jsdoc.js b/lib/jsdoc.js index 15b95ea..7b03cc7 100644 --- a/lib/jsdoc.js +++ b/lib/jsdoc.js @@ -1,3 +1,5 @@ +import fs from 'fs'; +import path from 'path'; import { pathFromRoot } from './utils.js'; import jsdoc2md from 'jsdoc-to-markdown'; import { outputFile } from 'fs-extra'; @@ -8,12 +10,15 @@ import { outputFile } from 'fs-extra'; * @param {string} dir - Directory to read files from, relative from root * @returns jsdoc2md config object */ -const generateJsdoc2Config = (title, dir, files=[]) => { - const template = `# ${title}\n\n{{>main}}`; +const generateJsdoc2Config = async (title, dir, files=[], filename="README.md") => { + let template = `# ${title}\n\n{{>main}}`; + if (fs.existsSync(path.join(dir, 'README.hbs'))) { + template = fs.readFileSync(path.join(dir, 'README.hbs'), 'utf8'); + } return { files, 'member-index-format': 'list', - outputFile: `${dir}/README.md`, + outputFile: `${dir}/${filename}`, exampleLang: 'js', template, } @@ -24,22 +29,34 @@ const generateJsdoc2Config = (title, dir, files=[]) => { * @todo set up for other directories */ export const generateDocs = async () => { - // const githubUtils = generateJsdoc2Config(`GitHub profile components' utilities`, 'src/github/utils'); - // const res = await jsdoc2md.render(githubUtils); const toDocs = [ + { + title: 'profile-components', + dir: pathFromRoot(), + files: [ + pathFromRoot('src/github/user/index.js'), + pathFromRoot('src/github/repository/index.js'), + pathFromRoot('src/devto/user/index.js'), + pathFromRoot('src/devto/post/index.js'), + ], + }, { title: `GitHub profile components' utilities`, dir: pathFromRoot('src/github/utils'), files: pathFromRoot('src/github/utils/*.js'), }, { - title: 'GitHub user profile component', - dir: pathFromRoot('src/github/user'), + title: 'GitHub utilities', + dir: pathFromRoot('src/github'), files: [ - pathFromRoot('src/github/user/index.js'), - pathFromRoot('src/github/user/content.js'), + pathFromRoot('src/github/styles/index.js'), pathFromRoot('src/github/user/html.js'), + pathFromRoot('src/github/user/content.js'), + pathFromRoot('src/github/repository/html.js'), + pathFromRoot('src/github/repository/content.js'), + pathFromRoot('src/github/index.js'), ], + filename: 'UTILITIES.md', }, { title: 'GitHub repository details component', @@ -75,7 +92,7 @@ export const generateDocs = async () => { }, ] for (const toDoc of toDocs) { - const githubComp = generateJsdoc2Config(toDoc.title, toDoc.dir, toDoc.files); + const githubComp = await generateJsdoc2Config(toDoc.title, toDoc.dir, toDoc.files, toDoc.filename); const res = await jsdoc2md.render(githubComp); await outputFile(githubComp.outputFile, res); } diff --git a/package.json b/package.json index e41df2c..6482adf 100644 --- a/package.json +++ b/package.json @@ -37,14 +37,8 @@ "LICENSE" ], "exports": { - "./*.js": { + "./*": { "import": "./dist/*.js" - }, - "./github/*.js": { - "import": "./src/github/*.js" - }, - "./github/utils/*.js": { - "import": "./src/github/utils/*.js" } }, "license": "MIT", diff --git a/src/github/UTILITIES.md b/src/github/UTILITIES.md new file mode 100644 index 0000000..1286b6a --- /dev/null +++ b/src/github/UTILITIES.md @@ -0,0 +1,165 @@ +# GitHub utilities + + + +## GitHubUtils : object +Utility functions for fetching and parsing GitHub api data, getting + styles and generating HTML for GitHub profile UIs + +**Kind**: global namespace +**Author**: @scottnath + +* [GitHubUtils](#GitHubUtils) : object + * [.repo](#GitHubUtils.repo) : object + * [.styles](#GitHubUtils.repo.styles) + * [.html(content)](#GitHubUtils.repo.html) ⇒ string + * [.generateContent(content, [fetch], [no_org])](#GitHubUtils.repo.generateContent) ⇒ GitHubRepositoryHTML + * [.GitHubRepositoryHTML](#GitHubUtils.repo.GitHubRepositoryHTML) : Object + * [.user](#GitHubUtils.user) : object + * [.styles](#GitHubUtils.user.styles) + * [.html(content)](#GitHubUtils.user.html) ⇒ string + * [.generateContent(content, [fetch])](#GitHubUtils.user.generateContent) ⇒ GitHubUserHTML + * [.GitHubUserHTML](#GitHubUtils.user.GitHubUserHTML) : Object + + + +### githubUtils.repo : object +Utility functions for a repository + +**Kind**: static namespace of [GitHubUtils](#GitHubUtils) +**Example** +```js +import {repo} from 'profile-components/github-utils'; +const content = repo.content({full_name: 'scottnath/profile-components'}, true); +const html = repo.html(content); +``` + +* [.repo](#GitHubUtils.repo) : object + * [.styles](#GitHubUtils.repo.styles) + * [.html(content)](#GitHubUtils.repo.html) ⇒ string + * [.generateContent(content, [fetch], [no_org])](#GitHubUtils.repo.generateContent) ⇒ GitHubRepositoryHTML + * [.GitHubRepositoryHTML](#GitHubUtils.repo.GitHubRepositoryHTML) : Object + + + +#### repo.styles +GitHub repository styles + +**Kind**: static property of [repo](#GitHubUtils.repo) + + +#### repo.html(content) ⇒ string +GitHub repository HTML generation + +**Kind**: static method of [repo](#GitHubUtils.repo) +**Returns**: string - HTML which represents a GitHub repository + +| Param | Type | Description | +| --- | --- | --- | +| content | GitHubRepositoryHTML | content needed to render a GitHub repository | + + + +#### repo.generateContent(content, [fetch], [no_org]) ⇒ GitHubRepositoryHTML +Generates an object of content for the repository HTML + +**Kind**: static method of [repo](#GitHubUtils.repo) +**Returns**: GitHubRepositoryHTML - content ready for HTML, possibly includes fetched content + +| Param | Type | +| --- | --- | +| content | GitHubRepositoryHTML | +| [fetch] | boolean | +| [no_org] | boolean | + + + +#### repo.GitHubRepositoryHTML : Object +Content needed to render a GitHub repository. This is a subset of the `repos` endpoint response + +**Kind**: static typedef of [repo](#GitHubUtils.repo) +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| full_name | string | repository org and name, as in `scottnath/profile-components` | +| name | string | repo name | +| [org] | string | repo owner organization's login, found at `.organization.login` | +| [description] | string | repo description | +| [language] | string | programming language used in repo | +| [stargazers_count] | string | number of stars | +| [forks_count] | string | number of forks | +| [subscribers_count] | string | number of watchers | +| [error] | string | error message, if any | + + + +### githubUtils.user : object +Utility functions for a user + +**Kind**: static namespace of [GitHubUtils](#GitHubUtils) +**Example** +```js +import {user} from 'profile-components/github-utils'; +const content = user.content({login: 'scottnath'}, true); +const html = user.html(content); +``` + +* [.user](#GitHubUtils.user) : object + * [.styles](#GitHubUtils.user.styles) + * [.html(content)](#GitHubUtils.user.html) ⇒ string + * [.generateContent(content, [fetch])](#GitHubUtils.user.generateContent) ⇒ GitHubUserHTML + * [.GitHubUserHTML](#GitHubUtils.user.GitHubUserHTML) : Object + + + +#### user.styles +GitHub user styles + +**Kind**: static property of [user](#GitHubUtils.user) + + +#### user.html(content) ⇒ string +Generates an HTML string for a GitHub user profile. + +**Kind**: static method of [user](#GitHubUtils.user) +**Returns**: string - HTML string + +| Param | Type | Description | +| --- | --- | --- | +| content | GitHubUserHTML | a content object representing a GitHub user | + + + +#### user.generateContent(content, [fetch]) ⇒ GitHubUserHTML +Generates an object of content for the repository HTML + +**Kind**: static method of [user](#GitHubUtils.user) +**Returns**: GitHubUserHTML - content ready for HTML, possibly includes fetched content + +| Param | Type | +| --- | --- | +| content | GitHubUserHTML | +| [fetch] | boolean | + + + +#### user.GitHubUserHTML : Object +Content needed to render a GitHub user. This is a subset of the `users` endpoint response + +**Kind**: static typedef of [user](#GitHubUtils.user) +**See**: https://docs.github.com/en/rest/users/users#get-a-user +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| login | string | User's GitHub login | +| name | string | User's name | +| [username] | string | alias for `login` | +| [avatar_url] | string | URL to user's avatar | +| [bio] | string | User's biography content | +| [following] | string | number of people user is following | +| [followers] | string | number of followers | +| [error] | string | error message, if any | +| [repositories] | Array.<GitHubRepositoryHTML> | array of repositories | + diff --git a/src/github/index.js b/src/github/index.js new file mode 100644 index 0000000..b6ea353 --- /dev/null +++ b/src/github/index.js @@ -0,0 +1,56 @@ +/** + * @name GitHub-Utilities + * @kind module + * @typicalname githubUtils + * @namespace GitHubUtils + * @description Utility functions for fetching and parsing GitHub api data, getting + * styles and generating HTML for GitHub profile UIs + * @author @scottnath + */ + +import {styles, repository} from './styles/index.js'; +import {generateUserContent} from './user/content.js'; +import userHTML from './user/html.js'; +import {generateRepoContent} from './repository/content.js'; +import repoHTML from './repository/html.js'; + +/** + * @name GitHub-Repository-Utilities + * @module + * @namespace repo + * @memberof GitHubUtils + * @description Utility functions for a repository + * + * @example + * import {repo} from 'profile-components/github-utils'; + * const content = repo.content({full_name: 'scottnath/profile-components'}, true); + * const html = repo.html(content); + */ +const repo = { + generateContent: generateRepoContent, + html: repoHTML, + styles: repository, +}; + +/** + * @name GitHub-User-Utilities + * @module + * @namespace user + * @memberof GitHubUtils + * @description Utility functions for a user + * + * @example + * import {user} from 'profile-components/github-utils'; + * const content = user.content({login: 'scottnath'}, true); + * const html = user.html(content); + */ +const user = { + generateContent: generateUserContent, + html: userHTML, + styles, +}; + +export default { + repo, + user +} \ No newline at end of file diff --git a/src/github/repository/README.md b/src/github/repository/README.md index 7343f4c..2f3386c 100644 --- a/src/github/repository/README.md +++ b/src/github/repository/README.md @@ -1,133 +1,13 @@ # GitHub repository details component -## Modules - -
-
GitHub-Repo-Utilities
-

Utility functions for fetching and parsing GitHub Repository data

-
-
- -## Members - -
-
GitHubRepository
-

GitHub repository web component

-
-
- -## Constants - -
-
componentStyles
-

Styles for the component, imported during development, inlined during build

-
-
- -## Functions - -
-
repository(content)string
-

GitHub repository HTML generation

-
-
- - - -## GitHub-Repo-Utilities -Utility functions for fetching and parsing GitHub Repository data - -**Author**: @scottnath - -* [GitHub-Repo-Utilities](#module_GitHub-Repo-Utilities) - * [.fetchRepo(full_name)](#module_GitHub-Repo-Utilities.fetchRepo) ⇒ Object - * [.parseFetchedRepo(repo)](#module_GitHub-Repo-Utilities.parseFetchedRepo) ⇒ GitHubRepositoryHTML - * [.cleanRepoContent(content, [no_org])](#module_GitHub-Repo-Utilities.cleanRepoContent) ⇒ GitHubRepositoryHTML - * [.generateRepoContent(content, [fetch], [no_org])](#module_GitHub-Repo-Utilities.generateRepoContent) ⇒ GitHubRepositoryHTML - * [~GitHubRepositoryHTML](#module_GitHub-Repo-Utilities..GitHubRepositoryHTML) : Object - - - -### githubRepoUtils.fetchRepo(full_name) ⇒ Object -Fetch a GitHub repository's content from the GitHub api - -**Kind**: static method of [GitHub-Repo-Utilities](#module_GitHub-Repo-Utilities) -**Returns**: Object - response status 200: repo; else {Object} error -**See**: https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#get-a-repository - -| Param | Type | -| --- | --- | -| full_name | string | - - - -### githubRepoUtils.parseFetchedRepo(repo) ⇒ GitHubRepositoryHTML -Parse a GitHub repository's content. This is a reducer on the endpoint response, - but generally reduces any object to just the data required for the repo component HTML - -**Kind**: static method of [GitHub-Repo-Utilities](#module_GitHub-Repo-Utilities) - -| Param | Type | Description | -| --- | --- | --- | -| repo | Object | GitHub repository object | - - - -### githubRepoUtils.cleanRepoContent(content, [no_org]) ⇒ GitHubRepositoryHTML -Parses and cleans repository content to match what is expected by the repository HTML - -**Kind**: static method of [GitHub-Repo-Utilities](#module_GitHub-Repo-Utilities) -**Returns**: GitHubRepositoryHTML - ready for HTML content - -| Param | Type | Description | -| --- | --- | --- | -| content | GitHubRepositoryHTML | a content object either from component or GitHub API | -| [no_org] | boolean | if true, remove the `org` attribute from the returned object | - - - -### githubRepoUtils.generateRepoContent(content, [fetch], [no_org]) ⇒ GitHubRepositoryHTML -Generates an object of content for the repository HTML - -**Kind**: static method of [GitHub-Repo-Utilities](#module_GitHub-Repo-Utilities) -**Returns**: GitHubRepositoryHTML - content ready for HTML, possibly includes fetched content - -| Param | Type | -| --- | --- | -| content | GitHubRepositoryHTML | -| [fetch] | boolean | -| [no_org] | boolean | - - - -### GitHub-Repo-Utilities~GitHubRepositoryHTML : Object -Content needed to render a GitHub repository. This is a subset of the `repos` endpoint response - -**Kind**: inner typedef of [GitHub-Repo-Utilities](#module_GitHub-Repo-Utilities) -**Properties** - -| Name | Type | Description | -| --- | --- | --- | -| full_name | string | repository org and name, as in `scottnath/profile-components` | -| name | string | repo name | -| [org] | string | repo owner organization's login, found at `.organization.login` | -| [description] | string | repo description | -| [language] | string | programming language used in repo | -| [stargazers_count] | string | number of stars | -| [forks_count] | string | number of forks | -| [subscribers_count] | string | number of watchers | -| [error] | string | error message, if any | - - + ## GitHubRepository GitHub repository web component -**Kind**: global variable **Summary**: Native web component which shows a GitHub repository's content. Can use local data, fetch data from the GitHub rest API, or use a combination of both. **Element**: github-repository -**See**: https://docs.github.com/en/rest/repos/repos#get-a-repository **Properties** | Name | Type | Description | @@ -140,29 +20,15 @@ GitHub repository web component | [stargazers_count] | string | number of stars | | [forks_count] | string | number of forks | | [subscribers_count] | string | number of watchers | -| [fetch] | boolean | when true, fetches repo from GitHub api | +| [fetch] | boolean | when true, fetches repo from [GitHub api](https://docs.github.com/en/rest/repos/repos#get-a-repository) | | [itemprop] | string | Itemprop content to go with a containing component's itemscope | | [no_org] | string | Do not include the repo owner or organization | **Example** ```js + + + + ``` - - -## componentStyles -Styles for the component, imported during development, inlined during build - -**Kind**: global constant - - -## repository(content) ⇒ string -GitHub repository HTML generation - -**Kind**: global function -**Returns**: string - HTML which represents a GitHub repository - -| Param | Type | Description | -| --- | --- | --- | -| content | GitHubRepositoryHTML | content needed to render a GitHub repository | - diff --git a/src/github/repository/content.js b/src/github/repository/content.js index 6079a1a..99d6ad4 100644 --- a/src/github/repository/content.js +++ b/src/github/repository/content.js @@ -1,11 +1,3 @@ -/** - * @name GitHub-Repo-Utilities - * @module - * @typicalname githubRepoUtils - * @description Utility functions for fetching and parsing GitHub Repository data - * @author @scottnath - */ - /** @ignore */ const githubApi = 'https://api.github.com'; @@ -22,6 +14,7 @@ const githubApi = 'https://api.github.com'; * @property {string} [forks_count] - number of forks * @property {string} [subscribers_count] - number of watchers * @property {string} [error] - error message, if any + * @memberof GitHubUtils.repo */ /** @@ -30,6 +23,7 @@ const githubApi = 'https://api.github.com'; * @param {string} full_name * @returns response status 200: {Object} repo; else {Object} error * @function + * @ignore */ export const fetchRepo = async (full_name) => { const response = await fetch(`${githubApi}/repos/${full_name}`); @@ -43,6 +37,7 @@ export const fetchRepo = async (full_name) => { * @param {Object} repo - GitHub repository object * @returns {GitHubRepositoryHTML} * @function + * @ignore */ export const parseFetchedRepo = (repo = {}) => { return { @@ -63,6 +58,7 @@ export const parseFetchedRepo = (repo = {}) => { * @param {boolean} [no_org] - if true, remove the `org` attribute from the returned object * @returns {GitHubRepositoryHTML} ready for HTML content * @function + * @ignore */ export const cleanRepoContent = (content, no_org) => { const repo = parseFetchedRepo(content); @@ -97,6 +93,8 @@ export const cleanRepoContent = (content, no_org) => { * @param {boolean} [no_org] * @returns {GitHubRepositoryHTML} content ready for HTML, possibly includes fetched content * @function + * @memberof GitHubUtils.repo + * @name generateContent */ export const generateRepoContent = async (content, fetch = false, no_org = false) => { const repoFromContent = cleanRepoContent(content, no_org); diff --git a/src/github/repository/html.js b/src/github/repository/html.js index ce6a2b2..0ecabd2 100644 --- a/src/github/repository/html.js +++ b/src/github/repository/html.js @@ -6,8 +6,10 @@ import { intToString } from '../../utils/index.js'; * GitHub repository HTML generation * @param {GitHubRepositoryHTML} content - content needed to render a GitHub repository * @returns {string} HTML which represents a GitHub repository + * @function + * @memberof GitHubUtils.repo */ -function repository(content) { +function html(content) { if (content.error) { return `
@@ -48,4 +50,4 @@ function repository(content) { `; } -export default repository; \ No newline at end of file +export default html; \ No newline at end of file diff --git a/src/github/repository/index.js b/src/github/repository/index.js index cabd54e..00a7d11 100644 --- a/src/github/repository/index.js +++ b/src/github/repository/index.js @@ -1,17 +1,6 @@ import { generateRepoContent } from './content.js'; -import repositoryHTML from './html.js'; -import stylesPrimer from '../styles/vars-primer.css?inline'; -import stylesGlobal from '../styles/vars-global.css?inline'; -import styles from '../styles/repository.css?inline'; - -/** - * Styles for the component, imported during development, inlined during build - */ -const componentStyles = ` -${stylesPrimer} -${stylesGlobal} -${styles} -`; +import html from './html.js'; +import { repository as styles } from '../styles/index.js'; /** * GitHub repository web component @@ -19,7 +8,7 @@ ${styles} * fetch data from the GitHub rest API, or use a combination of both. * @element github-repository * @name GitHubRepository - * @see https://docs.github.com/en/rest/repos/repos#get-a-repository + * @module * * @property {string} full_name - repository org and name, as in `scottnath/profile-components` * @property {string} [name] - repo name @@ -29,11 +18,15 @@ ${styles} * @property {string} [stargazers_count] - number of stars * @property {string} [forks_count] - number of forks * @property {string} [subscribers_count] - number of watchers - * @property {boolean} [fetch] - when true, fetches repo from GitHub api + * @property {boolean} [fetch] - when true, fetches repo from [GitHub api](https://docs.github.com/en/rest/repos/repos#get-a-repository) * @property {string} [itemprop] - Itemprop content to go with a containing component's itemscope * @property {string} [no_org] - Do not include the repo owner or organization * * @example + * + * + * + * * */ export class GitHubRepository extends HTMLElement { @@ -61,9 +54,9 @@ export class GitHubRepository extends HTMLElement { } async connectedCallback() { - let view = ``; + let view = ``; this.repo = await generateRepoContent(this.attrs, this.attrs.fetch, this.attrs.no_org); - view += repositoryHTML(this.repo); + view += html(this.repo); this.shadowRoot.innerHTML = view; if (this.attrs.itemprop) { this.setAttribute('itemprop', this.attrs.itemprop); diff --git a/src/github/repository/repository.stories.js b/src/github/repository/repository.stories.js index a88a17c..61fd833 100644 --- a/src/github/repository/repository.stories.js +++ b/src/github/repository/repository.stories.js @@ -22,11 +22,9 @@ export default { } }; - -export const FullNameOnly = { +export const Repository = { args: { - full_name: repoProfileComponents.full_name, - stargazers_count: '0' + ...parseFetchedRepo(repoFreeCodeCamp), }, play: async ({ args, canvasElement, step }) => { const elements = await getElements(canvasElement); @@ -34,33 +32,35 @@ export const FullNameOnly = { } } + +export const FullNameOnly = { + args: { + full_name: repoProfileComponents.full_name, + stargazers_count: '0' + }, + play: Repository.play, +} + export const OrgIsUser = { args: { full_name: repoProfileComponents.full_name, no_org: true, }, - play: FullNameOnly.play, + play: Repository.play, } export const WithOrgName = { args: { full_name: repoProfileComponents.full_name, org: 'different-org-name' }, - play: FullNameOnly.play, + play: Repository.play, } export const LanguageCircle = { args: { full_name: 'just-another/c-plus-plus-repo', language: 'C++', }, - play: FullNameOnly.play, -} - -export const AllRepoContent = { - args: { - ...parseFetchedRepo(repoFreeCodeCamp), - }, - play: FullNameOnly.play, + play: Repository.play, } export const Fetch = { @@ -113,5 +113,5 @@ export const FetchError = { } export const NoRepo = { - play: FullNameOnly.play, + play: Repository.play, }; \ No newline at end of file diff --git a/src/github/styles/index.js b/src/github/styles/index.js new file mode 100644 index 0000000..2910001 --- /dev/null +++ b/src/github/styles/index.js @@ -0,0 +1,28 @@ + +import primer from './vars-primer.css?inline'; +import global from './vars-global.css?inline'; +import repo from './repository.css?inline'; +import usr from './user.css?inline'; + +/** + * GitHub repository styles + * @memberof GitHubUtils.repo + * @name styles + */ +export const repository = ` +${primer} +${global} +${repo} +`; + +/** + * GitHub user styles + * @memberof GitHubUtils.user + * @name styles + */ +export const styles = ` +${primer} +${global} +${repo} +${usr} +`; \ No newline at end of file diff --git a/src/github/styles/user.css b/src/github/styles/user.css index ea6a8ce..b9a7245 100644 --- a/src/github/styles/user.css +++ b/src/github/styles/user.css @@ -70,9 +70,10 @@ section[itemscope] { border: var(--border-width) solid var(--border-color); border-radius: 50%; line-height: 1; - width: 70%; - height: auto; + width: 70cqw; + height: 70cqw; margin: 0 auto; + object-fit: cover; } } @@ -106,6 +107,10 @@ section[itemscope] { [itemprop="image"] { flex: 1 1 30%; + & img { + width: 27cqw; + height: 27cqw; + } } [itemprop="creator"] { flex: 1 0 70%; diff --git a/src/github/user/README.md b/src/github/user/README.md index 4e35ac4..80348d1 100644 --- a/src/github/user/README.md +++ b/src/github/user/README.md @@ -1,170 +1,2 @@ # GitHub user profile component -## Modules - -
-
GitHub-User-Utilities
-

Utility functions for fetching and parsing GitHub User data

-
-
- -## Members - -
-
GitHubUser
-

GitHub user profile web component

-
-
- -## Constants - -
-
componentStyles
-

Styles for the component, imported during development, inlined during build

-
-
- - - -## GitHub-User-Utilities -Utility functions for fetching and parsing GitHub User data - -**Author**: @scottnath - -* [GitHub-User-Utilities](#module_GitHub-User-Utilities) - * [.fetchUser(username)](#module_GitHub-User-Utilities.fetchUser) ⇒ Object - * [.parseFetchedUser(user)](#module_GitHub-User-Utilities.parseFetchedUser) ⇒ GitHubUserHTML - * [.parseReposString(reposStr, [owner])](#module_GitHub-User-Utilities.parseReposString) ⇒ - * [.cleanUserContent(content)](#module_GitHub-User-Utilities.cleanUserContent) ⇒ GitHubUserHTML - * [.generateUserContent(content, [fetch])](#module_GitHub-User-Utilities.generateUserContent) ⇒ GitHubUserHTML - * [~blankPng](#module_GitHub-User-Utilities..blankPng) - * [~GitHubUserHTML](#module_GitHub-User-Utilities..GitHubUserHTML) : Object - - - -### githubUserUtils.fetchUser(username) ⇒ Object -Fetch a user from - -**Kind**: static method of [GitHub-User-Utilities](#module_GitHub-User-Utilities) -**Returns**: Object - response status 200: user; else {Object} error -**See**: https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-a-user - -| Param | Type | -| --- | --- | -| username | string | - - - -### githubUserUtils.parseFetchedUser(user) ⇒ GitHubUserHTML -Parse a GitHub user from the `user` endpoint response down to - only the data required for the user component - -**Kind**: static method of [GitHub-User-Utilities](#module_GitHub-User-Utilities) -**Returns**: GitHubUserHTML - component-ready user object - -| Param | Type | -| --- | --- | -| user | Object | - - - -### githubUserUtils.parseReposString(reposStr, [owner]) ⇒ -Parses a string, which should be a JSON stringified array of GitHubRepository - objects or JSON stringified array of strings. If an array of string, - each string should be the `full_name` of a repository. - -**Kind**: static method of [GitHub-User-Utilities](#module_GitHub-User-Utilities) -**Returns**: array of strings of attributes for each repository - -| Param | Type | Description | -| --- | --- | --- | -| reposStr | string | String of GitHubRepository data | -| [owner] | string | GitHub user login, repository strings are not `full_name`s | - - - -### githubUserUtils.cleanUserContent(content) ⇒ GitHubUserHTML -Parses and cleans user content to match what is expected by the user HTML - -**Kind**: static method of [GitHub-User-Utilities](#module_GitHub-User-Utilities) -**Returns**: GitHubUserHTML - ready for HTML content - -| Param | Type | Description | -| --- | --- | --- | -| content | GitHubUserHTML | a content object representing a GitHub user | - - - -### githubUserUtils.generateUserContent(content, [fetch]) ⇒ GitHubUserHTML -Generates an object of content for the repository HTML - -**Kind**: static method of [GitHub-User-Utilities](#module_GitHub-User-Utilities) -**Returns**: GitHubUserHTML - content ready for HTML, possibly includes fetched content - -| Param | Type | -| --- | --- | -| content | GitHubUserHTML | -| [fetch] | boolean | - - - -### GitHub-User-Utilities~blankPng -Blank base64-encoded png - -**Kind**: inner constant of [GitHub-User-Utilities](#module_GitHub-User-Utilities) -**See**: https://png-pixel.com/ - - -### GitHub-User-Utilities~GitHubUserHTML : Object -Content needed to render a GitHub user. This is a subset of the `users` endpoint response - -**Kind**: inner typedef of [GitHub-User-Utilities](#module_GitHub-User-Utilities) -**See**: https://docs.github.com/en/rest/users/users#get-a-user -**Properties** - -| Name | Type | Description | -| --- | --- | --- | -| login | string | User's GitHub login | -| name | string | User's name | -| [username] | string | alias for `login` | -| [avatar_url] | string | URL to user's avatar | -| [bio] | string | User's biography content | -| [following] | string | number of people user is following | -| [followers] | string | number of followers | -| [error] | string | error message, if any | -| [repositories] | Array.<GitHubRepositoryHTML> | array of repositories | - - - -## GitHubUser -GitHub user profile web component - -**Kind**: global variable -**Summary**: Native web component which shows a GitHub user's profile content. Can use local data, - fetch data from the GitHub rest API, or use a combination of both. -**Element**: github-user -**See**: https://docs.github.com/en/rest/repos/repos#get-a-repository -**Properties** - -| Name | Type | Description | -| --- | --- | --- | -| login | string | User's GitHub login | -| avatar_url | string | URL to user's avatar | -| name | string | User's name | -| [fetch] | boolean | when true, fetches user from GitHub api | -| [username] | string | alias for `login` | -| [bio] | string | User's biography content | -| [following] | string | number of people user is following | -| [followers] | string | number of followers | -| [repos] | string | JSON stringified array of repositories | - -**Example** -```js - -``` - - -## componentStyles -Styles for the component, imported during development, inlined during build - -**Kind**: global constant diff --git a/src/github/user/content.js b/src/github/user/content.js index 29663a7..87b2416 100644 --- a/src/github/user/content.js +++ b/src/github/user/content.js @@ -1,11 +1,3 @@ -/** - * @name GitHub-User-Utilities - * @module - * @typicalname githubUserUtils - * @description Utility functions for fetching and parsing GitHub User data - * @author @scottnath - */ - import { generateRepoContent } from '../repository/content.js'; /** @ignore */ @@ -13,6 +5,7 @@ const githubApi = 'https://api.github.com'; /** * Blank base64-encoded png + * @ignore * @see https://png-pixel.com/ */ const blankPng = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mN8/x8AAuMB8DtXNJsAAAAASUVORK5CYII='; @@ -20,6 +13,7 @@ const blankPng = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1 /** * Content needed to render a GitHub user. This is a subset of the `users` endpoint response * @see https://docs.github.com/en/rest/users/users#get-a-user + * @memberof GitHubUtils.user * @typedef {Object} GitHubUserHTML * * @property {string} login - User's GitHub login @@ -39,6 +33,7 @@ const blankPng = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1 * @param {string} username * @returns response status 200: {Object} user; else {Object} error * @function + * @ignore */ export const fetchUser = async (username) => { const response = await fetch(`${githubApi}/users/${username}`); @@ -52,6 +47,7 @@ export const fetchUser = async (username) => { * @param {Object} user * @returns {GitHubUserHTML} component-ready user object * @function + * @ignore */ export const parseFetchedUser = (user = {}) => { return { @@ -73,8 +69,10 @@ export const parseFetchedUser = (user = {}) => { * @param {string} [owner] - GitHub user login, repository strings are not `full_name`s * @returns array of strings of attributes for each repository * @function + * @ignore */ export const parseReposString = (reposStr, owner) => { + if (typeof reposStr !== 'string') return reposStr; let repos = []; try { repos = JSON.parse(reposStr); @@ -107,6 +105,7 @@ export const parseReposString = (reposStr, owner) => { * @param {GitHubUserHTML} content - a content object representing a GitHub user * @returns {GitHubUserHTML} ready for HTML content * @function + * @ignore */ export const cleanUserContent = (content = {}) => { if (content.username && !content.login) { @@ -139,6 +138,8 @@ export const cleanUserContent = (content = {}) => { * @param {boolean} [fetch] * @returns {GitHubUserHTML} content ready for HTML, possibly includes fetched content * @function + * @memberof GitHubUtils.user + * @name generateContent */ export const generateUserContent = async (content, fetch = false) => { const userFromContent = cleanUserContent(content); diff --git a/src/github/user/html.js b/src/github/user/html.js index c9d798e..c365264 100644 --- a/src/github/user/html.js +++ b/src/github/user/html.js @@ -2,7 +2,14 @@ import repositoryHTML from '../repository/html.js'; import { intToString } from '../../utils/index.js'; -function user(content) { +/** + * Generates an HTML string for a GitHub user profile. + * @param {GitHubUserHTML} content - a content object representing a GitHub user + * @returns {string} HTML string + * @function + * @memberof GitHubUtils.user + */ +function html(content) { { if (content.error) { return ` @@ -63,4 +70,4 @@ function user(content) { } } -export default user; +export default html; diff --git a/src/github/user/index.js b/src/github/user/index.js index 81b79da..3297d25 100644 --- a/src/github/user/index.js +++ b/src/github/user/index.js @@ -1,32 +1,19 @@ import { generateUserContent } from './content.js'; -import userHTML from './html.js'; -import stylesPrimer from '../styles/vars-primer.css?inline'; -import stylesGlobal from '../styles/vars-global.css?inline'; -import stylesRepo from '../styles/repository.css?inline'; -import styles from '../styles/user.css?inline'; - -/** - * Styles for the component, imported during development, inlined during build - */ -const componentStyles = ` -${stylesPrimer} -${stylesGlobal} -${stylesRepo} -${styles} -`; +import html from './html.js'; +import { styles } from '../styles/index.js'; /** * GitHub user profile web component * @summary Native web component which shows a GitHub user's profile content. Can use local data, * fetch data from the GitHub rest API, or use a combination of both. - * @see https://docs.github.com/en/rest/repos/repos#get-a-repository * @element github-user * @name GitHubUser + * @module * * @property {string} login - User's GitHub login * @property {string} avatar_url - URL to user's avatar * @property {string} name - User's name - * @property {boolean} [fetch] - when true, fetches user from GitHub api + * @property {boolean} [fetch] - when true, fetches user from [GitHub api](https://docs.github.com/en/rest/users/users#get-a-user) * @property {string} [username] - alias for `login` * @property {string} [bio] - User's biography content * @property {string} [following] - number of people user is following @@ -34,6 +21,10 @@ ${styles} * @property {string} [repos] - JSON stringified array of repositories * * @example + * + * + * + * * */ export class GitHubUser extends HTMLElement { @@ -61,11 +52,11 @@ export class GitHubUser extends HTMLElement { } async connectedCallback() { - let view = ``; + let view = ``; this.content = await generateUserContent(this.attrs, this.attrs.fetch); - view += userHTML(this.content); + view += html(this.content); this.shadowRoot.innerHTML = view; } } -customElements.define('github-user', GitHubUser); \ No newline at end of file +customElements.define('github-user', GitHubUser); diff --git a/src/github/user/user.stories.js b/src/github/user/user.stories.js index 919c62d..55ee7eb 100644 --- a/src/github/user/user.stories.js +++ b/src/github/user/user.stories.js @@ -6,6 +6,8 @@ import { getElements, ensureElements } from './user.shared-spec'; import '.'; +const stringify = (obj) => JSON.stringify(obj).replace(/"/g, """); + export default { title: 'GitHub/github-user', component: 'github-user', @@ -33,7 +35,7 @@ export const User = { export const UserRepos = { args: { ...User.args, - repos: JSON.stringify([parseFetchedRepo(repoStorydocker), { ...parseFetchedRepo(repoScottnathdotcom), user_login: userScottnath.login }]).replace(/"/g, """), + repos: stringify([parseFetchedRepo(repoStorydocker), { ...parseFetchedRepo(repoScottnathdotcom), user_login: userScottnath.login }]), }, play: User.play, } @@ -50,11 +52,15 @@ export const OnlyRequired = { }, play: User.play, } - -export const ReposFetch = { +export const Fetch = { args: { - ...User.args, - repos: JSON.stringify([repoScottnathdotcom.name, repoStorydocker.full_name]).replace(/"/g, """), + login: userScottnath.login, + fetch: true, + }, + parameters: { + mockData: [ + generateMockResponse(userScottnath, 'users'), + ] }, play: async ({ args, canvasElement, step }) => { const elements = await getElements(canvasElement); @@ -62,19 +68,28 @@ export const ReposFetch = { ...parseFetchedUser(userScottnath), ...args, }; - await ensureElements(elements, args); + await ensureElements(elements, argsAfterFetch); } -} +}; -export const Fetch = { +export const FetchOverides = { args: { login: userScottnath.login, fetch: true, + name: "Meowy McMeowerstein", + bio: "Spending time purring and sleepin", + avatar_url: 'multi-face-image.jpeg', + followers: "500000", + following: "2980", + repos: stringify([{"full_name":"scottnath/profile-components","description":"Cool thing, does stuff","language":"HTML"}]) }, - parameters: { - mockData: [ - generateMockResponse(userScottnath, 'users'), - ] +} + +export const ReposFetch = { + args: { + login: userScottnath.login, + fetch: true, + repos: stringify([repoScottnathdotcom.name, repoStorydocker.full_name]), }, play: async ({ args, canvasElement, step }) => { const elements = await getElements(canvasElement); @@ -82,9 +97,10 @@ export const Fetch = { ...parseFetchedUser(userScottnath), ...args, }; - await ensureElements(elements, argsAfterFetch); + await ensureElements(elements, args); } -}; +} + export const FetchError = { args: { From c71b04e38e95d2f033db0c99b7a1e18aa21a58eb Mon Sep 17 00:00:00 2001 From: Scott Nath Date: Mon, 9 Oct 2023 18:02:58 -0400 Subject: [PATCH 2/8] :memo: update devto for docs --- lib/jsdoc.js | 15 ++- src/devto/UTILITIES.md | 193 +++++++++++++++++++++++++++++++++ src/devto/index.js | 69 ++++++++++++ src/devto/post/content.js | 18 +-- src/devto/post/html.js | 8 +- src/devto/post/index.js | 7 +- src/devto/styles/index.js | 5 + src/devto/styles/styles.css | 3 +- src/devto/user/content.js | 49 +++++++-- src/devto/user/html.js | 6 +- src/devto/user/index.js | 5 + src/devto/user/user.stories.js | 29 ++++- src/github/UTILITIES.md | 22 +++- src/github/index.js | 22 +++- 14 files changed, 413 insertions(+), 38 deletions(-) create mode 100644 src/devto/UTILITIES.md create mode 100644 src/devto/index.js diff --git a/lib/jsdoc.js b/lib/jsdoc.js index 7b03cc7..7b9b362 100644 --- a/lib/jsdoc.js +++ b/lib/jsdoc.js @@ -82,7 +82,7 @@ export const generateDocs = async () => { ], }, { - title: 'Dev post component', + title: 'DEV post component', dir: pathFromRoot('src/devto/post'), files: [ pathFromRoot('src/devto/post/index.js'), @@ -90,6 +90,19 @@ export const generateDocs = async () => { pathFromRoot('src/devto/post/html.js'), ], }, + { + title: 'DEV utilities', + dir: pathFromRoot('src/devto'), + files: [ + pathFromRoot('src/devto/styles/index.js'), + pathFromRoot('src/devto/user/html.js'), + pathFromRoot('src/devto/user/content.js'), + pathFromRoot('src/devto/post/html.js'), + pathFromRoot('src/devto/post/content.js'), + pathFromRoot('src/devto/index.js'), + ], + filename: 'UTILITIES.md', + }, ] for (const toDoc of toDocs) { const githubComp = await generateJsdoc2Config(toDoc.title, toDoc.dir, toDoc.files, toDoc.filename); diff --git a/src/devto/UTILITIES.md b/src/devto/UTILITIES.md new file mode 100644 index 0000000..022b356 --- /dev/null +++ b/src/devto/UTILITIES.md @@ -0,0 +1,193 @@ +# DEV utilities + + + +## DEVUtils : object +Utility functions for fetching and parsing dev.to api data, getting + styles and generating HTML for dev.to profile UIs + +**Kind**: global namespace +**Author**: @scottnath + +* [DEVUtils](#DEVUtils) : object + * [.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 + * [.user](#DEVUtils.user) : object + * [.styles](#DEVUtils.user.styles) + * [.html(content)](#DEVUtils.user.html) ⇒ string + * [.generateContent(content, [fetch])](#DEVUtils.user.generateContent) ⇒ ForemUserHTML + * [.ForemUser](#DEVUtils.user.ForemUser) : Object + * [.ForemUserHTML](#DEVUtils.user.ForemUserHTML) : ForemUser + + + +### devUtils.post : object +Utility functions for a post + +**Kind**: static namespace of [DEVUtils](#DEVUtils) +**Example** *(Server side rendering trick)* +```js + + + + + +``` + +* [.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 +dev.to (or forem.dev) post HTML generation + +**Kind**: static method of [post](#DEVUtils.post) +**Returns**: string - HTML string with added content + +| Param | Type | +| --- | --- | +| content | ForemPostHTML | + + + +#### post.generateContent(content, [fetch]) ⇒ ForemPost \| ForemError +Generates an object of content for the post HTML + +**Kind**: static method of [post](#DEVUtils.post) +**Returns**: ForemPost \| ForemError - content ready for HTML, possibly includes fetched content + +| Param | Type | Description | +| --- | --- | --- | +| content | ForemPost | | +| [fetch] | boolean | whether to fetch post content from the API | + + + +#### post.ForemPost : Object +Content about one post by dev.to (or Forem) user, sourced from a Forem API. + +**Kind**: static typedef of [post](#DEVUtils.post) +**See**: https://developers.forem.com/api/v1#tag/articles/operation/getLatestArticles +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| title | string | The title of the post | +| url | string | The URL of the post | +| cover_image | string | The URL of the post's full-size cover image | +| social_image | string | The URL of the post's social image | +| id | number | The ID of the post | + + + +#### post.ForemPostHTML : ForemPost +Forem post content, ready for HTML + +**Kind**: static typedef of [post](#DEVUtils.post) +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| [error] | string | An error message | + + + +### devUtils.user : object +Utility functions for a user + +**Kind**: static namespace of [DEVUtils](#DEVUtils) +**Example** *(Server side rendering trick)* +```js + + + + + +``` + +* [.user](#DEVUtils.user) : object + * [.styles](#DEVUtils.user.styles) + * [.html(content)](#DEVUtils.user.html) ⇒ string + * [.generateContent(content, [fetch])](#DEVUtils.user.generateContent) ⇒ ForemUserHTML + * [.ForemUser](#DEVUtils.user.ForemUser) : Object + * [.ForemUserHTML](#DEVUtils.user.ForemUserHTML) : ForemUser + + + +#### user.styles +DEV UI styles + +**Kind**: static property of [user](#DEVUtils.user) + + +#### user.html(content) ⇒ string +dev.to (or forem.dev) user HTML generation + +**Kind**: static method of [user](#DEVUtils.user) +**Returns**: string - HTML string with added content + +| Param | Type | +| --- | --- | +| content | ForemUser | + + + +#### user.generateContent(content, [fetch]) ⇒ ForemUserHTML +Generates an object of content for the user HTML + +**Kind**: static method of [user](#DEVUtils.user) +**Returns**: ForemUserHTML - content ready for HTML, possibly includes fetched content + +| Param | Type | +| --- | --- | +| content | ForemUserHTML | +| [fetch] | boolean | + + + +#### user.ForemUser : Object +Content about a dev.to (or Forem) user, sourced from the Forem API and combined with post data. +Only required properties from the api are defined. + +**Kind**: static typedef of [user](#DEVUtils.user) +**See**: https://developers.forem.com/api/v0#tag/users/operation/getUser +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| username | string | The username of the user | +| name | string | The name of the user | +| summary | string | The user's bio | +| joined_at | string | The date the user joined | +| profile_image | string | The URL of the user's profile image | + + + +#### user.ForemUserHTML : ForemUser +**Kind**: static typedef of [user](#DEVUtils.user) +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| [error] | string | An error message | +| [post_count] | number | The number of posts the user has published | +| [latest_post] | ForemPostHTML | User's latest post | +| [popular_post] | ForemPostHTML | User's most popular post | + diff --git a/src/devto/index.js b/src/devto/index.js new file mode 100644 index 0000000..529d5b3 --- /dev/null +++ b/src/devto/index.js @@ -0,0 +1,69 @@ +/** + * @name DEV-Utilities + * @kind module + * @typicalname devUtils + * @namespace DEVUtils + * @description Utility functions for fetching and parsing dev.to api data, getting + * styles and generating HTML for dev.to profile UIs + * @author @scottnath + */ +import {styles} from './styles/index.js'; +import {generateUserContent} from './user/content.js'; +import userHTML from './user/html.js'; +import {generatePostContent} from './post/content.js'; +import postHTML from './post/html.js'; + +/** + * @name DEV-Post-Utilities + * @module + * @namespace post + * @memberof DEVUtils + * @description Utility functions for a post + * + * @example Server side rendering trick + * + * + * + * + * + */ +const post = { + generateContent: generatePostContent, + html: postHTML, + styles, +}; + +/** + * @name DEV-User-Utilities + * @module + * @namespace user + * @memberof DEVUtils + * @description Utility functions for a user + * + * @example Server side rendering trick + * + * + * + * + * + */ +const user = { + generateContent: generateUserContent, + html: userHTML, + styles, +}; + +export default { + post, + user, +} \ No newline at end of file diff --git a/src/devto/post/content.js b/src/devto/post/content.js index 36af762..7cecd15 100644 --- a/src/devto/post/content.js +++ b/src/devto/post/content.js @@ -1,11 +1,3 @@ -/** - * @name DevTo-Post-Utilities - * @module - * @typicalname devtoPostUtils - * @description Utility functions for fetching and parsing post data - * @author @scottnath - */ - import { getApiUrl } from '../utils/index.js'; /** @@ -16,12 +8,15 @@ import { getApiUrl } from '../utils/index.js'; * @property {string} url - The URL of the post * @property {string} cover_image - The URL of the post's full-size cover image * @property {string} social_image - The URL of the post's social image + * @property {number} id - The ID of the post + * @memberof DEVUtils.post */ /** * Forem post content, ready for HTML * @typedef {ForemPost} ForemPostHTML * @property {string} [error] - An error message + * @memberof DEVUtils.post */ /** @@ -30,6 +25,7 @@ import { getApiUrl } from '../utils/index.js'; * @param {string} id - unique post identifier * @returns {Object} response status 200: article; else status 404: error * @function + * @ignore */ export const fetchPost = async (id) => { const response = await fetch(`${getApiUrl()}/articles/${id}`); @@ -41,6 +37,7 @@ export const fetchPost = async (id) => { * @function Fetch a user's posts from the Forem API * @param {string} username * @returns {ForemPost[]} - An array of posts + * @ignore */ export const fetchUserPosts = async (username) => { const articles = await fetch(`${getApiUrl()}/articles/latest?per_page=1000&username=${username?.toLowerCase()}`); @@ -53,6 +50,7 @@ export const fetchUserPosts = async (username) => { * @param {ForemPost[]} posts - array of posts * @param {string} [type='popular'] - type of post to find * @returns {ForemPost} - post + * @ignore */ export const findPost = (posts, type='popular') => { if (!posts.length) return {}; @@ -76,6 +74,7 @@ export const findPost = (posts, type='popular') => { * @param {Object} post - post object * @returns {ForemPost} * @function + * @ignore */ export const parseFetchedPost = (post = {}) => { return { @@ -90,6 +89,7 @@ export const parseFetchedPost = (post = {}) => { * Parses and confirms post content to match what is expected by the post HTML * @param {ForemPost} content * @returns {(ForemPost | ForemError)} + * @ignore */ export const cleanPostContent = (content = {}) => { const post = parseFetchedPost(content); @@ -105,6 +105,8 @@ export const cleanPostContent = (content = {}) => { * @param {boolean} [fetch] - whether to fetch post content from the API * @returns {(ForemPost | ForemError)} content ready for HTML, possibly includes fetched content * @function + * @memberof DEVUtils.post + * @name generateContent */ export const generatePostContent = async (content, fetch = false) => { if (fetch) { diff --git a/src/devto/post/html.js b/src/devto/post/html.js index 33cee62..f3632b6 100644 --- a/src/devto/post/html.js +++ b/src/devto/post/html.js @@ -3,9 +3,11 @@ * dev.to (or forem.dev) post HTML generation * @param {ForemPostHTML} content * @returns {string} HTML string with added content + * @function + * @memberof DEVUtils.post */ -function post(content) { - if (content.error) { +function html(content) { + if (content.error || !content.url || !content.title) { return ''; } @@ -19,4 +21,4 @@ function post(content) { `; } -export default post; \ No newline at end of file +export default html; \ No newline at end of file diff --git a/src/devto/post/index.js b/src/devto/post/index.js index c97bfc6..a543d11 100644 --- a/src/devto/post/index.js +++ b/src/devto/post/index.js @@ -3,11 +3,12 @@ import postHTML from './html.js'; import { styles } from '../styles/index.js'; /** - * dev.to (or forem.dev) post web component + * dev.to post web component * @summary Native web component which shows a dev.to (or forem.dev) post. Can use local data, * fetch data from the dev.to API, or use a combination of both. * @element devto-post * @name DevtoPost + * @module * * @property {number} id - Post ID * @property {string} title - Post title @@ -17,6 +18,10 @@ import { styles } from '../styles/index.js'; * @property {boolean} [fetch] - when true, fetches post from api * * @example + * + * + * + * * */ export class DevtoPost extends HTMLElement { diff --git a/src/devto/styles/index.js b/src/devto/styles/index.js index e4c2d1a..b434206 100644 --- a/src/devto/styles/index.js +++ b/src/devto/styles/index.js @@ -3,6 +3,11 @@ import devStyles from './vars-devto.css?inline'; import globalStyles from './global.css?inline'; import style from './styles.css?inline'; +/** + * DEV UI styles + * @memberof DEVUtils.user + * @name styles + */ export const styles = ` ${devStyles} ${globalStyles} diff --git a/src/devto/styles/styles.css b/src/devto/styles/styles.css index 2343970..02cad4c 100644 --- a/src/devto/styles/styles.css +++ b/src/devto/styles/styles.css @@ -62,7 +62,8 @@ section[itemscope] { & span { position: absolute; - top: calc(var(--logo-size) * -2); + top: calc(var(--logo-size) * -3); + font-size: .1em; } } diff --git a/src/devto/user/content.js b/src/devto/user/content.js index 8af40f4..30f3ef7 100644 --- a/src/devto/user/content.js +++ b/src/devto/user/content.js @@ -1,17 +1,10 @@ -/** - * @name DEV-User-Utilities - * @module - * @typicalname devUserUtils - * @description Utility functions for fetching and parsing user data - * @author @scottnath - */ - import { fetchUserPosts, findPost } from '../post/content.js'; import { getApiUrl } from '../utils/index.js'; /** * Blank base64-encoded png * @see https://png-pixel.com/ + * @ignore */ const blankPng = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mN8/x8AAuMB8DtXNJsAAAAASUVORK5CYII='; @@ -26,6 +19,7 @@ const blankPng = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1 * @property {string} summary - The user's bio * @property {string} joined_at - The date the user joined * @property {string} profile_image - The URL of the user's profile image + * @memberof DEVUtils.user */ /** @@ -35,6 +29,7 @@ const blankPng = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1 * @property {number} [post_count] - The number of posts the user has published * @property {ForemPostHTML} [latest_post] - User's latest post * @property {ForemPostHTML} [popular_post] - User's most popular post + * @memberof DEVUtils.user */ /** @@ -43,6 +38,7 @@ const blankPng = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1 * @param {string} id - the id of the user * @returns {(ForemUser | ForemError)} response status 200: article; else status 404: error * @function + * @ignore */ export const fetchUser = async (username, id) => { let response; @@ -61,6 +57,7 @@ export const fetchUser = async (username, id) => { * @param {ForemUser} user - user object * @returns {ForemUserHTML} * @function + * @ignore */ export const parseFetchedUser = (user = {}) => { if (!user.username) { @@ -74,7 +71,8 @@ export const parseFetchedUser = (user = {}) => { joined_at: user.joined_at, profile_image: user.profile_image, post_count: user.post_count, - posts: user.posts, + latest_post: user.latest_post, + popular_post: user.popular_post, } const usr = {}; // remove `undefined` values @@ -84,17 +82,40 @@ export const parseFetchedUser = (user = {}) => { return usr; } +/** + * Parses a string, which should be a JSON stringified array of DEV post + * objects + * @param {string} postStr - String of ForemPost data + * @returns {ForemPost} content for a post + * @function + * @ignore + */ +export const parsePostString = (postStr) => { + if (typeof postStr !== 'string') return postStr; + let post = {}; + try { + post = JSON.parse(postStr); + } catch (error) { + console.error(error); + return {}; + } + return post; +} + /** * Parses and cleans user content to match what is expected by the user HTML * @param {ForemUserHTML} content - a content object representing a dev.to user * @returns {ForemUserHTML} ready for HTML content * @function + * @ignore */ export const cleanUserContent = (content = {}) => { content.profile_image = content.profile_image || blankPng; content.name = content.name || `@${content.username}`; if (content.latest_post) { + content.latest_post = parsePostString(content.latest_post); if (content.popular_post) { + content.popular_post = parsePostString(content.popular_post); if (content.popular_post === content.latest_post) { delete content.popular_post; } else { @@ -112,11 +133,13 @@ export const cleanUserContent = (content = {}) => { * @param {boolean} [fetch] * @returns {ForemUserHTML} content ready for HTML, possibly includes fetched content * @function + * @memberof DEVUtils.user + * @name generateContent */ export const generateUserContent = async (content, fetch = false) => { const user = parseFetchedUser(content); let fetched = {}; - if (fetch) { + if (fetch && fetch !== 'false') { fetched = await fetchUser(user.username); if (fetched.error) { if (fetched.error === 'Not Found') { @@ -127,8 +150,10 @@ export const generateUserContent = async (content, fetch = false) => { const posts = await fetchUserPosts(user.username); if (posts.length) { fetched.post_count = posts.length; - fetched.latest_post = findPost(posts, 'latest'); - fetched.popular_post = findPost(posts, 'popular'); + if (fetch !== 'no-posts') { + fetched.latest_post = findPost(posts, 'latest'); + fetched.popular_post = findPost(posts, 'popular'); + } } } return cleanUserContent(Object.assign({}, fetched, user)); diff --git a/src/devto/user/html.js b/src/devto/user/html.js index 704c92c..d5fc7ef 100644 --- a/src/devto/user/html.js +++ b/src/devto/user/html.js @@ -5,8 +5,10 @@ import postHTML from "../post/html.js"; * dev.to (or forem.dev) user HTML generation * @param {ForemUser} content * @returns {string} HTML string with added content + * @function + * @memberof DEVUtils.user */ -function post(content) { +function html(content) { if (content.error) { return `
@@ -54,4 +56,4 @@ function post(content) { `; } -export default post; \ No newline at end of file +export default html; \ No newline at end of file diff --git a/src/devto/user/index.js b/src/devto/user/index.js index e269d08..56e3439 100644 --- a/src/devto/user/index.js +++ b/src/devto/user/index.js @@ -9,6 +9,7 @@ import { styles } from '../styles/index.js'; * Can use local data, or fetch data from the dev.to API, or use a combination of both. * @element devto-user * @name DevtoUser + * @module * * @property {string} username - User's dev.to username * @property {string} [name] - The name of the user @@ -21,6 +22,10 @@ import { styles } from '../styles/index.js'; * @property {string} [popular_post] - User's most popular post content, JSON stringified * * @example + * + * + * + * * */ export class DevtoUser extends HTMLElement { diff --git a/src/devto/user/user.stories.js b/src/devto/user/user.stories.js index fe5987f..8131bc8 100644 --- a/src/devto/user/user.stories.js +++ b/src/devto/user/user.stories.js @@ -5,6 +5,8 @@ import { default as userScottnath } from '../fixtures/generated/user--scottnath. import './index.js'; +const stringify = (obj) => JSON.stringify(obj).replace(/"/g, """); + export default { title: 'DevTo/devto-user', component: 'devto-user', @@ -32,9 +34,32 @@ export const User = { // } } -export const UserFetch = { +export const Fetch = { args: { username: userScottnath.username, fetch: true, }, -} \ No newline at end of file +} + +export const FetchWithoutPosts = { + args: { + username: userScottnath.username, + fetch: 'no-posts', + }, +} + +export const FetchOverides = { + args: { + username: userScottnath.username, + fetch: 'true', + name: "Meowy McMeowerstein", + summary: "Spending time purring and sleepin", + profile_image: 'multi-face-image.jpeg', + joined_at: 'Jan 1, 1979', + post_count: 1000000, + latest_post: stringify({ + title: 'Meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow', + url: 'http://example.com' + }), + }, +} diff --git a/src/github/UTILITIES.md b/src/github/UTILITIES.md index 1286b6a..634f74b 100644 --- a/src/github/UTILITIES.md +++ b/src/github/UTILITIES.md @@ -27,11 +27,18 @@ Utility functions for fetching and parsing GitHub api data, getting Utility functions for a repository **Kind**: static namespace of [GitHubUtils](#GitHubUtils) -**Example** +**Example** *(Server side rendering trick)* ```js + + + + + ``` * [.repo](#GitHubUtils.repo) : object @@ -98,11 +105,18 @@ Content needed to render a GitHub repository. This is a subset of the `repos` en Utility functions for a user **Kind**: static namespace of [GitHubUtils](#GitHubUtils) -**Example** +**Example** *(Server side rendering trick)* ```js + + + + + ``` * [.user](#GitHubUtils.user) : object diff --git a/src/github/index.js b/src/github/index.js index b6ea353..03c4e96 100644 --- a/src/github/index.js +++ b/src/github/index.js @@ -21,10 +21,17 @@ import repoHTML from './repository/html.js'; * @memberof GitHubUtils * @description Utility functions for a repository * - * @example + * @example Server side rendering trick + * + * + * + * + * */ const repo = { generateContent: generateRepoContent, @@ -39,10 +46,17 @@ const repo = { * @memberof GitHubUtils * @description Utility functions for a user * - * @example + * @example Server side rendering trick + * + * + * + * + * */ const user = { generateContent: generateUserContent, From 1e43faddedcab54e0a9e29af9dd56ba7adcc8bc0 Mon Sep 17 00:00:00 2001 From: Scott Nath Date: Mon, 9 Oct 2023 19:34:26 -0400 Subject: [PATCH 3/8] :art: add dark styles to dev css --- custom-elements.json | 6 +-- src/devto/styles/vars-devto.css | 57 +++++++++++++++++++- src/devto/user/user.stories.js | 10 ++++ src/devto/utils/forem.js | 83 +++++++++++++++++++++++++---- src/github/user/user.shared-spec.js | 1 + src/github/user/user.stories.js | 8 ++- 6 files changed, 151 insertions(+), 14 deletions(-) diff --git a/custom-elements.json b/custom-elements.json index 4b51b04..5633b74 100644 --- a/custom-elements.json +++ b/custom-elements.json @@ -95,7 +95,7 @@ "type": { "text": "boolean" }, - "description": "when true, fetches repo from GitHub api", + "description": "when true, fetches repo from [GitHub api](https://docs.github.com/en/rest/repos/repos#get-a-repository)", "name": "fetch", "kind": "field" }, @@ -196,7 +196,7 @@ "type": { "text": "boolean" }, - "description": "when true, fetches user from GitHub api", + "description": "when true, fetches user from [GitHub api](https://docs.github.com/en/rest/users/users#get-a-user)", "name": "fetch", "kind": "field" }, @@ -391,7 +391,7 @@ "declarations": [ { "kind": "class", - "description": "dev.to (or forem.dev) post web component", + "description": "dev.to post web component", "name": "DevtoPost", "members": [ { diff --git a/src/devto/styles/vars-devto.css b/src/devto/styles/vars-devto.css index 0c56c26..dff9f76 100644 --- a/src/devto/styles/vars-devto.css +++ b/src/devto/styles/vars-devto.css @@ -6,13 +6,28 @@ --svg-dev-logo: url('data:image/svg+xml, '); --svg-cake-icon: url('data:image/svg+xml, '); --svg-post-icon: url('data:image/svg+xml, '); + --base: #090909; --white: 255, 255, 255; --black: 0, 0, 0; --radius: 0.375rem; + --base-inverted: rgb(var(--white)); + --base-100: var(--base); + --base-90: #242424; + --base-80: #3d3d3d; + --base-70: #575757; + --base-60: #717171; + --base-50: #8a8a8a; + --base-40: #a3a3a3; + --base-30: #bdbdbd; + --base-20: #d6d6d7; + --base-10: #efefef; + --base-0: #f9f9f9; --indigo-500: 99, 102, 241; --indigo-600: 79, 70, 229; --indigo-700: 67, 56, 202; + --indigo-400: 129, 140, 248; + --grey-700: 64, 64, 64; --grey-900: 23, 23, 23; --grey-100: 245, 245, 245; @@ -42,8 +57,48 @@ --cta-branded-border-hover: var(--accent-brand-darker); --profile-brand-color: rgb(var(--black)); - --base-60: #717171; --card-bg: rgb(var(--white)); --card-border: rgba(var(--grey-900), 0.1); } + +/* dark variables */ +:host([data-theme="dark"]) { + --base-inverted: #000; + --base-100: var(--base); + --base-90: #efefef; + --base-80: #d6d6d7; + --base-70: #bdbdbd; + --base-60: #a3a3a3; + --base-50: #8a8a8a; + --base-40: #717171; + --base-30: #575757; + --base-20: #3d3d3d; + --base-10: #242424; + --base-0: #090909; + --grey-300: 212, 212, 212; + --indigo-300: 165, 180, 252; + --black: 0, 0, 0; + --grey-50: 250, 250, 250; + + --accent-brand-lighter: rgb(var(--indigo-400)); + --accent-brand: rgb(var(--indigo-500)); + --accent-brand-darker: rgb(var(--indigo-600)); + --link-color-current: var(--base-100); + --link-color-secondary: var(--base-70); + --link-color-secondary-hover: var(--base-80); + --link-color: rgb(var(--grey-300)); + --link-color-hover: rgb(var(--indigo-300)); + --card-bg: rgb(var(--grey-900)); + --card-border: rgba(var(--white), 0.15); + --body-bg: rgb(var(--black)); + --body-color: rgb(var(--grey-50)); + --body-color-inverted: rgb(var(--black)); + --cta-branded-bg: transparent; + --cta-branded-bg-hover: rgb(var(--indigo-600)); + --cta-branded-color: rgb(var(--indigo-400)); + --cta-branded-color-hover: rgb(var(--white)); + --cta-branded-border: rgb(var(--indigo-400)); + --cta-branded-border-hover: rgb(var(--indigo-500)); + +} diff --git a/src/devto/user/user.stories.js b/src/devto/user/user.stories.js index 8131bc8..c8ac450 100644 --- a/src/devto/user/user.stories.js +++ b/src/devto/user/user.stories.js @@ -1,7 +1,9 @@ import { generateMockResponse } from '../utils/testing'; import { parseFetchedUser } from './content'; +import { parseFetchedPost } from '../post/content'; import { default as userScottnath } from '../fixtures/generated/user--scottnath.json'; +import { default as postDependabot } from '../fixtures/generated/post--dependabot.json'; import './index.js'; @@ -27,6 +29,7 @@ export default { export const User = { args: { ...parseFetchedUser(userScottnath), + post_count: 123456, }, // play: async ({ args, canvasElement, step }) => { // const elements = await getElements(canvasElement); @@ -34,6 +37,13 @@ export const User = { // } } +export const UserPosts = { + args: { + ...User.args, + latest_post: stringify(parseFetchedPost(postDependabot)), + } +} + export const Fetch = { args: { username: userScottnath.username, diff --git a/src/devto/utils/forem.js b/src/devto/utils/forem.js index 2688f2b..8f44c81 100644 --- a/src/devto/utils/forem.js +++ b/src/devto/utils/forem.js @@ -19,15 +19,15 @@ const generatedFile = path.join(__dirname, '../styles', 'vars-devto.css'); /** * Forem CSS file source directory */ -const foremRawSource = 'https://raw.githubusercontent.com/forem/forem/main/app/assets/stylesheets/config/'; +const foremRawSource = 'https://raw.githubusercontent.com/forem/forem/main/app/assets/stylesheets/'; /** * Get Forem CSS file's contents * @param {string} filename - css file name * @returns {string} contents of css file from Forem repo */ -export const getForemCSS = async (filename = '_variables.scss') => { - const foremCSSRawSource = `${foremRawSource}${filename}`; +export const getForemCSS = async (filename = '_variables.scss', dir='config') => { + const foremCSSRawSource = `${foremRawSource}${dir}/${filename}`; try { const foremCSS = await fetch(foremCSSRawSource); const css = await foremCSS.text(); @@ -58,6 +58,8 @@ const copiedStyles = [ * @see https://github.com/forem/forem/blob/main/app/assets/images/post.svg?short_path=b79fa43 */ `--svg-post-icon: url('data:image/svg+xml, ');`, + + `--base: #090909;` ] /** @@ -71,7 +73,7 @@ export const getOneVarStyle = (style, css) => { if (!v) return null; const reg = new RegExp(`(${v[1]}.+);`, 'g'); const styl = css.match(reg); - return style.length ? styl[0] : null; + return styl && styl.length ? styl[0] : null; } /** @@ -92,7 +94,9 @@ export const getOneStyle = (name, css) => { */ export const getDevStyleVariables = async () => { const topVars = []; + const topVarsDark = []; const vars = []; + const dark = []; const styles = [ '--profile-brand-color: rgb(var(--black));' ]; @@ -104,11 +108,12 @@ export const getDevStyleVariables = async () => { const css = await getForemCSS('_colors.css'); const cssVars = await getForemCSS('_variables.scss'); + const cssDark = await getForemCSS('dark.css', 'themes'); const topStyleVars = [ '--white', '--black', - '--radius' + '--radius', ] topStyleVars.forEach((s) => { const style = getOneStyle(s, cssVars); @@ -116,6 +121,11 @@ export const getDevStyleVariables = async () => { topVars.push(style); }); + const baseVars = css.match(/(--base.+:.+);/g); + baseVars.forEach((b) => topVars.push(b)); + const baseVarsDark = cssDark.match(/(--base.+:.+);/g); + baseVarsDark.forEach((b) => topVarsDark.push(b)); + const accentVars = cssVars.match(/(--accent-.+);/g); accentVars.forEach((b) => { vars.push(b); @@ -125,15 +135,31 @@ export const getDevStyleVariables = async () => { const accentColors = css.match(/( --accent-brand.+);/g); accentColors.forEach(s => vars.push(s.trim())); + const accentColorsDark = cssDark.match(/( --accent-brand.+);/g); + accentColorsDark.forEach(s => { + dark.push(s.trim()); + const style = getOneVarStyle(s, cssVars); + (style && !duplicateCheck(style)) ? topVars.push(style) : null; + }); const linkColors = css.match(/( --link-color.+);/g); - linkColors.forEach(s => vars.push(s.trim())); + linkColors.forEach((b) => { + vars.push(b.trim()); + let style = getOneVarStyle(b, cssVars); + (style && !duplicateCheck(style)) ? topVars.push(style) : null; + }); + + const linkColorsDark = cssDark.match(/( --link-color.+);/g); + linkColorsDark.forEach((b) => { + dark.push(b.trim()); + let style = getOneVarStyle(b, cssVars); + (style && !duplicateCheck(style)) ? topVarsDark.push(style) : null; + }); const weights = cssVars.match(/(--fw-.+);/g); weights.forEach((w) => vars.push(w)); const specificStylesCss = [ - '--base-60', '--card-bg', '--card-border', ] @@ -146,6 +172,15 @@ export const getDevStyleVariables = async () => { } styles.push(style); }); + specificStylesCss.forEach((s) => { + const style = getOneStyle(s, cssDark); + if (!style) return; + if(style.includes('var(--')) { + const subStyle = getOneVarStyle(style, cssVars); + (subStyle && !duplicateCheck(subStyle)) ? topVarsDark.push(subStyle) : null; + } + dark.push(style); + }); const body = css.match(/( --body-.+);/g); body.forEach((b) => { @@ -153,6 +188,12 @@ export const getDevStyleVariables = async () => { const style = getOneVarStyle(b, cssVars); style ? topVars.push(style) : null; }) + const bodyDark = cssDark.match(/( --body-.+);/g); + bodyDark.forEach((b) => { + dark.push(b); + const style = getOneVarStyle(b, cssVars); + style ? topVarsDark.push(style) : null; + }) const ctas = css.match(/( --cta-branded.+);/g); ctas.forEach((b) => { @@ -160,12 +201,22 @@ export const getDevStyleVariables = async () => { const style = getOneVarStyle(b, cssVars); (style && !duplicateCheck(style)) ? topVars.push(style) : null; }) + const ctasDark = cssDark.match(/( --cta-branded.+);/g); + ctasDark.forEach((b) => { + dark.push(b); + const style = getOneVarStyle(b, cssVars); + (style && !duplicateCheck(style)) ? topVarsDark.push(style) : null; + }) const sections = [copiedStyles]; sections.push(topVars); sections.push(vars); sections.push(styles); - return sections; + const darkSections = [topVarsDark, dark]; + return { + all: sections, + dark: darkSections, + }; } /** @@ -179,7 +230,21 @@ export const getDevCss = async () => { :host { `; const sections = await getDevStyleVariables(); - sections.forEach(globals => { + sections.all.forEach(globals => { + globals + .filter((item, index) => globals.indexOf(item) === index) + .forEach(s => { + css += s.startsWith(' ') ? s : ` ${s}`; + css += '\n'; + }); + css += '\n'; + }) + css += `}\n`; + css += ` +/* dark variables */ +:host([data-theme="dark"]) { +`; + sections.dark.forEach(globals => { globals .filter((item, index) => globals.indexOf(item) === index) .forEach(s => { diff --git a/src/github/user/user.shared-spec.js b/src/github/user/user.shared-spec.js index 0af62e4..1da96e8 100644 --- a/src/github/user/user.shared-spec.js +++ b/src/github/user/user.shared-spec.js @@ -51,6 +51,7 @@ export const ensureElements = async (elements, args) => { await expect(elements.avatar).toBeTruthy(); await expect(elements.name).toBeTruthy(); await expect(elements.login).toBeTruthy(); + console.log(args) if (args?.bio) { await expect(elements.bio).toBeTruthy(); await expect(elements.bio).toHaveTextContent(args.bio); diff --git a/src/github/user/user.stories.js b/src/github/user/user.stories.js index 55ee7eb..012633e 100644 --- a/src/github/user/user.stories.js +++ b/src/github/user/user.stories.js @@ -52,6 +52,7 @@ export const OnlyRequired = { }, play: User.play, } + export const Fetch = { args: { login: userScottnath.login, @@ -91,13 +92,18 @@ export const ReposFetch = { fetch: true, repos: stringify([repoScottnathdotcom.name, repoStorydocker.full_name]), }, + parameters: { + mockData: [ + generateMockResponse(userScottnath, 'users'), + ] + }, play: async ({ args, canvasElement, step }) => { const elements = await getElements(canvasElement); const argsAfterFetch = { ...parseFetchedUser(userScottnath), ...args, }; - await ensureElements(elements, args); + await ensureElements(elements, argsAfterFetch); } } From cd5dd6fe24436f2aff61a23efb4525391629af44 Mon Sep 17 00:00:00 2001 From: Scott Nath Date: Mon, 9 Oct 2023 20:25:37 -0400 Subject: [PATCH 4/8] :art: fixtures and styling --- public/cat-square.jpeg | Bin 0 -> 18277 bytes src/devto/user/user.stories.js | 2 +- src/github/styles/user.css | 2 ++ src/github/user/user.stories.js | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 public/cat-square.jpeg diff --git a/public/cat-square.jpeg b/public/cat-square.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..9d9b10af4263c47beebc9c671887088b3a128be0 GIT binary patch literal 18277 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 literal 0 HcmV?d00001 diff --git a/src/devto/user/user.stories.js b/src/devto/user/user.stories.js index c8ac450..91e7a5e 100644 --- a/src/devto/user/user.stories.js +++ b/src/devto/user/user.stories.js @@ -64,7 +64,7 @@ export const FetchOverides = { fetch: 'true', name: "Meowy McMeowerstein", summary: "Spending time purring and sleepin", - profile_image: 'multi-face-image.jpeg', + profile_image: 'cat-square.jpeg', joined_at: 'Jan 1, 1979', post_count: 1000000, latest_post: stringify({ diff --git a/src/github/styles/user.css b/src/github/styles/user.css index b9a7245..a8f4ec0 100644 --- a/src/github/styles/user.css +++ b/src/github/styles/user.css @@ -26,6 +26,8 @@ section[itemscope] { mask-repeat: no-repeat; -webkit-mask-position: center; mask-position: center; + -webkit-mask-size: contain; + mask-size: contain; -webkit-mask-image: var(--svg-mark-github); mask-image: var(--svg-mark-github); } diff --git a/src/github/user/user.stories.js b/src/github/user/user.stories.js index 012633e..145ae33 100644 --- a/src/github/user/user.stories.js +++ b/src/github/user/user.stories.js @@ -79,7 +79,7 @@ export const FetchOverides = { fetch: true, name: "Meowy McMeowerstein", bio: "Spending time purring and sleepin", - avatar_url: 'multi-face-image.jpeg', + avatar_url: 'cat-square.jpeg', followers: "500000", following: "2980", repos: stringify([{"full_name":"scottnath/profile-components","description":"Cool thing, does stuff","language":"HTML"}]) From 671b691807216662b01436534c2bd847f677edb9 Mon Sep 17 00:00:00 2001 From: Scott Nath Date: Mon, 9 Oct 2023 20:36:21 -0400 Subject: [PATCH 5/8] :white_check_mark: add tests to story --- src/github/user/user.stories.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/github/user/user.stories.js b/src/github/user/user.stories.js index 145ae33..eb33169 100644 --- a/src/github/user/user.stories.js +++ b/src/github/user/user.stories.js @@ -84,6 +84,19 @@ export const FetchOverides = { following: "2980", repos: stringify([{"full_name":"scottnath/profile-components","description":"Cool thing, does stuff","language":"HTML"}]) }, + parameters: { + mockData: [ + generateMockResponse(userScottnath, 'users'), + ] + }, + play: async ({ args, canvasElement, step }) => { + const elements = await getElements(canvasElement); + const argsAfterFetch = { + ...parseFetchedUser(userScottnath), + ...args, + }; + await ensureElements(elements, argsAfterFetch); + } } export const ReposFetch = { From c505c26a850251d7b3a6fb5c9520af3238821d9a Mon Sep 17 00:00:00 2001 From: Scott Nath Date: Mon, 9 Oct 2023 20:40:58 -0400 Subject: [PATCH 6/8] Update user.css --- src/github/styles/user.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/github/styles/user.css b/src/github/styles/user.css index a8f4ec0..6de664f 100644 --- a/src/github/styles/user.css +++ b/src/github/styles/user.css @@ -110,8 +110,8 @@ section[itemscope] { [itemprop="image"] { flex: 1 1 30%; & img { - width: 27cqw; - height: 27cqw; + width: 26cqw; + height: 26cqw; } } [itemprop="creator"] { From 0d8c798e363de34a42b814793a7ca511a79ffd65 Mon Sep 17 00:00:00 2001 From: Scott Nath Date: Mon, 9 Oct 2023 20:57:26 -0400 Subject: [PATCH 7/8] :art: cat-1000 --- public/cat-1000-420.jpeg | Bin 0 -> 43632 bytes src/devto/user/user.stories.js | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 public/cat-1000-420.jpeg diff --git a/public/cat-1000-420.jpeg b/public/cat-1000-420.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..992e2151e534fc21ccfcf794ae746cf1ff54061e GIT binary patch literal 43632 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

jj~Q{SaY(&F^)FD%lnsx3}f&R264 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 eX(W}$v_ILY{uSZqUWpS+s}% literal 0 HcmV?d00001 diff --git a/src/devto/user/user.stories.js b/src/devto/user/user.stories.js index 91e7a5e..dcca59e 100644 --- a/src/devto/user/user.stories.js +++ b/src/devto/user/user.stories.js @@ -69,7 +69,8 @@ export const FetchOverides = { post_count: 1000000, latest_post: stringify({ title: 'Meow meow meow meow meow meow meow meow meow meow meow meow meow meow meow', - url: 'http://example.com' + url: 'http://example.com', + cover_image: 'cat-1000-420.jpeg' }), }, } From e42efb25ed4fdf2ddd10e138f766399d62ecff65 Mon Sep 17 00:00:00 2001 From: Scott Nath Date: Mon, 9 Oct 2023 21:19:51 -0400 Subject: [PATCH 8/8] :art: more viewed examples --- src/devto/user/user.stories.js | 26 ++++++++++++++++++++++---- src/github/user/user.stories.js | 30 ++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/devto/user/user.stories.js b/src/devto/user/user.stories.js index dcca59e..33c39a0 100644 --- a/src/devto/user/user.stories.js +++ b/src/devto/user/user.stories.js @@ -9,15 +9,17 @@ import './index.js'; const stringify = (obj) => JSON.stringify(obj).replace(/"/g, """); +const attrs = (args) => Object.entries(args) +.filter(([key, value]) => value) +.map(([key, value]) => `${key}="${value}"`) +.join(' '); + export default { title: 'DevTo/devto-user', component: 'devto-user', tags: ['autodocs'], render: (args) => { - const attributes = Object.entries(args) - .filter(([key, value]) => value) - .map(([key, value]) => `${key}="${value}"`) - .join(' '); + const attributes = attrs(args); return ` @@ -74,3 +76,19 @@ export const FetchOverides = { }), }, } + +export const ContainerCheck = { + args: FetchOverides.args, + + render: (args) => { + const attributes = attrs(args); + + return ` +

+ + + +
+ `; + } +} \ No newline at end of file diff --git a/src/github/user/user.stories.js b/src/github/user/user.stories.js index eb33169..1f1292c 100644 --- a/src/github/user/user.stories.js +++ b/src/github/user/user.stories.js @@ -8,15 +8,17 @@ import '.'; const stringify = (obj) => JSON.stringify(obj).replace(/"/g, """); +const attrs = (args) => Object.entries(args) +.filter(([key, value]) => value) +.map(([key, value]) => `${key}="${value}"`) +.join(' '); + export default { title: 'GitHub/github-user', component: 'github-user', tags: ['autodocs'], render: (args) => { - const attributes = Object.entries(args) - .filter(([key, value]) => value) - .map(([key, value]) => `${key}="${value}"`) - .join(' '); + const attributes = attrs(args); return ` @@ -120,7 +122,6 @@ export const ReposFetch = { } } - export const FetchError = { args: { login: 'not-a-real-user', @@ -139,4 +140,21 @@ export const FetchError = { }; await ensureElements(elements, argsAfterFetch); } -}; \ No newline at end of file +}; + + +export const ContainerCheck = { + args: FetchOverides.args, + + render: (args) => { + const attributes = attrs(args); + + return ` +
+ + + +
+ `; + } +} \ No newline at end of file