├── .gitignore
├── .nvmrc
├── .storybook
├── .babelrc
├── addons.js
├── config.js
├── default-storybook-webpack-config.js
├── presets.js
├── preview-head.html
└── webpack.config.js
├── README.md
├── add.html
├── es-dev-server.config.js
├── index.html
├── netlify.toml
├── package.json
├── packages
├── app
│ ├── src
│ │ ├── owc-cat-app.css.js
│ │ └── owc-cat-app.js
│ └── stories
│ │ ├── index.mdx
│ │ └── index.stories.js
├── filters
│ ├── src
│ │ └── owc-cat-filters.js
│ └── stories
│ │ ├── index.mdx
│ │ └── index.stories.js
├── header
│ ├── src
│ │ ├── open-wc-logo.svg.js
│ │ └── owc-cat-header.js
│ └── stories
│ │ ├── index.mdx
│ │ └── index.stories.js
├── intro
│ ├── src
│ │ ├── owc-button.css.js
│ │ └── owc-cat-intro.js
│ └── stories
│ │ ├── index.mdx
│ │ └── index.stories.js
└── item
│ ├── src
│ ├── github-markdown.css.js
│ ├── logos
│ │ ├── logo-github.js
│ │ ├── logo-npm.js
│ │ └── logo-unpkg.js
│ ├── owc-cat-item.css.js
│ ├── owc-cat-item.js
│ ├── owc-tabs.js
│ └── remarkable-markdown.js
│ └── stories
│ ├── index.mdx
│ └── index.stories.js
├── research
├── elasticIndex.json
├── faunadb.txt
└── normalized
│ ├── db.js
│ └── schema.gql
├── rollup.config.js
├── schema.gql
├── src
└── lambda
│ ├── check-package-with-version.js
│ ├── helpers
│ ├── db.js
│ ├── elastic.js
│ ├── helpers.js
│ └── npm.js
│ └── search.js
├── todo.md
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | ## editors
2 | /.idea
3 | /.vscode
4 |
5 | ## system files
6 | .DS_Store
7 |
8 | # code coverage folders
9 | coverage/
10 |
11 | ## npm
12 | node_modules
13 | npm-debug.log
14 |
15 | ## lerna
16 | lerna-debug.log
17 |
18 | ## temp folders
19 | /.tmp/
20 |
21 | # build hp
22 | /_site/
23 |
24 | ## build output
25 | /dist
26 | /build-stats.json
27 | /lambda
28 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 10
--------------------------------------------------------------------------------
/.storybook/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["@babel/plugin-syntax-dynamic-import", "@babel/plugin-proposal-object-rest-spread"],
3 | "presets": [
4 | [
5 | "@babel/preset-env",
6 | {
7 | "useBuiltIns": "entry",
8 | "corejs": "2"
9 | }
10 | ]
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-actions/register';
2 | import '@storybook/addon-storysource/register';
3 | import '@storybook/addon-knobs/register';
4 | import '@storybook/addon-backgrounds/register';
5 | import '@storybook/addon-a11y/register';
6 | import '@storybook/addon-links/register';
7 | import '@storybook/addon-viewport/register';
8 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure, addDecorator } from '@storybook/polymer';
2 | import { withA11y } from '@storybook/addon-a11y';
3 | import '@storybook/addon-console';
4 |
5 | // const req = require.context('../stories', true, /\.stories\.js$/);
6 | // function loadStories() {
7 | // req.keys().forEach(filename => req(filename));
8 | // }
9 |
10 | addDecorator(withA11y);
11 | // configure(loadStories, module);
12 |
13 | // import { configure } from '@storybook/polymer';
14 |
15 | const req = require.context('../packages', true, /\.stories\.(js|mdx)$/);
16 | configure(req, module);
17 |
18 | // force full reload to not reregister web components
19 | if (module.hot) {
20 | module.hot.accept(req.id, () => {
21 | const currentLocationHref = window.location.href;
22 | window.history.pushState(null, null, currentLocationHref);
23 | location.reload();
24 | });
25 | }
26 |
--------------------------------------------------------------------------------
/.storybook/default-storybook-webpack-config.js:
--------------------------------------------------------------------------------
1 | module.exports = ({ config, transpilePackages = ['lit-html', 'lit-element', '@open-wc'] }) => {
2 | config.module.rules.push({
3 | test: [/\.stories\.js$/, /index\.js$/],
4 | loaders: [require.resolve('@storybook/source-loader')],
5 | enforce: 'pre',
6 | });
7 |
8 | // this is a separate config for only those packages
9 | // the main storybook will use the .babelrc which is needed so storybook itself works in IE
10 | config.module.rules.push({
11 | test: new RegExp(`node_modules(\\/|\\\\)(${transpilePackages.join('|')})(.*)\\.js$`),
12 | use: {
13 | loader: 'babel-loader',
14 | options: {
15 | plugins: [
16 | '@babel/plugin-syntax-dynamic-import',
17 | '@babel/plugin-proposal-object-rest-spread',
18 | ],
19 | presets: [
20 | [
21 | '@babel/preset-env',
22 | {
23 | useBuiltIns: 'entry',
24 | corejs: '2',
25 | },
26 | ],
27 | ],
28 | babelrc: false,
29 | },
30 | },
31 | });
32 |
33 | return config;
34 | };
35 |
--------------------------------------------------------------------------------
/.storybook/presets.js:
--------------------------------------------------------------------------------
1 | module.exports = ['@storybook/addon-docs/html/preset'];
2 |
--------------------------------------------------------------------------------
/.storybook/preview-head.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
21 |
--------------------------------------------------------------------------------
/.storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | const defaultConfig = require('./default-storybook-webpack-config.js');
2 |
3 | module.exports = ({ config }) => {
4 | return defaultConfig({ config, transpilePackages: ['lit-html', 'lit-element', '@open-wc'] });
5 | };
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > ## 🛠 Status: In Development
2 | >
3 | > This custom elements catalog is currently in development.
4 |
5 |
6 |
7 |
8 |
9 | ## Custom Elements Catalog
10 |
11 | [](https://github.com/open-wc)
12 |
13 | This let's you search the npm registry for web components.
14 |
15 | ## Adding a web component
16 |
17 | Be sure to have a `custom-elements.json` describing all (or one) web components within your package.
18 |
19 | Example:
20 |
21 | ```json
22 | {
23 | "version": 2,
24 | "tags": [
25 | {
26 | "label": "test-wc-card"
27 | }
28 | ]
29 | }
30 | ```
31 |
32 | As this is still a pilot phase you will have to manually index your package.
33 |
34 | 1. Go to [http://catalog.open-wc.org/add.html](http://catalog.open-wc.org/add.html)
35 | 2. Enter you package name followed by `@` and version (example `test-wc-card@0.0.6` or `@foo/bar@1.0.0`)
36 |
37 | (only the latest npm versions get added to the search index - but older vesions can still be added to our database)
38 |
39 | ## Working on it
40 |
41 | ```bash
42 | yarn install
43 | yarn storybook # for individual parts with mocked data
44 | yarn start # for live page
45 | ```
46 |
47 | ## Background
48 |
49 | Uses
50 |
51 | - Funadb to store the data in a normalized way
52 | - Elasticsearch (aws) to store flattened docs to make them performant searchable
53 |
54 | ## custom-elements.json
55 |
56 | This is still a proposal so [follow the discussion!!](https://github.com/w3c/webcomponents/issues/776).
57 |
58 | Possible example:
59 |
60 | ```json
61 | {
62 | "version": 2,
63 | "tags": [
64 | {
65 | "label": "test-wc-card",
66 | "attributes": [
67 | {
68 | "label": "header"
69 | },
70 | {
71 | "label": "side",
72 | "values": [{ "label": "A" }, { "label": "B" }]
73 | }
74 | ],
75 | "properties": [
76 | {
77 | "label": "header"
78 | },
79 | {
80 | "label": "headerColor"
81 | },
82 | {
83 | "label": "side",
84 | "values": [{ "label": "A" }, { "label": "B" }]
85 | }
86 | ],
87 | "events": [],
88 | "slots": []
89 | }
90 | ]
91 | }
92 | ```
93 |
--------------------------------------------------------------------------------
/add.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
23 | Web Components Catalog
24 |
25 |
26 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/es-dev-server.config.js:
--------------------------------------------------------------------------------
1 | const proxy = require('koa-proxies');
2 |
3 | module.exports = {
4 | port: 8080,
5 | middlewares: [
6 | proxy('/.netlify/functions/', {
7 | target: 'http://localhost:9000',
8 | rewrite: path => path.replace(/^\/\\.netlify\/functions/, ''),
9 | }),
10 | ],
11 | };
12 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
23 | Web Components Catalog
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | functions = "lambda"
3 | publish = "dist"
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@open-wc/catalog",
3 | "version": "1.0.0",
4 | "description": "",
5 | "author": "open-wc",
6 | "homepage": "https://github.com/open-wc/open-wc/",
7 | "license": "MIT",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/open-wc/open-wc.git",
11 | "directory": "packages/generator-open-wc"
12 | },
13 | "private": true,
14 | "scripts": {
15 | "start": "run-p start:app start:lambda",
16 | "start:app": "es-dev-server --app-index index.html --node-resolve --watch --open",
17 | "start:lambda": "netlify-lambda serve src/lambda",
18 | "build": "npm run build:lambda && npm run build:app && cp ./add.html ./dist/add.html && npm run build:storybook",
19 | "build:lambda": "netlify-lambda build src/lambda",
20 | "build:app": "rimraf dist && rollup -c rollup.config.js",
21 | "lint": "npm run lint:eslint && npm run lint:prettier",
22 | "lint:eslint": "eslint --ext .js,.html . --ignore-path .gitignore",
23 | "lint:prettier": "prettier \"**/*.js\" --check --ignore-path .gitignore",
24 | "format": "npm run format:eslint && npm run format:prettier",
25 | "format:eslint": "eslint --ext .js,.html . --fix --ignore-path .gitignore",
26 | "format:prettier": "prettier \"**/*.js\" --write --ignore-path .gitignore",
27 | "start:build": "cd dist && es-dev-server --open",
28 | "storybook": "start-storybook -p 9001",
29 | "build:storybook": "build-storybook -o ./dist/storybook"
30 | },
31 | "devDependencies": {
32 | "@babel/core": "^7.3.3",
33 | "@babel/plugin-proposal-object-rest-spread": "^7.2.0",
34 | "@babel/plugin-syntax-dynamic-import": "^7.2.0",
35 | "@babel/preset-env": "^7.0.0",
36 | "@open-wc/building-rollup": "^0.9.0",
37 | "@open-wc/eslint-config": "^1.0.0",
38 | "@open-wc/prettier-config": "^0.1.10",
39 | "@storybook/addon-a11y": "~5.2.0",
40 | "@storybook/addon-actions": "~5.2.0",
41 | "@storybook/addon-backgrounds": "~5.2.0",
42 | "@storybook/addon-console": "^1.1.0",
43 | "@storybook/addon-docs": "^5.2.1",
44 | "@storybook/addon-knobs": "~5.2.0",
45 | "@storybook/addon-links": "~5.2.0",
46 | "@storybook/addon-notes": "~5.2.0",
47 | "@storybook/addon-storysource": "~5.2.0",
48 | "@storybook/addon-viewport": "~5.2.0",
49 | "@storybook/polymer": "~5.2.0",
50 | "@storybook/source-loader": "~5.2.0",
51 | "@types/storybook__addon-actions": "^3.4.1",
52 | "@types/storybook__addon-backgrounds": "^3.2.1",
53 | "@types/storybook__addon-links": "^3.3.3",
54 | "@types/storybook__addon-notes": "^3.3.3",
55 | "@webcomponents/webcomponentsjs": "^2.2.0",
56 | "babel-loader": "^8.0.0",
57 | "es-dev-server": "^1.18.0",
58 | "husky": "^1.0.0",
59 | "koa-proxies": "^0.8.1",
60 | "lint-staged": "^8.0.0",
61 | "moment": "^2.0.0",
62 | "netlify-lambda": "^1.6.3",
63 | "npm-run-all": "^4.1.5",
64 | "polymer-webpack-loader": "^2.0.0",
65 | "rimraf": "^2.6.3",
66 | "rollup": "^1.15.4"
67 | },
68 | "dependencies": {
69 | "@github/time-elements": "^3.0.7",
70 | "@storybook/html": "^5.2.1",
71 | "graphql-request": "^1.8.2",
72 | "h.js": "^4.0.0",
73 | "lit-element": "^2.0.1",
74 | "lit-html": "^1.0.0",
75 | "node-fetch": "^2.6.0",
76 | "remarkable": "^2.0.0",
77 | "semver": "^6.3.0",
78 | "wc-spinners": "^1.0.0"
79 | },
80 | "lint-staged": {
81 | "*.js": [
82 | "eslint --fix",
83 | "prettier --write",
84 | "git add"
85 | ]
86 | },
87 | "eslintConfig": {
88 | "extends": [
89 | "@open-wc/eslint-config",
90 | "eslint-config-prettier"
91 | ]
92 | },
93 | "prettier": "@open-wc/prettier-config",
94 | "husky": {
95 | "hooks": {
96 | "pre-commit": "lint-staged"
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/packages/app/src/owc-cat-app.css.js:
--------------------------------------------------------------------------------
1 | import { css } from 'lit-element';
2 |
3 | export default css`
4 | :host {
5 | color: #1a2b42;
6 | display: flex;
7 | flex-flow: column;
8 | min-height: 100vh;
9 | background: #eee;
10 | }
11 |
12 | a {
13 | color: #217ff9;
14 | }
15 |
16 | #content {
17 | display: flex;
18 | flex-grow: 1;
19 | flex-flow: column;
20 | margin: 0 auto;
21 | padding-top: 10px;
22 | }
23 |
24 | main {
25 | position: relative;
26 | flex-grow: 1;
27 | width: 100vw;
28 | }
29 |
30 | owc-cat-filters {
31 | z-index: 100;
32 | }
33 |
34 | .app-footer {
35 | color: #a8a8a8;
36 | font-size: calc(10px + 1vmin);
37 | text-align: center;
38 | font-size: 16px;
39 | font-weight: normal;
40 | }
41 |
42 | /* mobile details swipe/scroll */
43 | :host([show-mobile-detail]) .items-wrapper {
44 | margin-top: 15px;
45 | }
46 |
47 | .pill {
48 | white-space: nowrap;
49 | border-radius: 10px;
50 | padding: 5px 10px;
51 | font-size: 12px;
52 | text-transform: none;
53 | margin: 10px 5px;
54 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
55 | background: #ccc;
56 | color: #000;
57 | border: 1px solid #aaa;
58 | }
59 |
60 | [owc-tabs-active].pill {
61 | background: #666;
62 | color: #fff;
63 | }
64 |
65 | @keyframes app-logo-spin {
66 | from {
67 | transform: rotate(0deg);
68 | }
69 | to {
70 | transform: rotate(360deg);
71 | }
72 | }
73 |
74 | #loading {
75 | display: none;
76 | }
77 |
78 | :host([loading]) #loading {
79 | display: flex;
80 | align-items: center;
81 | justify-content: center;
82 | position: absolute;
83 | width: 100%;
84 | height: 100%;
85 | top: 0;
86 | left: 0;
87 | background: rgba(200, 200, 200, 0.5);
88 | --semipolar-spinner__color: #999;
89 | }
90 |
91 | @media only screen and (min-width: 420px) {
92 | :host {
93 | background: #f7f7f7;
94 | }
95 |
96 | #content {
97 | flex-flow: row;
98 | padding: 20px;
99 |
100 | max-width: 1080px;
101 | }
102 | }
103 | `;
104 |
--------------------------------------------------------------------------------
/packages/app/src/owc-cat-app.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css } from 'lit-element';
2 |
3 | import 'wc-spinners/dist/semipolar-spinner.js';
4 |
5 | import owcAppStyle from './owc-cat-app.css.js';
6 | import '../../header/src/owc-cat-header.js';
7 | import '../../intro/src/owc-cat-intro.js';
8 | import '../../item/src/owc-cat-item.js';
9 | import '../../filters/src/owc-cat-filters.js';
10 | import '../../item/src/owc-tabs.js';
11 |
12 | function isMobile() {
13 | return window.innerWidth < 600;
14 | }
15 |
16 | class OwcCatApp extends LitElement {
17 | static get properties() {
18 | return {
19 | data: { type: Array },
20 | intro: { type: Boolean },
21 | loading: { type: Boolean, reflect: true },
22 | showMobileDetail: { type: Boolean, reflect: true, attribute: 'show-mobile-detail' },
23 | detailsTabIndex: { type: Number },
24 | };
25 | }
26 |
27 | static get styles() {
28 | return [
29 | owcAppStyle,
30 | css`
31 | h1 {
32 | font-family: 'Roboto Mono';
33 | text-align: center;
34 | margin-bottom: 5px;
35 | }
36 |
37 | .not-found {
38 | padding: 60px;
39 | text-align: center;
40 | }
41 | `,
42 | ];
43 | }
44 |
45 | constructor() {
46 | super();
47 | this.intro = true;
48 | this.loading = false;
49 | this.showMobileDetail = false;
50 | this.detailsTabIndex = 1;
51 | this.data = [];
52 | this.query = '';
53 | this.__firstSearch = true;
54 |
55 | this.shadowRoot.addEventListener('search', () => {
56 | this.search();
57 | });
58 | }
59 |
60 | connectedCallback() {
61 | super.connectedCallback();
62 | const params = new URL(window.location.href).searchParams;
63 | if (params.get('q')) {
64 | this.search(params.get('q'), params.get('type'));
65 | }
66 | }
67 |
68 | render() {
69 | let list = '';
70 |
71 | if (this.data.length > 0) {
72 | list = this.data.map(
73 | (item, i) => html`
74 | ${this.showMobileDetail
75 | ? html`
76 | ${item.name}
77 | `
78 | : html``}
79 | {
83 | if (isMobile()) {
84 | const clickedItem = ev.target;
85 | this.showMobileDetail = clickedItem.showDetails;
86 |
87 | this.updateComplete.then(() => {
88 | if (this.showMobileDetail) {
89 | this.shadowRoot.querySelector('.items-wrapper').scrollIntoView();
90 | this.shadowRoot.querySelector('owc-tabs').activeIndex =
91 | clickedItem.__catItemIndex;
92 | } else {
93 | [...this.shadowRoot.querySelectorAll('owc-cat-item')][
94 | clickedItem.__catItemIndex
95 | ].scrollIntoView({ behavior: 'smooth' });
96 | }
97 | });
98 | }
99 | }}
100 | @detailsTabIndexChanged=${ev => {
101 | if (isMobile()) {
102 | this.detailsTabIndex = ev.target.detailsTabIndex;
103 | }
104 | }}
105 | .name=${item.name}
106 | .description=${item.description}
107 | .version=${item.version}
108 | .versionTime=${item.versionTime}
109 | .flattenedDependencies=${item.flattenedDependencies}
110 | .size=${item.size}
111 | .sizeGzip=${item.sizeGzip}
112 | .githubStars=${item.githubStars}
113 | .githubUrl=${item.githubUrl}
114 | .npmUrl=${item.npmUrl}
115 | .unpkgUrl=${item.unpkgUrl}
116 | .bundlephobiaUrl=${item.bundlephobiaUrl}
117 | .readme=${item.readme}
118 | .demoUrl=${item.demoUrl}
119 | .showDetails=${this.showMobileDetail}
120 | .detailsTabIndex=${this.detailsTabIndex}
121 | >
122 |
123 | `,
124 | );
125 | } else {
126 | list = html`
127 |
128 | We could not find any web component for "${this.header ? this.header.searchValue : '...'}
129 | ". Maybe too many filters are set?
130 |
131 | In alpha phase you need to manually index packages via
132 |
add.html .
133 |
134 | You can learn more how you can index a webcomponent under
135 |
136 | help
137 |
138 | .
139 |
140 | `;
141 | if (this.__firstSearch) {
142 | list = html``;
143 | }
144 | }
145 |
146 | return html`
147 | ${this.intro
148 | ? html`
149 |
150 | `
151 | : html`
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 | ${this.showMobileDetail
162 | ? html`
163 | {
165 | if (isMobile()) {
166 | this.shadowRoot.querySelector('.items-wrapper').scrollIntoView();
167 | }
168 | }}
169 | >
170 | ${list}
171 |
172 | `
173 | : html`
174 | ${list}
175 | `}
176 |
177 |
178 |
179 |
180 |
186 | `}
187 | `;
188 | }
189 |
190 | get header() {
191 | return this.shadowRoot.querySelector('owc-cat-header');
192 | }
193 |
194 | get filters() {
195 | return this.shadowRoot.querySelector('owc-cat-filters');
196 | }
197 |
198 | _introSearch(ev) {
199 | ev.stopPropagation();
200 | this.intro = false;
201 | const { searchValue } = this.shadowRoot.querySelector('owc-cat-intro');
202 | this.updateComplete.then(async () => {
203 | await this.header.updateComplete;
204 | this.header.searchValue = searchValue;
205 | this.search();
206 | });
207 | }
208 |
209 | async search() {
210 | this.loading = true;
211 |
212 | const data = new FormData(this.filters.formEl);
213 | data.append('queryString', this.header.searchValue);
214 | const urlParams = new URLSearchParams(data).toString();
215 |
216 | const url = `/.netlify/functions/search?${urlParams}`;
217 | const response = await fetch(url);
218 | const json = await response.json();
219 | this.data = Array.from(json);
220 |
221 | this.loading = false;
222 | this.__firstSearch = false;
223 | }
224 | }
225 |
226 | customElements.define('owc-cat-app', OwcCatApp);
227 |
--------------------------------------------------------------------------------
/packages/app/stories/index.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Preview, DocsStory } from '@storybook/addon-docs/blocks';
2 |
3 | # Catalog App
4 |
5 | This is the main entry point.
6 |
7 | ## Start
8 |
9 | When you "boot up" you just see a the input to start searching.
10 | This is intentionally kept very minimal to make it easy to get started.
11 |
12 |
13 |
14 |
15 |
16 | ## Loading
17 |
18 | Once you enter your search entry and hit enter the spinning starts.
19 |
20 |
21 |
22 |
23 |
24 | ## Result
25 |
26 | Which will bring you to your first result.
27 |
28 |
29 |
30 |
31 |
32 | ## Retrigger Search
33 |
34 | If you retrigger the search a spinner will appear on top of it.
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/packages/app/stories/index.stories.js:
--------------------------------------------------------------------------------
1 | // import { forceReRender } from '@storybook/polymer';
2 | import { html } from 'lit-html';
3 | import mdx from './index.mdx';
4 |
5 | import '../src/owc-cat-app.js';
6 |
7 | // const fullReadme = '# \\\n\nThis webcomponent follows the [open-wc](https://github.com/open-wc/open-wc) recommendation.\n\n## Installation\n```bash\nnpm i test-wc-card\n```\n\n## Usage\n```html\n\n\n \n```\n\n## Testing using karma (if applied by author)\n```bash\nnpm run test\n```\n\n## Testing using karma via browserstack (if applied by author)\n```bash\nnpm run test:bs\n```\n\n## Demoing using storybook (if applied by author)\n```bash\nnpm run storybook\n```\n\n## Linting (if applied by author)\n```bash\nnpm run lint\n```';
8 |
9 | export default {
10 | title: 'App',
11 | parameters: {
12 | docs: {
13 | page: mdx,
14 | },
15 | },
16 | };
17 |
18 | export const standard = () =>
19 | html`
20 |
21 | `;
22 |
23 | export const loadingFirstTime = () =>
24 | html`
25 |
26 | `;
27 |
28 | export const loading = () =>
29 | html`
30 |
53 | `;
54 |
55 | export const singleResult = () =>
56 | html`
57 |
79 | `;
80 |
81 | export const twoResults = () =>
82 | html`
83 |
137 | `;
138 |
--------------------------------------------------------------------------------
/packages/filters/src/owc-cat-filters.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css } from 'lit-element';
2 |
3 | class OwcCatFilters extends LitElement {
4 | static get styles() {
5 | return [
6 | css`
7 | label {
8 | display: block;
9 | }
10 |
11 | h3 {
12 | text-align: left;
13 | position: relative;
14 | padding: 7px 7px 7px 15px;
15 | margin: 0 10px;
16 | background: #fff;
17 | border: 1px solid #ccc;
18 | }
19 |
20 | h3::after {
21 | content: '∨';
22 | display: block;
23 | position: absolute;
24 | right: 15px;
25 | top: 2px;
26 | font-weight: normal;
27 | font-size: 24px;
28 | }
29 |
30 | #content {
31 | display: none;
32 | box-sizing: border-box;
33 | position: absolute;
34 | left: 0;
35 | top: 0;
36 | width: 100%;
37 | height: 100%;
38 | background: #fff;
39 | padding: 10px;
40 | text-align: center;
41 | }
42 |
43 | #content h3 button {
44 | float: right;
45 | }
46 |
47 | :host([opened]) #content {
48 | display: block;
49 | }
50 |
51 | :host([opened]) h3::after {
52 | transform: rotate(180deg);
53 | top: 6px;
54 | }
55 |
56 | @media only screen and (min-width: 420px) {
57 | :host {
58 | margin-right: 20px;
59 | }
60 |
61 | h1 {
62 | font-size: 30px;
63 | }
64 |
65 | h3 {
66 | text-align: left;
67 | padding: 0;
68 | border: none;
69 | margin: 0;
70 | background: none;
71 | }
72 |
73 | h3::after {
74 | display: none;
75 | }
76 |
77 | .mobile {
78 | display: none;
79 | }
80 |
81 | #content {
82 | display: block;
83 | box-sizing: border-box;
84 | position: static;
85 | width: auto;
86 | height: auto;
87 | padding: 0;
88 | text-align: left;
89 | background: none;
90 | }
91 | }
92 | `,
93 | ];
94 | }
95 |
96 | static get properties() {
97 | return {
98 | opened: { type: Boolean, reflect: true },
99 | };
100 | }
101 |
102 | toggle() {
103 | this.opened = !this.opened;
104 | }
105 |
106 | constructor() {
107 | super();
108 | this.addEventListener('submit', ev => {
109 | ev.preventDefault();
110 | });
111 | }
112 |
113 | firstUpdated() {
114 | super.firstUpdated();
115 | this.shadowRoot.addEventListener('change', () => {
116 | this._onSubmit();
117 | });
118 | }
119 |
120 | render() {
121 | return html`
122 |
179 | `;
180 | }
181 |
182 | get formEl() {
183 | return this.shadowRoot.getElementById('form');
184 | }
185 |
186 | _onSubmit(ev) {
187 | if (ev) {
188 | ev.preventDefault();
189 | }
190 | this.dispatchEvent(new Event('search', { bubbles: true }));
191 | }
192 | }
193 |
194 | customElements.define('owc-cat-filters', OwcCatFilters);
195 |
--------------------------------------------------------------------------------
/packages/filters/stories/index.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Preview, DocsStory } from '@storybook/addon-docs/blocks';
2 |
3 | # Filters
4 |
5 | The filters allow you narrow down your results to selection what is most important to your use-case.
6 |
7 | The most prominent filters are for dependencies.
8 |
9 | ## Standard Usage
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/filters/stories/index.stories.js:
--------------------------------------------------------------------------------
1 | // import { forceReRender } from '@storybook/polymer';
2 | import { html } from 'lit-html';
3 | import mdx from './index.mdx';
4 |
5 | import '../src/owc-cat-filters.js';
6 |
7 | export default {
8 | title: 'Filters',
9 | parameters: {
10 | docs: {
11 | page: mdx,
12 | },
13 | },
14 | };
15 |
16 | export const standard = () =>
17 | html`
18 |
19 | `;
20 |
--------------------------------------------------------------------------------
/packages/header/src/open-wc-logo.svg.js:
--------------------------------------------------------------------------------
1 | import { html } from 'lit-element';
2 |
3 | export default html`
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
23 |
24 |
25 | `;
26 |
--------------------------------------------------------------------------------
/packages/header/src/owc-cat-header.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css } from 'lit-element';
2 |
3 | import openWcLogo from './open-wc-logo.svg.js';
4 |
5 | class OwcCatHeader extends LitElement {
6 | static get styles() {
7 | return [
8 | css`
9 | :host {
10 | display: block;
11 | padding: 10px;
12 | background: linear-gradient(to bottom, #8e9eab, #eef2f3);
13 | }
14 |
15 | h1 {
16 | display: none;
17 | }
18 |
19 | #content {
20 | margin: 0 auto;
21 | display: flex;
22 | justify-content: center;
23 | max-width: 700px;
24 | }
25 |
26 | .app-header__logo {
27 | margin-right: 10px;
28 | }
29 |
30 | svg {
31 | width: 50px;
32 | animation: app-logo-spin infinite 20s linear;
33 | }
34 |
35 | .input-wrapper {
36 | display: flex;
37 | width: 100%;
38 | }
39 |
40 | input {
41 | outline: none;
42 | border: none;
43 | }
44 |
45 | input {
46 | width: 100%;
47 | height: 20px;
48 | padding: 16px 24px;
49 | border-radius: 28px;
50 | font-size: 16px;
51 | /* TODO: transition box-shadow is slow, use pseudo elements with opacity instead */
52 | transition: box-shadow 300ms;
53 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12),
54 | 0 3px 1px -2px rgba(0, 0, 0, 0.2);
55 | }
56 |
57 | input::placeholder {
58 | color: #9e9e9e;
59 | font-size: 16px;
60 | }
61 |
62 | input:focus {
63 | box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12),
64 | 0 3px 5px -1px rgba(0, 0, 0, 0.4);
65 | }
66 |
67 | button[type='submit'] {
68 | margin-right: 10px;
69 | height: 34px;
70 | margin-top: 8px;
71 | }
72 |
73 | @media only screen and (min-width: 640px) {
74 | :host {
75 | padding: 20px;
76 | }
77 |
78 | h1 {
79 | display: block;
80 | margin-top: 0;
81 | text-align: center;
82 | color: #fff;
83 | text-shadow: #333 3px 3px 2px;
84 | }
85 |
86 | .app-header__logo {
87 | margin-right: 20px;
88 | }
89 | }
90 | `,
91 | ];
92 | }
93 |
94 | render() {
95 | return html`
96 | Web Component Catalog
97 |
107 | `;
108 | }
109 |
110 | get searchValue() {
111 | return this.shadowRoot.getElementById('searchInput').value;
112 | }
113 |
114 | set searchValue(value) {
115 | this.shadowRoot.getElementById('searchInput').value = value;
116 | }
117 |
118 | _onSubmit(ev) {
119 | ev.preventDefault();
120 | this.dispatchEvent(new Event('search', { bubbles: true }));
121 | }
122 | }
123 |
124 | customElements.define('owc-cat-header', OwcCatHeader);
125 |
--------------------------------------------------------------------------------
/packages/header/stories/index.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Preview, DocsStory } from '@storybook/addon-docs/blocks';
2 |
3 | # Header
4 |
5 | The header is similar to the intro but it's placed at the top and behaviors differently on mobile.
6 |
7 | ## Standard
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/packages/header/stories/index.stories.js:
--------------------------------------------------------------------------------
1 | // import { forceReRender } from '@storybook/polymer';
2 | import { html } from 'lit-html';
3 | import mdx from './index.mdx';
4 |
5 | import '../src/owc-cat-header.js';
6 |
7 | export default {
8 | title: 'Header',
9 | parameters: {
10 | docs: {
11 | page: mdx,
12 | },
13 | },
14 | };
15 |
16 | export const standard = () =>
17 | html`
18 |
19 | `;
20 |
--------------------------------------------------------------------------------
/packages/intro/src/owc-button.css.js:
--------------------------------------------------------------------------------
1 | import { css } from 'lit-element';
2 |
3 | export default css`
4 | /* reset */
5 | .owc-button {
6 | font: inherit;
7 | font-size: 16px;
8 | margin: 0;
9 | border: 0;
10 | padding: 0;
11 | color: inherit;
12 | background-color: transparent;
13 | text-align: left;
14 | white-space: normal;
15 | overflow: visible;
16 |
17 | user-select: none;
18 | -moz-user-select: none;
19 | -webkit-user-select: none;
20 | -ms-user-select: none;
21 | }
22 |
23 | .owc-button {
24 | border-radius: 4px;
25 | padding: 8px;
26 | border: 1px solid #0077ff;
27 | display: block;
28 | text-decoration: none;
29 | cursor: pointer;
30 | }
31 |
32 | .owc-button-filled {
33 | background-color: #0077ff;
34 | color: white;
35 | }
36 | `;
37 |
--------------------------------------------------------------------------------
/packages/intro/src/owc-cat-intro.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css } from 'lit-element';
2 |
3 | import owcButtonStyles from './owc-button.css.js';
4 |
5 | class OwcCatIntro extends LitElement {
6 | static get styles() {
7 | return [
8 | owcButtonStyles,
9 | css`
10 | :host {
11 | display: flex;
12 | height: 100vh;
13 | background: linear-gradient(to bottom, #8e9eab, #eef2f3);
14 | flex-flow: column;
15 | justify-content: center;
16 | }
17 |
18 | h1 {
19 | font-size: 24px;
20 | text-align: center;
21 | color: #fff;
22 | text-shadow: #333 3px 3px 2px;
23 | }
24 |
25 | #content {
26 | width: 90%;
27 | max-width: 500px;
28 | margin: 0 auto;
29 | display: flex;
30 | justify-content: center;
31 | align-items: center;
32 | flex-flow: column;
33 | }
34 |
35 | .input-wrapper {
36 | display: flex;
37 | width: 100%;
38 | }
39 |
40 | input {
41 | outline: none;
42 | border: none;
43 | }
44 |
45 | input {
46 | width: 100%;
47 | height: 20px;
48 | padding: 16px 24px;
49 | border-radius: 28px;
50 | font-size: 16px;
51 | /* TODO: transition box-shadow is slow, use pseudo elements with opacity instead */
52 | transition: box-shadow 300ms;
53 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12),
54 | 0 3px 1px -2px rgba(0, 0, 0, 0.2);
55 | }
56 |
57 | input::placeholder {
58 | color: #9e9e9e;
59 | font-size: 16px;
60 | }
61 |
62 | input:focus {
63 | box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12),
64 | 0 3px 5px -1px rgba(0, 0, 0, 0.4);
65 | }
66 |
67 | button[type='submit'] {
68 | margin-top: 12px;
69 | max-width: 120px;
70 | height: 34px;
71 | text-align: center;
72 | }
73 |
74 | @media only screen and (min-width: 420px) {
75 | h1 {
76 | font-size: 30px;
77 | }
78 | }
79 | `,
80 | ];
81 | }
82 |
83 | render() {
84 | return html`
85 | Web Component Catalog
86 |
104 | `;
105 | }
106 |
107 | /**
108 | * The current filled in value of the search input
109 | *
110 | * @example getting search value
111 | * // user writes "button" into the input field
112 | * expect(el.searchValue).to.equal('button');
113 | *
114 | * @returns {string}
115 | */
116 | get searchValue() {
117 | return this.shadowRoot.getElementById('searchInput').value;
118 | }
119 |
120 | /**
121 | * @example search value in templates
122 | *
123 | * // users sees "button" filled in the search input
124 | *
125 | * @example setting search value
126 | * el.searchValue = 'button';
127 | * // users sees "button" filled in the search input
128 | *
129 | * @param {string} value Value to be set for the input
130 | */
131 | set searchValue(value) {
132 | this.shadowRoot.getElementById('searchInput').value = value;
133 | }
134 |
135 | _onSubmit(ev) {
136 | ev.preventDefault();
137 | /**
138 | * Fires when the user wishes to search
139 | *
140 | * @example
141 | *
142 | *
143 | * @example
144 | * el.addEventListener('search', this.yourSearchImplemntation.bind(this));
145 | */
146 | this.dispatchEvent(new Event('search', { bubbles: true }));
147 | }
148 | }
149 |
150 | customElements.define('owc-cat-intro', OwcCatIntro);
151 |
--------------------------------------------------------------------------------
/packages/intro/stories/index.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Preview, DocsStory } from '@storybook/addon-docs/blocks';
2 |
3 | # Intro
4 |
5 | The intro is a full view component which displays a single search input.
6 | On submit it fires a search event.
7 |
8 | ## Standard Usage
9 |
10 |
11 |
12 |
13 |
14 | ## Properties
15 |
16 | | Name | Type | Description |
17 | | ----------- | ------ | ----------------------------------------------- |
18 | | searchValue | string | The current filled in value of the search input |
19 |
20 | ## Events
21 |
22 | | Name | Description |
23 | | ------ | ------------------------------------ |
24 | | search | Fires when the user wishes to search |
25 |
26 | ## Property Examples
27 |
28 | ### searchValue
29 |
30 | ** getting search value **
31 |
32 | ```js
33 | // user writes "button" into the input field
34 | expect(el.searchValue).to.equal('button');
35 | ```
36 |
37 | ** search value in templates **
38 |
39 | ```js
40 |
41 | // users sees "button" filled in the search input
42 | ```
43 |
44 | ** setting search value **
45 |
46 | ```js
47 | el.searchValue = 'button';
48 | // users sees "button" filled in the search input
49 | ```
50 |
--------------------------------------------------------------------------------
/packages/intro/stories/index.stories.js:
--------------------------------------------------------------------------------
1 | // import { forceReRender } from '@storybook/polymer';
2 | import { html } from 'lit-html';
3 | import mdx from './index.mdx';
4 |
5 | import '../src/owc-cat-intro.js';
6 |
7 | export default {
8 | title: 'Intro',
9 | parameters: {
10 | docs: {
11 | page: mdx,
12 | },
13 | },
14 | };
15 |
16 | export const standard = () =>
17 | html`
18 |
19 | `;
20 |
--------------------------------------------------------------------------------
/packages/item/src/github-markdown.css.js:
--------------------------------------------------------------------------------
1 | function css(strings, ...values) {
2 | return values.reduce((acc, v, idx) => acc + v + strings[idx + 1], strings[0]);
3 | }
4 |
5 | export default css`
6 | @font-face {
7 | font-family: octicons-link;
8 | src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==)
9 | format('woff');
10 | }
11 |
12 | .markdown-body .octicon {
13 | display: inline-block;
14 | fill: currentColor;
15 | vertical-align: text-bottom;
16 | }
17 |
18 | .markdown-body .anchor {
19 | float: left;
20 | line-height: 1;
21 | margin-left: -20px;
22 | padding-right: 4px;
23 | }
24 |
25 | .markdown-body .anchor:focus {
26 | outline: none;
27 | }
28 |
29 | .markdown-body h1 .octicon-link,
30 | .markdown-body h2 .octicon-link,
31 | .markdown-body h3 .octicon-link,
32 | .markdown-body h4 .octicon-link,
33 | .markdown-body h5 .octicon-link,
34 | .markdown-body h6 .octicon-link {
35 | color: #1b1f23;
36 | vertical-align: middle;
37 | visibility: hidden;
38 | }
39 |
40 | .markdown-body h1:hover .anchor,
41 | .markdown-body h2:hover .anchor,
42 | .markdown-body h3:hover .anchor,
43 | .markdown-body h4:hover .anchor,
44 | .markdown-body h5:hover .anchor,
45 | .markdown-body h6:hover .anchor {
46 | text-decoration: none;
47 | }
48 |
49 | .markdown-body h1:hover .anchor .octicon-link,
50 | .markdown-body h2:hover .anchor .octicon-link,
51 | .markdown-body h3:hover .anchor .octicon-link,
52 | .markdown-body h4:hover .anchor .octicon-link,
53 | .markdown-body h5:hover .anchor .octicon-link,
54 | .markdown-body h6:hover .anchor .octicon-link {
55 | visibility: visible;
56 | }
57 |
58 | .markdown-body {
59 | -ms-text-size-adjust: 100%;
60 | -webkit-text-size-adjust: 100%;
61 | color: #24292e;
62 | line-height: 1.5;
63 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif,
64 | Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
65 | font-size: 16px;
66 | line-height: 1.5;
67 | word-wrap: break-word;
68 | }
69 |
70 | .markdown-body .pl-c {
71 | color: #6a737d;
72 | }
73 |
74 | .markdown-body .pl-c1,
75 | .markdown-body .pl-s .pl-v {
76 | color: #005cc5;
77 | }
78 |
79 | .markdown-body .pl-e,
80 | .markdown-body .pl-en {
81 | color: #6f42c1;
82 | }
83 |
84 | .markdown-body .pl-s .pl-s1,
85 | .markdown-body .pl-smi {
86 | color: #24292e;
87 | }
88 |
89 | .markdown-body .pl-ent {
90 | color: #22863a;
91 | }
92 |
93 | .markdown-body .pl-k {
94 | color: #d73a49;
95 | }
96 |
97 | .markdown-body .pl-pds,
98 | .markdown-body .pl-s,
99 | .markdown-body .pl-s .pl-pse .pl-s1,
100 | .markdown-body .pl-sr,
101 | .markdown-body .pl-sr .pl-cce,
102 | .markdown-body .pl-sr .pl-sra,
103 | .markdown-body .pl-sr .pl-sre {
104 | color: #032f62;
105 | }
106 |
107 | .markdown-body .pl-smw,
108 | .markdown-body .pl-v {
109 | color: #e36209;
110 | }
111 |
112 | .markdown-body .pl-bu {
113 | color: #b31d28;
114 | }
115 |
116 | .markdown-body .pl-ii {
117 | background-color: #b31d28;
118 | color: #fafbfc;
119 | }
120 |
121 | .markdown-body .pl-c2 {
122 | background-color: #d73a49;
123 | color: #fafbfc;
124 | }
125 |
126 | .markdown-body .pl-c2:before {
127 | content: '^M';
128 | }
129 |
130 | .markdown-body .pl-sr .pl-cce {
131 | color: #22863a;
132 | font-weight: 700;
133 | }
134 |
135 | .markdown-body .pl-ml {
136 | color: #735c0f;
137 | }
138 |
139 | .markdown-body .pl-mh,
140 | .markdown-body .pl-mh .pl-en,
141 | .markdown-body .pl-ms {
142 | color: #005cc5;
143 | font-weight: 700;
144 | }
145 |
146 | .markdown-body .pl-mi {
147 | color: #24292e;
148 | font-style: italic;
149 | }
150 |
151 | .markdown-body .pl-mb {
152 | color: #24292e;
153 | font-weight: 700;
154 | }
155 |
156 | .markdown-body .pl-md {
157 | background-color: #ffeef0;
158 | color: #b31d28;
159 | }
160 |
161 | .markdown-body .pl-mi1 {
162 | background-color: #f0fff4;
163 | color: #22863a;
164 | }
165 |
166 | .markdown-body .pl-mc {
167 | background-color: #ffebda;
168 | color: #e36209;
169 | }
170 |
171 | .markdown-body .pl-mi2 {
172 | background-color: #005cc5;
173 | color: #f6f8fa;
174 | }
175 |
176 | .markdown-body .pl-mdr {
177 | color: #6f42c1;
178 | font-weight: 700;
179 | }
180 |
181 | .markdown-body .pl-ba {
182 | color: #586069;
183 | }
184 |
185 | .markdown-body .pl-sg {
186 | color: #959da5;
187 | }
188 |
189 | .markdown-body .pl-corl {
190 | color: #032f62;
191 | text-decoration: underline;
192 | }
193 |
194 | .markdown-body details {
195 | display: block;
196 | }
197 |
198 | .markdown-body summary {
199 | display: list-item;
200 | }
201 |
202 | .markdown-body a {
203 | background-color: transparent;
204 | }
205 |
206 | .markdown-body a:active,
207 | .markdown-body a:hover {
208 | outline-width: 0;
209 | }
210 |
211 | .markdown-body strong {
212 | font-weight: inherit;
213 | font-weight: bolder;
214 | }
215 |
216 | .markdown-body h1 {
217 | font-size: 2em;
218 | margin: 0.67em 0;
219 | }
220 |
221 | .markdown-body img {
222 | border-style: none;
223 | }
224 |
225 | .markdown-body code,
226 | .markdown-body kbd,
227 | .markdown-body pre {
228 | font-family: monospace, monospace;
229 | font-size: 1em;
230 | }
231 |
232 | .markdown-body hr {
233 | box-sizing: content-box;
234 | height: 0;
235 | overflow: visible;
236 | }
237 |
238 | .markdown-body input {
239 | font: inherit;
240 | margin: 0;
241 | }
242 |
243 | .markdown-body input {
244 | overflow: visible;
245 | }
246 |
247 | .markdown-body [type='checkbox'] {
248 | box-sizing: border-box;
249 | padding: 0;
250 | }
251 |
252 | .markdown-body * {
253 | box-sizing: border-box;
254 | }
255 |
256 | .markdown-body input {
257 | font-family: inherit;
258 | font-size: inherit;
259 | line-height: inherit;
260 | }
261 |
262 | .markdown-body a {
263 | color: #0366d6;
264 | text-decoration: none;
265 | }
266 |
267 | .markdown-body a:hover {
268 | text-decoration: underline;
269 | }
270 |
271 | .markdown-body strong {
272 | font-weight: 600;
273 | }
274 |
275 | .markdown-body hr {
276 | background: transparent;
277 | border: 0;
278 | border-bottom: 1px solid #dfe2e5;
279 | height: 0;
280 | margin: 15px 0;
281 | overflow: hidden;
282 | }
283 |
284 | .markdown-body hr:before {
285 | content: '';
286 | display: table;
287 | }
288 |
289 | .markdown-body hr:after {
290 | clear: both;
291 | content: '';
292 | display: table;
293 | }
294 |
295 | .markdown-body table {
296 | border-collapse: collapse;
297 | border-spacing: 0;
298 | }
299 |
300 | .markdown-body td,
301 | .markdown-body th {
302 | padding: 0;
303 | }
304 |
305 | .markdown-body details summary {
306 | cursor: pointer;
307 | }
308 |
309 | .markdown-body h1,
310 | .markdown-body h2,
311 | .markdown-body h3,
312 | .markdown-body h4,
313 | .markdown-body h5,
314 | .markdown-body h6 {
315 | margin-bottom: 0;
316 | margin-top: 0;
317 | }
318 |
319 | .markdown-body h1 {
320 | font-size: 32px;
321 | }
322 |
323 | .markdown-body h1,
324 | .markdown-body h2 {
325 | font-weight: 600;
326 | }
327 |
328 | .markdown-body h2 {
329 | font-size: 24px;
330 | }
331 |
332 | .markdown-body h3 {
333 | font-size: 20px;
334 | }
335 |
336 | .markdown-body h3,
337 | .markdown-body h4 {
338 | font-weight: 600;
339 | }
340 |
341 | .markdown-body h4 {
342 | font-size: 16px;
343 | }
344 |
345 | .markdown-body h5 {
346 | font-size: 14px;
347 | }
348 |
349 | .markdown-body h5,
350 | .markdown-body h6 {
351 | font-weight: 600;
352 | }
353 |
354 | .markdown-body h6 {
355 | font-size: 12px;
356 | }
357 |
358 | .markdown-body p {
359 | margin-bottom: 10px;
360 | margin-top: 0;
361 | }
362 |
363 | .markdown-body blockquote {
364 | margin: 0;
365 | }
366 |
367 | .markdown-body ol,
368 | .markdown-body ul {
369 | margin-bottom: 0;
370 | margin-top: 0;
371 | padding-left: 0;
372 | }
373 |
374 | .markdown-body ol ol,
375 | .markdown-body ul ol {
376 | list-style-type: lower-roman;
377 | }
378 |
379 | .markdown-body ol ol ol,
380 | .markdown-body ol ul ol,
381 | .markdown-body ul ol ol,
382 | .markdown-body ul ul ol {
383 | list-style-type: lower-alpha;
384 | }
385 |
386 | .markdown-body dd {
387 | margin-left: 0;
388 | }
389 |
390 | .markdown-body code,
391 | .markdown-body pre {
392 | font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace;
393 | font-size: 12px;
394 | }
395 |
396 | .markdown-body pre {
397 | margin-bottom: 0;
398 | margin-top: 0;
399 | }
400 |
401 | .markdown-body input::-webkit-inner-spin-button,
402 | .markdown-body input::-webkit-outer-spin-button {
403 | -webkit-appearance: none;
404 | appearance: none;
405 | margin: 0;
406 | }
407 |
408 | .markdown-body .border {
409 | border: 1px solid #e1e4e8 !important;
410 | }
411 |
412 | .markdown-body .border-0 {
413 | border: 0 !important;
414 | }
415 |
416 | .markdown-body .border-bottom {
417 | border-bottom: 1px solid #e1e4e8 !important;
418 | }
419 |
420 | .markdown-body .rounded-1 {
421 | border-radius: 3px !important;
422 | }
423 |
424 | .markdown-body .bg-white {
425 | background-color: #fff !important;
426 | }
427 |
428 | .markdown-body .bg-gray-light {
429 | background-color: #fafbfc !important;
430 | }
431 |
432 | .markdown-body .text-gray-light {
433 | color: #6a737d !important;
434 | }
435 |
436 | .markdown-body .mb-0 {
437 | margin-bottom: 0 !important;
438 | }
439 |
440 | .markdown-body .my-2 {
441 | margin-bottom: 8px !important;
442 | margin-top: 8px !important;
443 | }
444 |
445 | .markdown-body .pl-0 {
446 | padding-left: 0 !important;
447 | }
448 |
449 | .markdown-body .py-0 {
450 | padding-bottom: 0 !important;
451 | padding-top: 0 !important;
452 | }
453 |
454 | .markdown-body .pl-1 {
455 | padding-left: 4px !important;
456 | }
457 |
458 | .markdown-body .pl-2 {
459 | padding-left: 8px !important;
460 | }
461 |
462 | .markdown-body .py-2 {
463 | padding-bottom: 8px !important;
464 | padding-top: 8px !important;
465 | }
466 |
467 | .markdown-body .pl-3,
468 | .markdown-body .px-3 {
469 | padding-left: 16px !important;
470 | }
471 |
472 | .markdown-body .px-3 {
473 | padding-right: 16px !important;
474 | }
475 |
476 | .markdown-body .pl-4 {
477 | padding-left: 24px !important;
478 | }
479 |
480 | .markdown-body .pl-5 {
481 | padding-left: 32px !important;
482 | }
483 |
484 | .markdown-body .pl-6 {
485 | padding-left: 40px !important;
486 | }
487 |
488 | .markdown-body .f6 {
489 | font-size: 12px !important;
490 | }
491 |
492 | .markdown-body .lh-condensed {
493 | line-height: 1.25 !important;
494 | }
495 |
496 | .markdown-body .text-bold {
497 | font-weight: 600 !important;
498 | }
499 |
500 | .markdown-body:before {
501 | content: '';
502 | display: table;
503 | }
504 |
505 | .markdown-body:after {
506 | clear: both;
507 | content: '';
508 | display: table;
509 | }
510 |
511 | .markdown-body > :first-child {
512 | margin-top: 0 !important;
513 | }
514 |
515 | .markdown-body > :last-child {
516 | margin-bottom: 0 !important;
517 | }
518 |
519 | .markdown-body a:not([href]) {
520 | color: inherit;
521 | text-decoration: none;
522 | }
523 |
524 | .markdown-body blockquote,
525 | .markdown-body dl,
526 | .markdown-body ol,
527 | .markdown-body p,
528 | .markdown-body pre,
529 | .markdown-body table,
530 | .markdown-body ul {
531 | margin-bottom: 16px;
532 | margin-top: 0;
533 | }
534 |
535 | .markdown-body hr {
536 | background-color: #e1e4e8;
537 | border: 0;
538 | height: 0.25em;
539 | margin: 24px 0;
540 | padding: 0;
541 | }
542 |
543 | .markdown-body blockquote {
544 | border-left: 0.25em solid #dfe2e5;
545 | color: #6a737d;
546 | padding: 0 1em;
547 | }
548 |
549 | .markdown-body blockquote > :first-child {
550 | margin-top: 0;
551 | }
552 |
553 | .markdown-body blockquote > :last-child {
554 | margin-bottom: 0;
555 | }
556 |
557 | .markdown-body kbd {
558 | background-color: #fafbfc;
559 | border: 1px solid #c6cbd1;
560 | border-bottom-color: #959da5;
561 | border-radius: 3px;
562 | box-shadow: inset 0 -1px 0 #959da5;
563 | color: #444d56;
564 | display: inline-block;
565 | font-size: 11px;
566 | line-height: 10px;
567 | padding: 3px 5px;
568 | vertical-align: middle;
569 | }
570 |
571 | .markdown-body h1,
572 | .markdown-body h2,
573 | .markdown-body h3,
574 | .markdown-body h4,
575 | .markdown-body h5,
576 | .markdown-body h6 {
577 | font-weight: 600;
578 | line-height: 1.25;
579 | margin-bottom: 16px;
580 | margin-top: 24px;
581 | }
582 |
583 | .markdown-body h1 {
584 | font-size: 2em;
585 | }
586 |
587 | .markdown-body h1,
588 | .markdown-body h2 {
589 | border-bottom: 1px solid #eaecef;
590 | padding-bottom: 0.3em;
591 | }
592 |
593 | .markdown-body h2 {
594 | font-size: 1.5em;
595 | }
596 |
597 | .markdown-body h3 {
598 | font-size: 1.25em;
599 | }
600 |
601 | .markdown-body h4 {
602 | font-size: 1em;
603 | }
604 |
605 | .markdown-body h5 {
606 | font-size: 0.875em;
607 | }
608 |
609 | .markdown-body h6 {
610 | color: #6a737d;
611 | font-size: 0.85em;
612 | }
613 |
614 | .markdown-body ol,
615 | .markdown-body ul {
616 | padding-left: 2em;
617 | }
618 |
619 | .markdown-body ol ol,
620 | .markdown-body ol ul,
621 | .markdown-body ul ol,
622 | .markdown-body ul ul {
623 | margin-bottom: 0;
624 | margin-top: 0;
625 | }
626 |
627 | .markdown-body li {
628 | word-wrap: break-all;
629 | }
630 |
631 | .markdown-body li > p {
632 | margin-top: 16px;
633 | }
634 |
635 | .markdown-body li + li {
636 | margin-top: 0.25em;
637 | }
638 |
639 | .markdown-body dl {
640 | padding: 0;
641 | }
642 |
643 | .markdown-body dl dt {
644 | font-size: 1em;
645 | font-style: italic;
646 | font-weight: 600;
647 | margin-top: 16px;
648 | padding: 0;
649 | }
650 |
651 | .markdown-body dl dd {
652 | margin-bottom: 16px;
653 | padding: 0 16px;
654 | }
655 |
656 | .markdown-body table {
657 | display: block;
658 | overflow: auto;
659 | width: 100%;
660 | }
661 |
662 | .markdown-body table th {
663 | font-weight: 600;
664 | }
665 |
666 | .markdown-body table td,
667 | .markdown-body table th {
668 | border: 1px solid #dfe2e5;
669 | padding: 6px 13px;
670 | }
671 |
672 | .markdown-body table tr {
673 | background-color: #fff;
674 | border-top: 1px solid #c6cbd1;
675 | }
676 |
677 | .markdown-body table tr:nth-child(2n) {
678 | background-color: #f6f8fa;
679 | }
680 |
681 | .markdown-body img {
682 | background-color: #fff;
683 | box-sizing: content-box;
684 | max-width: 100%;
685 | }
686 |
687 | .markdown-body img[align='right'] {
688 | padding-left: 20px;
689 | }
690 |
691 | .markdown-body img[align='left'] {
692 | padding-right: 20px;
693 | }
694 |
695 | .markdown-body code {
696 | background-color: rgba(27, 31, 35, 0.05);
697 | border-radius: 3px;
698 | font-size: 85%;
699 | margin: 0;
700 | padding: 0.2em 0.4em;
701 | }
702 |
703 | .markdown-body pre {
704 | word-wrap: normal;
705 | }
706 |
707 | .markdown-body pre > code {
708 | background: transparent;
709 | border: 0;
710 | font-size: 100%;
711 | margin: 0;
712 | padding: 0;
713 | white-space: pre;
714 | word-break: normal;
715 | }
716 |
717 | .markdown-body .highlight {
718 | margin-bottom: 16px;
719 | }
720 |
721 | .markdown-body .highlight pre {
722 | margin-bottom: 0;
723 | word-break: normal;
724 | }
725 |
726 | .markdown-body .highlight pre,
727 | .markdown-body pre {
728 | background-color: #f6f8fa;
729 | border-radius: 3px;
730 | font-size: 85%;
731 | line-height: 1.45;
732 | overflow: auto;
733 | padding: 16px;
734 | }
735 |
736 | .markdown-body pre code {
737 | background-color: transparent;
738 | border: 0;
739 | display: inline;
740 | line-height: inherit;
741 | margin: 0;
742 | max-width: auto;
743 | overflow: visible;
744 | padding: 0;
745 | word-wrap: normal;
746 | }
747 |
748 | .markdown-body .commit-tease-sha {
749 | color: #444d56;
750 | display: inline-block;
751 | font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace;
752 | font-size: 90%;
753 | }
754 |
755 | .markdown-body .blob-wrapper {
756 | border-bottom-left-radius: 3px;
757 | border-bottom-right-radius: 3px;
758 | overflow-x: auto;
759 | overflow-y: hidden;
760 | }
761 |
762 | .markdown-body .blob-wrapper-embedded {
763 | max-height: 240px;
764 | overflow-y: auto;
765 | }
766 |
767 | .markdown-body .blob-num {
768 | -moz-user-select: none;
769 | -ms-user-select: none;
770 | -webkit-user-select: none;
771 | color: rgba(27, 31, 35, 0.3);
772 | cursor: pointer;
773 | font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace;
774 | font-size: 12px;
775 | line-height: 20px;
776 | min-width: 50px;
777 | padding-left: 10px;
778 | padding-right: 10px;
779 | text-align: right;
780 | user-select: none;
781 | vertical-align: top;
782 | white-space: nowrap;
783 | width: 1%;
784 | }
785 |
786 | .markdown-body .blob-num:hover {
787 | color: rgba(27, 31, 35, 0.6);
788 | }
789 |
790 | .markdown-body .blob-num:before {
791 | content: attr(data-line-number);
792 | }
793 |
794 | .markdown-body .blob-code {
795 | line-height: 20px;
796 | padding-left: 10px;
797 | padding-right: 10px;
798 | position: relative;
799 | vertical-align: top;
800 | }
801 |
802 | .markdown-body .blob-code-inner {
803 | color: #24292e;
804 | font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace;
805 | font-size: 12px;
806 | overflow: visible;
807 | white-space: pre;
808 | word-wrap: normal;
809 | }
810 |
811 | .markdown-body .pl-token.active,
812 | .markdown-body .pl-token:hover {
813 | background: #ffea7f;
814 | cursor: pointer;
815 | }
816 |
817 | .markdown-body kbd {
818 | background-color: #fafbfc;
819 | border: 1px solid #d1d5da;
820 | border-bottom-color: #c6cbd1;
821 | border-radius: 3px;
822 | box-shadow: inset 0 -1px 0 #c6cbd1;
823 | color: #444d56;
824 | display: inline-block;
825 | font: 11px SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, monospace;
826 | line-height: 10px;
827 | padding: 3px 5px;
828 | vertical-align: middle;
829 | }
830 |
831 | .markdown-body :checked + .radio-label {
832 | border-color: #0366d6;
833 | position: relative;
834 | z-index: 1;
835 | }
836 |
837 | .markdown-body .tab-size[data-tab-size='1'] {
838 | -moz-tab-size: 1;
839 | tab-size: 1;
840 | }
841 |
842 | .markdown-body .tab-size[data-tab-size='2'] {
843 | -moz-tab-size: 2;
844 | tab-size: 2;
845 | }
846 |
847 | .markdown-body .tab-size[data-tab-size='3'] {
848 | -moz-tab-size: 3;
849 | tab-size: 3;
850 | }
851 |
852 | .markdown-body .tab-size[data-tab-size='4'] {
853 | -moz-tab-size: 4;
854 | tab-size: 4;
855 | }
856 |
857 | .markdown-body .tab-size[data-tab-size='5'] {
858 | -moz-tab-size: 5;
859 | tab-size: 5;
860 | }
861 |
862 | .markdown-body .tab-size[data-tab-size='6'] {
863 | -moz-tab-size: 6;
864 | tab-size: 6;
865 | }
866 |
867 | .markdown-body .tab-size[data-tab-size='7'] {
868 | -moz-tab-size: 7;
869 | tab-size: 7;
870 | }
871 |
872 | .markdown-body .tab-size[data-tab-size='8'] {
873 | -moz-tab-size: 8;
874 | tab-size: 8;
875 | }
876 |
877 | .markdown-body .tab-size[data-tab-size='9'] {
878 | -moz-tab-size: 9;
879 | tab-size: 9;
880 | }
881 |
882 | .markdown-body .tab-size[data-tab-size='10'] {
883 | -moz-tab-size: 10;
884 | tab-size: 10;
885 | }
886 |
887 | .markdown-body .tab-size[data-tab-size='11'] {
888 | -moz-tab-size: 11;
889 | tab-size: 11;
890 | }
891 |
892 | .markdown-body .tab-size[data-tab-size='12'] {
893 | -moz-tab-size: 12;
894 | tab-size: 12;
895 | }
896 |
897 | .markdown-body .task-list-item {
898 | list-style-type: none;
899 | }
900 |
901 | .markdown-body .task-list-item + .task-list-item {
902 | margin-top: 3px;
903 | }
904 |
905 | .markdown-body .task-list-item input {
906 | margin: 0 0.2em 0.25em -1.6em;
907 | vertical-align: middle;
908 | }
909 |
910 | .markdown-body hr {
911 | border-bottom-color: #eee;
912 | }
913 |
914 | .markdown-body .pl-0 {
915 | padding-left: 0 !important;
916 | }
917 |
918 | .markdown-body .pl-1 {
919 | padding-left: 4px !important;
920 | }
921 |
922 | .markdown-body .pl-2 {
923 | padding-left: 8px !important;
924 | }
925 |
926 | .markdown-body .pl-3 {
927 | padding-left: 16px !important;
928 | }
929 |
930 | .markdown-body .pl-4 {
931 | padding-left: 24px !important;
932 | }
933 |
934 | .markdown-body .pl-5 {
935 | padding-left: 32px !important;
936 | }
937 |
938 | .markdown-body .pl-6 {
939 | padding-left: 40px !important;
940 | }
941 |
942 | .markdown-body .pl-7 {
943 | padding-left: 48px !important;
944 | }
945 |
946 | .markdown-body .pl-8 {
947 | padding-left: 64px !important;
948 | }
949 |
950 | .markdown-body .pl-9 {
951 | padding-left: 80px !important;
952 | }
953 |
954 | .markdown-body .pl-10 {
955 | padding-left: 96px !important;
956 | }
957 |
958 | .markdown-body .pl-11 {
959 | padding-left: 112px !important;
960 | }
961 |
962 | .markdown-body .pl-12 {
963 | padding-left: 128px !important;
964 | }
965 |
966 | /** added hacks */
967 | .markdown-body pre {
968 | max-width: calc(100vw - 10px);
969 | }
970 | `;
971 |
--------------------------------------------------------------------------------
/packages/item/src/logos/logo-github.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css } from 'lit-element';
2 |
3 | class LogoGithub extends LitElement {
4 | static get styles() {
5 | return css`
6 | :host {
7 | display: block;
8 | }
9 | `;
10 | }
11 |
12 | // eslint-disable-next-line
13 | render() {
14 | return html`github `;
15 | }
16 | }
17 | customElements.define('logo-github', LogoGithub);
18 |
--------------------------------------------------------------------------------
/packages/item/src/logos/logo-npm.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css } from 'lit-element';
2 |
3 | class LogoNpm extends LitElement {
4 | static get styles() {
5 | return css`
6 | :host {
7 | display: block;
8 | width: 100%;
9 | }
10 |
11 | svg {
12 | width: 100%;
13 | height: 100%;
14 | }
15 | `;
16 | }
17 |
18 | // eslint-disable-next-line
19 | render() {
20 | return html`npm `;
21 | }
22 | }
23 | customElements.define('logo-npm', LogoNpm);
24 |
--------------------------------------------------------------------------------
/packages/item/src/logos/logo-unpkg.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css } from 'lit-element';
2 |
3 | class LogoUnpkg extends LitElement {
4 | static get styles() {
5 | return css`
6 | :host {
7 | display: block;
8 | }
9 | `;
10 | }
11 |
12 | // eslint-disable-next-line
13 | render() {
14 | return html`unpkg `;
15 | }
16 | }
17 | customElements.define('logo-unpkg', LogoUnpkg);
18 |
--------------------------------------------------------------------------------
/packages/item/src/owc-cat-item.css.js:
--------------------------------------------------------------------------------
1 | import { css } from 'lit-element';
2 |
3 | export default css`
4 | :host {
5 | --owc-blue: #217ff9;
6 | --owc-purple: #aa00ff;
7 | text-align: left;
8 | font-size: 15px;
9 | padding: 15px;
10 | background-color: white;
11 | min-height: 85px;
12 | box-sizing: border-box;
13 | margin: 15px 10px;
14 | border: 1px solid #ccc;
15 | display: block;
16 | }
17 |
18 | h1 {
19 | color: var(--owc-blue);
20 | margin-top: 0;
21 | margin-bottom: 10px;
22 | margin-right: 30px;
23 | font-family: 'Roboto';
24 | font-size: 22px;
25 | }
26 |
27 | h1 a {
28 | color: var(--owc-blue);
29 | text-decoration: none;
30 | }
31 |
32 | h1 a:hover {
33 | text-decoration: underline;
34 | }
35 |
36 | .big {
37 | font-size: 22px;
38 | font-weight: bold;
39 | margin-bottom: 4px;
40 | }
41 |
42 | .big--not-so-much {
43 | font-size: 20px;
44 | }
45 |
46 | .small {
47 | font-size: 13px;
48 | }
49 |
50 | .unit {
51 | color: #777;
52 | fill: #777;
53 | font-size: 18px;
54 | }
55 |
56 | a {
57 | text-decoration: none;
58 | color: inherit;
59 | }
60 |
61 | p {
62 | margin: 0;
63 | text-align: center;
64 | }
65 |
66 | iframe {
67 | width: 100%;
68 | border: none;
69 | height: calc(100vh - 132px);
70 | }
71 |
72 | owc-tabs {
73 | width: calc(100vw - 20px);
74 | }
75 |
76 | #badges {
77 | margin-top: 10px;
78 | }
79 |
80 | .desktop {
81 | display: none;
82 | }
83 |
84 | #details {
85 | display: none;
86 | }
87 |
88 | :host([show-details]) #details {
89 | display: block;
90 | }
91 |
92 | :host([show-details]) {
93 | margin: 0;
94 | padding: 10px;
95 | min-height: 100vh;
96 | width: 100vw;
97 | border-width: 0 1px;
98 | }
99 |
100 | :host([show-details]) h1 {
101 | margin-bottom: 10px;
102 | }
103 |
104 | :host([show-details]) #overview {
105 | display: none;
106 | }
107 |
108 | /* Grid */
109 |
110 | #overview,
111 | .info-grid {
112 | display: grid;
113 | grid-gap: 3px 10px;
114 | grid-template-columns: 1fr 1fr 1fr;
115 | grid-template-areas:
116 | 'info info info'
117 | 'dependencies githubStars lastRelease'
118 | 'downloadsNpm sizeGzip lastRelease';
119 | align-items: center;
120 | }
121 |
122 | #info {
123 | grid-area: info;
124 | }
125 |
126 | #lastRelease {
127 | grid-area: lastRelease;
128 | }
129 |
130 | #downloadsNpm {
131 | grid-area: downloadsNpm;
132 | }
133 |
134 | #dependencies {
135 | grid-area: dependencies;
136 | }
137 |
138 | #sizeGzip {
139 | grid-area: sizeGzip;
140 | }
141 |
142 | #githubStars {
143 | grid-area: githubStars;
144 | }
145 |
146 | #details {
147 | grid-area: details;
148 | }
149 |
150 | #links {
151 | display: grid;
152 | grid-template-columns: 130px 130px;
153 | grid-gap: 28px;
154 | }
155 |
156 | .link {
157 | border: 1px solid #ccc;
158 | width: 130px;
159 | height: 130px;
160 | }
161 |
162 | .link a {
163 | display: flex;
164 | flex-flow: column;
165 | height: 100%;
166 | align-items: center;
167 | justify-content: center;
168 | }
169 |
170 | .link__logo {
171 | width: 70px;
172 | height: 70px;
173 | margin-bottom: 10px;
174 | }
175 |
176 | .fake-url-bar {
177 | border: 7px solid #929292;
178 | display: block;
179 | padding: 5px;
180 | }
181 |
182 | @media only screen and (min-width: 768px) {
183 | h1 {
184 | font-size: 26px;
185 | }
186 |
187 | owc-tabs {
188 | width: auto;
189 | }
190 |
191 | #links {
192 | grid-template-columns: 130px 130px 130px 130px;
193 | }
194 |
195 | :host {
196 | border-radius: 10px;
197 | border: none;
198 | box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.2);
199 | }
200 |
201 | :host #overview {
202 | grid-gap: 20px;
203 | grid-template-columns: 4fr 1fr 1fr 1fr 1fr 1fr;
204 | grid-template-areas: 'info sizeGzip githubStars dependencies lastRelease downloadsNpm';
205 | }
206 |
207 | #lastRelease {
208 | min-width: 100px;
209 | }
210 |
211 | :host([show-details]) #overview {
212 | display: grid;
213 | }
214 |
215 | #details {
216 | margin-top: 15px;
217 | }
218 |
219 | :host([show-details]) {
220 | position: static;
221 | min-height: auto;
222 | min-width: auto;
223 | padding: 15px;
224 | margin: 15px 10px;
225 | width: auto;
226 | }
227 |
228 | :host([show-details]) h1 {
229 | margin-bottom: 10px;
230 | }
231 |
232 | .mobile {
233 | display: none;
234 | }
235 |
236 | .desktop {
237 | display: block;
238 | }
239 |
240 | .desktop--inline {
241 | display: inline;
242 | }
243 | }
244 | `;
245 |
--------------------------------------------------------------------------------
/packages/item/src/owc-cat-item.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html } from 'lit-element';
2 |
3 | import '@github/time-elements';
4 |
5 | import './remarkable-markdown.js';
6 | import './owc-tabs.js';
7 |
8 | import './logos/logo-github.js';
9 | import './logos/logo-npm.js';
10 | import './logos/logo-unpkg.js';
11 |
12 | import owcCatItemStyle from './owc-cat-item.css.js';
13 |
14 | const githubStar = html`
15 |
16 |
20 |
21 | `;
22 |
23 | export class OwcCatItem extends LitElement {
24 | static get properties() {
25 | return {
26 | name: { type: String },
27 | description: { type: String },
28 | size: { type: String },
29 | sizeGzip: { type: String },
30 | version: { type: String },
31 | versionTime: { type: String },
32 | flattenedDependencies: { type: Array },
33 | npmUrl: { type: String },
34 | unpkgUrl: { type: String },
35 | githubStars: { type: Number },
36 | githubUrl: { type: String },
37 | bundlephobiaUrl: { type: String },
38 | demoUrl: { type: String },
39 | showDetails: { type: Boolean, attribute: 'show-details', reflect: true },
40 | detailsTabIndex: { type: Number },
41 | };
42 | }
43 |
44 | constructor() {
45 | super();
46 | /**
47 | * A flag which decided if to show the overview or details style
48 | */
49 | this.showDetails = false;
50 | /**
51 | * Index of the open details tab
52 | * 0. Info
53 | * 1. Readme
54 | * 2. Demo
55 | * 3. Links
56 | * 4. Source
57 | */
58 | this.detailsTabIndex = 1;
59 | /**
60 | * Url that get shown in the demo tab
61 | */
62 | this.demoUrl = '';
63 | /**
64 | * A list of all flattened dependencies with versions.
65 | *
66 | * e.g. ['lit-element@2.x.x', 'lit-element@2.1.x', ...]
67 | *
68 | * @type {string[]}
69 | */
70 | this.flattenedDependencies = [];
71 | /**
72 | * The name of the web component. Needs to have a "-" in it.
73 | */
74 | this.name = '';
75 | /**
76 | * A Semver Version like "1.0.1" or "2.11.40"
77 | */
78 | this.version = '';
79 | this.description = '';
80 | this.size = 0;
81 | this.sizeGzip = 0;
82 | this.unpkgUrl = '';
83 | this.npmUrl = '';
84 | this.githubStars = 0;
85 | this.githubUrl = '';
86 | this.bundlephobiaUrl = '';
87 |
88 | this.addEventListener('click', this._handleClick.bind(this));
89 | }
90 |
91 | get dependenciesCount() {
92 | return this.flattenedDependencies.length / 4;
93 | }
94 |
95 | _handleClick(ev) {
96 | const path = ev.composedPath();
97 | if (!path.includes(this.shadowRoot.querySelector('#details'))) {
98 | this.toggle(ev);
99 | }
100 | if (path.includes(this.shadowRoot.querySelector('#details h1'))) {
101 | this.toggle(ev);
102 | }
103 | }
104 |
105 | toggle(ev) {
106 | if (ev) {
107 | ev.preventDefault();
108 | }
109 | this.showDetails = !this.showDetails;
110 | this.dispatchEvent(new Event('showDetailsChanged'));
111 | }
112 |
113 | renderRegisteredTypes() {
114 | return html`
115 | ${this.flattenedDependencies.map(type => {
116 | switch (type) {
117 | case 'lit-element@2.x.x':
118 | return html`
119 |
122 | `;
123 | case 'haunted@4.x.x':
124 | return html`
125 |
128 | `;
129 | default:
130 | return html``;
131 | }
132 | })}
133 | `;
134 | }
135 |
136 | __syncDetailsTabIndex() {
137 | this.detailsTabIndex = this.shadowRoot.querySelector('owc-tabs').activeIndex;
138 | this.dispatchEvent(new Event('detailsTabIndexChanged'));
139 | }
140 |
141 | render() {
142 | return html`
143 |
144 |
145 |
148 |
149 |
150 | ${this.description}
151 |
152 |
153 |
154 | ${this.renderRegisteredTypes()}
155 |
156 |
157 |
158 |
159 |
${this.dependenciesCount}dep
160 |
161 | ${this.dependenciesCount === 1 ? 'dependency' : 'dependencies'}
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 | ago
170 |
171 |
released on npm
172 |
173 |
174 |
175 |
${'?'}dl
176 |
downloads on npm
177 |
178 |
179 |
180 |
${(this.sizeGzip / 1024).toFixed(2)}kB
181 |
size gzipped
182 |
183 |
184 |
185 |
${this.githubStars}${githubStar}
186 |
on Github
187 |
188 |
189 |
190 |
191 |
194 |
195 |
200 | Info
201 |
202 |
203 | Description
204 | ${this.description}
205 | Badges
206 | ${this.renderRegisteredTypes()}
207 | Size gzipped
208 | ${(this.sizeGzip / 1024).toFixed(2)}
209 | Stars on Github
210 | ${this.githubStars}
211 | Dependencies
212 | ${this.dependenciesCount}
213 | Last release on npm
214 |
215 | Downloads on npm
216 | ?
217 |
218 |
219 |
220 | Readme
221 |
222 |
223 |
224 |
227 |
228 |
229 |
230 | Demo
231 |
232 | ${this.demoUrl
233 | ? html`
234 |
${this.demoUrl}
235 |
236 | `
237 | : `No demo could be found at demo/index.html. Neither a demoUrl in package.json was found. You can do it like that ...`}
238 |
239 |
240 | Links
241 |
242 |
243 |
249 |
255 |
261 |
267 |
268 |
269 |
270 | Source
271 |
275 |
276 |
277 | `;
278 | }
279 |
280 | static get styles() {
281 | return [owcCatItemStyle];
282 | }
283 | }
284 |
285 | customElements.define('owc-cat-item', OwcCatItem);
286 |
--------------------------------------------------------------------------------
/packages/item/src/owc-tabs.js:
--------------------------------------------------------------------------------
1 | import { LitElement, html, css } from 'lit-element';
2 |
3 | const debounce = (func, delay) => {
4 | let inDebounce;
5 | return function(...args) {
6 | const context = this;
7 | clearTimeout(inDebounce);
8 | inDebounce = setTimeout(() => func.apply(context, args), delay);
9 | };
10 | };
11 |
12 | export class OwcTabs extends LitElement {
13 | static get properties() {
14 | return {
15 | activeIndex: { type: Number },
16 | mode: { type: String, reflect: true },
17 | };
18 | }
19 |
20 | get tabs() {
21 | return [...this.querySelectorAll('[slot=tab]')];
22 | }
23 |
24 | get contents() {
25 | return [...this.querySelectorAll('[slot=tab-content]')];
26 | }
27 |
28 | constructor() {
29 | super();
30 | this.activeIndex = 0;
31 | this.mode = 'scroll'; // [scroll, display]
32 |
33 | this.addEventListener('click', this._onClick.bind(this));
34 | }
35 |
36 | _onClick(ev) {
37 | const path = ev.composedPath();
38 | const foundIndex = this.tabs.indexOf(path[0]);
39 | if (foundIndex !== -1) {
40 | this.activeIndex = foundIndex;
41 | this.dispatchEvent(new Event('activeIndexChanged'));
42 | }
43 | }
44 |
45 | __setAttributesForEls(activeIndex) {
46 | this.tabs.forEach((el, i) => {
47 | if (i === activeIndex) {
48 | el.setAttribute('owc-tabs-active', '');
49 | } else {
50 | el.removeAttribute('owc-tabs-active');
51 | }
52 | });
53 | this.contents.forEach((el, i) => {
54 | if (i === activeIndex) {
55 | el.setAttribute('owc-tabs-active', '');
56 | } else {
57 | el.removeAttribute('owc-tabs-active');
58 | }
59 | });
60 | }
61 |
62 | updated(changedProps) {
63 | super.updated(changedProps);
64 | if (changedProps.has('activeIndex')) {
65 | this.__setAttributesForEls(this.activeIndex);
66 | if (this.mode === 'scroll') {
67 | // this.contents[this.activeIndex].scrollIntoView({
68 | // behavior: 'smooth',
69 | // });
70 |
71 | this.wrapper.scrollLeft = this.contents[this.activeIndex].offsetLeft;
72 | this.tabsWrapper.scrollLeft = this.tabs[this.activeIndex].offsetLeft;
73 | }
74 | }
75 | }
76 |
77 | get tabsWrapper() {
78 | return this.shadowRoot.getElementById('tabs-wrapper');
79 | }
80 |
81 | get wrapper() {
82 | return this.shadowRoot.getElementById('wrapper');
83 | }
84 |
85 | connectedCallback() {
86 | super.connectedCallback();
87 | }
88 |
89 | firstUpdated() {
90 | super.firstUpdated();
91 | this.wrapper.addEventListener(
92 | 'scroll',
93 | debounce(() => {
94 | this.contents.forEach((el, i) => {
95 | if (this.wrapper.scrollLeft === el.offsetLeft) {
96 | this.activeIndex = i;
97 | }
98 | });
99 | }, 100),
100 | );
101 | }
102 |
103 | _updateSlotCount() {
104 | const visibleTabs = this.tabs.filter(tab => getComputedStyle(tab).display !== 'none');
105 | this.style.setProperty('--owc-tabs-count', visibleTabs.length);
106 | }
107 |
108 | render() {
109 | return html`
110 |
115 |
120 | `;
121 | }
122 |
123 | static get styles() {
124 | return css`
125 | :host {
126 | display: block;
127 | box-sizing: border-box;
128 |
129 | --owc-tabs-count: 2;
130 | }
131 |
132 | ::-webkit-scrollbar {
133 | height: 0;
134 | width: 0;
135 | background: transparent;
136 | }
137 |
138 | #tabs {
139 | display: flex;
140 | border-bottom: 2px solid #ccc;
141 | }
142 |
143 | #tabs ::slotted(*) {
144 | cursor: pointer;
145 | padding-bottom: 7px;
146 | margin-bottom: -2px;
147 | margin-right: 13px;
148 | text-transform: uppercase;
149 | }
150 |
151 | #tabs ::slotted([owc-tabs-active]) {
152 | border-bottom: 4px solid #217ff9;
153 | }
154 |
155 | #contents ::slotted(*) {
156 | display: none;
157 | }
158 |
159 | #contents ::slotted([owc-tabs-active]) {
160 | display: block;
161 | /* margin-top: 10px; */
162 | }
163 |
164 | /* mode = scroll */
165 | :host([mode='scroll']) #tabs-wrapper {
166 | overflow-x: scroll;
167 | scroll-behavior: smooth;
168 | }
169 |
170 | :host([mode='scroll']) #tabs {
171 | border: none;
172 | }
173 |
174 | /* :host([mode='scroll']) #tabs::before,
175 | :host([mode='scroll']) #tabs::after {
176 | content: '';
177 | display: block;
178 | padding: 5px;
179 | } */
180 |
181 | :host([mode='scroll']) #wrapper {
182 | scroll-snap-type: x mandatory;
183 | overflow-x: scroll;
184 | scroll-behavior: smooth;
185 | }
186 |
187 | :host([mode='scroll']) #contents {
188 | display: flex;
189 | /* flex-flow: column; */
190 | width: calc(100% * var(--owc-tabs-count));
191 | }
192 |
193 | :host([mode='scroll']) #contents ::slotted(*) {
194 | scroll-snap-align: start;
195 | display: block;
196 | /* width: 100%; */
197 | }
198 | `;
199 | }
200 | }
201 |
202 | customElements.define('owc-tabs', OwcTabs);
203 |
--------------------------------------------------------------------------------
/packages/item/src/remarkable-markdown.js:
--------------------------------------------------------------------------------
1 | import { Remarkable } from 'remarkable';
2 |
3 | import githubMarkdownCss from './github-markdown.css.js';
4 |
5 | /**
6 | * @param {string} text
7 | * @return {string}
8 | */
9 | function unindent(_text) {
10 | const text = _text.trim();
11 | if (!text) {
12 | return text;
13 | }
14 | const lines = text.replace(/\t/g, ' ').split('\n');
15 | const indent = lines.reduce((prev, line) => {
16 | if (/^\s*$/.test(line)) {
17 | return prev; // Completely ignore blank lines.
18 | }
19 |
20 | const lineIndent = line.match(/^(\s*)/)[0].length;
21 | if (prev === null) {
22 | return lineIndent;
23 | }
24 | return lineIndent < prev ? lineIndent : prev;
25 | }, null);
26 |
27 | return lines.map(l => l.substr(indent)).join('\n');
28 | }
29 |
30 | export class RemarkableMarkdown extends HTMLElement {
31 | constructor() {
32 | super();
33 | this.attachShadow({ mode: 'open' });
34 | }
35 |
36 | connectedCallback() {
37 | this.__md = new Remarkable();
38 |
39 | this._srcEl = this.querySelector('[type="text/markdown"]');
40 | if (!this._srcEl) {
41 | throw new Error('You need to provide');
42 | }
43 |
44 | if (this._srcEl.text) {
45 | const src = unindent(this._srcEl.text);
46 | this.markdown = this.__md.render(src);
47 | }
48 | this.shadowRoot.innerHTML = `
49 |
50 | ${this.markdown}
51 |
52 |
55 | `;
56 | }
57 | }
58 |
59 | customElements.define('remarkable-markdown', RemarkableMarkdown);
60 |
--------------------------------------------------------------------------------
/packages/item/stories/index.mdx:
--------------------------------------------------------------------------------
1 | import { Meta, Story, Preview, DocsStory } from '@storybook/addon-docs/blocks';
2 |
3 | # Item
4 |
5 | This shows all the info users are actually after.
6 |
7 | ## List View
8 |
9 | In the list view only the most important information is shown
10 |
11 | - Name
12 | - Version
13 | - Description
14 | - Highlight known libraries (like lit-element, haunted, stencil, ...)
15 | - Download Time (how much time it will take to download it on 3G)
16 | - Gzipped Size in kB
17 | - Github stars
18 |
19 | With this, we hope to give you all the information you need to decide which web component
20 | look like a good fit for your use case. For each of those, you will hopefully explore the details.
21 |
22 | #### Single item
23 |
24 |
25 |
26 |
27 |
28 | #### Multiple items
29 |
30 | In most cases you will see multiple items after each other.
31 |
32 |
33 |
34 |
35 |
36 | ## Detail View
37 |
38 | Here we are adding more information.
39 |
40 | #### Readme
41 |
42 | This renders the README.md in the root of the package.
43 |
44 |
45 |
46 |
47 |
48 | #### Demo
49 |
50 | The demo is a very important part of every web component.
51 | By default, if we find a `demo/index.html` file we will show it via unpkg.com with the module flag.
52 | We do NOT do any magic on it. e.g. you will need to make sure it works directly via unpkg.
53 | This usually needs to use imports directly from unpkg.
54 |
55 | So if you wanna show demos with using properties you can do it like so.
56 |
57 | ```html
58 |
59 |
60 |
72 | ```
73 |
74 | In case you wanna share the same demo with multiple packages you can set a `demoUrl` in your `package.json`.
75 | This demo url will take precedence over the default url.
76 |
77 | ```js
78 | // package.json
79 | {
80 | "demoUrl": "http://this-is-my-demo-2.0.1.domain.com"
81 | }
82 | ```
83 |
84 | Note that this means that you will need to use versioning yourself if you wish to have different demos for different versions.
85 |
86 | If your demo becomes big you can also publish it as a separate npm package and link it with a demoUrl.
87 | For example you want to published `@lib/my-button@1.2.2` then you could publish `@lib-demos/my-button@1.2.2`.
88 | So setting the `demoUrl` on `@lib/my-button` to `https://unpkg.com/@lib-demos/my-button@1.2.2/index.html?module` will give you versioned
89 | demos without adding weight to your source package.
90 |
91 | Be sure to NOT have a `custom-element.json` in `@lib-demos` as you don't want to have this package indexed in the catalog.
92 |
93 | Ok finally let's look at an actual demo
94 |
95 |
96 |
97 |
98 |
99 | #### Links
100 |
101 | Here we link to some of the resource you might be interested in
102 |
103 | - Github
104 | - Npm
105 | - Bundlephobia
106 | - Unpkg
107 |
108 |
109 |
110 |
111 |
112 | #### Source
113 |
114 | Directly look at the source files at unpkg.com
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/packages/item/stories/index.stories.js:
--------------------------------------------------------------------------------
1 | // import { forceReRender } from '@storybook/polymer';
2 | import { html } from 'lit-html';
3 | import mdx from './index.mdx';
4 |
5 | import '../src/owc-cat-item.js';
6 |
7 | export default {
8 | title: 'Item',
9 | parameters: {
10 | docs: {
11 | page: mdx,
12 | },
13 | },
14 | };
15 |
16 | export const tabs = () =>
17 | html`
18 |
19 | foo
20 | foo content
21 | bar
22 | bar content
23 | bar
24 | bar content
25 | bar
26 | bar content
27 |
28 | `;
29 |
30 | export const single = () =>
31 | html`
32 |
48 | `;
49 |
50 | export const three = () =>
51 | html`
52 |
68 |
83 |
99 | `;
100 |
101 | export const info = () =>
102 | html`
103 |
121 | `;
122 |
123 | export const readme = () =>
124 | html`
125 |
143 | `;
144 |
145 | export const demo = () =>
146 | html`
147 |
165 | `;
166 |
167 | export const links = () =>
168 | html`
169 |
187 | `;
188 |
189 | export const source = () =>
190 | html`
191 |
209 | `;
210 |
--------------------------------------------------------------------------------
/research/elasticIndex.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "test-wc-card$0.0.6",
3 | "name": "test-wc-card",
4 | "version": "0.0.6",
5 | "description": "Some text about",
6 | "authors": ["Thomas Allmer", "open-wc"],
7 | "homepage": "http://foo.com",
8 | "licence": "MIT",
9 | "module": true,
10 | "main": true,
11 | "dependencies": [
12 | "lit-element$2.x.x",
13 | "lit-element$2.0.x",
14 | "lit-element$2.0.1",
15 | "lit-element$^2.0.1",
16 | "lit-html$1.x.x",
17 | "lit-html$1.0.x",
18 | "lit-html$1.0.0",
19 | "lit-html$^1.0.0"
20 | ],
21 | "customElements": ["test-wc-card", "other-card"],
22 | "attributes": ["header", "foo"],
23 | "properties": ["side"]
24 | }
25 |
--------------------------------------------------------------------------------
/research/faunadb.txt:
--------------------------------------------------------------------------------
1 | # search function
2 | Map(
3 | Paginate(
4 | Filter(
5 | Match(Index("allCustomElements")),
6 | Lambda(
7 | "i",
8 | Not(
9 | Equals(FindStr(Select(["data", "name"], Get(Var("i"))), "test"), -1)
10 | )
11 | )
12 | )
13 | ),
14 | Lambda("X", Get(Var("X")))
15 | )
--------------------------------------------------------------------------------
/research/normalized/db.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | import { GraphQLClient } from 'graphql-request';
3 |
4 | const endpoint = 'https://graphql.fauna.com/graphql';
5 | const graphQLClient = new GraphQLClient(endpoint, {
6 | headers: {
7 | authorization: 'Bearer fnADYc2oY-ACAjR9EnRZT2RrGt5FifqJDpT2UM8H',
8 | },
9 | });
10 |
11 | /**
12 | * Object => GraphQL query
13 | *
14 | * @param {mixed} obj
15 | * @return template string.
16 | */
17 | const queryfy = obj => {
18 | if (typeof obj === 'number') {
19 | return obj;
20 | }
21 |
22 | if (Array.isArray(obj)) {
23 | const props = obj.map(value => `${queryfy(value)}`).join(',');
24 | return `[${props}]`;
25 | }
26 |
27 | if (typeof obj === 'object') {
28 | const props = Object.keys(obj)
29 | .map(key => `${key}:${queryfy(obj[key])}`)
30 | .join(',');
31 | return `{${props}}`;
32 | }
33 |
34 | return JSON.stringify(obj);
35 | };
36 |
37 | export async function dbHasPackage(id) {
38 | const result = await graphQLClient.request(`
39 | query {
40 | packageById(id: "${id}") {
41 | data {
42 | name
43 | }
44 | }
45 | }
46 | `);
47 | return result.packageById.data.length !== 0;
48 | }
49 |
50 | export async function dbGetPackage(id) {
51 | const result = await graphQLClient.request(`
52 | query {
53 | packageById(id: "${id}") {
54 | data {
55 | _id
56 | name
57 | description
58 | version
59 | module
60 | license
61 | customElements {
62 | data {
63 | name
64 | }
65 | }
66 | }
67 | }
68 | }
69 | `);
70 | return result.packageById.data;
71 | }
72 |
73 | export async function dbGetAllIdsForPackage(id) {
74 | const result = await graphQLClient.request(`
75 | query getAllIds {
76 | packageById(id: "${id}") {
77 | data {
78 | _id
79 | customElements {
80 | data {
81 | _id
82 | attributes {
83 | data {
84 | _id
85 | values {
86 | data {
87 | _id
88 | }
89 | }
90 | }
91 | }
92 | properties {
93 | data {
94 | _id
95 | values {
96 | data {
97 | _id
98 | }
99 | }
100 | }
101 | }
102 | }
103 | }
104 | }
105 | }
106 | }
107 | `);
108 | return result;
109 | }
110 |
111 | // TODO: split up this horrible giant function
112 | export async function dbAddPackage(pkg) {
113 | const customElementsCreate = [];
114 |
115 | pkg.customElements.tags.forEach(tag => {
116 | const attributesCreate = [];
117 | tag.attributes.forEach(attribute => {
118 | const newAttribute = {
119 | name: attribute.name,
120 | type: attribute.type,
121 | };
122 | if (attribute.values) {
123 | newAttribute.values = { create: attribute.values };
124 | }
125 | attributesCreate.push(newAttribute);
126 | });
127 | const propertiesCreate = [];
128 | tag.properties.forEach(property => {
129 | const newProperty = {
130 | name: property.name,
131 | type: property.type,
132 | };
133 | if (property.attribute) {
134 | newProperty.attribute = property.attribute;
135 | }
136 | if (property.reflect) {
137 | newProperty.reflect = property.reflect;
138 | }
139 | if (property.values) {
140 | newProperty.values = { create: property.values };
141 | }
142 | propertiesCreate.push(newProperty);
143 | });
144 |
145 | const newTag = {
146 | id: `${tag.name}$${pkg.version}`,
147 | name: tag.name,
148 | attributes: {
149 | create: attributesCreate,
150 | },
151 | properties: {
152 | create: propertiesCreate,
153 | },
154 | };
155 | customElementsCreate.push(newTag);
156 | });
157 |
158 | const data = {
159 | id: `${pkg.name}$${pkg.version}`,
160 | name: pkg.name,
161 | version: pkg.version,
162 | packageJsonString: JSON.stringify(pkg.packageJson),
163 | customElementsString: JSON.stringify(pkg.customElements),
164 | customElements: {
165 | create: customElementsCreate,
166 | },
167 | };
168 | if (pkg.description) {
169 | data.description = pkg.description;
170 | }
171 | if (pkg.homepage) {
172 | data.homepage = pkg.homepage;
173 | }
174 | if (pkg.license) {
175 | data.license = pkg.license;
176 | }
177 | if (pkg.module) {
178 | data.module = pkg.module;
179 | }
180 | if (pkg.main) {
181 | data.main = pkg.main;
182 | }
183 | if (pkg.size) {
184 | data.size = pkg.size;
185 | }
186 | if (pkg.sizeGzip) {
187 | data.sizeGzip = pkg.sizeGzip;
188 | }
189 |
190 | const query = `
191 | mutation createPackage {
192 | createPackage(
193 | data: ${queryfy(data)}
194 | ) {
195 | name
196 | id
197 | _id
198 | customElements {
199 | data {
200 | id
201 | name
202 | attributes {
203 | data {
204 | name
205 | type
206 | }
207 | }
208 | properties {
209 | data {
210 | name
211 | type
212 | attribute
213 | reflect
214 | }
215 | }
216 | }
217 | }
218 | }
219 | }
220 | `;
221 | console.log(`Adding Package ${pkg.name}@${pkg.version}`);
222 | pkg.customElements.tags.forEach(tag => {
223 | console.log(`Adding CustomElement ${tag.name}`);
224 | });
225 | console.log('---');
226 |
227 | try {
228 | await graphQLClient.request(query);
229 | } catch (err) {
230 | console.log('## Could not execute `createPackage` on db', err);
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/research/normalized/schema.gql:
--------------------------------------------------------------------------------
1 | type Package {
2 | id: String! @unique
3 | name: String!
4 | version: String!
5 | time: String
6 | description: String
7 | homepage: String
8 | license: String
9 | hasModule: Boolean
10 | hasMain: Boolean
11 | module: String
12 | main: String
13 | size: Int
14 | sizeGzip: Int
15 | packageJsonString: String
16 | customElementsString: String
17 | customElements: [CustomElement!]! @relation
18 | }
19 |
20 | type ValueSet {
21 | name: String!
22 | attribute: Attribute
23 | property: Property
24 | }
25 |
26 | type Attribute {
27 | name: String!
28 | type: String!
29 | values: [ValueSet!] @relation
30 | customElement: CustomElement
31 | deprecated: Boolean
32 | deprecatedMessage: String
33 | }
34 |
35 | type Property {
36 | name: String!
37 | type: String!
38 | attribute: String
39 | reflect: Boolean
40 | values: [ValueSet!] @relation
41 | customElement: CustomElement
42 | deprecated: Boolean
43 | deprecatedMessage: String
44 | }
45 |
46 | type CustomElement {
47 | id: String! @unique
48 | name: String!
49 | attributes: [Attribute!]! @relation
50 | properties: [Property!]! @relation
51 | package: Package
52 | }
53 |
54 | type Query {
55 | allAttributes: [Attribute!]
56 | allProperties: [Property!]
57 | allValueSets: [ValueSet!]
58 |
59 | allPackages: [Package!]
60 | packageById(id: String!): [Package!]
61 | packageByName(name: String!): [Package!]
62 |
63 | allCustomElements: [CustomElement!]
64 | customElementById(id: String!): [CustomElement!]
65 | customElementByName(name: String!): [CustomElement!]
66 | }
67 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-extraneous-dependencies */
2 | // @ts-nocheck
3 |
4 | const { DEFAULT_EXTENSIONS } = require('@babel/core');
5 | const { findSupportedBrowsers } = require('@open-wc/building-utils');
6 | const resolve = require('rollup-plugin-node-resolve');
7 | const { terser } = require('rollup-plugin-terser');
8 | const babel = require('rollup-plugin-babel');
9 | const indexHTML = require('rollup-plugin-index-html');
10 |
11 | const production = !process.env.ROLLUP_WATCH;
12 |
13 | /**
14 | * @typedef {ConfigOptions}
15 | * @param {*} _options
16 | * @param {*} legacy
17 | */
18 |
19 | module.exports = function createBasicConfig(_options) {
20 | const options = {
21 | outputDir: 'dist',
22 | extensions: DEFAULT_EXTENSIONS,
23 | indexHTMLPlugin: {},
24 | ..._options,
25 | };
26 |
27 | return {
28 | input: 'index.html',
29 | treeshake: !!production,
30 | output: {
31 | dir: options.outputDir,
32 | format: 'esm',
33 | sourcemap: true,
34 | dynamicImportFunction: 'importShim',
35 | },
36 | plugins: [
37 | // parse input index.html as input and feed any modules found to rollup
38 | indexHTML({
39 | ...(options.indexHTMLPlugin || {}),
40 | polyfills: {
41 | ...((options.indexHTMLPlugin && options.indexHTMLPlugin.polyfills) || {}),
42 | dynamicImport: true,
43 | webcomponents: true,
44 | },
45 | }),
46 |
47 | // resolve bare import specifiers
48 | resolve({
49 | extensions: options.extensions,
50 | }),
51 |
52 | // run code through babel
53 | babel({
54 | extensions: options.extensions,
55 | plugins: [
56 | '@babel/plugin-syntax-dynamic-import',
57 | '@babel/plugin-syntax-import-meta',
58 | // rollup rewrites import.meta.url, but makes them point to the file location after bundling
59 | // we want the location before bundling
60 | 'bundled-import-meta',
61 | ].filter(_ => !!_),
62 |
63 | presets: [
64 | [
65 | '@babel/preset-env',
66 | {
67 | targets: findSupportedBrowsers(),
68 | // preset-env compiles template literals for safari 12 due to a small bug which
69 | // doesn't affect most use cases. for example lit-html handles it: (https://github.com/Polymer/lit-html/issues/575)
70 | exclude: ['@babel/plugin-transform-template-literals'],
71 | useBuiltIns: false,
72 | modules: false,
73 | },
74 | ],
75 | ],
76 | }),
77 |
78 | // only minify if in production
79 | production && terser(),
80 | ],
81 | };
82 | };
83 |
--------------------------------------------------------------------------------
/schema.gql:
--------------------------------------------------------------------------------
1 | type Package {
2 | id: String! @unique
3 | name: String!
4 | version: String!
5 | description: String
6 | homepage: String
7 | license: String
8 | hasModule: Boolean
9 | hasMain: Boolean
10 | module: String
11 | main: String
12 | size: Int
13 | sizeGzip: Int
14 | githubStars: Int
15 | githubWatchers: Int
16 | githubForks: Int
17 | githubUrl: String
18 | unpkgUrl: String
19 | npmUrl: String
20 | packageJsonString: String
21 | customElementsString: String
22 | versionTime: String
23 | readme: String
24 | demoUrl: String
25 | flattenedDependencies: String
26 | }
27 |
28 | type Query {
29 | allPackages: [Package!]
30 | packageById(id: String!): [Package!]
31 | packageByName(name: String!): [Package!]
32 | }
33 |
--------------------------------------------------------------------------------
/src/lambda/check-package-with-version.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console, no-await-in-loop */
2 | import semver from 'semver';
3 |
4 | import { aRequest, aTimeout, githubRequest } from './helpers/helpers.js';
5 | import { dbAddPackage, dbHasPackage, dbDeletePackage } from './helpers/db.js';
6 | import { searchUpdatePackage } from './helpers/elastic.js';
7 | import { npmIsValidVersion, isLatestNpmVersion } from './helpers/npm.js';
8 |
9 | const customElementsFile = 'custom-elements.json';
10 |
11 | function isValidPackageId(_id) {
12 | let id = _id;
13 | if (!id) {
14 | return false;
15 | }
16 | if (id[0] === '@') {
17 | // remove npm scope @
18 | id = id.substr(1);
19 | }
20 | return id.includes('@');
21 | }
22 |
23 | async function getSizes(name, version) {
24 | const result = await aRequest(`https://bundlephobia.com/api/size?package=${name}@${version}`);
25 | const data = JSON.parse(result);
26 | return {
27 | size: data.size,
28 | sizeGzip: data.gzip,
29 | dependencySizes: data.dependencySizes,
30 | };
31 | }
32 |
33 | // TODO: get downloads from NPM
34 | // https://github.com/npm/registry/blob/master/docs/download-counts.md
35 |
36 | // TODO: will need to rotate Github API keys because of rate limits
37 | // potentially switch to v4 API with GraphQL (rate limits are worse so only if queried info is worth it)
38 | // https://developer.github.com/v4/explorer/
39 | async function getGithubInfo(pkg) {
40 | if (pkg.repository && pkg.repository.url && pkg.repository.url.includes('github.com/')) {
41 | const { url } = pkg.repository;
42 | const start = url.indexOf('github.com/') + 11;
43 | const end = url.lastIndexOf('.git');
44 | const repoName = url.substring(start, end);
45 |
46 | const data = await githubRequest(`/repos/${repoName}`);
47 | return {
48 | githubUrl: `https://github.com/${repoName}`,
49 | githubStars: data.stargazers_count,
50 | githubWatchers: data.watchers_count,
51 | githubForks: data.forks_count,
52 | githubOpenIssues: data.open_issues_count,
53 | githubLastPush: data.pushed_at,
54 | };
55 | }
56 | return {};
57 | }
58 |
59 | function flattenDependencies(deps) {
60 | const flattened = [];
61 | Object.keys(deps).forEach(dep => {
62 | const val = deps[dep];
63 | const versionData = semver.minVersion(val);
64 | flattened.push(`${dep}@${versionData.major}.x.x`);
65 | flattened.push(`${dep}@${versionData.major}.${versionData.minor}.x`);
66 | flattened.push(`${dep}@${versionData.major}.${versionData.minor}.${versionData.patch}`);
67 | flattened.push(`${dep}@${val}`);
68 | });
69 | return flattened;
70 | }
71 |
72 | async function getDemoUrl(packageJson) {
73 | const { name, version, demoUrl } = packageJson;
74 | if (demoUrl) {
75 | return demoUrl;
76 | }
77 | const potentialDemoUrl = `https://unpkg.com/${name}@${version}/demo/index.html?module`;
78 | const demoResponse = await aRequest(potentialDemoUrl);
79 | let findDemoUrl = '';
80 | if (demoResponse !== `Cannot find "/demo/index.html" in ${name}@${version}`) {
81 | findDemoUrl = potentialDemoUrl;
82 | }
83 | return findDemoUrl;
84 | }
85 |
86 | export async function handler(ev) {
87 | const { id } = ev.queryStringParameters;
88 |
89 | if (!isValidPackageId(id)) {
90 | return {
91 | statusCode: 200,
92 | body: `"${id}" is not valid. Format is like "my-npm-package@1.0.0" or "my-scope@my-npm-package@1.0.0"`,
93 | };
94 | }
95 |
96 | const splitIndex = id.lastIndexOf('@');
97 | const name = id.substr(0, splitIndex);
98 | const version = id.substr(splitIndex + 1);
99 | const ceFileUrl = `https://unpkg.com/${name}@${version}/${customElementsFile}`;
100 | const npmMetaDataUrl = `https://registry.npmjs.org/${name}`;
101 | const packageMetaDataString = await aRequest(npmMetaDataUrl);
102 | const packageMetaData = JSON.parse(packageMetaDataString);
103 |
104 | if (!npmIsValidVersion(packageMetaData, version)) {
105 | return {
106 | statusCode: 200,
107 | body: `Version ${version} for "${name}" could not be found. Are you sure it is published?`,
108 | };
109 | }
110 |
111 | let unpkgResponse;
112 | // if unpkg does not have the package yet then wait/fetch with increasing intervales
113 | try {
114 | const timeouts = [10, 100, 1000, 2000];
115 | unpkgResponse = await aRequest(ceFileUrl);
116 | if (unpkgResponse === `Cannot find package ${name}@${version}`) {
117 | let i = 0;
118 | do {
119 | unpkgResponse = await aRequest(ceFileUrl);
120 | await aTimeout(timeouts[i]);
121 | i += 1;
122 | } while (unpkgResponse === `Cannot find package ${name}@${version}` && i < timeouts.length);
123 | }
124 | if (unpkgResponse === `Cannot find package ${name}@${version}`) {
125 | return {
126 | statusCode: 200,
127 | body: `Package "${name}@${version}" was not found. Is it published? We checked via https://unpkg.com/${name}@${version}/`,
128 | };
129 | }
130 | } catch (err) {
131 | return {
132 | statusCode: 200,
133 | body: `We could not reach unpkg.com. Please try again later.`,
134 | };
135 | }
136 |
137 | if (unpkgResponse === `Cannot find "/${customElementsFile}" in ${name}@${version}`) {
138 | return {
139 | statusCode: 200,
140 | body: `Package "${name}@${version}" does not have a ${customElementsFile} and can therefore not be added to the web components catalog.`,
141 | };
142 | }
143 |
144 | if (unpkgResponse[0] === '{') {
145 | const packageJson = packageMetaData.versions[version];
146 | const customElements = JSON.parse(unpkgResponse);
147 | let body = `The package "${id}" got processed:`;
148 |
149 | const sizes = await getSizes(name, version);
150 | const githubInfo = await getGithubInfo(packageJson);
151 | const readme = await aRequest(`https://unpkg.com/${name}@${version}/README.md`);
152 | const demoUrl = await getDemoUrl(packageJson);
153 |
154 | const pkg = {
155 | ...packageJson,
156 | ...sizes,
157 | ...githubInfo,
158 | readme,
159 | demoUrl,
160 | versionTime: packageMetaData.time[version],
161 | customElements,
162 | packageJson,
163 | // TODO: gather nested dependencies instead of using packageJson.dependencies
164 | // mimic https://github.com/npm/npm-remote-ls/blob/master/lib/remote-ls.js without all the heavy dependencies
165 | flattenedDependencies: flattenDependencies(
166 | packageJson.dependencies ? packageJson.dependencies : {},
167 | ),
168 | };
169 |
170 | if (!(await dbHasPackage(id))) {
171 | await dbAddPackage(pkg);
172 | body += `- added to database`;
173 | } else {
174 | await dbDeletePackage(id);
175 | await dbAddPackage(pkg);
176 | body += `- updated in database`;
177 | }
178 |
179 | // TODO: better heuristic? check for version in elastic if lower then updated
180 | // even if not latest... is that better?
181 | if (isLatestNpmVersion(packageMetaData, version)) {
182 | await searchUpdatePackage(pkg);
183 | body += `- updated search index (as this is the latest version)`;
184 | }
185 |
186 | return {
187 | statusCode: 200,
188 | body,
189 | };
190 | }
191 |
192 | return {
193 | statusCode: 200,
194 | body: `Unknown error - something went seriously wrong :/ please file an issue.`,
195 | };
196 | }
197 |
--------------------------------------------------------------------------------
/src/lambda/helpers/db.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | import { GraphQLClient } from 'graphql-request';
3 |
4 | const endpoint = 'https://graphql.fauna.com/graphql';
5 |
6 | // TODO: use just fetch
7 | const graphQLClient = new GraphQLClient(endpoint, {
8 | headers: {
9 | // Key Name/ID: Public Full Access
10 | authorization: 'Bearer fnADZViyjRACCwKie9EtJDoUOW2TCzFn0FzR5mH1',
11 | },
12 | });
13 |
14 | /**
15 | * Object => GraphQL query
16 | *
17 | * @param {mixed} obj
18 | * @return template string.
19 | */
20 | const queryfy = obj => {
21 | if (typeof obj === 'number') {
22 | return obj;
23 | }
24 |
25 | if (Array.isArray(obj)) {
26 | const props = obj.map(value => `${queryfy(value)}`).join(',');
27 | return `[${props}]`;
28 | }
29 |
30 | if (typeof obj === 'object') {
31 | const props = Object.keys(obj)
32 | .map(key => `${key}:${queryfy(obj[key])}`)
33 | .join(',');
34 | return `{${props}}`;
35 | }
36 |
37 | return JSON.stringify(obj);
38 | };
39 |
40 | export async function dbHasPackage(id) {
41 | const result = await graphQLClient.request(`
42 | query {
43 | packageById(id: "${id}") {
44 | data {
45 | _id
46 | }
47 | }
48 | }
49 | `);
50 | return result.packageById.data.length !== 0;
51 | }
52 |
53 | export async function dbGetPackage(id) {
54 | const result = await graphQLClient.request(`
55 | query {
56 | packageById(id: "${id}") {
57 | data {
58 | _id
59 | name
60 | description
61 | version
62 | module
63 | license
64 | size
65 | sizeGzip
66 | packageJsonString
67 | customElementsString
68 | githubStars
69 | githubWatchers
70 | githubForks
71 | githubUrl
72 | unpkgUrl
73 | npmUrl
74 | versionTime
75 | readme
76 | demoUrl
77 | flattenedDependencies
78 | }
79 | }
80 | }
81 | `);
82 | return result.packageById.data[0];
83 | }
84 |
85 | export async function dbDeletePackage(id) {
86 | const packageInfo = await dbGetPackage(id);
87 | await graphQLClient.request(`
88 | mutation {
89 | deletePackage(id: "${packageInfo._id}") {
90 | _id
91 | }
92 | }
93 | `);
94 | }
95 |
96 | export async function dbAddPackage(pkg) {
97 | const data = {
98 | id: `${pkg.name}@${pkg.version}`,
99 | name: pkg.name,
100 | version: pkg.version,
101 | packageJsonString: JSON.stringify(pkg.packageJson),
102 | customElementsString: JSON.stringify(pkg.customElements),
103 | unpkgUrl: `https://unpkg.com/${pkg.name}@${pkg.version}/`,
104 | npmUrl: `https://www.npmjs.com/package/${pkg.name}/v/${pkg.version}`,
105 | flattenedDependencies: JSON.stringify(pkg.flattenedDependencies),
106 | };
107 | const addIfFound = [
108 | 'description',
109 | 'homepage',
110 | 'licence',
111 | 'module',
112 | 'main',
113 | 'size',
114 | 'sizeGzip',
115 | 'githubStars',
116 | 'githubWatchers',
117 | 'githubForks',
118 | 'githubUrl',
119 | 'versionTime',
120 | 'readme',
121 | 'demoUrl',
122 | ];
123 | addIfFound.forEach(item => {
124 | if (pkg[item] !== undefined) {
125 | data[item] = pkg[item];
126 | }
127 | });
128 |
129 | const query = `
130 | mutation createPackage {
131 | createPackage(
132 | data: ${queryfy(data)}
133 | ) {
134 | _id
135 | }
136 | }
137 | `;
138 |
139 | try {
140 | await graphQLClient.request(query);
141 | } catch (err) {
142 | console.log('## Could not execute `createPackage` on db', err);
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/lambda/helpers/elastic.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console, no-await-in-loop */
2 |
3 | import fetch from 'node-fetch';
4 |
5 | // // playground login
6 | // const username = '6VWygp4joU';
7 | // const password = '9NBHqCyR6EVxhWLu7tArK53F';
8 |
9 | function elastic(_url, method = 'GET', body = null) {
10 | const url = `https://search-catalog-testing-xty6jjvifguawmpk2f7iop3b2a.us-east-2.es.amazonaws.com${_url}`;
11 | const headers = {
12 | 'Content-Type': 'application/json',
13 | // Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`,
14 | };
15 | const options = { method, headers };
16 |
17 | if (body) {
18 | options.body = JSON.stringify(body);
19 | }
20 |
21 | return new Promise((resolve, reject) => {
22 | fetch(url, options)
23 | .then(res => res.json())
24 | .then(json => {
25 | resolve(json);
26 | })
27 | .catch(err => {
28 | console.error(JSON.stringify(err, null, 2));
29 | reject();
30 | });
31 | });
32 | }
33 |
34 | function elasticPut(_url, body) {
35 | return elastic(_url, 'PUT', body);
36 | }
37 |
38 | function elasticPost(_url, body) {
39 | return elastic(_url, 'POST', body);
40 | }
41 |
42 | function parseV2CustomElements(customElements) {
43 | const data = {
44 | tags: [],
45 | };
46 |
47 | customElements.tags.forEach(tag => {
48 | data.tags.push(tag.name);
49 |
50 | if (tag.attributes) {
51 | data.attributes = tag.attributes.reduce((acc, attr) => [...acc, attr.name], []);
52 | }
53 | if (tag.properties) {
54 | data.properties = tag.properties.reduce((acc, attr) => [...acc, attr.name], []);
55 | }
56 | });
57 |
58 | return data;
59 | }
60 |
61 | function createFlatSearchablePackage(pkg) {
62 | const flatPkg = {
63 | id: `${pkg.name}@${pkg.version}`,
64 | name: pkg.name,
65 | version: pkg.version,
66 | module: !!pkg.module,
67 | main: !!pkg.main,
68 | hasDemo: !!pkg.demoUrl,
69 | };
70 | if (pkg.customElements && pkg.customElements.version === 2) {
71 | Object.assign(flatPkg, parseV2CustomElements(pkg.customElements));
72 | }
73 |
74 | const addIfFound = [
75 | 'description',
76 | 'homepage',
77 | 'licence',
78 | 'size',
79 | 'sizeGzip',
80 | 'githubStars',
81 | 'githubWatchers',
82 | 'githubForks',
83 | 'versionTime',
84 | 'readme',
85 | 'flattenedDependencies',
86 | ];
87 | addIfFound.forEach(item => {
88 | if (pkg[item] !== undefined) {
89 | flatPkg[item] = pkg[item];
90 | }
91 | });
92 |
93 | return flatPkg;
94 | }
95 |
96 | export async function searchUpdatePackage(pkg) {
97 | const searchablePkg = createFlatSearchablePackage(pkg);
98 |
99 | const result = await elasticPut(`/packages/_doc/${pkg.name}`, searchablePkg);
100 | if (result && result.error) {
101 | console.error(result.error);
102 | throw new Error('Could not index file');
103 | }
104 | return result;
105 | }
106 |
107 | export async function search(queryString) {
108 | const result = await elasticPost('/packages/_search', {
109 | query: {
110 | multi_match: {
111 | query: queryString,
112 | fields: ['name^4', 'description^2', 'homepage', 'tags', 'attributes', 'properties'],
113 | fuzziness: 3,
114 | prefix_length: 1,
115 | },
116 | },
117 | });
118 | return result;
119 | }
120 |
121 | export async function searchFilterDependencies(queryString, dependencies) {
122 | const should = dependencies.map(dep => ({
123 | match_phrase: {
124 | flattenedDependencies: {
125 | query: dep,
126 | },
127 | },
128 | }));
129 |
130 | const filter = {
131 | bool: {
132 | should,
133 | },
134 | };
135 |
136 | const result = await elasticPost('/packages/_search', {
137 | query: {
138 | bool: {
139 | must: {
140 | multi_match: {
141 | query: queryString,
142 | fields: ['name^4', 'description^2', 'homepage', 'tags', 'attributes', 'properties'],
143 | fuzziness: 3,
144 | prefix_length: 1,
145 | },
146 | },
147 | filter,
148 | },
149 | },
150 | });
151 | return result;
152 | }
153 |
--------------------------------------------------------------------------------
/src/lambda/helpers/helpers.js:
--------------------------------------------------------------------------------
1 | import fetch from 'node-fetch';
2 | import https from 'https';
3 |
4 | // TODO: use fetch
5 | export const aRequest = url =>
6 | new Promise((resolve, reject) => {
7 | https
8 | .get(url, resp => {
9 | let data = '';
10 | resp.on('data', chunk => {
11 | data += chunk;
12 | });
13 |
14 | resp.on('end', () => {
15 | resolve(data);
16 | });
17 | })
18 | .on('error', err => {
19 | reject(err.message);
20 | });
21 | });
22 |
23 | export function aTimeout(ms) {
24 | return new Promise(resolve => {
25 | setTimeout(resolve, ms);
26 | });
27 | }
28 |
29 | export function githubRequest(_url) {
30 | const url = `https://api.github.com${_url}`;
31 | const headers = {
32 | 'User-Agent': 'open-wc calendar',
33 | };
34 | const options = { method: 'GET', headers };
35 |
36 | return new Promise((resolve, reject) => {
37 | fetch(url, options)
38 | .then(res => res.json())
39 | .then(json => {
40 | resolve(json);
41 | })
42 | .catch(err => {
43 | // eslint-disable-next-line no-console
44 | console.error(JSON.stringify(err, null, 2));
45 | reject();
46 | });
47 | });
48 | }
49 |
--------------------------------------------------------------------------------
/src/lambda/helpers/npm.js:
--------------------------------------------------------------------------------
1 | export function npmIsValidVersion(meta, version) {
2 | return meta && meta.versions && meta.versions[version];
3 | }
4 |
5 | export function npmGetLatestVersion(meta) {
6 | const versions = Object.keys(meta.versions);
7 | return versions[versions.length - 1];
8 | }
9 |
10 | export function isLatestNpmVersion(meta, version) {
11 | return npmGetLatestVersion(meta) === version;
12 | }
13 |
--------------------------------------------------------------------------------
/src/lambda/search.js:
--------------------------------------------------------------------------------
1 | import { search, searchFilterDependencies } from './helpers/elastic.js';
2 | import { dbGetPackage } from './helpers/db.js';
3 |
4 | export async function handler(ev) {
5 | const { queryString, dependencies } = ev.queryStringParameters;
6 |
7 | // this normalizes the array handling of queryStringParameters
8 | // locally I get "foo" and ["foo", "bar"]
9 | // on netlify I get "foo" and "foo, bar"
10 | // TODO: move to separate function
11 | let dependenciesArray = dependencies;
12 | if (dependencies && !Array.isArray(dependencies)) {
13 | if (dependencies.includes(',')) {
14 | dependenciesArray = dependencies.split(',');
15 | } else {
16 | dependenciesArray = [dependencies];
17 | }
18 | }
19 |
20 | // 1. search in elasticsearch
21 | const result = dependencies
22 | ? await searchFilterDependencies(queryString, dependenciesArray)
23 | : await search(queryString);
24 |
25 | if (result.hits.total.value === 0) {
26 | return {
27 | statusCode: 200,
28 | body: JSON.stringify({ 'no-result': true }, null, 2),
29 | };
30 | }
31 |
32 | // 2. query faunadb for all returned elasticsearch ids
33 | const dbRequests = [];
34 | result.hits.hits.forEach(hit => {
35 | dbRequests.push(dbGetPackage(hit._source.id));
36 | });
37 | const filled = await Promise.all(dbRequests);
38 |
39 | // preprocess json encoded flattenDependencies
40 | // => do not want to add more collections to faunadb to keep it simple for now
41 | filled.forEach((pkg, i) => {
42 | filled[i].flattenedDependencies = JSON.parse(pkg.flattenedDependencies);
43 | });
44 |
45 | // 3. return db filled data
46 | return {
47 | statusCode: 200,
48 | body: JSON.stringify(filled, null, 2),
49 | };
50 | }
51 |
--------------------------------------------------------------------------------
/todo.md:
--------------------------------------------------------------------------------
1 | ## TODO
2 |
3 | - Current user/passwords are hard coded with write rights (to allow for easy playing around) before publish revoke those users and use netlify secretes
4 |
--------------------------------------------------------------------------------