Feature/website overhaul (#49)

* Reorganize repo
* Overhaul website design and rewrite in NextJS and Typescript
* Delete website-publish.yml
This commit is contained in:
Daniel Lautzenheiser 2022-10-25 09:18:18 -04:00 committed by GitHub
parent 3674ee5bd8
commit 421ecf17e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
629 changed files with 6917 additions and 17824 deletions

View File

@ -1,32 +0,0 @@
name: Website Publish
on:
push:
paths:
- website/**
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'yarn'
cache-dependency-path: 'website/yarn.lock'
- name: Install
working-directory: website
run: yarn install --immutable
- name: Build
working-directory: website
run: yarn build
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.ACCESS_TOKEN }}
publish_dir: website/dist

2
.gitignore vendored
View File

@ -1,6 +1,5 @@
dist/ dist/
bin/ bin/
public/
node_modules/ node_modules/
npm-debug.log npm-debug.log
.DS_Store .DS_Store
@ -20,3 +19,4 @@ coverage/
.env .env
.temp/ .temp/
*.tgz *.tgz
old-website

1
.nvmrc
View File

@ -1 +0,0 @@
--lts

View File

@ -1,33 +0,0 @@
{
"processors": ["stylelint-processor-styled-components"],
"extends": ["stylelint-config-standard-scss", "stylelint-config-styled-components"],
"customSyntax": "postcss-scss",
"rules": {
"block-no-empty": null,
"no-duplicate-selectors": null,
"no-empty-source": null,
"no-extra-semicolons": null,
"declaration-empty-line-before": null,
"string-quotes": null,
"selector-class-pattern": null,
"selector-pseudo-element-colon-notation": null,
"rule-empty-line-before": null,
"declaration-colon-newline-after": null,
"at-rule-empty-line-before": null,
"alpha-value-notation": null,
"color-function-notation": null,
"keyframes-name-pattern": null,
"value-list-comma-newline-after": null,
"no-descending-specificity": null,
"selector-type-no-unknown": [
true,
{
"ignoreTypes": ["$dummyValue"]
}
],
"value-keyword-case": [
"lower",
{ "ignoreKeywords": ["dummyValue"], "camelCaseSvgKeywords": true }
]
}
}

View File

@ -1,6 +0,0 @@
StylesPath = website/src/writing-guide/styles
Vocab = website/src/writing-guide/vocab
MinAlertLevel = warning
[*.md]
BasedOnStyles = Avoid, Replace

View File

@ -3,6 +3,7 @@
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/StaticJsCMS/static-cms/blob/main/LICENSE) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/StaticJsCMS/static-cms/blob/main/LICENSE)
[![npm latest package](https://img.shields.io/npm/v/@staticcms/core/latest.svg)](https://www.npmjs.com/package/@staticcms/core) [![npm latest package](https://img.shields.io/npm/v/@staticcms/core/latest.svg)](https://www.npmjs.com/package/@staticcms/core)
[![npm next package](https://img.shields.io/npm/v/@staticcms/core/next.svg)](https://www.npmjs.com/package/@staticcms/core/v/next)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/StaticJsCMS/static-cms/blob/main/CONTRIBUTING.md) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/StaticJsCMS/static-cms/blob/main/CONTRIBUTING.md)
</div> </div>
@ -13,6 +14,8 @@ A CMS for static site generators. Give users a simple way to edit
and add content to any site built with a static site generator. and add content to any site built with a static site generator.
> **DISCLAIMER**: This package/repository is under heavy development and as such is very unstable at current. A stable release is targeted for the end of Novemeber and will be labeled `1.0.0`. Documentation may not be completely accurate prior to the `1.0.0` release. > **DISCLAIMER**: This package/repository is under heavy development and as such is very unstable at current. A stable release is targeted for the end of Novemeber and will be labeled `1.0.0`. Documentation may not be completely accurate prior to the `1.0.0` release.
>
> An alpha build for the stable release is available under the `@next` tack on npm.
## Community Chat ## Community Chat
@ -35,87 +38,9 @@ Read more about Static CMS [Core Concepts](https://staticjscms.github.io/static-
The Static CMS can be used in two different ways. The Static CMS can be used in two different ways.
* A Quick and easy install, that requires you to create a single HTML file and a configuration file. All the CMS JavaScript and CSS are loaded from a CDN. * A Quick and easy install, that requires you to create a single HTML file and a configuration file. All the CMS JavaScript and CSS are loaded from a CDN.
To learn more about this installation method, refer to the [Quick Start Guide](https://staticjscms.github.io/static-cms/docs/start-with-a-template/) To learn more about this installation method, refer to the [CDN Hosting Guide](https://staticjscms.github.io/static-cms/docs/add-to-your-site-cdn/)
* A complete, more complex install, that gives you more flexibility but requires that you use a static site builder with a build system that supports npm packages. * A complete, more complex install, that gives you more flexibility but requires that you use a static site builder with a build system that supports npm packages.
To learn more about this installation method, refer to the [Bundling Guide](https://staticjscms.github.io/static-cms/docs/add-to-your-site-bundling/)
# static-cms-core
## Installation
`npm install @staticcms/core`
## Setup
```tsx
import React from 'react';
import {
AzureBackend,
BitbucketBackend,
BooleanWidget,
CodeWidget,
ColorStringWidget,
DateTimeWidget,
FileWidget,
GitGatewayBackend,
GitHubBackend,
GitLabBackend,
imageEditorComponent,
ImageWidget,
ListWidget,
MapWidget,
MarkdownWidget,
StaticCmsCore as CMS,
NumberWidget,
ObjectWidget,
ProxyBackend,
RelationWidget,
SelectWidget,
StringWidget,
TestBackend,
TextWidget,
locales,
Icon,
images
} from '@static-cms/static-cms-core';
// Register all the things
CMS.registerBackend('git-gateway', GitGatewayBackend);
CMS.registerBackend('azure', AzureBackend);
CMS.registerBackend('github', GitHubBackend);
CMS.registerBackend('gitlab', GitLabBackend);
CMS.registerBackend('bitbucket', BitbucketBackend);
CMS.registerBackend('test-repo', TestBackend);
CMS.registerBackend('proxy', ProxyBackend);
CMS.registerWidget([
StringWidget.Widget(),
NumberWidget.Widget(),
TextWidget.Widget(),
ImageWidget.Widget(),
FileWidget.Widget(),
SelectWidget.Widget(),
MarkdownWidget.Widget(),
ListWidget.Widget(),
ObjectWidget.Widget(),
RelationWidget.Widget(),
BooleanWidget.Widget(),
MapWidget.Widget(),
DateTimeWidget.Widget(),
CodeWidget.Widget(),
ColorStringWidget.Widget(),
]);
CMS.registerEditorComponent(imageEditorComponent);
CMS.registerEditorComponent({
id: 'code-block',
label: 'Code Block',
widget: 'code',
type: 'code-block',
});
CMS.registerLocale('en', locales.en);
Object.keys(images).forEach(iconName => {
CMS.registerIcon(iconName, <Icon type={iconName} />);
});
```
# Contributing # Contributing
@ -133,6 +58,6 @@ Please make sure you understand its [implications and guarantees](https://writin
# Netlify CMS # Netlify CMS
Static CMS is a fork of Netlify CMS focusing on the core product over adding massive new features. Static CMS is a fork of Netlify CMS focusing on the core product over adding massive, scope expanding, new features.
# Thanks # Thanks

View File

Before

Width:  |  Height:  |  Size: 808 KiB

After

Width:  |  Height:  |  Size: 808 KiB

View File

Before

Width:  |  Height:  |  Size: 310 KiB

After

Width:  |  Height:  |  Size: 310 KiB

View File

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -52,17 +52,13 @@
"@emotion/styled": "11.10.4", "@emotion/styled": "11.10.4",
"@iarna/toml": "2.2.5", "@iarna/toml": "2.2.5",
"@mui/icons-material": "5.10.6", "@mui/icons-material": "5.10.6",
"@mui/material": "5.10.6", "@mui/material": "5.10.10",
"@mui/x-date-pickers": "5.0.4", "@mui/x-date-pickers": "5.0.4",
"@reduxjs/toolkit": "1.8.5", "@reduxjs/toolkit": "1.8.5",
"@toast-ui/react-editor": "3.2.2", "@toast-ui/react-editor": "3.2.2",
"ajv": "8.11.0", "ajv": "8.11.0",
"ajv-errors": "3.0.0", "ajv-errors": "3.0.0",
"ajv-keywords": "5.1.0", "ajv-keywords": "5.1.0",
"apollo-cache-inmemory": "1.6.6",
"apollo-client": "2.6.10",
"apollo-link-context": "1.0.20",
"apollo-link-http": "1.5.17",
"array-move": "4.0.0", "array-move": "4.0.0",
"buffer": "6.0.3", "buffer": "6.0.3",
"clean-stack": "4.2.0", "clean-stack": "4.2.0",

View File

@ -19,7 +19,6 @@ import {
} from '../../lib/util'; } from '../../lib/util';
import API, { API_NAME } from './API'; import API, { API_NAME } from './API';
import AuthenticationPage from './AuthenticationPage'; import AuthenticationPage from './AuthenticationPage';
import GraphQLAPI from './GraphQLAPI';
import type { Octokit } from '@octokit/rest'; import type { Octokit } from '@octokit/rest';
import type { Semaphore } from 'semaphore'; import type { Semaphore } from 'semaphore';
@ -66,7 +65,6 @@ export default class GitHub implements BackendClass {
apiRoot: string; apiRoot: string;
mediaFolder?: string; mediaFolder?: string;
token: string | null; token: string | null;
useGraphql: boolean;
_currentUserPromise?: Promise<GitHubUser>; _currentUserPromise?: Promise<GitHubUser>;
_userIsOriginMaintainerPromises?: { _userIsOriginMaintainerPromises?: {
[key: string]: Promise<boolean>; [key: string]: Promise<boolean>;
@ -92,7 +90,6 @@ export default class GitHub implements BackendClass {
this.branch = config.backend.branch?.trim() || 'main'; this.branch = config.backend.branch?.trim() || 'main';
this.apiRoot = config.backend.api_root || 'https://api.github.com'; this.apiRoot = config.backend.api_root || 'https://api.github.com';
this.token = ''; this.token = '';
this.useGraphql = config.backend.use_graphql || false;
this.mediaFolder = config.media_folder; this.mediaFolder = config.media_folder;
this.lock = asyncLock(); this.lock = asyncLock();
} }
@ -179,7 +176,7 @@ export default class GitHub implements BackendClass {
async authenticate(state: Credentials) { async authenticate(state: Credentials) {
this.token = state.token as string; this.token = state.token as string;
const apiCtor = this.useGraphql ? GraphQLAPI : API; const apiCtor = API;
this.api = new apiCtor({ this.api = new apiCtor({
token: this.token, token: this.token,
branch: this.branch, branch: this.branch,

View File

@ -1,7 +1,3 @@
import { InMemoryCache } from 'apollo-cache-inmemory';
import { ApolloClient } from 'apollo-client';
import { setContext } from 'apollo-link-context';
import { createHttpLink } from 'apollo-link-http';
import { Base64 } from 'js-base64'; import { Base64 } from 'js-base64';
import partial from 'lodash/partial'; import partial from 'lodash/partial';
import result from 'lodash/result'; import result from 'lodash/result';
@ -20,25 +16,18 @@ import {
throwOnConflictingBranches, throwOnConflictingBranches,
unsentRequest, unsentRequest,
} from '../../lib/util'; } from '../../lib/util';
import * as queries from './queries';
import type { NormalizedCacheObject } from 'apollo-cache-inmemory'; import type { DataFile, PersistOptions } from '../../interface';
import type { ApolloQueryResult } from 'apollo-client';
import type { DataFile, ImplementationFile, PersistOptions } from '../../interface';
import type { ApiRequest, FetchError } from '../../lib/util'; import type { ApiRequest, FetchError } from '../../lib/util';
import type AssetProxy from '../../valueObjects/AssetProxy'; import type AssetProxy from '../../valueObjects/AssetProxy';
export const API_NAME = 'GitLab'; export const API_NAME = 'GitLab';
const NO_CACHE = 'no-cache';
export interface Config { export interface Config {
apiRoot?: string; apiRoot?: string;
graphQLAPIRoot?: string;
token?: string; token?: string;
branch?: string; branch?: string;
repo?: string; repo?: string;
useGraphQL?: boolean;
} }
export interface CommitAuthor { export interface CommitAuthor {
@ -130,65 +119,20 @@ export function getMaxAccess(groups: { group_access_level: number }[]) {
}, groups[0]); }, groups[0]);
} }
function batch<T>(items: T[], maxPerBatch: number, action: (items: T[]) => void) {
for (let index = 0; index < items.length; index = index + maxPerBatch) {
const itemsSlice = items.slice(index, index + maxPerBatch);
action(itemsSlice);
}
}
export default class API { export default class API {
apiRoot: string; apiRoot: string;
graphQLAPIRoot: string;
token: string | boolean; token: string | boolean;
branch: string; branch: string;
repo: string; repo: string;
repoURL: string; repoURL: string;
commitAuthor?: CommitAuthor; commitAuthor?: CommitAuthor;
graphQLClient?: ApolloClient<NormalizedCacheObject>;
constructor(config: Config) { constructor(config: Config) {
this.apiRoot = config.apiRoot || 'https://gitlab.com/api/v4'; this.apiRoot = config.apiRoot || 'https://gitlab.com/api/v4';
this.graphQLAPIRoot = config.graphQLAPIRoot || 'https://gitlab.com/api/graphql';
this.token = config.token || false; this.token = config.token || false;
this.branch = config.branch || 'main'; this.branch = config.branch || 'main';
this.repo = config.repo || ''; this.repo = config.repo || '';
this.repoURL = `/projects/${encodeURIComponent(this.repo)}`; this.repoURL = `/projects/${encodeURIComponent(this.repo)}`;
if (config.useGraphQL === true) {
this.graphQLClient = this.getApolloClient();
}
}
getApolloClient() {
const authLink = setContext((_, { headers }) => {
return {
headers: {
'Content-Type': 'application/json; charset=utf-8',
...headers,
authorization: this.token ? `token ${this.token}` : '',
},
};
});
const httpLink = createHttpLink({ uri: this.graphQLAPIRoot });
return new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
defaultOptions: {
watchQuery: {
fetchPolicy: NO_CACHE,
errorPolicy: 'ignore',
},
query: {
fetchPolicy: NO_CACHE,
errorPolicy: 'all',
},
},
});
}
reset() {
return this.graphQLClient?.resetStore();
} }
withAuthorizationHeaders = (req: ApiRequest) => { withAuthorizationHeaders = (req: ApiRequest) => {
@ -374,102 +318,7 @@ export default class API {
}; };
}; };
listAllFilesGraphQL = async (path: string, recursive: boolean, branch: String) => {
const files: FileEntry[] = [];
let blobsPaths;
let cursor;
do {
blobsPaths = await this.graphQLClient!.query({
query: queries.files,
variables: { repo: this.repo, branch, path, recursive, cursor },
});
files.push(...blobsPaths.data.project.repository.tree.blobs.nodes);
cursor = blobsPaths.data.project.repository.tree.blobs.pageInfo.endCursor;
} while (blobsPaths.data.project.repository.tree.blobs.pageInfo.hasNextPage);
return files;
};
readFilesGraphQL = async (files: ImplementationFile[]) => {
const paths = files.map(({ path }) => path);
type BlobResult = {
project: { repository: { blobs: { nodes: { id: string; data: string }[] } } };
};
const blobPromises: Promise<ApolloQueryResult<BlobResult>>[] = [];
batch(paths, 90, slice => {
blobPromises.push(
this.graphQLClient!.query({
query: queries.blobs,
variables: {
repo: this.repo,
branch: this.branch,
paths: slice,
},
fetchPolicy: 'cache-first',
}),
);
});
type LastCommit = {
id: string;
authoredDate: string;
authorName: string;
author?: {
name: string;
username: string;
publicEmail: string;
};
};
type CommitResult = {
project: { repository: { [tree: string]: { lastCommit: LastCommit } } };
};
const commitPromises: Promise<ApolloQueryResult<CommitResult>>[] = [];
batch(paths, 8, slice => {
commitPromises.push(
this.graphQLClient!.query({
query: queries.lastCommits(slice),
variables: {
repo: this.repo,
branch: this.branch,
},
fetchPolicy: 'cache-first',
}),
);
});
const [blobsResults, commitsResults] = await Promise.all([
(await Promise.all(blobPromises)).map(result => result.data.project.repository.blobs.nodes),
(
await Promise.all(commitPromises)
).map(
result =>
Object.values(result.data.project.repository)
.map(({ lastCommit }) => lastCommit)
.filter(Boolean) as LastCommit[],
),
]);
const blobs = blobsResults.flat().map(result => result.data) as string[];
const metadata = commitsResults.flat().map(({ author, authoredDate, authorName }) => ({
author: author ? author.name || author.username || author.publicEmail : authorName,
updatedOn: authoredDate,
}));
const filesWithData = files.map((file, index) => ({
file: { ...file, ...metadata[index] },
data: blobs[index],
}));
return filesWithData;
};
listAllFiles = async (path: string, recursive = false, branch = this.branch) => { listAllFiles = async (path: string, recursive = false, branch = this.branch) => {
if (this.graphQLClient) {
return await this.listAllFilesGraphQL(path, recursive, branch);
}
const entries = []; const entries = [];
// eslint-disable-next-line prefer-const // eslint-disable-next-line prefer-const
let { cursor, entries: initialEntries } = await this.fetchCursorAndEntries({ let { cursor, entries: initialEntries } = await this.fetchCursorAndEntries({

View File

@ -3,7 +3,7 @@ import React, { useCallback, useMemo, useState } from 'react';
import AuthenticationPage from '../../components/UI/AuthenticationPage'; import AuthenticationPage from '../../components/UI/AuthenticationPage';
import Icon from '../../components/UI/Icon'; import Icon from '../../components/UI/Icon';
import { ImplicitAuthenticator, NetlifyAuthenticator, PkceAuthenticator } from '../../lib/auth'; import { NetlifyAuthenticator, PkceAuthenticator } from '../../lib/auth';
import { isNotEmpty } from '../../lib/util/string.util'; import { isNotEmpty } from '../../lib/util/string.util';
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
@ -19,7 +19,6 @@ const LoginButtonIcon = styled(Icon)`
const clientSideAuthenticators = { const clientSideAuthenticators = {
pkce: (config: AuthenticatorConfig) => new PkceAuthenticator(config), pkce: (config: AuthenticatorConfig) => new PkceAuthenticator(config),
implicit: (config: AuthenticatorConfig) => new ImplicitAuthenticator(config),
} as const; } as const;
const GitLabAuthenticationPage = ({ const GitLabAuthenticationPage = ({

View File

@ -49,8 +49,6 @@ export default class GitLab implements BackendClass {
apiRoot: string; apiRoot: string;
token: string | null; token: string | null;
mediaFolder?: string; mediaFolder?: string;
useGraphQL: boolean;
graphQLAPIRoot: string;
_mediaDisplayURLSem?: Semaphore; _mediaDisplayURLSem?: Semaphore;
@ -75,8 +73,6 @@ export default class GitLab implements BackendClass {
this.apiRoot = config.backend.api_root || 'https://gitlab.com/api/v4'; this.apiRoot = config.backend.api_root || 'https://gitlab.com/api/v4';
this.token = ''; this.token = '';
this.mediaFolder = config.media_folder; this.mediaFolder = config.media_folder;
this.useGraphQL = config.backend.use_graphql || false;
this.graphQLAPIRoot = config.backend.graphql_api_root || 'https://gitlab.com/api/graphql';
this.lock = asyncLock(); this.lock = asyncLock();
} }
@ -112,8 +108,6 @@ export default class GitLab implements BackendClass {
branch: this.branch, branch: this.branch,
repo: this.repo, repo: this.repo,
apiRoot: this.apiRoot, apiRoot: this.apiRoot,
useGraphQL: this.useGraphQL,
graphQLAPIRoot: this.graphQLAPIRoot,
}); });
const user = await this.api.user(); const user = await this.api.user();
const isCollab = await this.api.hasWriteAccess().catch((error: Error) => { const isCollab = await this.api.hasWriteAccess().catch((error: Error) => {
@ -200,7 +194,7 @@ export default class GitLab implements BackendClass {
getDifferences: (to, from) => this.api!.getDifferences(to, from), getDifferences: (to, from) => this.api!.getDifferences(to, from),
getFileId: path => this.api!.getFileId(path, this.branch), getFileId: path => this.api!.getFileId(path, this.branch),
filterFile: file => this.filterFile(folder, file, extension, depth), filterFile: file => this.filterFile(folder, file, extension, depth),
customFetch: this.useGraphQL ? files => this.api!.readFilesGraphQL(files) : undefined, customFetch: undefined,
}); });
return files; return files;

Some files were not shown because too many files have changed in this diff Show More