feat: init

This commit is contained in:
Morten Olsen
2022-12-06 09:12:53 +01:00
commit 3f5e941446
115 changed files with 13148 additions and 0 deletions

5
packages/goodwrites-viewer/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
/node_modules/
/*.logs
/.yarn/
/dist/

View File

@@ -0,0 +1,37 @@
{
"name": "@morten-olsen/goodwrites-viewer",
"main": "./dist/index.js",
"dependencies": {
"@fontsource/merriweather": "^4.5.14",
"@morten-olsen/goodwrites": "workspace:^",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.10",
"babel-loader": "^9.1.0",
"css-loader": "^6.7.2",
"express": "^4.18.2",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"raw-loader": "^4.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^8.0.4",
"react-pdf": "^6.2.0",
"react-refresh": "^0.14.0",
"style-loader": "^3.3.1",
"styled-components": "^5.3.6",
"webpack": "^5.75.0",
"webpack-dev-middleware": "^6.0.1",
"webpack-hot-middleware": "^2.25.3"
},
"devDependencies": {
"@types/express": "^4.17.14",
"@types/html-webpack-plugin": "^3.2.6",
"@types/loader-utils": "^2.0.3",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"@types/react-pdf": "^5.7.4",
"@types/styled-components": "^5.1.26",
"@types/webpack": "^5.28.0",
"@types/webpack-dev-middleware": "^5.3.0",
"@types/webpack-hot-middleware": "^2.25.6"
}
}

View File

@@ -0,0 +1 @@
export * from './webpack';

View File

@@ -0,0 +1,62 @@
import React, { FC } from 'react';
import '@fontsource/merriweather/index.css';
import styled, { createGlobalStyle } from 'styled-components';
import { Block } from './components/block';
import { useDoc } from './features/doc';
import { Document, Page } from 'react-pdf/dist/esm/entry.webpack5';
const GlobalStyle = createGlobalStyle`
* {
box-sizing: border-box;
}
html {
font-size: 16px;
}
h1, h2, h3,h4, h5, h6 {
margin: 0;
}
html, body, body > #__next {
height: 100%;
}
body {
background: #fff;
width: 100%;
max-width: 800px;
margin: auto;
font-family: 'Merriweather', sans-serif;
}
a {
text-decoration: none;
color: #3498db;
}
`;
const Cover = styled.img`
max-width: 100%;
`;
const Title = styled.h1`
font-size: 3rem;
line-height: 3.8rem;
margin-bottom: 0px;
`;
const App: FC = () => {
const doc = useDoc();
return (
<div>
<GlobalStyle />
<Title>{doc.title}</Title>
<Cover src={doc.cover} />
<Document file={doc.pdf}>
<Page pageNumber={1} renderTextLayer={false} />
</Document>
{doc.parts.map((part, index) => (
<Block key={index} block={part} doc={doc} />
))}
<a href={(doc as any).pdf}>PDF {(doc as any).pdf}</a>
</div>
);
};
export { App };

View File

@@ -0,0 +1,105 @@
import React, { FC } from 'react';
import ReactMarkdown from 'react-markdown';
import styled from 'styled-components';
import { DocumentResult } from '@morten-olsen/goodwrites';
type Block = DocumentResult['parts'][0];
interface BlockProps {
block: Block;
doc: DocumentResult;
}
const getBreadcrumb = (block: Block, doc: DocumentResult) => {
const path = [...block.path];
const elements: any[] = [
{
title: '#',
},
];
path.reduce((acc, part) => {
const parent = acc[part as keyof typeof acc] as any;
elements.push({
...parent,
title: parent.title ?? part,
});
return parent.content;
}, doc.original.content as any);
return elements.map((s: any) => s.title).join(' > ');
};
const Wrapper = styled.div`
margin: 8px 0;
border-radius: 5px;
position: relative;
`;
const Note = styled.div`
background-color: #efefef;
text-align: center;
`;
const Content = styled.div<{
level: number;
border?: string;
}>`
letter-spacing: 0.5px;
line-height: 2.1rem;
border: solid 5px ${(props) => props.border || '#fff'};
padding: 8px 32px;
p:first-of-type::first-letter {
font-size: 6rem;
float: left;
padding: 1rem;
margin: 0px 2rem;
font-weight: 100;
margin-left: 0rem;
}
p + p::first-letter {
margin-left: 1.8rem;
}
`;
const Breacrumb = styled.div`
font-size: 10px;
text-align: center;
font-family: monospace;
text-transform: uppercase;
font-weight: bold;
opacity: 0.5;
`;
const getBorderColor = (state: Block['state']) => {
switch (state) {
case 'first-draft':
return '#3498db';
case 'final-draft':
return '#2ecc71';
case 'revisions':
return '#f1c40f';
case 'placeholder':
return '#e74c3c';
}
return '#fff';
};
const Block: FC<BlockProps> = ({ block, doc }) => {
return (
<Wrapper>
<Breacrumb>{getBreadcrumb(block, doc)}</Breacrumb>
{block.content && (
<Content level={1} border={getBorderColor(block.state)}>
<ReactMarkdown>{block.content}</ReactMarkdown>
</Content>
)}
{block.notes && <Note>{block.notes}</Note>}
</Wrapper>
);
};
export { Block };

View File

@@ -0,0 +1,49 @@
import React, {
createContext,
useContext,
FC,
useState,
useEffect,
ReactNode,
} from 'react';
import { DocumentResult } from '@morten-olsen/goodwrites';
type Document = DocumentResult;
declare var __DOC_LOCATION__: string;
type DocContextValue = {
doc: Document;
};
type DocProviderProps = {
children: ReactNode;
};
const DocContext = createContext<DocContextValue>(null as any);
const DocProvider: FC<DocProviderProps> = ({ children }) => {
const [doc, setDoc] = useState<Document>(
require(__DOC_LOCATION__) as Document
);
useEffect(() => {
const hot = (module as any).hot;
if (hot) {
hot.accept('../../../../demo-article/index.yml', () => {
const newDoc = require(__DOC_LOCATION__) as Document;
setDoc(newDoc);
});
}
}, []);
return <DocContext.Provider value={{ doc }}>{children}</DocContext.Provider>;
};
const useDoc = () => {
const { doc } = useContext(DocContext);
return doc;
};
export { DocProvider, DocContext, useDoc };

View File

@@ -0,0 +1,23 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './app';
import { DocProvider } from './features/doc';
const createRootElement = () => {
const rootContainer = document.createElement('div');
rootContainer.setAttribute('id', 'root');
document.body.appendChild(rootContainer);
return rootContainer;
};
const rootElement = document.getElementById('root') || createRootElement();
const Root = () => {
return (
<DocProvider>
<App />
</DocProvider>
);
};
createRoot(rootElement).render(<Root />);

View File

@@ -0,0 +1,82 @@
import express from 'express';
import webpack, { Configuration } from 'webpack';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import { join } from 'path';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import hotMiddleware from 'webpack-hot-middleware';
import devMiddleware from 'webpack-dev-middleware';
type WebpackOptions = {
dev?: boolean;
documentLocation: string;
};
const createWebpack = ({
documentLocation,
dev = false,
}: WebpackOptions): Configuration => ({
mode: dev ? 'development' : 'production',
context: join(__dirname, 'ui'),
entry: ['webpack-hot-middleware/client', join(__dirname, 'ui', 'index.js')],
module: {
rules: [
{
test: documentLocation,
loader: require.resolve('@morten-olsen/goodwrites-webpack-loader'),
},
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve('babel-loader'),
options: {
plugins: [require.resolve('react-refresh/babel')],
},
},
],
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'images/',
},
},
],
},
],
},
plugins: [
new HtmlWebpackPlugin({}),
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
__DOC_LOCATION__: JSON.stringify(documentLocation),
}),
new ReactRefreshWebpackPlugin(),
],
});
const createServer = (options: WebpackOptions) => {
const server = express();
const webpackConfig = createWebpack(options);
const compiler = webpack(webpackConfig);
server.use(devMiddleware(compiler));
server.use(hotMiddleware(compiler));
return server;
};
export { createWebpack, createServer };

View File

@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"jsx": "react"
},
"include": [
"./src"
]
}