Niedzielski has uploaded a new change for review. ( https://gerrit.wikimedia.org/r/378267 )
Change subject: New: add page summaries fetcher and UI ...................................................................... New: add page summaries fetcher and UI Bug: T173323 Change-Id: I7456e654e24db34343bd8e108bc990be8fff31fe --- M docs/development.md M package-lock.json M package.json M src/client/tsconfig.json M src/common/components/app/app.tsx M src/common/components/link.tsx A src/common/components/page-summary/page-summary.css A src/common/components/page-summary/page-summary.tsx M src/common/components/paper/paper.tsx A src/common/data-clients/page-data-client.ts M src/common/models/page.ts M src/common/pages/wiki.tsx M src/common/routers/api.ts M src/common/routers/route.ts M src/common/routers/router.ts A src/common/types/isomorphic-unfetch.d.ts M src/common/types/preact.d.ts M src/server/components/page.tsx M src/server/index.tsx M src/server/tsconfig.json M tsconfig.json M webpack.config.ts 22 files changed, 227 insertions(+), 967 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/marvin refs/changes/67/378267/1 diff --git a/docs/development.md b/docs/development.md index 41111d2..bf1b6f3 100644 --- a/docs/development.md +++ b/docs/development.md @@ -124,8 +124,13 @@ - Static constants should be written in `SHOUTING_SNAKE_CASE`. All other variables should be written in `camelCase`. - Preact components should be written in PascalCase. -- Preact component properties variables and types, especially - `ComponentProps` implementations, should be called "props" and "Props". + +#### Abbreviations + +Marvin uses the following abbreviations: + +- Properties => props +- Parameters => params ### Filenaming diff --git a/package-lock.json b/package-lock.json index 319c07a..4b41755 100644 --- a/package-lock.json +++ b/package-lock.json @@ -711,7 +711,6 @@ "requires": { "anymatch": "1.3.2", "async-each": "1.0.1", - "fsevents": "1.1.2", "glob-parent": "2.0.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -1445,6 +1444,14 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" }, + "encoding": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", + "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "requires": { + "iconv-lite": "0.4.18" + } + }, "end-of-stream": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz", @@ -2119,905 +2126,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "fsevents": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz", - "integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==", - "dev": true, - "optional": true, - "requires": { - "nan": "2.7.0", - "node-pre-gyp": "0.6.36" - }, - "dependencies": { - "abbrev": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "ajv": { - "version": "4.11.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "aproba": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.2.9" - } - }, - "asn1": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws4": { - "version": "1.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "balanced-match": { - "version": "0.4.2", - "bundled": true, - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "block-stream": { - "version": "0.0.9", - "bundled": true, - "dev": true, - "requires": { - "inherits": "2.0.3" - } - }, - "boom": { - "version": "2.10.1", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "brace-expansion": { - "version": "1.1.7", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "0.4.2", - "concat-map": "0.0.1" - } - }, - "buffer-shims": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "caseless": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true - }, - "co": { - "version": "4.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "combined-stream": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "requires": { - "delayed-stream": "1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "cryptiles": { - "version": "2.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "boom": "2.10.1" - } - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "debug": { - "version": "2.6.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.4.2", - "bundled": true, - "dev": true, - "optional": true - }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "extend": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "extsprintf": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true, - "dev": true, - "optional": true - }, - "form-data": { - "version": "2.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.15" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "fstream": { - "version": "1.0.11", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.1" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" - } - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "1.1.1", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "bundled": true, - "dev": true - }, - "har-schema": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "har-validator": { - "version": "4.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "hawk": { - "version": "3.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "bundled": true, - "dev": true - }, - "http-signature": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.0", - "sshpk": "1.13.0" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.4", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "isstream": { - "version": "0.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "jodid25519": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "jsonify": { - "version": "0.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "jsprim": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.0.2", - "json-schema": "0.2.3", - "verror": "1.3.6" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "mime-db": { - "version": "1.27.0", - "bundled": true, - "dev": true - }, - "mime-types": { - "version": "2.1.15", - "bundled": true, - "dev": true, - "requires": { - "mime-db": "1.27.0" - } - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "node-pre-gyp": { - "version": "0.6.36", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "mkdirp": "0.5.1", - "nopt": "4.0.1", - "npmlog": "4.1.0", - "rc": "1.2.1", - "request": "2.81.0", - "rimraf": "2.6.1", - "semver": "5.3.0", - "tar": "2.2.1", - "tar-pack": "3.4.0" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1.1.0", - "osenv": "0.1.4" - } - }, - "npmlog": { - "version": "4.1.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "oauth-sign": { - "version": "0.8.2", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "performance-now": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "1.0.7", - "bundled": true, - "dev": true - }, - "punycode": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true - }, - "qs": { - "version": "6.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.2.9", - "bundled": true, - "dev": true, - "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.1", - "util-deprecate": "1.0.2" - } - }, - "request": { - "version": "2.81.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.15", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.0.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", - "tunnel-agent": "0.6.0", - "uuid": "3.0.1" - } - }, - "rimraf": { - "version": "2.6.1", - "bundled": true, - "dev": true, - "requires": { - "glob": "7.1.2" - } - }, - "safe-buffer": { - "version": "5.0.1", - "bundled": true, - "dev": true - }, - "semver": { - "version": "5.3.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sntp": { - "version": "1.0.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "hoek": "2.16.3" - } - }, - "sshpk": { - "version": "1.13.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jodid25519": "1.0.2", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "stringstream": { - "version": "0.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "2.2.1", - "bundled": true, - "dev": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - }, - "tar-pack": { - "version": "3.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "2.6.8", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.2.9", - "rimraf": "2.6.1", - "tar": "2.2.1", - "uid-number": "0.0.6" - } - }, - "tough-cookie": { - "version": "2.3.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "dev": true, - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "uuid": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "verror": { - "version": "1.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "extsprintf": "1.0.2" - } - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - } - } - }, "function-bind": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", @@ -3363,8 +2471,7 @@ "iconv-lite": { "version": "0.4.18", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", - "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==", - "dev": true + "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==" }, "icss-replace-symbols": { "version": "1.1.0", @@ -3831,8 +2938,7 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-svg": { "version": "2.1.0", @@ -3874,6 +2980,15 @@ "dev": true, "requires": { "isarray": "1.0.0" + } + }, + "isomorphic-unfetch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-2.0.0.tgz", + "integrity": "sha1-9QFApMFj11grXzfxWRloxPgJpkU=", + "requires": { + "node-fetch": "1.7.3", + "unfetch": "3.0.0" } }, "jest-docblock": { @@ -4684,13 +3799,6 @@ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, - "nan": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", - "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=", - "dev": true, - "optional": true - }, "native-promise-only": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", @@ -4750,6 +3858,15 @@ "isarray": "0.0.1" } } + } + }, + "node-fetch": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", + "requires": { + "encoding": "0.1.12", + "is-stream": "1.1.0" } }, "node-forge": { @@ -6726,6 +5843,15 @@ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", "dev": true }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, "string-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", @@ -6771,15 +5897,6 @@ "define-properties": "1.1.2", "es-abstract": "1.8.0", "function-bind": "1.1.0" - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" } }, "strip-ansi": { @@ -7198,6 +6315,11 @@ "integrity": "sha1-7Mo6A+VrmvFzhbqsgSrIO5lKli8=", "dev": true }, + "unfetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-3.0.0.tgz", + "integrity": "sha1-jR4FE6Ts0OX/LUGmund3Gq6LZII=" + }, "uniq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", diff --git a/package.json b/package.json index 10e9a83..ba0eccd 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ }, "dependencies": { "express": "^4.15.3", + "isomorphic-unfetch": "^2.0.0", "path-to-regexp": "^2.0.0", "preact": "^8.2.1", "preact-render-to-string": "^3.6.3" diff --git a/src/client/tsconfig.json b/src/client/tsconfig.json index 89f6fd7..27816ce 100644 --- a/src/client/tsconfig.json +++ b/src/client/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.json", "include": [ - "." + "../common", "." ], "compilerOptions": { "target": "ES5", diff --git a/src/common/components/app/app.tsx b/src/common/components/app/app.tsx index df006bd..c7aa9a3 100644 --- a/src/common/components/app/app.tsx +++ b/src/common/components/app/app.tsx @@ -1,11 +1,15 @@ import "./app.css"; -import { ComponentProps, h } from "preact"; import { about, index, wiki } from "../../../common/routers/api"; +import { FunctionalProps } from "../../types/preact"; import Link from "../link"; +import { h } from "preact"; -export default function App({ children }: ComponentProps<any>): JSX.Element { +export default function App({ + children, + ...props +}: FunctionalProps<void>): JSX.Element { return ( - <div class="App"> + <div class="App" {...props as JSX.HTMLAttributes}> <ul> <li> <Link href={index.url()}>Home</Link> @@ -17,9 +21,19 @@ <Link href={wiki.url({ title: "Banana" })}>Banana</Link> </li> <li> + <Link + href={wiki.url({ title: "Bill_%26_Ted%27s_Excellent_Adventure" })} + > + Bill & Ted's Excellent Adventure + </Link> + </li> + <li> <Link href={wiki.url({ title: "Cucumber" })}>Cucumber</Link> </li> <li> + <Link href={wiki.url({ title: "Ice_cream" })}>Ice cream</Link> + </li> + <li> <Link href="/404">404</Link> </li> </ul> diff --git a/src/common/components/link.tsx b/src/common/components/link.tsx index 60dc612..a8e20b0 100644 --- a/src/common/components/link.tsx +++ b/src/common/components/link.tsx @@ -1,7 +1,8 @@ -import { ComponentProps, h } from "preact"; +import { FunctionalProps } from "../types/preact"; import { History } from "history"; +import { h } from "preact"; -export interface Props extends ComponentProps<any> { +export interface Props extends FunctionalProps<void> { href: string; } @@ -16,7 +17,7 @@ const { children, ...anchorProps } = props; return ( <a - {...anchorProps} + {...anchorProps as JSX.HTMLAttributes} onClick={event => { // todo: check that link is internal (relative). if (context.history) { diff --git a/src/common/components/page-summary/page-summary.css b/src/common/components/page-summary/page-summary.css new file mode 100644 index 0000000..fb07c7c --- /dev/null +++ b/src/common/components/page-summary/page-summary.css @@ -0,0 +1,24 @@ +.PageSummary { +} + +.PageSummary-header {} +.PageSummary-title {} +.PageSummary-description {} +.PageSummary-description::first-letter { + text-transform: uppercase; +} + +.PageSummary-body { +} +.PageSummary-thumbnail { + float: left; + margin: 0 var(--space) var(--space) 0; +} +.PageSummary-extract { +} + +.PageSummary-footer { + clear: both; +} +.PageSummary-lastModified { +} diff --git a/src/common/components/page-summary/page-summary.tsx b/src/common/components/page-summary/page-summary.tsx new file mode 100644 index 0000000..662a187 --- /dev/null +++ b/src/common/components/page-summary/page-summary.tsx @@ -0,0 +1,44 @@ +import "./page-summary.css"; +import { FunctionalProps } from "../../types/preact"; +import { PageSummary as PageSummaryModel } from "../../models/page"; +import Paper from "../paper/paper"; +import { h } from "preact"; + +export interface Props extends FunctionalProps<void> { + summary: PageSummaryModel; +} + +export const PageSummary = ({ summary, ...props }: Props): JSX.Element => ( + <Paper className="PageSummary" {...props}> + <div className="PageSummary-header"> + <h2 + className="PageSummary-title" + dangerouslySetInnerHTML={{ __html: summary.titleHTML }} + /> + {summary.descriptionText && ( + <p className="PageSummary-description">{summary.descriptionText}</p> + )} + </div> + <div className="PageSummary-body"> + {summary.thumbnail && ( + <img + className="PageSummary-thumbnail" + src={summary.thumbnail.URL} + width={summary.thumbnail.width} + height={summary.thumbnail.height} + /> + )} + {summary.extractHTML && ( + <p + className="PageSummary-extract" + dangerouslySetInnerHTML={{ __html: summary.extractHTML }} + /> + )} + </div> + <div className="PageSummary-footer"> + <p className="PageSummary-lastModified"> + {summary.lastModified.toLocaleString()} + </p> + </div> + </Paper> +); diff --git a/src/common/components/paper/paper.tsx b/src/common/components/paper/paper.tsx index 6e8ba60..1028da4 100644 --- a/src/common/components/paper/paper.tsx +++ b/src/common/components/paper/paper.tsx @@ -1,6 +1,14 @@ import "./paper.css"; -import { ComponentProps, h } from "preact"; +import { FunctionalProps } from "../../types/preact"; +import { h } from "preact"; -export default function Paper({ children }: ComponentProps<any>): JSX.Element { - return <div class="Paper">{children}</div>; +export default function Paper({ + children, + ...props +}: FunctionalProps<void>): JSX.Element { + return ( + <div className="Paper" {...props as JSX.HTMLAttributes}> + {children} + </div> + ); } diff --git a/src/common/data-clients/page-data-client.ts b/src/common/data-clients/page-data-client.ts new file mode 100644 index 0000000..fabff7f --- /dev/null +++ b/src/common/data-clients/page-data-client.ts @@ -0,0 +1,19 @@ +import * as fetch from "isomorphic-unfetch"; +import { PageSummary } from "../models/page"; +import { unmarshalPageSummary } from "../marshallers/page-unmarshaller"; + +export interface Params { + /** + * URL encoded wiki page title. e.g.: Banana, Ice_cream, + * Bill_%26_Ted%27s_Excellent_Adventure. + */ + title: string; +} + +const url = ({ title }: Params) => + `https://en.wikipedia.org/api/rest_v1/page/summary/${title}`; + +export const requestPageSummary = (params: Params): Promise<PageSummary> => + fetch(url(params)) + .then((response: Response) => response.json()) + .then(unmarshalPageSummary); diff --git a/src/common/models/page.ts b/src/common/models/page.ts index 1411bc6..6be38a1 100644 --- a/src/common/models/page.ts +++ b/src/common/models/page.ts @@ -21,6 +21,10 @@ localeDirection: string; pageID: number; lastModified: Date; + /** + * Page title in English. e.g.: Banana, Ice cream, Bill & Ted's Excellent + * Adventure. + */ titleText: string; titleHTML: string; descriptionText: string; diff --git a/src/common/pages/wiki.tsx b/src/common/pages/wiki.tsx index 995f443..c67efa9 100644 --- a/src/common/pages/wiki.tsx +++ b/src/common/pages/wiki.tsx @@ -1,21 +1,26 @@ import { ComponentProps, h } from "preact"; import App from "../components/app/app"; -import { RouteParameters } from "../routers/route"; +import { PageSummary } from "../components/page-summary/page-summary"; +import { PageSummary as PageSummaryModel } from "../models/page"; +import Paper from "../components/paper/paper"; +import { RouteParams } from "../routers/route"; +import { requestPageSummary } from "../data-clients/page-data-client"; -export interface Parameters extends RouteParameters { +export interface Params extends RouteParams { title: string; } export interface Props extends ComponentProps<any> { - title: string; + summary: PageSummaryModel; } -export const initialProps = ({ title }: Parameters): Promise<Props> => { - return Promise.resolve({ title }); -}; +export const initialProps = ({ title }: Params): Promise<Props> => + requestPageSummary({ title }).then(summary => ({ summary })); -export const Component = ({ title }: Props): JSX.Element => ( +export const Component = ({ summary }: Props): JSX.Element => ( <App> - <p>{title}</p> + <Paper> + <PageSummary summary={summary} /> + </Paper> </App> ); diff --git a/src/common/routers/api.ts b/src/common/routers/api.ts index c4855b2..726e4fb 100644 --- a/src/common/routers/api.ts +++ b/src/common/routers/api.ts @@ -1,5 +1,5 @@ import { AnyRoute, Route, newRoute } from "./route"; -import { Props as WikiProps } from "../pages/wiki"; +import { Params as WikiParams, Props as WikiProps } from "../pages/wiki"; export const index: Route = newRoute({ path: "/", @@ -15,7 +15,7 @@ chunkName: "pages/about" }); -export const wiki: Route<WikiProps, void, { title: string }> = newRoute({ +export const wiki: Route<WikiProps, void, WikiParams> = newRoute({ path: "/wiki/:title", endpoint: () => import(/* webpackChunkName: "pages/wiki" */ "../pages/wiki"), chunkName: "pages/wiki" diff --git a/src/common/routers/route.ts b/src/common/routers/route.ts index af75f4b..cbd5cb3 100644 --- a/src/common/routers/route.ts +++ b/src/common/routers/route.ts @@ -1,7 +1,7 @@ import * as pathToRegExp from "path-to-regexp"; import { AnyComponent } from "preact"; -export interface RouteParameters { +export interface RouteParams { [name: string]: string; } @@ -13,7 +13,7 @@ * A function that returns a Promise for the dependencies needed to construct * the view component such as a remote resource. */ - initialProps?: (parameters: RouteParameters) => Promise<Props>; + initialProps?: (parameters: RouteParams) => Promise<Props>; } export interface RouteConfiguration<Props = void, State = void> { @@ -28,7 +28,7 @@ status: number; /** Generates a URL from parameters. */ - url: (properties?: Parameters) => string; + url: (parameters?: Parameters) => string; } export type AnyRoute = Route<any, any, any>; diff --git a/src/common/routers/router.ts b/src/common/routers/router.ts index 695329d..af6e684 100644 --- a/src/common/routers/router.ts +++ b/src/common/routers/router.ts @@ -1,9 +1,5 @@ import * as pathToRegExp from "path-to-regexp"; -import { - AnyRoute, - Endpoint, - RouteParameters -} from "../../common/routers/route"; +import { AnyRoute, Endpoint, RouteParams } from "../../common/routers/route"; import { AnyComponent } from "preact"; export interface RouteResponse<Props, State> { @@ -40,10 +36,10 @@ const newRouteParameters = ( parameterNames: pathToRegExp.Key[], parameterValues: string[] -): RouteParameters => +): RouteParams => parameterNames.reduce( ( - parameters: RouteParameters, + parameters: RouteParams, parameterName: pathToRegExp.Key, index: number ) => { @@ -55,7 +51,7 @@ function requestInitialProps<Props>( endpoint: Endpoint<Props, any>, - parameters: RouteParameters + parameters: RouteParams ): Promise<Props | {}> { if (endpoint.initialProps) { return endpoint.initialProps(parameters); @@ -66,7 +62,7 @@ const respond = ( route: ParsedRoute, url: string, - parameters: RouteParameters + parameters: RouteParams ): Promise<RouteResponse<any, any>> => route.endpoint().then((endpoint: Endpoint<any, any>) => requestInitialProps(endpoint, parameters).then((props: any) => ({ diff --git a/src/common/types/isomorphic-unfetch.d.ts b/src/common/types/isomorphic-unfetch.d.ts new file mode 100644 index 0000000..7dd0676 --- /dev/null +++ b/src/common/types/isomorphic-unfetch.d.ts @@ -0,0 +1,5 @@ +declare const unfetch: typeof fetch; + +declare module "isomorphic-unfetch" { + export = unfetch; +} diff --git a/src/common/types/preact.d.ts b/src/common/types/preact.d.ts index 7ac7b19..46709d3 100644 --- a/src/common/types/preact.d.ts +++ b/src/common/types/preact.d.ts @@ -1,2 +1,14 @@ +import { ComponentProps, FunctionalComponent } from "preact"; + // Delete pending https://github.com/developit/preact/pull/869. -export type Children = (JSX.Element | JSX.Element[] | string)[]; +export type ComponentChild = JSX.Element | string; +export type ComponentChildren = (ComponentChild | ComponentChild[])[]; + +// A subset of JSX.HTMLAttributes that make sense to expose for any component. +export interface ComponentAttributes { + className?: string; +} + +export interface FunctionalProps<T> + extends ComponentProps<FunctionalComponent<T>>, + ComponentAttributes {} diff --git a/src/server/components/page.tsx b/src/server/components/page.tsx index 18099e9..60bc7ff 100644 --- a/src/server/components/page.tsx +++ b/src/server/components/page.tsx @@ -1,5 +1,5 @@ import { Manifest, asset, scripts, style } from "../assets/manifest"; -import { Children } from "../../common/types/preact"; +import { ComponentChildren } from "../../common/types/preact"; import { h } from "preact"; export interface Props { @@ -8,7 +8,7 @@ manifest: Manifest; chunkName: string; // HTML to render in the body of the page - children?: Children; + children?: ComponentChildren; } export function Page({ @@ -24,7 +24,7 @@ <head> <meta charSet="utf-8" /> <meta http-equiv="x-ua-compatible" content="ie=edge" /> - <meta name="viewport" content="width=device-width,initial-scale=1" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>{title ? `${title} - ` : ""}Marvin</title> <base href="/" /> <link rel="stylesheet" href={style(manifest)} /> diff --git a/src/server/index.tsx b/src/server/index.tsx index aea614a..c28215f 100644 --- a/src/server/index.tsx +++ b/src/server/index.tsx @@ -24,12 +24,12 @@ server.use("/public", express.static("dist/public")); -const render = ({ chunkName, Component }: RouteResponse<any, any>) => { +const render = ({ chunkName, Component, props }: RouteResponse<any, any>) => { return ( "<!doctype html>" + // eslint-disable-line prefer-template renderToString( <Page title="" manifest={manifest} chunkName={chunkName}> - <Component /> + <Component {...props} /> </Page> ) ); diff --git a/src/server/tsconfig.json b/src/server/tsconfig.json index 423de29..0bd1839 100644 --- a/src/server/tsconfig.json +++ b/src/server/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.json", "include": [ - "." + "../common", "." ], "compilerOptions": { "target": "ES2015", diff --git a/tsconfig.json b/tsconfig.json index 4d3ea6e..6d9edb1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,9 +5,6 @@ "test/**/*.test.ts", "test/**/*.test.js" ], - "include": [ - "src/common" - ], "compilerOptions": { "jsx": "react", "jsxFactory": "h", diff --git a/webpack.config.ts b/webpack.config.ts index c9e514a..8694499 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -57,10 +57,10 @@ // each entry which breaks caching. This chunk changes whenever any code // anywhere changes. - // Client package dependencies (these should match package.json's + // Client package dependencies (these should be a subset of package.json's // `dependencies`). This chunk changes when one of the specified // dependencies changes. - vendor: ["history", "path-to-regexp", "preact"] + vendor: ["history", "isomorphic-unfetch", "path-to-regexp", "preact"] }, stats: STATS, @@ -184,6 +184,8 @@ new webpack.NamedModulesPlugin(), new ExtractTextPlugin({ + // `contenthash` is not actually a chunk hash: + // https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/504#issuecomment-306581954. filename: PRODUCTION ? "[name].[contenthash].css" : "[name].css" }), @@ -206,7 +208,8 @@ // during execution for module resolution, dynamic importing, and more. // Without this distinct runtime chunk, it's instead bundled into each entry, // including vendor, which breaks caching. This chunk changes whenever any - // other file changes. + // other file changes. See + // https://webpack.js.org/plugins/commons-chunk-plugin/#manifest-file. new webpack.optimize.CommonsChunkPlugin({ // This name should NOT match any `configuration.entry`. name: "runtime" -- To view, visit https://gerrit.wikimedia.org/r/378267 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I7456e654e24db34343bd8e108bc990be8fff31fe Gerrit-PatchSet: 1 Gerrit-Project: marvin Gerrit-Branch: master Gerrit-Owner: Niedzielski <sniedziel...@wikimedia.org> Gerrit-Reviewer: Sniedzielski <sniedziel...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits