├── logo.png ├── .editorconfig ├── tslint.json ├── api-test.http ├── jest.config.js ├── .prettierrc ├── src ├── components │ ├── error-list.tsx │ ├── page-list.tsx │ ├── modal.tsx │ ├── article-list.tsx │ ├── header.tsx │ ├── article-meta.tsx │ ├── comment-list.tsx │ ├── register.tsx │ ├── signin.tsx │ ├── editor.tsx │ ├── settings.tsx │ ├── article.tsx │ ├── profile.tsx │ └── home.tsx ├── main.ts ├── models.ts ├── fetch.ts └── api.ts ├── .gitignore ├── tsconfig.json ├── tests ├── home.view.spec.ts ├── mocks.ts ├── home.spec.ts ├── auto-events.spec.ts └── snapshot.spec.ts ├── webpack.config.js ├── .vscode └── launch.json ├── package.json ├── .eslintrc.js ├── index.html ├── readme.md ├── app.js └── app.js.map /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gothinkster/apprun-realworld-example-app/HEAD/logo.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | indent_style = space 6 | indent_size = 2 7 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-unused-expression": true, 4 | "no-duplicate-variable": true, 5 | "class-name": true 6 | } 7 | } -------------------------------------------------------------------------------- /api-test.http: -------------------------------------------------------------------------------- 1 | ### Old 2 | GET https://conduit.productionready.io/api/articles?limit=10&offset=0 3 | 4 | 5 | ### New 6 | GET https://api.realworld.io/api/articles?limit=10&offset=0 -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 3 | module.exports = { 4 | preset: 'ts-jest', 5 | testEnvironment: 'jsdom', 6 | 'extensionsToTreatAsEsm': ['.esm.js'] 7 | }; -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "bracketSpacing": true, 8 | "jsxBracketSameLine": true, 9 | "arrowParens": "avoid" 10 | } 11 | -------------------------------------------------------------------------------- /src/components/error-list.tsx: -------------------------------------------------------------------------------- 1 | import app from 'apprun'; 2 | 3 | export default function ({ errors }) { 4 | return ( 5 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /bower_components 6 | 7 | # IDEs and editors 8 | /.idea 9 | .project 10 | .classpath 11 | *.launch 12 | .settings/ 13 | 14 | #System Files 15 | .DS_Store 16 | Thumbs.db 17 | 18 | coverage 19 | __snapshots__ 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "es2015", 5 | "moduleResolution": "node", 6 | "jsx": "react", 7 | "jsxFactory": "app.h", 8 | "jsxFragmentFactory": "app.Fragment", 9 | "lib": ["dom", "es2015", "es5"], 10 | "sourceMap": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "esModuleInterop": true 14 | } 15 | } -------------------------------------------------------------------------------- /tests/home.view.spec.ts: -------------------------------------------------------------------------------- 1 | import home from '../src/components/home'; 2 | 3 | describe('home component', () => { 4 | it('view test', () => { 5 | const state = { 6 | type: 'feed', 7 | articles: [], 8 | tags: ['1', '2', '3'], 9 | max: 10, 10 | page: 1 11 | } 12 | const vdom = home['view'](state); 13 | expect(JSON.stringify(vdom, undefined, 2)).toMatchSnapshot(); 14 | }) 15 | }); 16 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import app from 'apprun'; 2 | 3 | import './components/header'; 4 | import './components/home'; 5 | import './components/signin'; 6 | import './components/register'; 7 | import './components/profile'; 8 | import './components/settings'; 9 | import './components/editor'; 10 | import './components/article'; 11 | 12 | app.on('#', (route, ...p) => { 13 | app.run(`#/${route || ''}`, ...p); 14 | }); 15 | 16 | app.once('/set-user', () => { 17 | app.route(location.hash); 18 | }); 19 | 20 | app.run('/get-user'); -------------------------------------------------------------------------------- /src/components/page-list.tsx: -------------------------------------------------------------------------------- 1 | import app from 'apprun'; 2 | 3 | export default function ({ max, selected, link }) { 4 | const pages = new Array(max).fill(0); 5 | return ( 6 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/models.ts: -------------------------------------------------------------------------------- 1 | export interface IUser { 2 | username: string; 3 | bio: string; 4 | image: string; 5 | following: boolean; 6 | } 7 | 8 | export interface IProfile extends IUser { 9 | email: string; 10 | token: string; 11 | } 12 | 13 | export interface IArticle { 14 | slug: string; 15 | title: string; 16 | description: string; 17 | body: string; 18 | createdAt: Date; 19 | updatedAt: Date; 20 | favorited: boolean; 21 | favoritesCount: number; 22 | author: IUser; 23 | tagList: Array; 24 | } 25 | 26 | export interface IComment { 27 | id: number; 28 | body: string; 29 | createdAt: string; 30 | author: IUser; 31 | } 32 | 33 | export type ITag = string; 34 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | const path = require('path'); 3 | module.exports = { 4 | entry: { 5 | 'app': './src/main.ts', 6 | }, 7 | output: { 8 | filename: '[name].js', 9 | path: path.resolve(__dirname) 10 | }, 11 | resolve: { 12 | extensions: ['.ts', '.tsx', '.js'] 13 | }, 14 | module: { 15 | rules: [ 16 | { test: /.tsx?$/, loader: 'ts-loader' }, 17 | { test: /\.js$/, use: ['source-map-loader'], enforce: 'pre' } 18 | ] 19 | }, 20 | externals: { apprun: 'apprun', marked: 'marked' }, 21 | devtool: 'source-map', 22 | devServer: { 23 | static: { 24 | directory: __dirname, 25 | }, 26 | compress: true, 27 | }, 28 | }; -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Jest", 11 | "program": "${workspaceFolder}/node_modules/jest/bin/jest.js", 12 | "args": [ 13 | "${relativeFile}" 14 | ], 15 | "console": "integratedTerminal", 16 | "internalConsoleOptions": "neverOpen" 17 | }, 18 | { 19 | "type": "chrome", 20 | "request": "launch", 21 | "name": "Launch App", 22 | "url": "http://localhost:8080", 23 | "webRoot": "${workspaceRoot}", 24 | "disableNetworkCache": true, 25 | "sourceMapPathOverrides": { 26 | "webpack:///*": "${webRoot}/*" 27 | } 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /src/components/modal.tsx: -------------------------------------------------------------------------------- 1 | import app from 'apprun'; 2 | 3 | export default function ({ title, body, ok, cancel, onOK, onCancel }) { 4 | return ( 5 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apprun-realworld-example-app", 3 | "version": "1.0.0", 4 | "description": "> ### AppRun codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the [RealWorld](https://github.com/gothinkster/realworld) spec and API.", 5 | "main": "index.js", 6 | "scripts": { 7 | "jest": "jest", 8 | "test": "jest --watch", 9 | "start": "webpack serve --mode development", 10 | "build": "webpack --mode production", 11 | "lint": "eslint src" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/yysun/realworld-starter-kit.git" 16 | }, 17 | "keywords": [], 18 | "author": "", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/yysun/realworld-starter-kit/issues" 22 | }, 23 | "homepage": "https://github.com/yysun/realworld-starter-kit#readme", 24 | "devDependencies": { 25 | "@types/jest": "^27.0.2", 26 | "@types/marked": "^4.0.0", 27 | "@typescript-eslint/eslint-plugin": "^5.3.1", 28 | "@typescript-eslint/parser": "^5.3.1", 29 | "apprun": "^3.28.7", 30 | "eslint": "^8.2.0", 31 | "jest": "^27.3.1", 32 | "marked": "^4.0.10", 33 | "source-map-loader": "^3.0.0", 34 | "ts-jest": "^27.0.7", 35 | "ts-loader": "^9.2.6", 36 | "tslib": "^2.3.1", 37 | "typescript": "^4.4.4", 38 | "webpack": "^5.64.0", 39 | "webpack-cli": "^4.9.1", 40 | "webpack-dev-server": "^4.4.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | module.exports = { 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | }, 7 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { 10 | ecmaVersion: 12, 11 | sourceType: 'module', 12 | }, 13 | 14 | plugins: ['@typescript-eslint'], 15 | rules: { 16 | indent: ['error', 2], 17 | quotes: ['error', 'single'], 18 | semi: ['error', 'always'], 19 | 'no-trailing-spaces': 'error', 20 | 'no-multiple-empty-lines': ['error', { 'max': 1, 'maxEOF': 0 }], 21 | '@typescript-eslint/explicit-module-boundary-types': 'off', 22 | 'no-case-declarations': 'off', 23 | 'eqeqeq': 'error', 24 | 'curly': 'error', 25 | 'brace-style': ['error', '1tbs', { 'allowSingleLine': true }], 26 | 'no-extra-parens': ['error', 'functions'], 27 | 'no-useless-catch': 'error', 28 | 'no-console': 'error', 29 | 'space-before-function-paren': ['error', { 'anonymous': 'always', 'named': 'never', 'asyncArrow': 'always' }], 30 | 'object-curly-spacing': ['error', 'always'], 31 | 'array-bracket-spacing': ['error', 'never'], 32 | 'no-unused-expressions': ['error', { allowShortCircuit: true }], 33 | 'arrow-parens': ['error', 'as-needed', { 'requireForBlockBody': true }], 34 | '@typescript-eslint/no-unused-vars': ['error', { varsIgnorePattern: 'app' }], 35 | 'no-invalid-this': 'off', 36 | '@typescript-eslint/no-invalid-this': ['error'] 37 | }, 38 | }; -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Conduit 7 | 8 | 9 | 11 | 12 | 13 | 18 | 19 | 20 | 26 |
27 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/components/article-list.tsx: -------------------------------------------------------------------------------- 1 | import app from 'apprun'; 2 | import { IArticle } from '../models'; 3 | 4 | function Article(props) { 5 | const article = props.article as IArticle; 6 | const favClass = article.favorited ? 'btn-primary' : 'btn-outline-primary'; 7 | return ( 8 |
9 | 25 | 26 |

{article.title}

27 |

{article.description}

28 | Read more... 29 |
36 | 37 |
38 | ); 39 | } 40 | 41 | export default function ({ articles, component }: { articles: Array; component }) { 42 | return articles.length ? ( 43 | articles.map(article =>
) 44 | ) : ( 45 |
No articles are here... yet.
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /tests/mocks.ts: -------------------------------------------------------------------------------- 1 | import app from 'apprun'; 2 | import { auth, tags, articles, comments, profile } from '../src/api'; 3 | 4 | app.on('#', async (route, ...p) => { 5 | app.run(`#/${route || ''}`, ...p); 6 | }) 7 | 8 | auth.current = jest.fn(() => null); 9 | auth.signIn = jest.fn(() => new Promise(r => r( 10 | { 11 | user: { 12 | username: '', 13 | bio: '', 14 | image: '', 15 | following: true 16 | } 17 | } 18 | ))); 19 | 20 | auth.register = jest.fn(() => null); 21 | auth.save = jest.fn(() => null); 22 | 23 | tags.all = jest.fn(() => new Promise(r => r({ tags: ['1', '2', '3'] }))); 24 | 25 | articles.search = jest.fn(() => new Promise(r => r(({ articles: [], articlesCount: 101 })))); 26 | articles.feed = jest.fn(() => new Promise(r => r({ articles: [], articlesCount: 5 }))); 27 | articles.get = jest.fn(() => new Promise(r => r( 28 | { 29 | article: { 30 | slug: '', 31 | author: { 32 | username: '', 33 | bio: '', 34 | image: '', 35 | following: true 36 | }, 37 | title: '', 38 | body: '', 39 | description: '', 40 | createdAt: new Date(), 41 | updatedAt: new Date(), 42 | favorited: true, 43 | favoritesCount: 0, 44 | tagList: [] 45 | } 46 | } 47 | ))); 48 | 49 | articles.delete = jest.fn(() => null); 50 | articles.favorite = jest.fn(() => null); 51 | articles.unfavorite = jest.fn(() => null); 52 | articles.update = jest.fn(() => null); 53 | articles.create = jest.fn(() => null); 54 | 55 | comments.create = jest.fn(() => null); 56 | comments.delete = jest.fn(() => null); 57 | comments.forArticle = jest.fn(() => new Promise(r => r(({ comments: [] })))); 58 | 59 | profile.get = jest.fn(() => null); 60 | profile.follow = jest.fn(() => null); 61 | profile.unfollow = jest.fn(() => null); 62 | -------------------------------------------------------------------------------- /src/components/header.tsx: -------------------------------------------------------------------------------- 1 | import app, { Component, on } from 'apprun'; 2 | 3 | class HeaderComponent extends Component { 4 | state = {}; 5 | view = (state) => { 6 | const user = state.user; 7 | return ( 8 | 57 | ); 58 | }; 59 | 60 | @on('/set-user') setUser = (state, user) => ({ ...state, user }); 61 | } 62 | 63 | export default new HeaderComponent().start('header'); 64 | -------------------------------------------------------------------------------- /src/components/article-meta.tsx: -------------------------------------------------------------------------------- 1 | import app from 'apprun'; 2 | import { IArticle } from '../models'; 3 | 4 | export default function ArticleMeta({ article, component }: { article: IArticle; component }) { 5 | const favClass = article.favorited ? 'btn-primary' : 'btn-outline-primary'; 6 | const followClass = article.author.following ? 'btn-secondary' : 'btn-outline-secondary'; 7 | return ( 8 | 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /src/components/comment-list.tsx: -------------------------------------------------------------------------------- 1 | import app from 'apprun'; 2 | import { IComment, IProfile } from '../models'; 3 | import { marked } from 'marked'; 4 | 5 | function Comment({ comment }: { comment: IComment }) { 6 | return ( 7 |
8 |
9 |

10 |

{`_html:${marked(comment.body)}`}

11 |

12 |
13 | 28 |
29 | ); 30 | } 31 | 32 | export default function ({ comments }: { comments: Array }) { 33 | const user = app['user'] as IProfile; 34 | return ( 35 |
36 |
37 | {!user ? ( 38 |

39 | Sign in or  40 | sign up to add comments on this article. 41 |

42 | ) : ( 43 |
44 |
45 | 50 |
51 | 61 |
62 | )} 63 | {comments.map(comment => ( 64 | 65 | ))} 66 |
67 |
68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /src/components/register.tsx: -------------------------------------------------------------------------------- 1 | import app, { Component, on } from 'apprun'; 2 | import { auth, serializeObject } from '../api'; 3 | import Errors from './error-list'; 4 | 5 | class RegisterComponent extends Component { 6 | state = {}; 7 | 8 | view = (state) => { 9 | if (!state || state instanceof Promise) {return;} 10 | return ( 11 |
12 |
13 |
14 |
15 |

Sign Up

16 |

17 | Have an account? 18 |

19 | 20 | {state.errors && } 21 | 22 |
23 |
24 | 30 |
31 |
32 | 38 |
39 |
40 | 46 |
47 | 48 |
49 |
50 |
51 |
52 |
53 | ); 54 | }; 55 | 56 | @on('#/register') register = (state, messages) => !auth.current() ? { ...state, messages } : location.href = '#/'; 57 | 58 | @on('register') submitRegistration = async (state, e) => { 59 | try { 60 | e.preventDefault(); 61 | const session = await auth.register(serializeObject(e.target)); 62 | app.run('/set-user', session.user); 63 | app.run('route', '#/'); 64 | } catch ({ errors }) { 65 | return { ...state, errors }; 66 | } 67 | }; 68 | } 69 | 70 | export default new RegisterComponent().mount('my-app'); 71 | -------------------------------------------------------------------------------- /src/components/signin.tsx: -------------------------------------------------------------------------------- 1 | import app, { Component, on } from 'apprun'; 2 | import { auth, serializeObject } from '../api'; 3 | 4 | import Errors from './error-list'; 5 | 6 | class SigninComponent extends Component { 7 | state = {}; 8 | 9 | view = (state) => { 10 | if (!state || state instanceof Promise) {return;} 11 | return ( 12 |
13 |
14 |
15 |
16 |

Sign In

17 |

18 | Need an account? 19 |

20 | 21 | {state.errors && } 22 | 23 |
24 |
25 | 31 |
32 |
33 | 39 |
40 | 41 |
42 |
43 |
44 |
45 |
46 | ); 47 | }; 48 | 49 | @on('#/login') login = state => !auth.current() ? { ...state, messages: [], returnTo: document.location.hash } : location.href = '#/'; 50 | @on('#/logout') logout = () => { 51 | app.run('/set-user', null); 52 | document.location.hash = '#/'; 53 | }; 54 | 55 | @on('sign-in') signIn = async (state, e) => { 56 | try { 57 | e.preventDefault(); 58 | const session = await auth.signIn(serializeObject(e.target)); 59 | app.run('/set-user', session.user); 60 | const returnTo: string = (state.returnTo || '').replace(/#\/login\/?/, ''); 61 | if (!returnTo) {document.location.hash = '#/feed';} else { 62 | app.run('route', returnTo); 63 | history.pushState(null, null, returnTo); 64 | } 65 | } catch ({ errors }) { 66 | return { ...state, errors }; 67 | } 68 | }; 69 | } 70 | 71 | export default new SigninComponent().mount('my-app'); 72 | -------------------------------------------------------------------------------- /src/fetch.ts: -------------------------------------------------------------------------------- 1 | declare let defaultBasePath; 2 | 3 | let access_token: string = 4 | (window && window.localStorage && window.localStorage.getItem('jwt')) || ''; 5 | export function getToken() { 6 | return access_token; 7 | } 8 | 9 | export function setToken(token: string) { 10 | access_token = token; 11 | if (!window.localStorage) {return;} 12 | if (token) {window.localStorage.setItem('jwt', token);} else {window.localStorage.removeItem('jwt');} 13 | } 14 | 15 | export async function fetchAsync( 16 | method: 'GET' | 'POST' | 'DELETE' | 'PUT', 17 | url: string, 18 | body? 19 | ) { 20 | const headers = { 'Content-Type': 'application/json; charset=utf-8' }; 21 | if (access_token) {headers['Authorization'] = `Token ${access_token}`;} 22 | const response = await window['fetch'](`${defaultBasePath}${url}`, { 23 | method, 24 | headers, 25 | body: body && JSON.stringify(body) 26 | }); 27 | if (response.status === 401) { 28 | setToken(null); 29 | throw new Error('401'); 30 | } 31 | const result = await response.json(); 32 | if (!response.ok) {throw result;} 33 | return result; 34 | } 35 | 36 | export function get(url: string): Promise { 37 | return fetchAsync('GET', url); 38 | } 39 | 40 | export function post(url: string, body?): Promise { 41 | return fetchAsync('POST', url, body); 42 | } 43 | 44 | export function del(url: string) { 45 | return fetchAsync('DELETE', url); 46 | } 47 | 48 | export function put(url: string, body?) { 49 | return fetchAsync('PUT', url, body); 50 | } 51 | export function toQueryString(obj) { 52 | const parts = []; 53 | for (const i in obj) { 54 | if (Object.prototype.hasOwnProperty.call(obj, i)) { 55 | parts.push(encodeURIComponent(i) + '=' + encodeURIComponent(obj[i])); 56 | } 57 | } 58 | return parts.join('&'); 59 | } 60 | 61 | export function serializeObject(form) { 62 | const obj = {}; 63 | if (typeof form === 'object' && form.nodeName === 'FORM') { 64 | for (let i = 0; i < form.elements.length; i++) { 65 | const field = form.elements[i]; 66 | if ( 67 | field.name && 68 | field.type !== 'file' && 69 | field.type !== 'reset' && 70 | field.type !== 'submit' && 71 | field.type !== 'button' 72 | ) { 73 | if (field.type === 'select-multiple') { 74 | obj[field.name] = ''; 75 | let tempvalue = ''; 76 | for (let j = 0; j < form.elements[i].options.length; j++) { 77 | if (field.options[j].selected) {tempvalue += field.options[j].value + ';';} 78 | } 79 | if (tempvalue.charAt(tempvalue.length - 1) === ';') {obj[field.name] = tempvalue.substring(0, tempvalue.length - 1);} 80 | } else if ((field.type !== 'checkbox' && field.type !=='radio') || field.checked) { 81 | obj[field.name] = field.value; 82 | } 83 | } 84 | } 85 | } 86 | return obj as T; 87 | } 88 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ![RealWorld Example App](logo.png) 2 | 3 | > ### [AppRun](https://github.com/yysun/apprun) codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the [RealWorld](https://github.com/gothinkster/realworld) spec and API. 4 | 5 | 6 | ### [Demo](https://gothinkster.github.io/apprun-realworld-example-app)    [RealWorld](https://github.com/gothinkster/realworld) 7 | 8 | 9 | This codebase was created to demonstrate application frontend built with **[AppRun](https://github.com/yysun/apprun)** including CRUD operations, authentication, routing, pagination, and more. 10 | 11 | We've gone to great lengths to adhere to the [RealWorld](https://github.com/gothinkster/realworld) community style guides & best practices. 12 | 13 | This codebase has about 1000 lines of source code that can be gziped to 20K. 14 | 15 | See a comparison from [A Real-World Comparison of Front-End Frameworks with Benchmarks (2019 Update)](https://medium.freecodecamp.org/a-realworld-comparison-of-front-end-frameworks-with-benchmarks-2019-update-4be0d3c78075). 16 | 17 | ## General functionality 18 | 19 | * Authenticate users via JWT (login/signup pages + logout button) 20 | * CRU* users (sign up & settings page - no deleting required) 21 | * CRUD Articles 22 | * CR*D Comments on articles (no updating required) 23 | * GET and display paginated lists of articles 24 | * Favorite articles 25 | * Follow other users 26 | 27 | ## Extra functionality 28 | * **Modal dialog for deleting articles and comments** 29 | * **Static Typed** 30 | * **AppRun CLI in console enabled** 31 | * **Connect to the Redux devtool extensions** 32 | 33 | ## Getting started 34 | 35 | * Visit the [live demo](https://gothinkster.github.io/apprun-realworld-example-app) 36 | * clone this repo 37 | * npm install 38 | * npm start 39 | * open http://localhost:8080 in browser 40 | 41 | ## About AppRun 42 | [AppRun](https://github.com/yysun/apprun) is a 3K library for developing applications using the elm inspired architecture and event pub and sub pattern. 43 | 44 | In the AppRun Book published by Apress, the Chapter 10-12 describe this application. 45 | * [Order from Amazon](https://www.amazon.com/Practical-Application-Development-AppRun-High-Performance/dp/1484240685/) 46 | 47 | [![Order from Amazon](https://images-na.ssl-images-amazon.com/images/I/51cr-t1pdSL._SY291_BO1,204,203,200_QL40_FMwebp_.jpg)](https://www.amazon.com/Practical-Application-Development-AppRun-High-Performance/dp/1484240685/) 48 | 49 | 50 | 51 | Following articles also have details of this application and AppRun. 52 | * [Deep Dive into AppRun State](https://medium.com/@yiyisun/deep-dive-into-apprun-state-3d6fb58b1521) 53 | 54 | * [Deep Dive into AppRun Events]( 55 | https://medium.com/@yiyisun/deep-dive-into-apprun-events-1650dc7811ea) 56 | 57 | * [Building Applications with AppRun](https://yysun.github.io/apprun/#/) 58 | 59 | 60 | 61 | Pull requests are welcome. 62 | 63 | ## License 64 | 65 | MIT 66 | 67 | Copyright (c) 2017 Yiyi Sun 68 | -------------------------------------------------------------------------------- /tests/home.spec.ts: -------------------------------------------------------------------------------- 1 | import app from 'apprun'; 2 | import home from '../src/components/home'; 3 | import { auth, tags, articles } from '../src/api'; 4 | import './mocks'; 5 | 6 | describe('home component', () => { 7 | 8 | it('should update state: #/', (done) => { 9 | app.run('route', '#/'); 10 | setTimeout(() => { 11 | const state = home.state; 12 | expect(state.type).toBe(''); 13 | expect(state.page).toBe(1); 14 | expect(state.max).toBe(101); 15 | expect(tags.all).toHaveBeenCalledWith(); 16 | expect(articles.search).toHaveBeenCalledWith({ offset: 0, limit: 10 }); 17 | done(); 18 | }) 19 | }) 20 | 21 | it('should update state: #//2', (done) => { 22 | app.run('route', '#//2'); 23 | setTimeout(() => { 24 | const state = home.state; 25 | expect(state.type).toBe(''); 26 | expect(state.page).toBe(2); 27 | expect(state.max).toBe(101); 28 | expect(tags.all).toHaveBeenCalledWith(); 29 | expect(articles.search).toHaveBeenCalledWith({ offset: 10, limit: 10 }); 30 | done(); 31 | }) 32 | }) 33 | 34 | it('should not call #/feed w/o user', () => { 35 | app.on('#/login', auth.signIn); 36 | app.run('route', '#/feed'); 37 | expect(auth.authorized()).toBeFalsy(); 38 | expect(auth.signIn).toHaveBeenCalled(); 39 | expect(articles.feed).not.toHaveBeenCalled(); 40 | }) 41 | 42 | it('should update state: #/feed/3', (done) => { 43 | app['user'] = {}; 44 | app.run('route', '#/feed/3'); 45 | setTimeout(() => { 46 | const state = home.state; 47 | expect(state.type).toBe('feed'); 48 | expect(state.page).toBe(1); 49 | expect(state.max).toBe(5); 50 | expect(tags.all).toHaveBeenCalledWith(); 51 | expect(articles.feed).toHaveBeenCalledWith({ offset: 20, limit: 10 }); 52 | done(); 53 | }) 54 | }) 55 | 56 | it('should update state: #/tag', (done) => { 57 | app.run('route', '#/tag'); 58 | setTimeout(() => { 59 | const state = home.state; 60 | expect(state.type).toBe('tag'); 61 | expect(state.tag).toBeUndefined(); 62 | expect(state.page).toBe(1); 63 | expect(state.max).toBe(101); 64 | expect(tags.all).toHaveBeenCalledWith(); 65 | expect(articles.search).toHaveBeenCalledWith({ tag:undefined, offset: 0, limit: 10 }); 66 | done(); 67 | }) 68 | }) 69 | 70 | it('should update state: #/tag/t2', (done) => { 71 | app.run('route', '#/tag/t2'); 72 | setTimeout(() => { 73 | const state = home.state; 74 | expect(state.type).toBe('tag'); 75 | expect(state.max).toBe(101); 76 | expect(state.tag).toBe('t2'); 77 | expect(state.page).toBe(1); 78 | expect(tags.all).toHaveBeenCalledWith(); 79 | expect(articles.search).toHaveBeenCalledWith({ tag: 't2', offset: 0, limit: 10 }); 80 | done(); 81 | }) 82 | }) 83 | 84 | it('should update state: #/tag/t3/20', (done) => { 85 | app.run('route', '#/tag/t3/20'); 86 | setTimeout(() => { 87 | const state = home.state; 88 | expect(state.type).toBe('tag'); 89 | expect(state.tag).toBe('t3'); 90 | expect(state.page).toBe(11); 91 | expect(tags.all).toHaveBeenCalledWith(); 92 | expect(articles.search).toHaveBeenCalledWith({ tag: 't3', offset: 190, limit: 10 }); 93 | done(); 94 | }) 95 | }) 96 | 97 | }) -------------------------------------------------------------------------------- /src/components/editor.tsx: -------------------------------------------------------------------------------- 1 | import app, { Component, on } from 'apprun'; 2 | import { serializeObject, articles, auth } from '../api'; 3 | import Errors from './error-list'; 4 | 5 | class EditorComponent extends Component { 6 | state = {}; 7 | 8 | view = (state) => { 9 | if (!app['user'] || !state.article) {return;} 10 | const article = state.article; 11 | return ( 12 |
13 |
14 |
15 |
16 | {state.errors && } 17 |
18 | {article.slug && } 19 |
20 |
21 | 28 |
29 |
30 | 37 |
38 |
39 | 46 |
47 |
48 | 55 |
56 |
57 | 60 |
61 |
62 |
63 |
64 |
65 |
66 | ); 67 | }; 68 | 69 | @on('#/editor') root = async (state, slug) => { 70 | if (!auth.authorized()) {return;} 71 | let article; 72 | if (slug) { 73 | const result = await articles.get(slug); 74 | article = result.article; 75 | } 76 | article = article || { title: '', description: '', body: '', tagList: [] }; 77 | return { article }; 78 | }; 79 | 80 | @on('submit-article') submitArticle = async (state, e) => { 81 | try { 82 | e.preventDefault(); 83 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 84 | const article = serializeObject(e.target); 85 | article.tagList = article.tags.split(','); 86 | const result = article.slug ? await articles.update(article) : await articles.create(article); 87 | document.location.hash = `#/article/${result.article.slug}`; 88 | } catch ({ errors }) { 89 | return { ...state, errors }; 90 | } 91 | }; 92 | } 93 | 94 | export default new EditorComponent().mount('my-app'); 95 | -------------------------------------------------------------------------------- /src/components/settings.tsx: -------------------------------------------------------------------------------- 1 | import app, { Component, on } from 'apprun'; 2 | import { serializeObject, auth } from '../api'; 3 | import Errors from './error-list'; 4 | import Modal from './modal'; 5 | 6 | class SettingsComponent extends Component { 7 | state = {}; 8 | 9 | view = (state) => { 10 | const user = state.user; 11 | if (!user) {return;} 12 | return ( 13 |
14 | {state.showModal ? ( 15 | 23 | ) : ( 24 | '' 25 | )} 26 |
27 |
28 |
29 | {state.errors && } 30 |

Your Settings

31 |
32 |
33 |
34 | 41 |
42 |
43 | 50 |
51 |
52 | 59 |
60 |
61 | 68 |
69 |
70 | 77 |
78 | 79 |
80 |
81 |
82 |
83 |
84 |
85 | ); 86 | }; 87 | 88 | @on('#/settings') settings = () => { 89 | if (!auth.authorized()) {return;} 90 | return { user: app['user'] }; 91 | }; 92 | 93 | @on('submit-settings') submitSettings = async (state, e) => { 94 | try { 95 | e.preventDefault(); 96 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 97 | const user = serializeObject(e.target); 98 | const result = await auth.save(user); 99 | app.run('/set-user', result.user); 100 | return { user: result.user, showModal: true }; 101 | } catch ({ errors }) { 102 | return { ...state, errors }; 103 | } 104 | }; 105 | 106 | @on('ok, cancel') ok = (state) => { 107 | return { ...state, showModal: false }; 108 | }; 109 | } 110 | 111 | export default new SettingsComponent().mount('my-app'); 112 | -------------------------------------------------------------------------------- /src/api.ts: -------------------------------------------------------------------------------- 1 | import app from 'apprun'; 2 | 3 | // Conduit API 4 | window['defaultBasePath'] = 'https://api.realworld.io/api'; 5 | 6 | import { toQueryString, serializeObject, getToken, setToken, get, post, del, put } from './fetch'; 7 | export { toQueryString, serializeObject }; 8 | import { IUser, IProfile, IArticle, IComment } from './models'; 9 | 10 | export interface IAuthResponse { 11 | user: IUser; 12 | } 13 | 14 | export interface ITags { 15 | tags: Array; 16 | } 17 | 18 | export interface IFeed { 19 | articles: Array; 20 | articlesCount: number; 21 | } 22 | 23 | export interface IArticlesRequest { 24 | tag?: string; 25 | author?: string; 26 | favorited?: string; 27 | limit: number; 28 | offset: number; 29 | } 30 | 31 | export interface INewArticle { 32 | title: string; 33 | description: string; 34 | body: string; 35 | tagList: Array; 36 | } 37 | 38 | export interface IArticlesResponse { 39 | article: IArticle; 40 | } 41 | 42 | export interface ICommentsResponse { 43 | comments: Array; 44 | } 45 | 46 | export interface IProfileResponse { 47 | profile: IProfile; 48 | } 49 | 50 | export const tags = { 51 | all: () => get('/tags') 52 | }; 53 | 54 | export const auth = { 55 | current: () => (getToken() ? get('/user') : null), 56 | signIn: (user: { email: string; password: string }) => 57 | post('/users/login', { user }), 58 | register: (user: { username: string; email: string; password: string }) => 59 | post('/users', { user }), 60 | save: user => put('/user', { user }), 61 | authorized: (): boolean => (app['user'] ? true : app.run('#/login') && false) // app.run returns true if found event handlers 62 | }; 63 | 64 | export const articles = { 65 | search: (request: IArticlesRequest) => get(`/articles?${toQueryString(request)}`), 66 | feed: (request: { limit: number; offset: number }) => 67 | get(`/articles/feed?${toQueryString(request)}`), 68 | get: (slug: string) => get(`/articles/${slug}`), 69 | delete: (slug: string) => del(`/articles/${slug}`), 70 | favorite: (slug: string) => post(`/articles/${slug}/favorite`), 71 | unfavorite: (slug: string) => del(`/articles/${slug}/favorite`), 72 | update: (article: IArticle) => put(`/articles/${article.slug}`, { article }), 73 | create: (article: INewArticle) => post('/articles', { article }) 74 | }; 75 | 76 | export const comments = { 77 | create: (slug: string, comment: { body: string }) => 78 | post(`/articles/${slug}/comments`, { comment }), 79 | delete: (slug: string, commentId: string) => del(`/articles/${slug}/comments/${commentId}`), 80 | forArticle: (slug: string) => get(`/articles/${slug}/comments`) 81 | }; 82 | 83 | export const profile = { 84 | get: (username: string) => get(`/profiles/${username}`), 85 | follow: (username: string) => post(`/profiles/${username}/follow`), 86 | unfollow: (username: string) => del(`/profiles/${username}/follow`) 87 | }; 88 | 89 | app.on('/get-user', async () => { 90 | try { 91 | const current = await auth.current(); 92 | app.run('/set-user', current?.user); 93 | } catch { 94 | setToken(null); 95 | document.location.reload(); 96 | } 97 | }); 98 | 99 | app.on('/set-user', (user) => { 100 | app['user'] = user; 101 | setToken(user ? user.token : null); 102 | }); 103 | 104 | app.on('/toggle-follow', async (author: IProfile, component) => { 105 | if (!auth.authorized()) {return;} 106 | const result = author.following 107 | ? await profile.unfollow(author.username) 108 | : await profile.follow(author.username); 109 | component.run('update-follow', result.profile); 110 | }); 111 | 112 | app.on('/toggle-fav-article', async (article: IArticle, component) => { 113 | if (!auth.authorized()) {return;} 114 | const result = article.favorited 115 | ? await articles.unfavorite(article.slug) 116 | : await articles.favorite(article.slug); 117 | component.run('update-article', result.article); 118 | }); 119 | -------------------------------------------------------------------------------- /src/components/article.tsx: -------------------------------------------------------------------------------- 1 | import app, { Component, on } from 'apprun'; 2 | import { articles, comments } from '../api'; 3 | import { IArticle } from '../models'; 4 | import Comments from './comment-list'; 5 | import ArticleMeta from './article-meta'; 6 | import Modal from './modal'; 7 | import { marked } from 'marked'; 8 | 9 | class ArticleComponent extends Component { 10 | state = { 11 | article: null, 12 | comments: [] 13 | }; 14 | 15 | view = (state) => { 16 | const article = state.article as IArticle; 17 | if (!article) {return;} 18 | return ( 19 |
20 | {state.deleting && ( 21 | 29 | )} 30 | 31 | 37 | 38 |
39 |
40 |
41 |

{`_html:${marked(article.body)}`}

42 |
43 |
44 | {article.tagList.map(tag => ( 45 |
  • 46 | {tag} 47 |
  • 48 | ))} 49 |
    50 |
    51 |
    52 |
    53 |
    54 | 55 |
    56 | 57 |
    58 |
    59 | ); 60 | }; 61 | 62 | @on('#/article') root = async (state, slug) => { 63 | let article = state.article as IArticle; 64 | let _comments = state.comments; 65 | if (!article || article.slug !== slug) { 66 | const result = await articles.get(slug); 67 | article = result.article; 68 | const commentsResponse = await comments.forArticle(article.slug); 69 | _comments = commentsResponse.comments; 70 | } 71 | return { ...state, article, comments: _comments }; 72 | }; 73 | 74 | @on('/new-comment') newComment = async (state, e) => { 75 | try { 76 | e.preventDefault(); 77 | const comment = e.target['comment'].value; 78 | if (!comment) {return;} 79 | await comments.create(state.article.slug, { body: comment }); 80 | const commentsResponse = await comments.forArticle(state.article.slug); 81 | return { ...state, comments: commentsResponse.comments }; 82 | } catch ({ errors }) { 83 | return { ...state, errors }; 84 | } 85 | }; 86 | 87 | @on('/delete-comment') deleteComment = async (state, comment) => { 88 | await comments.delete(this.state.article.slug, comment.id); 89 | const commentsResponse = await comments.forArticle(state.article.slug); 90 | return { ...state, comments: commentsResponse.comments }; 91 | }; 92 | 93 | @on('update-article') updateArticle = (state, article) => ({ 94 | ...state, 95 | article 96 | }); 97 | 98 | @on('update-follow') updateFollow = (state, author) => { 99 | state.article.author = author; 100 | return state; 101 | }; 102 | 103 | @on('edit-article') editArticle = (state, article) => { 104 | document.location.hash = `#/editor/${article.slug}`; 105 | }; 106 | 107 | @on('delete-article') deleteArticle = state => ({ 108 | ...state, 109 | deleting: true 110 | }); 111 | 112 | @on('ok-delete-article') okDelete = (state) => { 113 | articles.delete(state.article.slug); 114 | document.location.hash = '#/'; 115 | return { ...state, deleting: false }; 116 | }; 117 | 118 | @on('cancel-delete-article') cancelDelete = state => ({ 119 | ...state, 120 | deleting: false 121 | }); 122 | } 123 | 124 | export default new ArticleComponent().mount('my-app'); 125 | -------------------------------------------------------------------------------- /src/components/profile.tsx: -------------------------------------------------------------------------------- 1 | import app, { Component, on } from 'apprun'; 2 | import { IProfile } from '../models'; 3 | import { articles, profile , auth } from '../api'; 4 | import Articles from './article-list'; 5 | import Pages from './page-list'; 6 | 7 | const PAGE_SIZE = 10; 8 | 9 | class ProfileComponent extends Component { 10 | state = { 11 | name: '', 12 | type: 'articles', 13 | articles: [], 14 | page: 1 15 | }; 16 | 17 | view = (state) => { 18 | const profile = state.profile as IProfile; 19 | if (!profile) {return;} 20 | return ( 21 |
    22 | 46 | 47 |
    48 |
    49 |
    50 |
    51 | 67 |
    68 | 69 | 74 |
    75 |
    76 |
    77 |
    78 | ); 79 | }; 80 | 81 | updateState = async (state, name, type, page) => { 82 | name = decodeURIComponent(name || state.name); 83 | type = type || state.type; 84 | page = parseInt(page) || state.page; 85 | let newState = state; 86 | if (name !== state.name) { 87 | const profileResult = await profile.get(name); 88 | newState = { ...newState, profile: profileResult.profile }; 89 | } 90 | if (name !== state.name || type !== state.type || page !== state.page) { 91 | const limit = PAGE_SIZE; 92 | const offset = (page - 1) * limit; 93 | const articleResult = 94 | type === 'favorites' 95 | ? await articles.search({ favorited: name, offset, limit }) 96 | : await articles.search({ author: name, offset, limit }); 97 | newState = { 98 | ...newState, 99 | name, 100 | type, 101 | page, 102 | articles: articleResult.articles, 103 | max: articleResult.articlesCount 104 | }; 105 | } 106 | return newState; 107 | }; 108 | 109 | @on('#/profile') root = (state, name, type, page) => auth.authorized() ? this.updateState(state, name, type, page) : null; 110 | 111 | @on('update-article') updateArticle = (state, article) => { 112 | state.articles = state.articles.map((a) => { 113 | return a.slug === article.slug ? article : a; 114 | }); 115 | return state; 116 | }; 117 | 118 | @on('update-follow') updateFollow = (state, profile) => ({ ...state, profile }); 119 | } 120 | 121 | export default new ProfileComponent().mount('my-app'); 122 | -------------------------------------------------------------------------------- /tests/auto-events.spec.ts: -------------------------------------------------------------------------------- 1 | import home from '../src/components/home'; 2 | import signin from '../src/components/signin'; 3 | import register from '../src/components/register'; 4 | import editor from '../src/components/editor'; 5 | import settings from '../src/components/settings'; 6 | import profile from '../src/components/profile'; 7 | import article from '../src/components/article'; 8 | import header from '../src/components/header'; 9 | 10 | // import { tags, articles, comments } from '../src/api'; 11 | import './mocks'; 12 | 13 | describe('HeaderComponent', ()=>{ 14 | it ('should handle event: /set-user', ()=>{ 15 | header.run('/set-user'); 16 | expect(header.state).toBeTruthy(); 17 | }) 18 | }); 19 | 20 | describe('HomeComponent', () => { 21 | it('should handle event: #/', () => { 22 | home.run('#/'); 23 | expect(home.state).toBeTruthy(); 24 | }) 25 | it('should handle event: #/feed', () => { 26 | home.run('#/feed'); 27 | expect(home.state).toBeTruthy(); 28 | }) 29 | it('should handle event: #/tag', () => { 30 | home.run('#/tag'); 31 | expect(home.state).toBeTruthy(); 32 | }) 33 | it('should handle event: update-article', () => { 34 | home.run('update-article'); 35 | expect(home.state).toBeTruthy(); 36 | }) 37 | }); 38 | 39 | describe('SigninComponent', () => { 40 | it('should handle event: #/login', () => { 41 | signin.run('#/login'); 42 | expect(signin.state).toBeTruthy(); 43 | }) 44 | it('should handle event: #/logout', () => { 45 | signin.run('#/logout'); 46 | expect(signin.state).toBeTruthy(); 47 | }) 48 | it('should handle event: sign-in', () => { 49 | signin.run('sign-in'); 50 | expect(signin.state).toBeTruthy(); 51 | }) 52 | }); 53 | 54 | describe('RegisterComponent', () => { 55 | it('should handle event: #/register', () => { 56 | register.run('#/register'); 57 | expect(register.state).toBeTruthy(); 58 | }) 59 | it('should handle event: register', () => { 60 | register.run('register'); 61 | expect(register.state).toBeTruthy(); 62 | }) 63 | }); 64 | 65 | describe('ProfileComponent', () => { 66 | it('should handle event: #/profile', () => { 67 | profile.run('#/profile'); 68 | expect(profile.state).toBeTruthy(); 69 | }) 70 | it('should handle event: update-article', () => { 71 | profile.run('update-article'); 72 | expect(profile.state).toBeTruthy(); 73 | }) 74 | it('should handle event: update-follow', () => { 75 | profile.run('update-follow'); 76 | expect(profile.state).toBeTruthy(); 77 | }) 78 | }); 79 | 80 | describe('SettingsComponent', () => { 81 | it('should handle event: #/settings', () => { 82 | settings.run('#/settings'); 83 | expect(settings.state).toBeTruthy(); 84 | }) 85 | it('should handle event: submit-settings', () => { 86 | settings.run('submit-settings'); 87 | expect(settings.state).toBeTruthy(); 88 | }) 89 | xit('should handle event: ok', () => { 90 | settings.run('ok'); 91 | expect(settings.state).toBeTruthy(); 92 | }) 93 | xit('should handle event: cancel', () => { 94 | settings.run('cancel'); 95 | expect(settings.state).toBeTruthy(); 96 | }) 97 | }); 98 | 99 | describe('EditorComponent', () => { 100 | it('should handle event: #/editor', () => { 101 | editor.run('#/editor'); 102 | expect(editor.state).toBeTruthy(); 103 | }) 104 | it('should handle event: submit-article', () => { 105 | editor.run('submit-article'); 106 | expect(editor.state).toBeTruthy(); 107 | }) 108 | }); 109 | 110 | describe('ArticleComponent', () => { 111 | it('should handle event: #/article', () => { 112 | article.run('#/article', 'slug'); 113 | expect(article.state).toBeTruthy(); 114 | }) 115 | it('should handle event: /new-comment', () => { 116 | article.run('/new-comment'); 117 | expect(article.state).toBeTruthy(); 118 | }) 119 | xit('should handle event: /delete-comment', () => { 120 | article.run('/delete-comment'); 121 | expect(article.state).toBeTruthy(); 122 | }) 123 | it('should handle event: update-article', () => { 124 | article.run('update-article'); 125 | expect(article.state).toBeTruthy(); 126 | }) 127 | xit('should handle event: update-follow', () => { 128 | article.run('update-follow'); 129 | expect(article.state).toBeTruthy(); 130 | }) 131 | xit('should handle event: edit-article', () => { 132 | article.run('edit-article'); 133 | expect(article.state).toBeTruthy(); 134 | }) 135 | it('should handle event: delete-article', () => { 136 | article.run('delete-article'); 137 | expect(article.state).toBeTruthy(); 138 | }) 139 | xit('should handle event: ok-delete-article', () => { 140 | article.run('ok-delete-article'); 141 | expect(article.state).toBeTruthy(); 142 | }) 143 | xit('should handle event: cancel-delete-article', () => { 144 | article.run('cancel-delete-article'); 145 | expect(article.state).toBeTruthy(); 146 | }) 147 | }); 148 | -------------------------------------------------------------------------------- /src/components/home.tsx: -------------------------------------------------------------------------------- 1 | import app, { Component, on } from 'apprun'; 2 | import { auth, tags, articles } from '../api'; 3 | import { IArticle } from '../models'; 4 | import Articles from './article-list'; 5 | import Pages from './page-list'; 6 | 7 | const PAGE_SIZE = 10; 8 | const Tag = ({ tag }) => ( 9 | 10 | {tag} 11 | 12 | ); 13 | 14 | declare interface IState { 15 | type: '' | 'feed' | 'tag'; 16 | articles: Array; 17 | tags: Array; 18 | max: number; 19 | page: number; 20 | } 21 | 22 | class HomeComponent extends Component { 23 | state: IState = { 24 | type: '', 25 | articles: [], 26 | tags: [], 27 | max: 1, 28 | page: 1 29 | }; 30 | 31 | view = (state) => { 32 | const tag = state.type === 'tag' && state.tag ? `/${state.tag}` : ''; 33 | return ( 34 |
    35 | 41 |
    42 |
    43 |
    44 |
    45 | 72 |
    73 | 74 | 79 |
    80 |
    81 | 89 |
    90 |
    91 |
    92 |
    93 | ); 94 | }; 95 | 96 | updateState = async (state, type: '' | 'feed' | 'tag', page, tag?: string) => { 97 | try { 98 | const tagList = state.tags.length ? { tags: state.tags } : await tags.all(); 99 | page = parseInt(page) || 1; 100 | tag = tag || state.tag; 101 | const limit = PAGE_SIZE; 102 | const offset = (page - 1) * PAGE_SIZE; 103 | let feed; 104 | switch (type) { 105 | case 'feed': 106 | if (!auth.authorized()) {return { ...state, articles: [], max: 0 };} 107 | feed = await articles.feed({ limit, offset }); 108 | break; 109 | case 'tag': 110 | feed = await articles.search({ tag, limit, offset }); 111 | break; 112 | default: 113 | feed = await articles.search({ limit, offset }); 114 | break; 115 | } 116 | page = Math.min(page, Math.floor(feed.articlesCount / PAGE_SIZE) + 1); 117 | return { 118 | ...state, 119 | tags: tagList.tags, 120 | type, 121 | page, 122 | tag, 123 | articles: feed.articles, 124 | max: feed.articlesCount 125 | }; 126 | } catch ({ errors }) { 127 | return { ...state, errors, articles: [], max: 0 }; 128 | } 129 | }; 130 | 131 | @on('#/') root = async (state, page) => await this.updateState(state, '', page); 132 | 133 | @on('#/feed') feed = async (state, page) => await this.updateState(state, 'feed', page); 134 | 135 | @on('#/tag') tag = async (state, tag, page) => await this.updateState(state, 'tag', page, tag); 136 | 137 | @on('update-article') updateArticle = (state, article) => { 138 | state.articles = state.articles.map((a) => { 139 | return a.slug === article.slug ? article : a; 140 | }); 141 | return state; 142 | }; 143 | } 144 | 145 | export default new HomeComponent().mount('my-app'); 146 | -------------------------------------------------------------------------------- /tests/snapshot.spec.ts: -------------------------------------------------------------------------------- 1 | import home from '../src/components/home'; 2 | import signin from '../src/components/signin'; 3 | import register from '../src/components/register'; 4 | import editor from '../src/components/editor'; 5 | import settings from '../src/components/settings'; 6 | import profile from '../src/components/profile'; 7 | import article from '../src/components/article'; 8 | 9 | describe('home component view', () => { 10 | it('view test', () => { 11 | const state = { 12 | type: 'feed', 13 | articles: [], 14 | tags: ['1', '2', '3'], 15 | max: 10, 16 | page: 1 17 | }; 18 | const vdom = home['view'](state); 19 | expect(JSON.stringify(vdom, undefined, 2)).toMatchSnapshot(); 20 | }); 21 | }); 22 | 23 | describe('signin component', () => { 24 | it('view snapshot: #1', () => { 25 | const component = signin; 26 | const state = { 27 | 'messages': [], 28 | 'returnTo': '#/login' 29 | }; 30 | const vdom = component['view'](state); 31 | expect(JSON.stringify(vdom, undefined, 2)).toMatchSnapshot(); 32 | }); 33 | }); 34 | 35 | describe('register component', () => { 36 | it('view snapshot: #1', () => { 37 | const component = register; 38 | const state = {}; 39 | const vdom = component['view'](state); 40 | expect(JSON.stringify(vdom, undefined, 2)).toMatchSnapshot(); 41 | }); 42 | }); 43 | 44 | describe('editor component - new article', () => { 45 | it('view snapshot: #1', () => { 46 | const component = editor; 47 | const state = { 48 | 'article': { 49 | 'title': '', 50 | 'description': '', 51 | 'body': '', 52 | 'tagList': [] 53 | } 54 | }; 55 | const vdom = component['view'](state); 56 | expect(JSON.stringify(vdom, undefined, 2)).toMatchSnapshot(); 57 | }); 58 | }); 59 | 60 | describe('settings component', () => { 61 | it('view snapshot: #1', () => { 62 | const component = settings; 63 | const state = { 64 | 'user': { 65 | 'id': 11159, 66 | 'email': '1@1test.com', 67 | 'createdAt': '2017-09-03T21:33:35.494Z', 68 | 'updatedAt': '2017-09-15T20:43:17.712Z', 69 | 'username': 'user 1', 70 | 'bio': 'test user 1', 71 | 'image': 'https://static.productionready.io/images/smiley-cyrus.jpg', 72 | 'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MTExNTksInVzZXJuYW1lIjoidXNlciAxIiwiZXhwIjoxNTM3NTg0ODY3fQ.oKbqH7fNkLQE7IuwsIlBHXcPJ9rP7VTeBBiIAXgS0Gw' 73 | } 74 | }; 75 | const vdom = component['view'](state); 76 | expect(JSON.stringify(vdom, undefined, 2)).toMatchSnapshot(); 77 | }); 78 | }); 79 | 80 | describe('profile component', () => { 81 | it('view snapshot: #1', () => { 82 | const component = profile; 83 | const state = { 84 | 'name': 'user 1', 85 | 'type': 'articles', 86 | 'articles': [ 87 | { 88 | 'title': 'Hello world', 89 | 'slug': 'hello-world-6n0suv', 90 | 'body': 'Hello world', 91 | 'createdAt': '2018-07-21T13:55:08.520Z', 92 | 'updatedAt': '2018-07-21T13:55:08.520Z', 93 | 'tagList': [], 94 | 'description': 'Hello world', 95 | 'author': { 96 | 'username': 'user 1', 97 | 'bio': 'test user 1', 98 | 'image': 'https://static.productionready.io/images/smiley-cyrus.jpg', 99 | 'following': false 100 | }, 101 | 'favorited': false, 102 | 'favoritesCount': 0 103 | }, 104 | { 105 | 'title': 'OK', 106 | 'slug': 'ok-fakc89', 107 | 'body': '1,2', 108 | 'createdAt': '2018-01-03T16:54:12.060Z', 109 | 'updatedAt': '2018-01-03T16:54:12.060Z', 110 | 'tagList': [ 111 | '2', 112 | '1' 113 | ], 114 | 'description': 'OK', 115 | 'author': { 116 | 'username': 'user 1', 117 | 'bio': 'test user 1', 118 | 'image': 'https://static.productionready.io/images/smiley-cyrus.jpg', 119 | 'following': false 120 | }, 121 | 'favorited': false, 122 | 'favoritesCount': 1 123 | }, 124 | { 125 | 'title': 'new post - edit', 126 | 'slug': 'new-post-qmr2hl', 127 | 'body': 'new - edit asasasasasa dsdsds', 128 | 'createdAt': '2017-09-11T03:10:42.711Z', 129 | 'updatedAt': '2017-09-11T03:24:07.751Z', 130 | 'tagList': [], 131 | 'description': '', 132 | 'author': { 133 | 'username': 'user 1', 134 | 'bio': 'test user 1', 135 | 'image': 'https://static.productionready.io/images/smiley-cyrus.jpg', 136 | 'following': false 137 | }, 138 | 'favorited': true, 139 | 'favoritesCount': 3 140 | }, 141 | { 142 | 'title': 'An article - edited', 143 | 'slug': 'an-article-edited-n993c6', 144 | 'body': 'We\'ve gone to great lengths to adhere to the [RealWorld](https://github.com/gothinkster/realworld) community style guides & best practices.', 145 | 'createdAt': '2017-09-08T01:27:01.178Z', 146 | 'updatedAt': '2017-09-08T01:27:01.178Z', 147 | 'tagList': [], 148 | 'description': '', 149 | 'author': { 150 | 'username': 'user 1', 151 | 'bio': 'test user 1', 152 | 'image': 'https://static.productionready.io/images/smiley-cyrus.jpg', 153 | 'following': false 154 | }, 155 | 'favorited': true, 156 | 'favoritesCount': 2 157 | }, 158 | { 159 | 'title': 'An article', 160 | 'slug': 'an-article-kchbx7', 161 | 'body': 'We\'ve gone to great lengths to adhere to the [RealWorld](https://github.com/gothinkster/realworld) community style guides & best practices.', 162 | 'createdAt': '2017-09-08T01:14:49.190Z', 163 | 'updatedAt': '2017-09-08T01:14:49.190Z', 164 | 'tagList': [], 165 | 'description': '', 166 | 'author': { 167 | 'username': 'user 1', 168 | 'bio': 'test user 1', 169 | 'image': 'https://static.productionready.io/images/smiley-cyrus.jpg', 170 | 'following': false 171 | }, 172 | 'favorited': false, 173 | 'favoritesCount': 0 174 | }, 175 | { 176 | 'title': 't2', 177 | 'slug': 't2-b5bp4w', 178 | 'body': 't2', 179 | 'createdAt': '2017-09-05T03:14:26.450Z', 180 | 'updatedAt': '2017-09-05T03:14:26.450Z', 181 | 'tagList': [ 182 | '3', 183 | '2', 184 | '1' 185 | ], 186 | 'description': '', 187 | 'author': { 188 | 'username': 'user 1', 189 | 'bio': 'test user 1', 190 | 'image': 'https://static.productionready.io/images/smiley-cyrus.jpg', 191 | 'following': false 192 | }, 193 | 'favorited': true, 194 | 'favoritesCount': 3 195 | }, 196 | { 197 | 'title': 'Test New Post', 198 | 'slug': 't1-vd5veq', 199 | 'body': '[AppRun](https://github.com/yysun/apprun) is a 3K library for developing applications using the elm style model-view-update architecture and event pub and sub pattern.\n\nThe RealWorld example application is built using [AppRun](https://github.com/yysun/apprun) component that implements the model-view-update architecture.', 200 | 'createdAt': '2017-09-05T03:03:14.420Z', 201 | 'updatedAt': '2017-09-12T00:06:14.036Z', 202 | 'tagList': [ 203 | 'test', 204 | 'AppRun', 205 | 'typescript' 206 | ], 207 | 'description': '', 208 | 'author': { 209 | 'username': 'user 1', 210 | 'bio': 'test user 1', 211 | 'image': 'https://static.productionready.io/images/smiley-cyrus.jpg', 212 | 'following': false 213 | }, 214 | 'favorited': false, 215 | 'favoritesCount': 1 216 | } 217 | ], 218 | 'page': 1, 219 | 'profile': { 220 | 'username': 'user 1', 221 | 'bio': 'test user 1', 222 | 'image': 'https://static.productionready.io/images/smiley-cyrus.jpg', 223 | 'following': false 224 | }, 225 | 'max': 7 226 | }; 227 | const vdom = component['view'](state); 228 | expect(JSON.stringify(vdom, undefined, 2)).toMatchSnapshot(); 229 | }); 230 | }); 231 | 232 | describe('article component', () => { 233 | it('view snapshot: #1', () => { 234 | const component = article; 235 | const state = { 236 | 'article': { 237 | 'title': 'Hello world', 238 | 'slug': 'hello-world-6n0suv', 239 | 'body': 'Hello world', 240 | 'createdAt': '2018-07-21T13:55:08.520Z', 241 | 'updatedAt': '2018-07-21T13:55:08.520Z', 242 | 'tagList': [], 243 | 'description': 'Hello world', 244 | 'author': { 245 | 'username': 'user 1', 246 | 'bio': 'test user 1', 247 | 'image': 'https://static.productionready.io/images/smiley-cyrus.jpg', 248 | 'following': false 249 | }, 250 | 'favorited': false, 251 | 'favoritesCount': 0 252 | }, 253 | 'comments': [] 254 | }; 255 | 256 | const vdom = component['view'](state); 257 | expect(JSON.stringify(vdom, undefined, 2)).toMatchSnapshot(); 258 | }); 259 | }); -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";var t={n:e=>{var s=e&&e.__esModule?()=>e.default:()=>e;return t.d(s,{a:s}),s},d:(e,s)=>{for(var o in s)t.o(s,o)&&!t.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:s[o]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)};const e=apprun;var s=t.n(e);function o(t,e,s,o){var a,n=arguments.length,i=n<3?e:null===o?o=Object.getOwnPropertyDescriptor(e,s):o;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(t,e,s,o);else for(var r=t.length-1;r>=0;r--)(a=t[r])&&(i=(n<3?a(i):n>3?a(e,s,i):a(e,s))||i);return n>3&&i&&Object.defineProperty(e,s,i),i}function a(t,e,s,o){return new(s||(s=Promise))((function(a,n){function i(t){try{l(o.next(t))}catch(t){n(t)}}function r(t){try{l(o.throw(t))}catch(t){n(t)}}function l(t){var e;t.done?a(t.value):(e=t.value,e instanceof s?e:new s((function(t){t(e)}))).then(i,r)}l((o=o.apply(t,e||[])).next())}))}Object.create,Object.create;class n extends e.Component{constructor(){super(...arguments),this.state={},this.view=t=>{const e=t.user;return s().h("ul",{class:"nav navbar-nav pull-xs-right"},s().h("li",{class:"nav-item"},s().h("a",{class:"nav-link active",href:"#/"},"Home")),e&&s().h("li",{class:"nav-item"},s().h("a",{class:"nav-link",href:"#/editor"},s().h("i",{class:"ion-compose"})," New Post")),e&&s().h("li",{class:"nav-item"},s().h("a",{class:"nav-link",href:"#/settings"},s().h("i",{class:"ion-gear-a"})," Settings")),!e&&s().h("li",{class:"nav-item"},s().h("a",{class:"nav-link",href:"#/login"},"Sign In")),!e&&s().h("li",{class:"nav-item"},s().h("a",{class:"nav-link",href:"#/register"},"Sign Up")),e&&s().h("li",{class:"nav-item"},s().h("a",{class:"nav-link",href:`#/profile/${e.username}`},e.username)),e&&s().h("li",{class:"nav-item"},s().h("a",{class:"nav-link",href:"#/logout"},"Sign Out")))},this.setUser=(t,e)=>Object.assign(Object.assign({},t),{user:e})}}o([(0,e.on)("/set-user")],n.prototype,"setUser",void 0),(new n).start("header");let i=window&&window.localStorage&&window.localStorage.getItem("jwt")||"";function r(t){i=t,window.localStorage&&(t?window.localStorage.setItem("jwt",t):window.localStorage.removeItem("jwt"))}function l(t,e,s){return a(this,void 0,void 0,(function*(){const o={"Content-Type":"application/json; charset=utf-8"};i&&(o.Authorization=`Token ${i}`);const a=yield window.fetch(`${defaultBasePath}${e}`,{method:t,headers:o,body:s&&JSON.stringify(s)});if(401===a.status)throw r(null),new Error("401");const n=yield a.json();if(!a.ok)throw n;return n}))}function c(t){return l("GET",t)}function u(t,e){return l("POST",t,e)}function h(t){return l("DELETE",t)}function d(t,e){return l("PUT",t,e)}function m(t){const e=[];for(const s in t)Object.prototype.hasOwnProperty.call(t,s)&&e.push(encodeURIComponent(s)+"="+encodeURIComponent(t[s]));return e.join("&")}function p(t){const e={};if("object"==typeof t&&"FORM"===t.nodeName)for(let s=0;si?c("/user"):null,f=()=>!!s().user||s().run("#/login")&&!1,v=t=>c(`/articles?${m(t)}`),b=t=>c(`/articles/${t}`),y=t=>c(`/articles/${t}/comments`);function w(t){const e=t.article,o=e.favorited?"btn-primary":"btn-outline-primary";return s().h("div",{class:"article-preview"},s().h("div",{class:"article-meta"},s().h("a",{href:e.author.image},s().h("img",{src:e.author.image})),s().h("div",{class:"info"},s().h("a",{href:`#/profile/${e.author.username}`,class:"author"},e.author.username),s().h("span",{class:"date"},new Date(e.updatedAt).toLocaleString())),s().h("button",{class:`btn btn-sm pull-xs-right ${o}`,$onclick:["/toggle-fav-article",e,t.component]},s().h("i",{class:"ion-heart"})," ",e.favoritesCount)),s().h("a",{href:`#/article/${e.slug}`,class:"preview-link"},s().h("h1",null,e.title),s().h("p",null,e.description),s().h("span",null,"Read more..."),s().h("ul",{class:"tag-list"},e.tagList.map((t=>s().h("li",{class:"tag-default tag-pill tag-outline"},s().h("a",{href:`#/tag/${t}`},t," ")))))))}function $({articles:t,component:e}){return t.length?t.map((t=>s().h(w,{article:t,component:e}))):s().h("div",{class:"article-preview"},"No articles are here... yet.")}function O({max:t,selected:e,link:o}){const a=new Array(t).fill(0);return s().h("nav",null,s().h("ul",{class:"pagination"},a.map(((t,a)=>s().h("li",{class:"page-item "+(a+1===e?"active":"")},s().h("a",{href:`${o}/${a+1}`,class:"page-link"},a+1))))))}s().on("/get-user",(()=>a(void 0,void 0,void 0,(function*(){try{const t=yield g();s().run("/set-user",null==t?void 0:t.user)}catch(t){r(null),document.location.reload()}})))),s().on("/set-user",(t=>{s().user=t,r(t?t.token:null)})),s().on("/toggle-follow",((t,e)=>a(void 0,void 0,void 0,(function*(){if(!f())return;const s=t.following?yield(o=t.username,h(`/profiles/${o}/follow`)):yield(t=>u(`/profiles/${t}/follow`))(t.username);var o;e.run("update-follow",s.profile)})))),s().on("/toggle-fav-article",((t,e)=>a(void 0,void 0,void 0,(function*(){if(!f())return;const s=t.favorited?yield(o=t.slug,h(`/articles/${o}/favorite`)):yield(t=>u(`/articles/${t}/favorite`))(t.slug);var o;e.run("update-article",s.article)}))));const j=({tag:t})=>s().h("a",{href:`#/tag/${t}/1`,class:"tag-pill tag-default"},t);class x extends e.Component{constructor(){super(...arguments),this.state={type:"",articles:[],tags:[],max:1,page:1},this.view=t=>{const e="tag"===t.type&&t.tag?`/${t.tag}`:"";return s().h("div",{class:"home-page"},s().h("div",{class:"banner"},s().h("div",{class:"container"},s().h("h1",{class:"logo-font"},"conduit"),s().h("p",null,"A place to share your knowledge."))),s().h("div",{class:"container page"},s().h("div",{class:"row"},s().h("div",{class:"col-md-9"},s().h("div",{class:"feed-toggle"},s().h("ul",{class:"nav nav-pills outline-active"},s().h("li",{class:"nav-item"},s().h("a",{class:`nav-link ${s().user?"":"disabled"} ${"feed"===t.type?"active":""}`,href:"#/feed"},"Your Feed")),s().h("li",{class:"nav-item"},s().h("a",{class:"nav-link "+(""===t.type?"active":""),href:"#/"},"Global Feed")),t.tag?s().h("li",{class:"nav-item"},s().h("a",{class:"nav-link "+("tag"===t.type?"active":""),href:`#/tag/${t.tag}`},"#",t.tag)):"")),s().h($,{articles:t.articles,component:this}),s().h(O,{max:Math.floor(t.max/10),selected:t.page,link:`#/${t.type}${e}`})),s().h("div",{class:"col-md-3"},s().h("div",{class:"sidebar"},s().h("p",null,"Popular Tags"),s().h("div",{class:"tag-list"},t.tags.map((t=>s().h(j,{tag:t})))))))))},this.updateState=(t,e,s,o)=>a(this,void 0,void 0,(function*(){try{const n=t.tags.length?{tags:t.tags}:yield c("/tags");s=parseInt(s)||1,o=o||t.tag;const i=10,r=10*(s-1);let l;switch(e){case"feed":if(!f())return Object.assign(Object.assign({},t),{articles:[],max:0});l=yield(a={limit:i,offset:r},c(`/articles/feed?${m(a)}`));break;case"tag":l=yield v({tag:o,limit:i,offset:r});break;default:l=yield v({limit:i,offset:r})}return s=Math.min(s,Math.floor(l.articlesCount/10)+1),Object.assign(Object.assign({},t),{tags:n.tags,type:e,page:s,tag:o,articles:l.articles,max:l.articlesCount})}catch({errors:e}){return Object.assign(Object.assign({},t),{errors:e,articles:[],max:0})}var a})),this.root=(t,e)=>a(this,void 0,void 0,(function*(){return yield this.updateState(t,"",e)})),this.feed=(t,e)=>a(this,void 0,void 0,(function*(){return yield this.updateState(t,"feed",e)})),this.tag=(t,e,s)=>a(this,void 0,void 0,(function*(){return yield this.updateState(t,"tag",s,e)})),this.updateArticle=(t,e)=>(t.articles=t.articles.map((t=>t.slug===e.slug?e:t)),t)}}function k({errors:t}){return s().h("ul",{class:"error-messages"},Object.keys(t).map((e=>s().h("li",null,`${e} ${t[e]}`))))}o([(0,e.on)("#/")],x.prototype,"root",void 0),o([(0,e.on)("#/feed")],x.prototype,"feed",void 0),o([(0,e.on)("#/tag")],x.prototype,"tag",void 0),o([(0,e.on)("update-article")],x.prototype,"updateArticle",void 0),(new x).mount("my-app");class S extends e.Component{constructor(){super(...arguments),this.state={},this.view=t=>{if(t&&!(t instanceof Promise))return s().h("div",{class:"auth-page"},s().h("div",{class:"container page"},s().h("div",{class:"row"},s().h("div",{class:"col-md-6 offset-md-3 col-xs-12"},s().h("h1",{class:"text-xs-center"},"Sign In"),s().h("p",{class:"text-xs-center"},s().h("a",{href:"#/register"},"Need an account?")),t.errors&&s().h(k,{errors:t.errors}),s().h("form",{$onsubmit:"sign-in"},s().h("fieldset",{class:"form-group"},s().h("input",{class:"form-control form-control-lg",type:"text",placeholder:"Email",name:"email"})),s().h("fieldset",{class:"form-group"},s().h("input",{class:"form-control form-control-lg",type:"password",placeholder:"Password",name:"password"})),s().h("button",{class:"btn btn-lg btn-primary pull-xs-right"},"Sign In"))))))},this.login=t=>g()?location.href="#/":Object.assign(Object.assign({},t),{messages:[],returnTo:document.location.hash}),this.logout=()=>{s().run("/set-user",null),document.location.hash="#/"},this.signIn=(t,e)=>a(this,void 0,void 0,(function*(){try{e.preventDefault();const a=yield(o=p(e.target),u("/users/login",{user:o}));s().run("/set-user",a.user);const n=(t.returnTo||"").replace(/#\/login\/?/,"");n?(s().run("route",n),history.pushState(null,null,n)):document.location.hash="#/feed"}catch({errors:e}){return Object.assign(Object.assign({},t),{errors:e})}var o}))}}o([(0,e.on)("#/login")],S.prototype,"login",void 0),o([(0,e.on)("#/logout")],S.prototype,"logout",void 0),o([(0,e.on)("sign-in")],S.prototype,"signIn",void 0),(new S).mount("my-app");class C extends e.Component{constructor(){super(...arguments),this.state={},this.view=t=>{if(t&&!(t instanceof Promise))return s().h("div",{class:"auth-page"},s().h("div",{class:"container page"},s().h("div",{class:"row"},s().h("div",{class:"col-md-6 offset-md-3 col-xs-12"},s().h("h1",{class:"text-xs-center"},"Sign Up"),s().h("p",{class:"text-xs-center"},s().h("a",{href:"#/login"},"Have an account?")),t.errors&&s().h(k,{errors:t.errors}),s().h("form",{$onsubmit:"register"},s().h("fieldset",{class:"form-group"},s().h("input",{class:"form-control form-control-lg",type:"text",placeholder:"Your Name",name:"username"})),s().h("fieldset",{class:"form-group"},s().h("input",{class:"form-control form-control-lg",type:"text",placeholder:"Email",name:"email"})),s().h("fieldset",{class:"form-group"},s().h("input",{class:"form-control form-control-lg",type:"password",placeholder:"Password",name:"password"})),s().h("button",{class:"btn btn-lg btn-primary pull-xs-right"},"Sign up"))))))},this.register=(t,e)=>g()?location.href="#/":Object.assign(Object.assign({},t),{messages:e}),this.submitRegistration=(t,e)=>a(this,void 0,void 0,(function*(){try{e.preventDefault();const t=yield(o=p(e.target),u("/users",{user:o}));s().run("/set-user",t.user),s().run("route","#/")}catch({errors:e}){return Object.assign(Object.assign({},t),{errors:e})}var o}))}}o([(0,e.on)("#/register")],C.prototype,"register",void 0),o([(0,e.on)("register")],C.prototype,"submitRegistration",void 0),(new C).mount("my-app");class A extends e.Component{constructor(){super(...arguments),this.state={name:"",type:"articles",articles:[],page:1},this.view=t=>{const e=t.profile;if(e)return s().h("div",{class:"profile-page"},s().h("div",{class:"user-info"},s().h("div",{class:"container"},s().h("div",{class:"row"},s().h("div",{class:"col-xs-12 col-md-10 offset-md-1"},s().h("img",{src:e.image,class:"user-img"}),s().h("h4",null,e.username),s().h("p",null,e.bio),s().h("button",{class:"btn btn-sm btn-outline-secondary action-btn",$onclick:["/toggle-follow",e,this]},e.following?s().h("span",null,s().h("i",{class:"ion-minus-round"})," Unfollow ",e.username):s().h("span",null,s().h("i",{class:"ion-plus-round"})," Follow ",e.username)))))),s().h("div",{class:"container"},s().h("div",{class:"row"},s().h("div",{class:"col-xs-12 col-md-10 offset-md-1"},s().h("div",{class:"articles-toggle"},s().h("ul",{class:"nav nav-pills outline-active"},s().h("li",{class:"nav-item"},s().h("a",{class:"nav-link "+("articles"===t.type?"active":""),href:`#/profile/${e.username}/articles/1`},"My Articles")),s().h("li",{class:"nav-item"},s().h("a",{class:"nav-link "+("favorites"===t.type?"active":""),href:`#/profile/${e.username}/favorites/1`},"Favorited Articles")))),s().h($,{articles:t.articles,component:this}),s().h(O,{max:Math.floor(t.max/10),selected:t.page,link:`#/profile/${t.profile.username}/${t.type}`})))))},this.updateState=(t,e,s,o)=>a(this,void 0,void 0,(function*(){e=decodeURIComponent(e||t.name),s=s||t.type,o=parseInt(o)||t.page;let a=t;if(e!==t.name){const t=yield(n=e,c(`/profiles/${n}`));a=Object.assign(Object.assign({},a),{profile:t.profile})}var n;if(e!==t.name||s!==t.type||o!==t.page){const t=10,n=(o-1)*t,i="favorites"===s?yield v({favorited:e,offset:n,limit:t}):yield v({author:e,offset:n,limit:t});a=Object.assign(Object.assign({},a),{name:e,type:s,page:o,articles:i.articles,max:i.articlesCount})}return a})),this.root=(t,e,s,o)=>f()?this.updateState(t,e,s,o):null,this.updateArticle=(t,e)=>(t.articles=t.articles.map((t=>t.slug===e.slug?e:t)),t),this.updateFollow=(t,e)=>Object.assign(Object.assign({},t),{profile:e})}}function P({title:t,body:e,ok:o,cancel:a,onOK:n,onCancel:i}){return s().h("div",{class:"modal-open"},s().h("div",{class:"modal-dialog",role:"document"},s().h("div",{class:"modal-content"},s().h("div",{class:"modal-header"},s().h("h5",{class:"modal-title"},t,s().h("button",{type:"button",class:"close","data-dismiss":"modal","aria-label":"Close",$onclick:i},s().h("span",{"aria-hidden":"true"},"×")))),s().h("div",{class:"modal-body"},s().h("p",null,e)),s().h("div",{class:"modal-footer"},a&&s().h("button",{type:"button",class:"btn btn-secondary","data-dismiss":"modal",$onclick:i},a),"  ",s().h("button",{type:"button",class:"btn btn-primary",$onclick:n},o)))),s().h("div",{class:"modal-backdrop show",$onclick:i}))}o([(0,e.on)("#/profile")],A.prototype,"root",void 0),o([(0,e.on)("update-article")],A.prototype,"updateArticle",void 0),o([(0,e.on)("update-follow")],A.prototype,"updateFollow",void 0),(new A).mount("my-app");class D extends e.Component{constructor(){super(...arguments),this.state={},this.view=t=>{const e=t.user;if(e)return s().h("div",{class:"settings-page"},t.showModal?s().h(P,{title:"Confirmation",body:"Your settings has been updated successfully.",ok:"OK",cancel:"Cancel",onOK:"ok",onCancel:"cancel"}):"",s().h("div",{class:"container page"},s().h("div",{class:"row"},s().h("div",{class:"col-md-6 offset-md-3 col-xs-12"},t.errors&&s().h(k,{errors:t.errors}),s().h("h1",{class:"text-xs-center"},"Your Settings"),s().h("form",{$onsubmit:"submit-settings"},s().h("fieldset",null,s().h("fieldset",{class:"form-group"},s().h("input",{class:"form-control",type:"text",placeholder:"URL of profile picture",name:"image",value:e.image})),s().h("fieldset",{class:"form-group"},s().h("input",{class:"form-control form-control-lg",type:"text",placeholder:"Your Name",name:"username",value:e.username})),s().h("fieldset",{class:"form-group"},s().h("textarea",{class:"form-control form-control-lg",rows:"8",placeholder:"Short bio about you",name:"bio"},e.bio)),s().h("fieldset",{class:"form-group"},s().h("input",{class:"form-control form-control-lg",type:"text",placeholder:"Email",name:"email",value:e.email})),s().h("fieldset",{class:"form-group"},s().h("input",{class:"form-control form-control-lg",type:"password",placeholder:"Password",name:"password",value:e.password})),s().h("button",{class:"btn btn-lg btn-primary pull-xs-right"},"Update Settings")))))))},this.settings=()=>{if(f())return{user:s().user}},this.submitSettings=(t,e)=>a(this,void 0,void 0,(function*(){try{e.preventDefault();const t=p(e.target),o=yield(t=>d("/user",{user:t}))(t);return s().run("/set-user",o.user),{user:o.user,showModal:!0}}catch({errors:e}){return Object.assign(Object.assign({},t),{errors:e})}})),this.ok=t=>Object.assign(Object.assign({},t),{showModal:!1})}}o([(0,e.on)("#/settings")],D.prototype,"settings",void 0),o([(0,e.on)("submit-settings")],D.prototype,"submitSettings",void 0),o([(0,e.on)("ok, cancel")],D.prototype,"ok",void 0),(new D).mount("my-app");class I extends e.Component{constructor(){super(...arguments),this.state={},this.view=t=>{if(!s().user||!t.article)return;const e=t.article;return s().h("div",{class:"editor-page"},s().h("div",{class:"container page"},s().h("div",{class:"row"},s().h("div",{class:"col-md-10 offset-md-1 col-xs-12"},t.errors&&s().h(k,{errors:t.errors}),s().h("form",{$onsubmit:"submit-article"},e.slug&&s().h("input",{type:"hidden",name:"slug",value:e.slug}),s().h("fieldset",null,s().h("fieldset",{class:"form-group"},s().h("input",{type:"text",class:"form-control form-control-lg",placeholder:"Article Title",name:"title",value:e.title})),s().h("fieldset",{class:"form-group"},s().h("input",{type:"text",class:"form-control",placeholder:"What's this article about?",name:"description",value:e.description})),s().h("fieldset",{class:"form-group"},s().h("textarea",{class:"form-control",rows:"8",placeholder:"Write your article (in markdown)",name:"body"},e.body)),s().h("fieldset",{class:"form-group"},s().h("input",{type:"text",class:"form-control",placeholder:"Enter tags",name:"tags",value:e.tagList.join(", ")}),s().h("div",{class:"tag-list"})),s().h("button",{class:"btn btn-lg pull-xs-right btn-primary",type:"submit"},"Publish Article")))))))},this.root=(t,e)=>a(this,void 0,void 0,(function*(){if(!f())return;let t;return e&&(t=(yield b(e)).article),t=t||{title:"",description:"",body:"",tagList:[]},{article:t}})),this.submitArticle=(t,e)=>a(this,void 0,void 0,(function*(){try{e.preventDefault();const t=p(e.target);t.tagList=t.tags.split(",");const s=t.slug?yield(t=>d(`/articles/${t.slug}`,{article:t}))(t):yield(t=>u("/articles",{article:t}))(t);document.location.hash=`#/article/${s.article.slug}`}catch({errors:e}){return Object.assign(Object.assign({},t),{errors:e})}}))}}o([(0,e.on)("#/editor")],I.prototype,"root",void 0),o([(0,e.on)("submit-article")],I.prototype,"submitArticle",void 0),(new I).mount("my-app");const U=marked;function F({comment:t}){return s().h("div",{class:"card"},s().h("div",{class:"card-block"},s().h("p",{class:"card-text"},s().h("p",null,`_html:${(0,U.marked)(t.body)}`))),s().h("div",{class:"card-footer"},s().h("a",{class:"comment-author"},s().h("img",{src:t.author.image,class:"comment-author-img"}))," ",s().h("a",{class:"comment-author",href:`#/profile/${t.author.username}`},t.author.username),s().h("span",{class:"date-posted"},new Date(t.createdAt).toLocaleString()),s().user&&s().user.username===t.author.username&&s().h("span",{class:"mod-options"},s().h("i",{class:"ion-trash-a",$onclick:["/delete-comment",t]}))))}function R({comments:t}){const e=s().user;return s().h("div",{class:"row"},s().h("div",{class:"col-xs-12 col-md-8 offset-md-2"},e?s().h("form",{class:"card comment-form",$onsubmit:"/new-comment"},s().h("div",{class:"card-block"},s().h("textarea",{class:"form-control",placeholder:"Write a comment...",rows:"3",name:"comment"})),s().h("div",{class:"card-footer"},e.image?s().h("img",{src:e.image,class:"comment-author-img"}):s().h("span",null,"@",e.username),s().h("button",{class:"btn btn-sm btn-primary",type:"submit"},"Post Comment"))):s().h("p",null,s().h("a",{href:`#/login/${document.location.hash}`},"Sign in")," or ",s().h("a",{href:"#/register"},"sign up")," to add comments on this article."),t.map((t=>s().h(F,{comment:t})))))}function E({article:t,component:e}){const o=t.favorited?"btn-primary":"btn-outline-primary",a=t.author.following?"btn-secondary":"btn-outline-secondary";return s().h("div",{class:"article-meta"},s().h("a",{href:t.author.image},s().h("img",{src:t.author.image})),s().h("div",{class:"info"},s().h("a",{href:`#/profile/${t.author.username}`,class:"author"},t.author.username),s().h("span",{class:"date"},new Date(t.updatedAt).toLocaleString())),s().user&&s().user.username===t.author.username?s().h("span",null,s().h("button",{class:"btn btn-sm btn-outline-secondary",$onclick:["edit-article",t]},s().h("i",{class:"ion-edit"}),"  Edit Article"),"  ",s().h("button",{class:"btn btn-sm btn-outline-danger",$onclick:["delete-article",t]},s().h("i",{class:"ion-trash-o"}),"  Delete Article")):s().h("span",null,s().h("button",{class:`btn btn-sm ${a}`,$onclick:["/toggle-follow",t.author,e]},t.author.following?s().h("span",null,s().h("i",{class:"ion-minus-round"})," Unfollow ",t.author.username):s().h("span",null,s().h("i",{class:"ion-plus-round"})," Follow ",t.author.username))," ","  ",s().h("button",{class:`btn btn-sm ${o}`,$onclick:["/toggle-fav-article",t,e]},s().h("i",{class:"ion-heart"}),"  Favorite Post ",s().h("span",{class:"counter"},"(",t.favoritesCount,")"))))}class L extends e.Component{constructor(){super(...arguments),this.state={article:null,comments:[]},this.view=t=>{const e=t.article;if(e)return s().h("div",{class:"article-page"},t.deleting&&s().h(P,{title:"Delete Article",body:"Are you sure you want to delete this article?",ok:"Delete",cancel:"No",onOK:"ok-delete-article",onCancel:"cancel-delete-article"}),s().h("div",{class:"banner"},s().h("div",{class:"container"},s().h("h1",null,e.title),s().h(E,{article:e,component:this}))),s().h("div",{class:"container page"},s().h("div",{class:"row article-content"},s().h("div",{class:"col-md-12"},s().h("p",null,`_html:${(0,U.marked)(e.body)}`),s().h("div",{class:"tag-list"},s().h("br",null),e.tagList.map((t=>s().h("li",{class:"tag-default tag-pill tag-outline"},s().h("a",{href:`#/tag/${t}`},t," "))))))),s().h("hr",null),s().h("div",{class:"article-actions"},s().h(E,{article:e,component:this})),s().h(R,{comments:t.comments})))},this.root=(t,e)=>a(this,void 0,void 0,(function*(){let s=t.article,o=t.comments;return s&&s.slug===e||(s=(yield b(e)).article,o=(yield y(s.slug)).comments),Object.assign(Object.assign({},t),{article:s,comments:o})})),this.newComment=(t,e)=>a(this,void 0,void 0,(function*(){try{e.preventDefault();const s=e.target.comment.value;if(!s)return;yield((t,e)=>u(`/articles/${t}/comments`,{comment:e}))(t.article.slug,{body:s});const o=yield y(t.article.slug);return Object.assign(Object.assign({},t),{comments:o.comments})}catch({errors:e}){return Object.assign(Object.assign({},t),{errors:e})}})),this.deleteComment=(t,e)=>a(this,void 0,void 0,(function*(){var s,o;yield(s=this.state.article.slug,o=e.id,h(`/articles/${s}/comments/${o}`));const a=yield y(t.article.slug);return Object.assign(Object.assign({},t),{comments:a.comments})})),this.updateArticle=(t,e)=>Object.assign(Object.assign({},t),{article:e}),this.updateFollow=(t,e)=>(t.article.author=e,t),this.editArticle=(t,e)=>{document.location.hash=`#/editor/${e.slug}`},this.deleteArticle=t=>Object.assign(Object.assign({},t),{deleting:!0}),this.okDelete=t=>(h(`/articles/${t.article.slug}`),document.location.hash="#/",Object.assign(Object.assign({},t),{deleting:!1})),this.cancelDelete=t=>Object.assign(Object.assign({},t),{deleting:!1})}}o([(0,e.on)("#/article")],L.prototype,"root",void 0),o([(0,e.on)("/new-comment")],L.prototype,"newComment",void 0),o([(0,e.on)("/delete-comment")],L.prototype,"deleteComment",void 0),o([(0,e.on)("update-article")],L.prototype,"updateArticle",void 0),o([(0,e.on)("update-follow")],L.prototype,"updateFollow",void 0),o([(0,e.on)("edit-article")],L.prototype,"editArticle",void 0),o([(0,e.on)("delete-article")],L.prototype,"deleteArticle",void 0),o([(0,e.on)("ok-delete-article")],L.prototype,"okDelete",void 0),o([(0,e.on)("cancel-delete-article")],L.prototype,"cancelDelete",void 0),(new L).mount("my-app"),s().on("#",((t,...e)=>{s().run(`#/${t||""}`,...e)})),s().once("/set-user",(()=>{s().route(location.hash)})),s().run("/get-user")})(); 2 | //# sourceMappingURL=app.js.map -------------------------------------------------------------------------------- /app.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"app.js","mappings":"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","sources":["webpack://apprun-realworld-example-app/webpack/bootstrap","webpack://apprun-realworld-example-app/webpack/runtime/compat get default export","webpack://apprun-realworld-example-app/webpack/runtime/define property getters","webpack://apprun-realworld-example-app/webpack/runtime/hasOwnProperty shorthand","webpack://apprun-realworld-example-app/external var \"apprun\"","webpack://apprun-realworld-example-app/./node_modules/tslib/tslib.es6.js","webpack://apprun-realworld-example-app/./src/components/header.tsx","webpack://apprun-realworld-example-app/./src/fetch.ts","webpack://apprun-realworld-example-app/./src/api.ts","webpack://apprun-realworld-example-app/./src/components/article-list.tsx","webpack://apprun-realworld-example-app/./src/components/page-list.tsx","webpack://apprun-realworld-example-app/./src/components/home.tsx","webpack://apprun-realworld-example-app/./src/components/error-list.tsx","webpack://apprun-realworld-example-app/./src/components/signin.tsx","webpack://apprun-realworld-example-app/./src/components/register.tsx","webpack://apprun-realworld-example-app/./src/components/profile.tsx","webpack://apprun-realworld-example-app/./src/components/modal.tsx","webpack://apprun-realworld-example-app/./src/components/settings.tsx","webpack://apprun-realworld-example-app/./src/components/editor.tsx","webpack://apprun-realworld-example-app/external var \"marked\"","webpack://apprun-realworld-example-app/./src/components/comment-list.tsx","webpack://apprun-realworld-example-app/./src/components/article-meta.tsx","webpack://apprun-realworld-example-app/./src/components/article.tsx","webpack://apprun-realworld-example-app/./src/main.ts"],"sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","const __WEBPACK_NAMESPACE_OBJECT__ = apprun;","/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation.\r\n\r\nPermission to use, copy, modify, and/or distribute this software for any\r\npurpose with or without fee is hereby granted.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\r\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\r\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\r\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\r\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\r\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\r\nPERFORMANCE OF THIS SOFTWARE.\r\n***************************************************************************** */\r\n/* global Reflect, Promise */\r\n\r\nvar extendStatics = function(d, b) {\r\n extendStatics = Object.setPrototypeOf ||\r\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\r\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\r\n return extendStatics(d, b);\r\n};\r\n\r\nexport function __extends(d, b) {\r\n if (typeof b !== \"function\" && b !== null)\r\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\r\n extendStatics(d, b);\r\n function __() { this.constructor = d; }\r\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\r\n}\r\n\r\nexport var __assign = function() {\r\n __assign = Object.assign || function __assign(t) {\r\n for (var s, i = 1, n = arguments.length; i < n; i++) {\r\n s = arguments[i];\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\r\n }\r\n return t;\r\n }\r\n return __assign.apply(this, arguments);\r\n}\r\n\r\nexport function __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\r\n\r\nexport function __decorate(decorators, target, key, desc) {\r\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\r\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\r\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\r\n return c > 3 && r && Object.defineProperty(target, key, r), r;\r\n}\r\n\r\nexport function __param(paramIndex, decorator) {\r\n return function (target, key) { decorator(target, key, paramIndex); }\r\n}\r\n\r\nexport function __metadata(metadataKey, metadataValue) {\r\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\r\n}\r\n\r\nexport function __awaiter(thisArg, _arguments, P, generator) {\r\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\r\n return new (P || (P = Promise))(function (resolve, reject) {\r\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\r\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\r\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\r\n step((generator = generator.apply(thisArg, _arguments || [])).next());\r\n });\r\n}\r\n\r\nexport function __generator(thisArg, body) {\r\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;\r\n return g = { next: verb(0), \"throw\": verb(1), \"return\": verb(2) }, typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\r\n function verb(n) { return function (v) { return step([n, v]); }; }\r\n function step(op) {\r\n if (f) throw new TypeError(\"Generator is already executing.\");\r\n while (_) try {\r\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\r\n if (y = 0, t) op = [op[0] & 2, t.value];\r\n switch (op[0]) {\r\n case 0: case 1: t = op; break;\r\n case 4: _.label++; return { value: op[1], done: false };\r\n case 5: _.label++; y = op[1]; op = [0]; continue;\r\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\r\n default:\r\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\r\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\r\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\r\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\r\n if (t[2]) _.ops.pop();\r\n _.trys.pop(); continue;\r\n }\r\n op = body.call(thisArg, _);\r\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\r\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\r\n }\r\n}\r\n\r\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });\r\n}) : (function(o, m, k, k2) {\r\n if (k2 === undefined) k2 = k;\r\n o[k2] = m[k];\r\n});\r\n\r\nexport function __exportStar(m, o) {\r\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\r\n}\r\n\r\nexport function __values(o) {\r\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\r\n if (m) return m.call(o);\r\n if (o && typeof o.length === \"number\") return {\r\n next: function () {\r\n if (o && i >= o.length) o = void 0;\r\n return { value: o && o[i++], done: !o };\r\n }\r\n };\r\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\r\n}\r\n\r\nexport function __read(o, n) {\r\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\r\n if (!m) return o;\r\n var i = m.call(o), r, ar = [], e;\r\n try {\r\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\r\n }\r\n catch (error) { e = { error: error }; }\r\n finally {\r\n try {\r\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\r\n }\r\n finally { if (e) throw e.error; }\r\n }\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spread() {\r\n for (var ar = [], i = 0; i < arguments.length; i++)\r\n ar = ar.concat(__read(arguments[i]));\r\n return ar;\r\n}\r\n\r\n/** @deprecated */\r\nexport function __spreadArrays() {\r\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\r\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\r\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\r\n r[k] = a[j];\r\n return r;\r\n}\r\n\r\nexport function __spreadArray(to, from, pack) {\r\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\r\n if (ar || !(i in from)) {\r\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\r\n ar[i] = from[i];\r\n }\r\n }\r\n return to.concat(ar || Array.prototype.slice.call(from));\r\n}\r\n\r\nexport function __await(v) {\r\n return this instanceof __await ? (this.v = v, this) : new __await(v);\r\n}\r\n\r\nexport function __asyncGenerator(thisArg, _arguments, generator) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\r\n return i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i;\r\n function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }\r\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\r\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\r\n function fulfill(value) { resume(\"next\", value); }\r\n function reject(value) { resume(\"throw\", value); }\r\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\r\n}\r\n\r\nexport function __asyncDelegator(o) {\r\n var i, p;\r\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\r\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === \"return\" } : f ? f(v) : v; } : f; }\r\n}\r\n\r\nexport function __asyncValues(o) {\r\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\r\n var m = o[Symbol.asyncIterator], i;\r\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\r\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\r\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\r\n}\r\n\r\nexport function __makeTemplateObject(cooked, raw) {\r\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\r\n return cooked;\r\n};\r\n\r\nvar __setModuleDefault = Object.create ? (function(o, v) {\r\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\r\n}) : function(o, v) {\r\n o[\"default\"] = v;\r\n};\r\n\r\nexport function __importStar(mod) {\r\n if (mod && mod.__esModule) return mod;\r\n var result = {};\r\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\r\n __setModuleDefault(result, mod);\r\n return result;\r\n}\r\n\r\nexport function __importDefault(mod) {\r\n return (mod && mod.__esModule) ? mod : { default: mod };\r\n}\r\n\r\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\r\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\r\n}\r\n\r\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\r\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\r\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\r\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\r\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\r\n}\r\n","import app, { Component, on } from 'apprun';\n\nclass HeaderComponent extends Component {\n state = {};\n view = (state) => {\n const user = state.user;\n return (\n \n );\n };\n\n @on('/set-user') setUser = (state, user) => ({ ...state, user });\n}\n\nexport default new HeaderComponent().start('header');\n","declare let defaultBasePath;\n\nlet access_token: string =\n (window && window.localStorage && window.localStorage.getItem('jwt')) || '';\nexport function getToken() {\n return access_token;\n}\n\nexport function setToken(token: string) {\n access_token = token;\n if (!window.localStorage) {return;}\n if (token) {window.localStorage.setItem('jwt', token);} else {window.localStorage.removeItem('jwt');}\n}\n\nexport async function fetchAsync(\n method: 'GET' | 'POST' | 'DELETE' | 'PUT',\n url: string,\n body?\n) {\n const headers = { 'Content-Type': 'application/json; charset=utf-8' };\n if (access_token) {headers['Authorization'] = `Token ${access_token}`;}\n const response = await window['fetch'](`${defaultBasePath}${url}`, {\n method,\n headers,\n body: body && JSON.stringify(body)\n });\n if (response.status === 401) {\n setToken(null);\n throw new Error('401');\n }\n const result = await response.json();\n if (!response.ok) {throw result;}\n return result;\n}\n\nexport function get(url: string): Promise {\n return fetchAsync('GET', url);\n}\n\nexport function post(url: string, body?): Promise {\n return fetchAsync('POST', url, body);\n}\n\nexport function del(url: string) {\n return fetchAsync('DELETE', url);\n}\n\nexport function put(url: string, body?) {\n return fetchAsync('PUT', url, body);\n}\nexport function toQueryString(obj) {\n const parts = [];\n for (const i in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, i)) {\n parts.push(encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]));\n }\n }\n return parts.join('&');\n}\n\nexport function serializeObject(form) {\n const obj = {};\n if (typeof form === 'object' && form.nodeName === 'FORM') {\n for (let i = 0; i < form.elements.length; i++) {\n const field = form.elements[i];\n if (\n field.name &&\n field.type !== 'file' &&\n field.type !== 'reset' &&\n field.type !== 'submit' &&\n field.type !== 'button'\n ) {\n if (field.type === 'select-multiple') {\n obj[field.name] = '';\n let tempvalue = '';\n for (let j = 0; j < form.elements[i].options.length; j++) {\n if (field.options[j].selected) {tempvalue += field.options[j].value + ';';}\n }\n if (tempvalue.charAt(tempvalue.length - 1) === ';') {obj[field.name] = tempvalue.substring(0, tempvalue.length - 1);}\n } else if ((field.type !== 'checkbox' && field.type !=='radio') || field.checked) {\n obj[field.name] = field.value;\n }\n }\n }\n }\n return obj as T;\n}\n","import app from 'apprun';\r\n\r\n// Conduit API\r\nwindow['defaultBasePath'] = 'https://api.realworld.io/api';\r\n\r\nimport { toQueryString, serializeObject, getToken, setToken, get, post, del, put } from './fetch';\r\nexport { toQueryString, serializeObject };\r\nimport { IUser, IProfile, IArticle, IComment } from './models';\r\n\r\nexport interface IAuthResponse {\r\n user: IUser;\r\n}\r\n\r\nexport interface ITags {\r\n tags: Array;\r\n}\r\n\r\nexport interface IFeed {\r\n articles: Array;\r\n articlesCount: number;\r\n}\r\n\r\nexport interface IArticlesRequest {\r\n tag?: string;\r\n author?: string;\r\n favorited?: string;\r\n limit: number;\r\n offset: number;\r\n}\r\n\r\nexport interface INewArticle {\r\n title: string;\r\n description: string;\r\n body: string;\r\n tagList: Array;\r\n}\r\n\r\nexport interface IArticlesResponse {\r\n article: IArticle;\r\n}\r\n\r\nexport interface ICommentsResponse {\r\n comments: Array;\r\n}\r\n\r\nexport interface IProfileResponse {\r\n profile: IProfile;\r\n}\r\n\r\nexport const tags = {\r\n all: () => get('/tags')\r\n};\r\n\r\nexport const auth = {\r\n current: () => (getToken() ? get('/user') : null),\r\n signIn: (user: { email: string; password: string }) =>\r\n post('/users/login', { user }),\r\n register: (user: { username: string; email: string; password: string }) =>\r\n post('/users', { user }),\r\n save: user => put('/user', { user }),\r\n authorized: (): boolean => (app['user'] ? true : app.run('#/login') && false) // app.run returns true if found event handlers\r\n};\r\n\r\nexport const articles = {\r\n search: (request: IArticlesRequest) => get(`/articles?${toQueryString(request)}`),\r\n feed: (request: { limit: number; offset: number }) =>\r\n get(`/articles/feed?${toQueryString(request)}`),\r\n get: (slug: string) => get(`/articles/${slug}`),\r\n delete: (slug: string) => del(`/articles/${slug}`),\r\n favorite: (slug: string) => post(`/articles/${slug}/favorite`),\r\n unfavorite: (slug: string) => del(`/articles/${slug}/favorite`),\r\n update: (article: IArticle) => put(`/articles/${article.slug}`, { article }),\r\n create: (article: INewArticle) => post('/articles', { article })\r\n};\r\n\r\nexport const comments = {\r\n create: (slug: string, comment: { body: string }) =>\r\n post(`/articles/${slug}/comments`, { comment }),\r\n delete: (slug: string, commentId: string) => del(`/articles/${slug}/comments/${commentId}`),\r\n forArticle: (slug: string) => get(`/articles/${slug}/comments`)\r\n};\r\n\r\nexport const profile = {\r\n get: (username: string) => get(`/profiles/${username}`),\r\n follow: (username: string) => post(`/profiles/${username}/follow`),\r\n unfollow: (username: string) => del(`/profiles/${username}/follow`)\r\n};\r\n\r\napp.on('/get-user', async () => {\r\n try {\r\n const current = await auth.current();\r\n app.run('/set-user', current?.user);\r\n } catch {\r\n setToken(null);\r\n document.location.reload();\r\n }\r\n});\r\n\r\napp.on('/set-user', (user) => {\r\n app['user'] = user;\r\n setToken(user ? user.token : null);\r\n});\r\n\r\napp.on('/toggle-follow', async (author: IProfile, component) => {\r\n if (!auth.authorized()) {return;}\r\n const result = author.following\r\n ? await profile.unfollow(author.username)\r\n : await profile.follow(author.username);\r\n component.run('update-follow', result.profile);\r\n});\r\n\r\napp.on('/toggle-fav-article', async (article: IArticle, component) => {\r\n if (!auth.authorized()) {return;}\r\n const result = article.favorited\r\n ? await articles.unfavorite(article.slug)\r\n : await articles.favorite(article.slug);\r\n component.run('update-article', result.article);\r\n});\r\n","import app from 'apprun';\nimport { IArticle } from '../models';\n\nfunction Article(props) {\n const article = props.article as IArticle;\n const favClass = article.favorited ? 'btn-primary' : 'btn-outline-primary';\n return (\n
    \n
    \n \n \n \n
    \n \n {article.author.username}\n \n {new Date(article.updatedAt).toLocaleString()}\n
    \n \n {article.favoritesCount}\n \n
    \n \n

    {article.title}

    \n

    {article.description}

    \n Read more...\n
    \n \n
    \n );\n}\n\nexport default function ({ articles, component }: { articles: Array; component }) {\n return articles.length ? (\n articles.map(article =>
    )\n ) : (\n
    No articles are here... yet.
    \n );\n}\n","import app from 'apprun';\n\nexport default function ({ max, selected, link }) {\n const pages = new Array(max).fill(0);\n return (\n \n );\n}\n","import app, { Component, on } from 'apprun';\nimport { auth, tags, articles } from '../api';\nimport { IArticle } from '../models';\nimport Articles from './article-list';\nimport Pages from './page-list';\n\nconst PAGE_SIZE = 10;\nconst Tag = ({ tag }) => (\n \n {tag}\n \n);\n\ndeclare interface IState {\n type: '' | 'feed' | 'tag';\n articles: Array;\n tags: Array;\n max: number;\n page: number;\n}\n\nclass HomeComponent extends Component {\n state: IState = {\n type: '',\n articles: [],\n tags: [],\n max: 1,\n page: 1\n };\n\n view = (state) => {\n const tag = state.type === 'tag' && state.tag ? `/${state.tag}` : '';\n return (\n
    \n
    \n
    \n

    conduit

    \n

    A place to share your knowledge.

    \n
    \n
    \n
    \n
    \n
    \n
    \n
      \n
    • \n \n Your Feed\n \n
    • \n
    • \n \n Global Feed\n \n
    • \n {state.tag ? (\n
    • \n \n #{state.tag}\n \n
    • \n ) : (\n ''\n )}\n
    \n
    \n \n \n
    \n
    \n
    \n

    Popular Tags

    \n
    \n {state.tags.map(tag => (\n \n ))}\n
    \n
    \n
    \n
    \n
    \n
    \n );\n };\n\n updateState = async (state, type: '' | 'feed' | 'tag', page, tag?: string) => {\n try {\n const tagList = state.tags.length ? { tags: state.tags } : await tags.all();\n page = parseInt(page) || 1;\n tag = tag || state.tag;\n const limit = PAGE_SIZE;\n const offset = (page - 1) * PAGE_SIZE;\n let feed;\n switch (type) {\n case 'feed':\n if (!auth.authorized()) {return { ...state, articles: [], max: 0 };}\n feed = await articles.feed({ limit, offset });\n break;\n case 'tag':\n feed = await articles.search({ tag, limit, offset });\n break;\n default:\n feed = await articles.search({ limit, offset });\n break;\n }\n page = Math.min(page, Math.floor(feed.articlesCount / PAGE_SIZE) + 1);\n return {\n ...state,\n tags: tagList.tags,\n type,\n page,\n tag,\n articles: feed.articles,\n max: feed.articlesCount\n };\n } catch ({ errors }) {\n return { ...state, errors, articles: [], max: 0 };\n }\n };\n\n @on('#/') root = async (state, page) => await this.updateState(state, '', page);\n\n @on('#/feed') feed = async (state, page) => await this.updateState(state, 'feed', page);\n\n @on('#/tag') tag = async (state, tag, page) => await this.updateState(state, 'tag', page, tag);\n\n @on('update-article') updateArticle = (state, article) => {\n state.articles = state.articles.map((a) => {\n return a.slug === article.slug ? article : a;\n });\n return state;\n };\n}\n\nexport default new HomeComponent().mount('my-app');\n","import app from 'apprun';\n\nexport default function ({ errors }) {\n return (\n
      \n {Object.keys(errors).map(key => (\n
    • {`${key} ${errors[key]}`}
    • \n ))}\n
    \n );\n}\n","import app, { Component, on } from 'apprun';\nimport { auth, serializeObject } from '../api';\n\nimport Errors from './error-list';\n\nclass SigninComponent extends Component {\n state = {};\n\n view = (state) => {\n if (!state || state instanceof Promise) {return;}\n return (\n
    \n
    \n
    \n
    \n

    Sign In

    \n

    \n Need an account?\n

    \n\n {state.errors && }\n\n
    \n
    \n \n
    \n
    \n \n
    \n \n
    \n
    \n
    \n
    \n
    \n );\n };\n\n @on('#/login') login = state => !auth.current() ? { ...state, messages: [], returnTo: document.location.hash } : location.href = '#/';\n @on('#/logout') logout = () => {\n app.run('/set-user', null);\n document.location.hash = '#/';\n };\n\n @on('sign-in') signIn = async (state, e) => {\n try {\n e.preventDefault();\n const session = await auth.signIn(serializeObject(e.target));\n app.run('/set-user', session.user);\n const returnTo: string = (state.returnTo || '').replace(/#\\/login\\/?/, '');\n if (!returnTo) {document.location.hash = '#/feed';} else {\n app.run('route', returnTo);\n history.pushState(null, null, returnTo);\n }\n } catch ({ errors }) {\n return { ...state, errors };\n }\n };\n}\n\nexport default new SigninComponent().mount('my-app');\n","import app, { Component, on } from 'apprun';\nimport { auth, serializeObject } from '../api';\nimport Errors from './error-list';\n\nclass RegisterComponent extends Component {\n state = {};\n\n view = (state) => {\n if (!state || state instanceof Promise) {return;}\n return (\n
    \n
    \n
    \n
    \n

    Sign Up

    \n

    \n Have an account?\n

    \n\n {state.errors && }\n\n
    \n
    \n \n
    \n
    \n \n
    \n
    \n \n
    \n \n
    \n
    \n
    \n
    \n
    \n );\n };\n\n @on('#/register') register = (state, messages) => !auth.current() ? { ...state, messages } : location.href = '#/';\n\n @on('register') submitRegistration = async (state, e) => {\n try {\n e.preventDefault();\n const session = await auth.register(serializeObject(e.target));\n app.run('/set-user', session.user);\n app.run('route', '#/');\n } catch ({ errors }) {\n return { ...state, errors };\n }\n };\n}\n\nexport default new RegisterComponent().mount('my-app');\n","import app, { Component, on } from 'apprun';\nimport { IProfile } from '../models';\nimport { articles, profile , auth } from '../api';\nimport Articles from './article-list';\nimport Pages from './page-list';\n\nconst PAGE_SIZE = 10;\n\nclass ProfileComponent extends Component {\n state = {\n name: '',\n type: 'articles',\n articles: [],\n page: 1\n };\n\n view = (state) => {\n const profile = state.profile as IProfile;\n if (!profile) {return;}\n return (\n
    \n
    \n
    \n
    \n
    \n \n

    {profile.username}

    \n

    {profile.bio}

    \n \n {profile.following ? (\n \n Unfollow {profile.username}\n \n ) : (\n \n Follow {profile.username}\n \n )}\n \n
    \n
    \n
    \n
    \n\n
    \n
    \n
    \n
    \n
      \n
    • \n \n My Articles\n \n
    • \n
    • \n \n Favorited Articles\n \n
    • \n
    \n
    \n \n \n
    \n
    \n
    \n
    \n );\n };\n\n updateState = async (state, name, type, page) => {\n name = decodeURIComponent(name || state.name);\n type = type || state.type;\n page = parseInt(page) || state.page;\n let newState = state;\n if (name !== state.name) {\n const profileResult = await profile.get(name);\n newState = { ...newState, profile: profileResult.profile };\n }\n if (name !== state.name || type !== state.type || page !== state.page) {\n const limit = PAGE_SIZE;\n const offset = (page - 1) * limit;\n const articleResult =\n type === 'favorites'\n ? await articles.search({ favorited: name, offset, limit })\n : await articles.search({ author: name, offset, limit });\n newState = {\n ...newState,\n name,\n type,\n page,\n articles: articleResult.articles,\n max: articleResult.articlesCount\n };\n }\n return newState;\n };\n\n @on('#/profile') root = (state, name, type, page) => auth.authorized() ? this.updateState(state, name, type, page) : null;\n\n @on('update-article') updateArticle = (state, article) => {\n state.articles = state.articles.map((a) => {\n return a.slug === article.slug ? article : a;\n });\n return state;\n };\n\n @on('update-follow') updateFollow = (state, profile) => ({ ...state, profile });\n}\n\nexport default new ProfileComponent().mount('my-app');\n","import app from 'apprun';\n\nexport default function ({ title, body, ok, cancel, onOK, onCancel }) {\n return (\n
    \n
    \n
    \n
    \n
    \n {title}\n \n ×\n \n
    \n
    \n
    \n

    {body}

    \n
    \n
    \n {cancel && (\n \n {cancel}\n \n )}\n   \n \n
    \n
    \n
    \n
    \n
    \n );\n}\n","import app, { Component, on } from 'apprun';\nimport { serializeObject, auth } from '../api';\nimport Errors from './error-list';\nimport Modal from './modal';\n\nclass SettingsComponent extends Component {\n state = {};\n\n view = (state) => {\n const user = state.user;\n if (!user) {return;}\n return (\n
    \n {state.showModal ? (\n \n ) : (\n ''\n )}\n
    \n
    \n
    \n {state.errors && }\n

    Your Settings

    \n
    \n
    \n
    \n \n
    \n
    \n \n
    \n
    \n \n {user.bio}\n \n
    \n
    \n \n
    \n
    \n \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n );\n };\n\n @on('#/settings') settings = () => {\n if (!auth.authorized()) {return;}\n return { user: app['user'] };\n };\n\n @on('submit-settings') submitSettings = async (state, e) => {\n try {\n e.preventDefault();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const user = serializeObject(e.target);\n const result = await auth.save(user);\n app.run('/set-user', result.user);\n return { user: result.user, showModal: true };\n } catch ({ errors }) {\n return { ...state, errors };\n }\n };\n\n @on('ok, cancel') ok = (state) => {\n return { ...state, showModal: false };\n };\n}\n\nexport default new SettingsComponent().mount('my-app');\n","import app, { Component, on } from 'apprun';\nimport { serializeObject, articles, auth } from '../api';\nimport Errors from './error-list';\n\nclass EditorComponent extends Component {\n state = {};\n\n view = (state) => {\n if (!app['user'] || !state.article) {return;}\n const article = state.article;\n return (\n
    \n
    \n
    \n
    \n {state.errors && }\n
    \n {article.slug && }\n
    \n
    \n \n
    \n
    \n \n
    \n
    \n \n {article.body}\n \n
    \n
    \n \n
    \n
    \n \n
    \n
    \n
    \n
    \n
    \n
    \n );\n };\n\n @on('#/editor') root = async (state, slug) => {\n if (!auth.authorized()) {return;}\n let article;\n if (slug) {\n const result = await articles.get(slug);\n article = result.article;\n }\n article = article || { title: '', description: '', body: '', tagList: [] };\n return { article };\n };\n\n @on('submit-article') submitArticle = async (state, e) => {\n try {\n e.preventDefault();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const article = serializeObject(e.target);\n article.tagList = article.tags.split(',');\n const result = article.slug ? await articles.update(article) : await articles.create(article);\n document.location.hash = `#/article/${result.article.slug}`;\n } catch ({ errors }) {\n return { ...state, errors };\n }\n };\n}\n\nexport default new EditorComponent().mount('my-app');\n","const __WEBPACK_NAMESPACE_OBJECT__ = marked;","import app from 'apprun';\nimport { IComment, IProfile } from '../models';\nimport { marked } from 'marked';\n\nfunction Comment({ comment }: { comment: IComment }) {\n return (\n
    \n
    \n

    \n

    {`_html:${marked(comment.body)}`}

    \n

    \n
    \n
    \n \n \n \n  \n \n {comment.author.username}\n \n {new Date(comment.createdAt).toLocaleString()}\n {app['user'] && app['user'].username === comment.author.username && (\n \n \n \n )}\n
    \n
    \n );\n}\n\nexport default function ({ comments }: { comments: Array }) {\n const user = app['user'] as IProfile;\n return (\n
    \n
    \n {!user ? (\n

    \n Sign in or \n sign up to add comments on this article.\n

    \n ) : (\n
    \n
    \n \n
    \n
    \n {user.image ? (\n \n ) : (\n @{user.username}\n )}\n \n
    \n
    \n )}\n {comments.map(comment => (\n \n ))}\n
    \n
    \n );\n}\n","import app from 'apprun';\nimport { IArticle } from '../models';\n\nexport default function ArticleMeta({ article, component }: { article: IArticle; component }) {\n const favClass = article.favorited ? 'btn-primary' : 'btn-outline-primary';\n const followClass = article.author.following ? 'btn-secondary' : 'btn-outline-secondary';\n return (\n
    \n \n \n \n
    \n \n {article.author.username}\n \n {new Date(article.updatedAt).toLocaleString()}\n
    \n\n {app['user'] && app['user'].username === article.author.username ? (\n \n \n   Edit Article\n \n   \n \n   Delete Article\n \n \n ) : (\n \n \n {article.author.following ? (\n \n Unfollow {article.author.username}\n \n ) : (\n \n Follow {article.author.username}\n \n )}\n {' '}\n   \n \n \n   Favorite Post ({article.favoritesCount})\n \n \n )}\n
    \n );\n}\n","import app, { Component, on } from 'apprun';\nimport { articles, comments } from '../api';\nimport { IArticle } from '../models';\nimport Comments from './comment-list';\nimport ArticleMeta from './article-meta';\nimport Modal from './modal';\nimport { marked } from 'marked';\n\nclass ArticleComponent extends Component {\n state = {\n article: null,\n comments: []\n };\n\n view = (state) => {\n const article = state.article as IArticle;\n if (!article) {return;}\n return (\n
    \n {state.deleting && (\n \n )}\n\n
    \n
    \n

    {article.title}

    \n \n
    \n
    \n\n
    \n
    \n
    \n

    {`_html:${marked(article.body)}`}

    \n
    \n
    \n {article.tagList.map(tag => (\n
  • \n {tag} \n
  • \n ))}\n
    \n
    \n
    \n
    \n
    \n \n
    \n \n
    \n
    \n );\n };\n\n @on('#/article') root = async (state, slug) => {\n let article = state.article as IArticle;\n let _comments = state.comments;\n if (!article || article.slug !== slug) {\n const result = await articles.get(slug);\n article = result.article;\n const commentsResponse = await comments.forArticle(article.slug);\n _comments = commentsResponse.comments;\n }\n return { ...state, article, comments: _comments };\n };\n\n @on('/new-comment') newComment = async (state, e) => {\n try {\n e.preventDefault();\n const comment = e.target['comment'].value;\n if (!comment) {return;}\n await comments.create(state.article.slug, { body: comment });\n const commentsResponse = await comments.forArticle(state.article.slug);\n return { ...state, comments: commentsResponse.comments };\n } catch ({ errors }) {\n return { ...state, errors };\n }\n };\n\n @on('/delete-comment') deleteComment = async (state, comment) => {\n await comments.delete(this.state.article.slug, comment.id);\n const commentsResponse = await comments.forArticle(state.article.slug);\n return { ...state, comments: commentsResponse.comments };\n };\n\n @on('update-article') updateArticle = (state, article) => ({\n ...state,\n article\n });\n\n @on('update-follow') updateFollow = (state, author) => {\n state.article.author = author;\n return state;\n };\n\n @on('edit-article') editArticle = (state, article) => {\n document.location.hash = `#/editor/${article.slug}`;\n };\n\n @on('delete-article') deleteArticle = state => ({\n ...state,\n deleting: true\n });\n\n @on('ok-delete-article') okDelete = (state) => {\n articles.delete(state.article.slug);\n document.location.hash = '#/';\n return { ...state, deleting: false };\n };\n\n @on('cancel-delete-article') cancelDelete = state => ({\n ...state,\n deleting: false\n });\n}\n\nexport default new ArticleComponent().mount('my-app');\n","import app from 'apprun';\n\nimport './components/header';\nimport './components/home';\nimport './components/signin';\nimport './components/register';\nimport './components/profile';\nimport './components/settings';\nimport './components/editor';\nimport './components/article';\n\napp.on('#', (route, ...p) => {\n app.run(`#/${route || ''}`, ...p);\n});\n\napp.once('/set-user', () => {\n app.route(location.hash);\n});\n\napp.run('/get-user');"],"names":["__webpack_require__","module","getter","__esModule","d","a","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","apprun","__decorate","decorators","target","desc","c","arguments","length","r","getOwnPropertyDescriptor","Reflect","decorate","i","__awaiter","thisArg","_arguments","P","generator","Promise","resolve","reject","fulfilled","value","step","next","e","rejected","result","done","then","apply","create","HeaderComponent","Component","state","view","user","class","href","username","setUser","on","start","access_token","window","localStorage","getItem","setToken","token","setItem","removeItem","fetchAsync","method","url","body","headers","response","defaultBasePath","JSON","stringify","status","Error","json","ok","post","del","put","toQueryString","parts","push","encodeURIComponent","join","serializeObject","form","nodeName","elements","field","name","type","tempvalue","j","options","selected","charAt","substring","checked","auth","articles","request","slug","comments","Article","props","article","favClass","favorited","author","image","src","Date","updatedAt","toLocaleString","component","favoritesCount","title","description","tagList","map","tag","max","link","pages","Array","fill","page","idx","current","document","location","reload","following","profile","run","Tag","HomeComponent","tags","this","Math","floor","updateState","parseInt","limit","offset","feed","min","articlesCount","errors","root","updateArticle","keys","mount","SigninComponent","placeholder","login","messages","returnTo","hash","logout","signIn","preventDefault","session","replace","history","pushState","RegisterComponent","register","submitRegistration","ProfileComponent","bio","decodeURIComponent","newState","profileResult","articleResult","updateFollow","cancel","onOK","onCancel","role","SettingsComponent","showModal","rows","email","password","settings","submitSettings","EditorComponent","submitArticle","split","marked","Comment","comment","createdAt","ArticleMeta","followClass","ArticleComponent","deleting","_comments","newComment","commentsResponse","deleteComment","commentId","id","editArticle","deleteArticle","okDelete","cancelDelete","route","p"],"sourceRoot":""} --------------------------------------------------------------------------------