From 7e7b024a4c57cbf05c417aa3f3472a61c08cef2a Mon Sep 17 00:00:00 2001 From: officeyutong Date: Sun, 18 Jul 2021 23:07:57 +0800 Subject: [PATCH 1/7] I'm creating folders! --- .../src/components/Collection/Collection.tsx | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx diff --git a/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx b/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx new file mode 100644 index 00000000..5f9f9e73 --- /dev/null +++ b/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx @@ -0,0 +1,6 @@ +import React from 'react' +const Collection: React.FC = () => { + return
+} + +export default Collection From 0d1f1b5cb8dd737ba48761f1be80ff4a165bd8ed Mon Sep 17 00:00:00 2001 From: officeyutong Date: Tue, 3 Aug 2021 00:58:11 +0800 Subject: [PATCH 2/7] add: Almost-working proposal component --- gatsby-theme-oi-wiki/gatsby-config.js | 4 +- gatsby-theme-oi-wiki/package-lock.json | 328 ++++++++++++++++++ gatsby-theme-oi-wiki/package.json | 6 +- .../src/components/Collection/Collection.tsx | 205 ++++++++++- .../components/Collection/CollectionClient.ts | 179 ++++++++++ .../Collection/DetailInputDialog.tsx | 67 ++++ .../components/Collection/ProposalCard.tsx | 102 ++++++ .../src/components/Collection/index.tsx | 29 ++ .../src/components/Collection/types.ts | 58 ++++ gatsby-theme-oi-wiki/src/components/Layout.js | 5 + gatsby-theme-oi-wiki/src/components/doc.js | 2 + 11 files changed, 979 insertions(+), 6 deletions(-) create mode 100644 gatsby-theme-oi-wiki/package-lock.json create mode 100644 gatsby-theme-oi-wiki/src/components/Collection/CollectionClient.ts create mode 100644 gatsby-theme-oi-wiki/src/components/Collection/DetailInputDialog.tsx create mode 100644 gatsby-theme-oi-wiki/src/components/Collection/ProposalCard.tsx create mode 100644 gatsby-theme-oi-wiki/src/components/Collection/index.tsx create mode 100644 gatsby-theme-oi-wiki/src/components/Collection/types.ts diff --git a/gatsby-theme-oi-wiki/gatsby-config.js b/gatsby-theme-oi-wiki/gatsby-config.js index cee60002..c11cf4e7 100644 --- a/gatsby-theme-oi-wiki/gatsby-config.js +++ b/gatsby-theme-oi-wiki/gatsby-config.js @@ -3,8 +3,8 @@ const path = require('path') const IS_EXEC_BUILD = process.env.gatsby_executing_command === 'build' const IS_PROD = process.env.PRODUCTION === 'true' || - process.env.NODE_ENV === 'production' || - process.env.RENDER === 'true' + process.env.NODE_ENV === 'production' || + process.env.RENDER === 'true' /** * 根据条件生成配置,需要展开 diff --git a/gatsby-theme-oi-wiki/package-lock.json b/gatsby-theme-oi-wiki/package-lock.json new file mode 100644 index 00000000..f5ad4488 --- /dev/null +++ b/gatsby-theme-oi-wiki/package-lock.json @@ -0,0 +1,328 @@ +{ + "name": "gatsby-theme-oi-wiki", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.8.tgz", + "integrity": "sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg==", + "dev": true + }, + "@types/prop-types": { + "version": "15.7.4", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz", + "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==", + "dev": true + }, + "@types/react": { + "version": "17.0.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.14.tgz", + "integrity": "sha512-0WwKHUbWuQWOce61UexYuWTGuGY/8JvtUe/dtQ6lR4sZ3UiylHotJeWpf3ArP9+DSGUoLY3wbU59VyMrJps5VQ==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==", + "dev": true + }, + "@types/use-persisted-state": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@types/use-persisted-state/-/use-persisted-state-0.3.0.tgz", + "integrity": "sha512-ZT98QuckR95qM7W97lGVqc7fFS9TT6f3txp7R40fl0zxa5BLm3GG7j0i51G12h8DkoJxFAf2oQyYKU99h0pxFA==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", + "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/typescript-estree": "3.10.1", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz", + "integrity": "sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==", + "dev": true, + "requires": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "3.10.1", + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/typescript-estree": "3.10.1", + "eslint-visitor-keys": "^1.1.0" + } + }, + "@typescript-eslint/types": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", + "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", + "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "3.10.1", + "@typescript-eslint/visitor-keys": "3.10.1", + "debug": "^4.1.1", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", + "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "csstype": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.8.tgz", + "integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==", + "dev": true + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} diff --git a/gatsby-theme-oi-wiki/package.json b/gatsby-theme-oi-wiki/package.json index 3fa19b80..ad033035 100644 --- a/gatsby-theme-oi-wiki/package.json +++ b/gatsby-theme-oi-wiki/package.json @@ -18,6 +18,7 @@ "@mgtd/remark-shiki": "^2.0.1", "@mgtd/vssue-api-github-v3": "^2.4.8", "@mgtd/vssue-api-github-v4": "^2.4.5", + "@octokit/graphql": "^4.6.4", "autosuggest-highlight": "^3.1.1", "classnames": "^2.2.6", "clsx": "^1.1.1", @@ -55,10 +56,13 @@ "remark-details": "^3.0.0", "remark-math": "^3.0.1", "simple-git": "^2.41.1", - "use-persisted-state": "^0.3.0" + "use-persisted-state": "^0.3.0", + "js-sha256": "^0.9.0", + "axios": "^0.21.1" }, "devDependencies": { "@types/react-helmet": "^6.1.1", + "@types/use-persisted-state": "^0.3.0", "@typescript-eslint/eslint-plugin": "^4.28.1", "@typescript-eslint/parser": "^3.10.1", "eslint": "^7.29.0", diff --git a/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx b/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx index 5f9f9e73..12f957ac 100644 --- a/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx +++ b/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx @@ -1,6 +1,205 @@ -import React from 'react' -const Collection: React.FC = () => { - return
+// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { Avatar, Button, CircularProgress, createStyles, Divider, FormControl, Grid, GridSize, Hidden, InputLabel, makeStyles, MenuItem, Select, Theme, Tooltip, Typography } from '@material-ui/core' +import { Pagination } from '@material-ui/lab' +import GithubV4 from '@mgtd/vssue-api-github-v4' +import React, { useState, useEffect } from 'react' +import createPersistedState from 'use-persisted-state' +import { CollectionClient } from './CollectionClient' +import DetailInputDialog from './DetailInputDialog' +import ProposalCard from './ProposalCard' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { CollectionItem, CollectionUser, SortMethod } from './types' + +const useToken = createPersistedState('github-access-token') +const useItemsPerPage = createPersistedState('collection-items-per-page') +const usePreferredSortMethod = createPersistedState('collection-preferred-sort-method') +const REPO_OWNER = process.env.GATSBY_GITHUB_COLLECTION_REPO_OWNER || 'officeyutong' +const REPO_NAME = process.env.GATSBY_GITHUB_COLLECTION_REPO_NAME || 'collection-test' + +const apiClient = new GithubV4({ + baseURL: 'https://github.com', + owner: REPO_OWNER, + repo: REPO_NAME, + clientId: process.env.GATSBY_GITHUB_COLLECTION_CLIENT_ID, + clientSecret: process.env.GATSBY_GITHUB_COLLECTION_CLIENT_SECRET, + labels: [], + proxy: (url: string) => `https://sparkling-silence-bf63.officeyutong.workers.dev/?${url}`, +}) + +const useStyles = makeStyles((theme: Theme) => createStyles({ + divider: { + marginTop: theme.spacing(1), + marginBottom: theme.spacing(1), + }, + formControl: { + width: '100%', + }, + pagination: { + paddingTop: theme.spacing(2), + }, +})) + +// eslint-disable-next-line @typescript-eslint/ban-types +const Collection: React.FC<{ id: string }> = ({ id }) => { + const styles = useStyles() + const [token, setToken] = useToken(null) + const [data, setData] = useState([]) + const [loaded, setLoaded] = useState(false) + const [user, setUser] = useState({ login: false }) + const [pageCount, setPageCount] = useState(0) + const [page, setPage] = useState(1) + const [, setCount] = useState(0) + const [itemsPerPage, setItemsPerPage] = useItemsPerPage(10) + const [sortMethod, setSortMethod] = usePreferredSortMethod('support') + const [contentLoading, setContentLoading] = useState(false) + const [showDetailInput, setShowDetailInput] = useState(false) + const login = user.login + const client = new CollectionClient(token, REPO_OWNER, REPO_NAME) + const revokeToken = (): void => { + setToken(null) + setUser({ login: false }) + } + const loadPage = async (page: number): Promise => { + setContentLoading(true) + const resp = await client.getCollection(id, page, itemsPerPage, sortMethod) + setContentLoading(false) + setPage(page) + setPageCount(resp.totalPage) + setCount(resp.proposalCount) + setData(resp.data) + } + useEffect(() => { + (async () => { + if (token) { + try { + const resp = await apiClient.getUser({ accessToken: token }) + setUser({ ...resp, login: true } as CollectionUser) + } catch (e) { + revokeToken() + return + } + } else { + const token = await apiClient.handleAuth() + console.log('token=', token) + setToken(token) + } + await loadPage(1) + setLoaded(true) + })() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loaded, token]) + useEffect(() => { + if (loaded) loadPage(1) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [itemsPerPage, sortMethod]) + return
+ {!loaded + ?
+ + + + + +
+ :
+ + + {user.login && + + + + } + + + {user.login + ? + {user.username} + + : } + + + +
+ +
+
+
+
+ + {contentLoading + ?
+ + + + + +
+ :
+ {data.map((x, i) => )} +
} + + + + + 排序方式 + + + + + + 每页条数 + + + + + {pageCount >= 1 && + + loadPage(p)} + className={styles.pagination} + > + + } + + + {login && } + + +
} + {showDetailInput && setShowDetailInput(false)} finishCallback={() => loadPage(page)} client={client} pageId={id}>} +
} export default Collection diff --git a/gatsby-theme-oi-wiki/src/components/Collection/CollectionClient.ts b/gatsby-theme-oi-wiki/src/components/Collection/CollectionClient.ts new file mode 100644 index 00000000..c757c602 --- /dev/null +++ b/gatsby-theme-oi-wiki/src/components/Collection/CollectionClient.ts @@ -0,0 +1,179 @@ +/* eslint-disable*/ +import axios, { AxiosInstance } from 'axios' +import { sha256 } from 'js-sha256' +import { CollectionItem, GeneralGithubUser, ProposalMeta, SortMethod } from './types'; +import { graphql } from '@octokit/graphql' +import { graphql as TGraphQL } from '@octokit/graphql/dist-types/types'; +// import { GraphQlQueryResponseData } from "@octokit/graphql"; +const LABEL_PROPOSAL = 'collection-proposal' +// const LABEL_COMMENT = 'collection-comment' +class CollectionClient { + private owner: string; + private repo: string; + private client: AxiosInstance; + private gqlclient: TGraphQL; + private token: string | null; + // eslint-disable-next-line space-before-function-paren + constructor(token: string | null, owner: string, repo: string) { + this.repo = repo + this.owner = owner + this.token = token + this.client = axios.create({ + headers: { + Authorization: token !== null ? `token ${this.token}` : undefined, + Accept: 'application/vnd.github.v3+json;application/vnd.github.squirrel-girl-preview' + }, + baseURL: 'https://api.github.com' + }) + this.client.interceptors.response.use(r => r, err => { + console.log(err) + err.data = null + return err + }) + this.gqlclient = graphql.defaults({ + headers: { + Authorization: token !== null ? `token ${this.token}` : undefined, + } + }) + } + makeRepoPrefix(): string { + return `/repos/${this.owner}/${this.repo}` + } + async checkLabel() { + if (this.token === null) return + for (const name of [LABEL_PROPOSAL]) { + const resp = await this.client.get(`${this.makeRepoPrefix()}/labels/${name}`); + if (resp.status !== 200) { + console.log("Creating label", name) + await this.client.post(`${this.makeRepoPrefix()}/labels`, { name: name }) + } + } + } + async checkData() { + await this.checkLabel() + } + makeSearchPrefix(pageId: string): string { + return `collection-${sha256(pageId).substr(0, 8)}` + } + makeProposalTitle(pageId: string, name: string): string { + return `collection-${sha256(pageId).substr(0, 8)}-${sha256(name)}` + } + async deleteProposal(issueId: string): Promise<{ ok: boolean; message?: string; }> { + // const title = this.makeProposalTitle(id, name) + await this.gqlclient(` + mutation ($input: DeleteIssueInput!) { + deleteIssue(input: $input) { + clientMutationId + } + } + `, { input: issueId }) + return { ok: true } + } + async sendProposal(id: string, name: string, url: string, description: string): Promise<{ ok: boolean; message: string }> { + const proposalTitle = this.makeProposalTitle(id, name) + const searchtext = `${proposalTitle} repo:${this.owner}/${this.repo} in:title` + { + const resp = (await this.client.get('/search/issues', { params: { q: searchtext, page: 1 } })).data as { total_count: number } + if (resp.total_count > 0) return { ok: false, message: '此标题的提案已经存在!' } + } + { + const resp = (await this.client.post(`/repos/${this.owner}/${this.repo}/issues`, { + title: proposalTitle, + body: JSON.stringify({ name: name, url: url, description: description }), + labels: [LABEL_PROPOSAL], + })) + if (resp.status !== 201) { + return { ok: false, message: `${resp.status}: ${resp.statusText}` } + } + } + + return { ok: true, message: '' } + } + async getComments(issueId: number, page: number = 1, itemsPerPage: number = 20) { + + } + async getSelfSupported(id: number): Promise { + const { repository } = await this.gqlclient(` + query repository( + $owner: String!, + $name: String!, + $number: Int!) { + repository(owner: $owner, name: $name) { + issue(number: $number) { + reactionGroups { + content + # reactors { + # totalCount + # } + viewerHasReacted + } + } + } + } + `, { owner: this.owner, name: this.repo, number: id }) as { + repository: { + issue: { + reactionGroups: { + content: string; + // reactors: { totalCount: number; }; + viewerHasReacted: boolean; + }[] + } + } + } + for (const reaction of repository.issue.reactionGroups) { + if (reaction.content === 'THUMBS_UP') { + return reaction.viewerHasReacted + } + } + return false + } + async getCollection(id: string, page: number = 1, itemsPerPage: number = 10, orderBy: SortMethod = 'support'): Promise<{ totalPage: number; proposalCount: number; data: CollectionItem[] }> { + await this.checkData() + const query = `${this.makeSearchPrefix(id)} label:${LABEL_PROPOSAL} repo:${this.owner}/${this.repo} in:title` + const resp = (await this.client.get('/search/issues', { + params: { + q: query, + sort: orderBy === 'support' ? 'reactions-+1' : 'comments', + order: 'desc', + page: page, + per_page: itemsPerPage + } + })).data as { + total_count: number; + items: { + comments: number; + number: number; + title: string; + body: string + reactions: { + "+1": number; + } + user: GeneralGithubUser; + node_id: string; + }[] + } + const totalPage = Math.ceil(resp.total_count / itemsPerPage) + return { + totalPage: totalPage, + proposalCount: resp.total_count, + data: resp.items.map(x => { + const meta = JSON.parse(x.body) as ProposalMeta + return { + commentCount: x.comments, + description: meta.description, + id: x.number, + name: meta.name, + url: meta.url, + supportCount: x.reactions['+1'], + user: x.user, + nodeId: x.node_id, + } + }) + } + } +} + +export { + CollectionClient, +} diff --git a/gatsby-theme-oi-wiki/src/components/Collection/DetailInputDialog.tsx b/gatsby-theme-oi-wiki/src/components/Collection/DetailInputDialog.tsx new file mode 100644 index 00000000..54a1a4df --- /dev/null +++ b/gatsby-theme-oi-wiki/src/components/Collection/DetailInputDialog.tsx @@ -0,0 +1,67 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { Button, CircularProgress, createStyles, Dialog, DialogActions, DialogContent, DialogTitle, Grid, makeStyles, TextField, Theme } from '@material-ui/core' +import React, { useState } from 'react' +import { CollectionClient } from './CollectionClient' + +const useStyles = makeStyles((theme: Theme) => createStyles({ + input: { + marginBottom: theme.spacing(1), + width: '100%', + }, + dialogContent: { + width: '400px', + }, +})) + +const DetailInputDialog: React.FC<{ open: boolean; onClose: () => void; finishCallback: () => void; client: CollectionClient; pageId: string }> = ({ + finishCallback, + onClose, + open, + client, + pageId, +}) => { + const styles = useStyles() + const [name, setName] = useState('') + const [url, setUrl] = useState('') + const [description, setDescription] = useState('') + const [loading, setLoading] = useState(false) + const [nameErrorText, setNameErrorText] = useState('') + const confirm = async (): Promise => { + setLoading(true) + const resp = await client.sendProposal(pageId, name, url, description) + if (!resp.ok) { + setNameErrorText(resp.message) + setLoading(false) + return + } + onClose() + finishCallback() + } + return
+ + 添加提案 + +
+ { setName(e.target.value); setNameErrorText('') }} helperText={nameErrorText !== '' ? nameErrorText : '题目名用以同其他提案相区分,请保证唯一性'}> +
+
+ setUrl(e.target.value)}> +
+
+ setDescription(e.target.value)}> +
+ {loading && + + + + } +
+ + + + +
+
+} + +export default DetailInputDialog diff --git a/gatsby-theme-oi-wiki/src/components/Collection/ProposalCard.tsx b/gatsby-theme-oi-wiki/src/components/Collection/ProposalCard.tsx new file mode 100644 index 00000000..50f72266 --- /dev/null +++ b/gatsby-theme-oi-wiki/src/components/Collection/ProposalCard.tsx @@ -0,0 +1,102 @@ +import { Accordion, AccordionDetails, AccordionProps, AccordionSummary, Badge, CircularProgress, Container, createStyles, Grid, makeStyles, Theme, Typography } from '@material-ui/core' +import React, { useEffect, useRef, useState } from 'react' +import { CollectionClient } from './CollectionClient' +import { CollectionItem, CollectionUser, SortMethod } from './types' +import { ExpandMore as ExpandMoreIcon, ThumbUp as ThumbUpIcon, Comment as CommentIcon } from '@material-ui/icons' +interface ProposalCardProps extends CollectionItem { + client: CollectionClient; + sortMethod: SortMethod; + user: CollectionUser; +} +const useStyles = makeStyles((theme: Theme) => createStyles({ + card: { + marginBottom: theme.spacing(1), + }, +})) +const ProposalCard: React.FC = (props) => { + const { + client, + name, + commentCount, + url, + id, + description, + } = props + const styles = useStyles() + const [supportStateLoaded, setSupportStateLoaded] = useState(false) + const [selfSupported, setSelfSupported] = useState(false) + const [supportCount, setSupportCount] = useState(props.supportCount) + const [expanding, setExpanding] = useState(false) + useEffect(() => { + if (!supportStateLoaded) { + client.getSelfSupported(id).then(ok => { + setSelfSupported(ok) + setSupportStateLoaded(true) + }) + } + }, [client, id, supportStateLoaded]) + return
+ setExpanding(!expanding)}> + } + > + + + + {name} + + + + + + + + + + + + + + + + + + + + + {/* {commentLoading && + + + + } */} + {url !== '' && + 题目链接: + {url} + + } + + {description} + + + + +
+} + +export default ProposalCard diff --git a/gatsby-theme-oi-wiki/src/components/Collection/index.tsx b/gatsby-theme-oi-wiki/src/components/Collection/index.tsx new file mode 100644 index 00000000..9a570ecd --- /dev/null +++ b/gatsby-theme-oi-wiki/src/components/Collection/index.tsx @@ -0,0 +1,29 @@ +import Collection from './Collection' +import { Accordion, AccordionDetails, AccordionSummary, Container, makeStyles, Typography } from '@material-ui/core' +import React from 'react' +import { ExpandMore as ExpandMoreIcon, List as ListIcon } from '@material-ui/icons' +const useStyles = makeStyles({ + metaicon: { + verticalAlign: 'sub', + }, +}) +const CollectionComponent: React.FC<{ id: string }> = ({ id }) => { + const classes = useStyles() + return + } + aria-controls="problem collection" + > + + 题单 + + + + + + + + +} + +export default CollectionComponent diff --git a/gatsby-theme-oi-wiki/src/components/Collection/types.ts b/gatsby-theme-oi-wiki/src/components/Collection/types.ts new file mode 100644 index 00000000..424e57f1 --- /dev/null +++ b/gatsby-theme-oi-wiki/src/components/Collection/types.ts @@ -0,0 +1,58 @@ +/* eslint-disable no-undef */ +/* eslint-disable camelcase */ +/* eslint-disable spaced-comment */ + +type CollectionUser = { login: false; } | { login: true; username: string; avatar: string; homepage: string } + +interface GeneralGithubUser { + login: string;//用户名 + id: number; + node_id: string; + avatar_id: string; + gravatar_id: string; + url: string; + html_url: string; + followers_url: string; + following_url: string; + gists_url: string; + starred_url: string; + subscriptions_url: string; + organizations_url: string; + repos_url: string; + events_url: string; + received_events_url: string; + type: 'User'; + site_admin: boolean; +} + +interface Response_getIssue { + id: string; + title: string; + content: string; + link: string; +} + +type ReturnValue_getIssue = Response_getIssue | null; + +interface ProposalMeta { + name: string; + url: string; + description: string; +} +interface CollectionItem extends ProposalMeta { + id: number; + commentCount: number; + supportCount: number; + user: GeneralGithubUser; + nodeId: string; +} +type SortMethod = 'support' | 'comment' +export type { + CollectionUser, + ReturnValue_getIssue, + Response_getIssue, + GeneralGithubUser, + ProposalMeta, + CollectionItem, + SortMethod, +} diff --git a/gatsby-theme-oi-wiki/src/components/Layout.js b/gatsby-theme-oi-wiki/src/components/Layout.js index 32e3c439..264fdf21 100644 --- a/gatsby-theme-oi-wiki/src/components/Layout.js +++ b/gatsby-theme-oi-wiki/src/components/Layout.js @@ -22,6 +22,7 @@ import { SecondaryColorCssBaseline, } from '../theme' import Comment from './Comment' +import Collection from './Collection' import BackTop from './BackTop' import Footer from './Footer' import Meta from './Meta' @@ -91,6 +92,7 @@ function MyLayout ({ noToC, overflow, isWIP, + noCollection, }) { const theme = useTheme() const classes = useStyles() @@ -156,6 +158,9 @@ function MyLayout ({ )} + {noCollection === 'false' &&
+ +
} diff --git a/gatsby-theme-oi-wiki/src/components/doc.js b/gatsby-theme-oi-wiki/src/components/doc.js index 37888b5a..460e2145 100644 --- a/gatsby-theme-oi-wiki/src/components/doc.js +++ b/gatsby-theme-oi-wiki/src/components/doc.js @@ -17,6 +17,7 @@ function Mdx ({ data: { mdx }, location }) { const tags = mdx.frontmatter.tags || null const noMeta = mdx.frontmatter.noMeta || 'false' const noComment = mdx.frontmatter.noComment || 'false' + const noCollection = mdx.frontmatter.noCollection || 'false' const noEdit = mdx.frontmatter.noEdit || 'false' const toc = mdx.toc || null const relativePath = mdx.parent.relativePath || '' @@ -100,6 +101,7 @@ function Mdx ({ data: { mdx }, location }) { modifiedTime={modifiedTime} noMeta={noMeta} noComment={noComment} + noCollection={noCollection} noEdit={noEdit} isWIP={isWIP} > From 4ab99ef3ff1e1ad50293d56ee687752403aaa7f8 Mon Sep 17 00:00:00 2001 From: officeyutong Date: Tue, 10 Aug 2021 22:33:20 +0800 Subject: [PATCH 3/7] fix: do not use graphql-api when the user didn't login --- example/.env.development | 2 +- .../src/components/Collection/Collection.tsx | 4 +- .../components/Collection/ProposalCard.tsx | 13 +++- yarn.lock | 78 +++++++++++++++++++ 4 files changed, 90 insertions(+), 7 deletions(-) diff --git a/example/.env.development b/example/.env.development index fd0a22f8..f0c69447 100644 --- a/example/.env.development +++ b/example/.env.development @@ -1,3 +1,3 @@ GATSBY_IS_DEV=true GATSBY_GITHUB_CLIENT_ID=23f389d8ae84d868b545 -GATSBY_GITHUB_CLIENT_SECRET=38d366c69a5c7caca8b1f066ebed85ba62fc4141 +GATSBY_GITHUB_CLIENT_SECRET=38d366c69a5c7caca8b1f066ebed85ba62fc4141 \ No newline at end of file diff --git a/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx b/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx index 12f957ac..ec54152f 100644 --- a/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx +++ b/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx @@ -20,8 +20,8 @@ const apiClient = new GithubV4({ baseURL: 'https://github.com', owner: REPO_OWNER, repo: REPO_NAME, - clientId: process.env.GATSBY_GITHUB_COLLECTION_CLIENT_ID, - clientSecret: process.env.GATSBY_GITHUB_COLLECTION_CLIENT_SECRET, + clientId: process.env.GATSBY_GITHUB_CLIENT_ID, + clientSecret: process.env.GATSBY_GITHUB_CLIENT_SECRET, labels: [], proxy: (url: string) => `https://sparkling-silence-bf63.officeyutong.workers.dev/?${url}`, }) diff --git a/gatsby-theme-oi-wiki/src/components/Collection/ProposalCard.tsx b/gatsby-theme-oi-wiki/src/components/Collection/ProposalCard.tsx index 50f72266..c4a1d146 100644 --- a/gatsby-theme-oi-wiki/src/components/Collection/ProposalCard.tsx +++ b/gatsby-theme-oi-wiki/src/components/Collection/ProposalCard.tsx @@ -1,12 +1,16 @@ import { Accordion, AccordionDetails, AccordionProps, AccordionSummary, Badge, CircularProgress, Container, createStyles, Grid, makeStyles, Theme, Typography } from '@material-ui/core' import React, { useEffect, useRef, useState } from 'react' import { CollectionClient } from './CollectionClient' -import { CollectionItem, CollectionUser, SortMethod } from './types' +import { CollectionItem, CollectionUser, ProposalMeta, SortMethod } from './types' import { ExpandMore as ExpandMoreIcon, ThumbUp as ThumbUpIcon, Comment as CommentIcon } from '@material-ui/icons' -interface ProposalCardProps extends CollectionItem { +interface ProposalCardProps extends ProposalMeta { client: CollectionClient; sortMethod: SortMethod; user: CollectionUser; + id: number; + commentCount: number; + supportCount: number; + nodeId: string; } const useStyles = makeStyles((theme: Theme) => createStyles({ card: { @@ -21,6 +25,7 @@ const ProposalCard: React.FC = (props) => { url, id, description, + user, } = props const styles = useStyles() const [supportStateLoaded, setSupportStateLoaded] = useState(false) @@ -28,13 +33,13 @@ const ProposalCard: React.FC = (props) => { const [supportCount, setSupportCount] = useState(props.supportCount) const [expanding, setExpanding] = useState(false) useEffect(() => { - if (!supportStateLoaded) { + if (!supportStateLoaded && user.login) { client.getSelfSupported(id).then(ok => { setSelfSupported(ok) setSupportStateLoaded(true) }) } - }, [client, id, supportStateLoaded]) + }, [client, id, supportStateLoaded, user.login]) return
setExpanding(!expanding)}> Date: Fri, 24 Sep 2021 22:50:46 +0800 Subject: [PATCH 4/7] feat: finished proposal-voting --- gatsby-theme-oi-wiki/package.json | 8 +- .../Collection/AddCommentDialog.tsx | 98 +++++++++++ .../src/components/Collection/Collection.tsx | 100 +++++++---- .../components/Collection/CollectionClient.ts | 63 ++++++- .../Collection/DetailInputDialog.tsx | 54 ++++-- .../components/Collection/ProposalCard.tsx | 91 ++++++++-- .../Collection/ProposalComments.tsx | 163 ++++++++++++++++++ .../src/components/Collection/types.ts | 23 ++- 8 files changed, 534 insertions(+), 66 deletions(-) create mode 100644 gatsby-theme-oi-wiki/src/components/Collection/AddCommentDialog.tsx create mode 100644 gatsby-theme-oi-wiki/src/components/Collection/ProposalComments.tsx diff --git a/gatsby-theme-oi-wiki/package.json b/gatsby-theme-oi-wiki/package.json index ad033035..72bdcadf 100644 --- a/gatsby-theme-oi-wiki/package.json +++ b/gatsby-theme-oi-wiki/package.json @@ -20,6 +20,7 @@ "@mgtd/vssue-api-github-v4": "^2.4.5", "@octokit/graphql": "^4.6.4", "autosuggest-highlight": "^3.1.1", + "axios": "^0.21.1", "classnames": "^2.2.6", "clsx": "^1.1.1", "constate": "^3.3.0", @@ -41,7 +42,9 @@ "gatsby-transformer-remark-rehype": "^1.0.3", "gatsby-transformer-sharp": "^3.9", "hast-util-to-text": "^3.0.0", + "js-sha256": "^0.9.0", "lodash": "^4.17.21", + "luxon": "^2.0.2", "mark.js": "^8.11.1", "mdast-util-toc": "^6.0.0", "path-browserify": "^1.0.1", @@ -56,11 +59,10 @@ "remark-details": "^3.0.0", "remark-math": "^3.0.1", "simple-git": "^2.41.1", - "use-persisted-state": "^0.3.0", - "js-sha256": "^0.9.0", - "axios": "^0.21.1" + "use-persisted-state": "^0.3.0" }, "devDependencies": { + "@types/luxon": "^2.0.4", "@types/react-helmet": "^6.1.1", "@types/use-persisted-state": "^0.3.0", "@typescript-eslint/eslint-plugin": "^4.28.1", diff --git a/gatsby-theme-oi-wiki/src/components/Collection/AddCommentDialog.tsx b/gatsby-theme-oi-wiki/src/components/Collection/AddCommentDialog.tsx new file mode 100644 index 00000000..8348eb3c --- /dev/null +++ b/gatsby-theme-oi-wiki/src/components/Collection/AddCommentDialog.tsx @@ -0,0 +1,98 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { Button, CircularProgress, createStyles, Dialog, DialogActions, DialogContent, DialogTitle, Grid, TextField } from '@material-ui/core' +import { makeStyles } from '@material-ui/styles' +import React, { useState } from 'react' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { CollectionClient } from './CollectionClient' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { CollectionUser } from './types' + +const useStyles = makeStyles(() => createStyles({ + textBox: { + // width: theme.spacing(), + }, + dialog: { + // width: theme.spacing(30), + }, +})) + +interface AddCommentDialogProps { + issueId: number; + user: CollectionUser; + client: CollectionClient; + finishCallback: () => void; + onClose: () => void; + open: boolean; +} + +const AddCommentDialog: React.FC = (props) => { + const styles = useStyles() + const { + client, + issueId, + finishCallback, + onClose, + open, + } = props + const [errorText, setErrorText] = useState('') + const [text, setText] = useState('') + const [loading, setLoading] = useState(false) + const sendComment = async (): Promise => { + if (text === '') { + setErrorText('请输入评论内容') + return + } + setLoading(true) + await client.sendComment(issueId, text) + setLoading(false) + onClose() + finishCallback() + } + return <> + { + if (reason !== 'backdropClick') onClose() + }} + > + 发表评论 + +
+ { + setText(e.target.value) + setErrorText('') + }} + > +
+ {loading && + + + + } +
+ + + + +
+ +} + +export default AddCommentDialog diff --git a/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx b/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx index ec54152f..4dfa7383 100644 --- a/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx +++ b/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx @@ -20,6 +20,7 @@ const apiClient = new GithubV4({ baseURL: 'https://github.com', owner: REPO_OWNER, repo: REPO_NAME, + // 使用和评论区相同的OAuth App clientId: process.env.GATSBY_GITHUB_CLIENT_ID, clientSecret: process.env.GATSBY_GITHUB_CLIENT_SECRET, labels: [], @@ -50,11 +51,16 @@ const Collection: React.FC<{ id: string }> = ({ id }) => { const [page, setPage] = useState(1) const [, setCount] = useState(0) const [itemsPerPage, setItemsPerPage] = useItemsPerPage(10) - const [sortMethod, setSortMethod] = usePreferredSortMethod('support') + const [sortMethod0, setSortMethod] = usePreferredSortMethod('support') const [contentLoading, setContentLoading] = useState(false) const [showDetailInput, setShowDetailInput] = useState(false) + const [shouldLoad, setShouldLoad] = useState(false) const login = user.login const client = new CollectionClient(token, REPO_OWNER, REPO_NAME) + const sortMethod: SortMethod = React.useMemo(() => { + if (sortMethod0 === 'comment' || sortMethod0 === 'support') return sortMethod0 + return 'support' + }, [sortMethod0]) const revokeToken = (): void => { setToken(null) setUser({ login: false }) @@ -67,31 +73,42 @@ const Collection: React.FC<{ id: string }> = ({ id }) => { setPageCount(resp.totalPage) setCount(resp.proposalCount) setData(resp.data) + setLoaded(true) } useEffect(() => { (async () => { if (token) { try { - const resp = await apiClient.getUser({ accessToken: token }) - setUser({ ...resp, login: true } as CollectionUser) + const { username, avatar, homepage } = await apiClient.getUser({ accessToken: token }) + const resp = await client.getUserDetails(username) + setUser({ + login: true, + avatar: avatar as string, + homepage: homepage as string, + id: resp.id, + node_id: resp.node_id, + username: username, + }) + // console.log(resp) } catch (e) { revokeToken() - return + // return } } else { const token = await apiClient.handleAuth() console.log('token=', token) setToken(token) } - await loadPage(1) - setLoaded(true) + // await loadPage(1) + // setLoaded(true) + setShouldLoad(true) })() // eslint-disable-next-line react-hooks/exhaustive-deps }, [loaded, token]) useEffect(() => { - if (loaded) loadPage(1) + if (shouldLoad) loadPage(1) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [itemsPerPage, sortMethod]) + }, [itemsPerPage, sortMethod, shouldLoad]) return
{!loaded ?
@@ -102,29 +119,29 @@ const Collection: React.FC<{ id: string }> = ({ id }) => {
:
- - - {user.login && - + + {user.login && + + - - } - - - {user.login - ? - {user.username} - - : } - - - -
- -
+
+
} + + {user.login + ? + + {user.username} + + + : } -
+ +
+ +
+
+ {contentLoading ?
@@ -136,11 +153,26 @@ const Collection: React.FC<{ id: string }> = ({ id }) => {
:
{data.map((x, i) => { + const newval = [...data] + for (const val of newval) { if (val.id === x.id) { val.supportCount = v } } + setData(newval) + }} + removeCallback={() => { + setData(data.filter(y => y.id !== x.id)) + }} >)}
} @@ -153,10 +185,12 @@ const Collection: React.FC<{ id: string }> = ({ id }) => { labelId='collection-sort-method-input-label' id='collection-sort-method-select' value={sortMethod} - onChange={e => setSortMethod(e.target.value as SortMethod)} + onChange={e => { + setSortMethod(e.target.value as SortMethod) + }} > - 支持数 - 评论数 + 支持数 + 评论数 diff --git a/gatsby-theme-oi-wiki/src/components/Collection/CollectionClient.ts b/gatsby-theme-oi-wiki/src/components/Collection/CollectionClient.ts index c757c602..f8d2dee4 100644 --- a/gatsby-theme-oi-wiki/src/components/Collection/CollectionClient.ts +++ b/gatsby-theme-oi-wiki/src/components/Collection/CollectionClient.ts @@ -1,12 +1,10 @@ /* eslint-disable*/ import axios, { AxiosInstance } from 'axios' import { sha256 } from 'js-sha256' -import { CollectionItem, GeneralGithubUser, ProposalMeta, SortMethod } from './types'; +import { CollectionItem, GeneralGithubUser, ProposalComment, ProposalMeta, SortMethod } from './types'; import { graphql } from '@octokit/graphql' import { graphql as TGraphQL } from '@octokit/graphql/dist-types/types'; -// import { GraphQlQueryResponseData } from "@octokit/graphql"; const LABEL_PROPOSAL = 'collection-proposal' -// const LABEL_COMMENT = 'collection-comment' class CollectionClient { private owner: string; private repo: string; @@ -58,7 +56,14 @@ class CollectionClient { makeProposalTitle(pageId: string, name: string): string { return `collection-${sha256(pageId).substr(0, 8)}-${sha256(name)}` } - async deleteProposal(issueId: string): Promise<{ ok: boolean; message?: string; }> { + async sendComment(issueId: number, text: string): Promise { + await this.client.post(`/repos/${this.owner}/${this.repo}/issues/${issueId}/comments`, { body: text }) + } + async getIssueCommentCount(issudId: number): Promise { + const resp = (await this.client.get(`/repos/${this.owner}/${this.repo}/issues/${issudId}`)).data + return resp.comments as number + } + async deleteProposal(nodeId: string): Promise<{ ok: boolean; message?: string; }> { // const title = this.makeProposalTitle(id, name) await this.gqlclient(` mutation ($input: DeleteIssueInput!) { @@ -66,7 +71,11 @@ class CollectionClient { clientMutationId } } - `, { input: issueId }) + `, { + input: { + issueId: nodeId + } + }) return { ok: true } } async sendProposal(id: string, name: string, url: string, description: string): Promise<{ ok: boolean; message: string }> { @@ -89,8 +98,48 @@ class CollectionClient { return { ok: true, message: '' } } - async getComments(issueId: number, page: number = 1, itemsPerPage: number = 20) { - + async getUserDetails(username: string): Promise { + return (await this.client.get(`/users/${username}`)).data + } + async getComments(issueId: number, page: number = 1, itemsPerPage: number = 20): Promise { + const resp = (await this.client.get(`/repos/${this.owner}/${this.repo}/issues/${issueId}/comments`, { + params: { + per_page: itemsPerPage, + page: page + } + })).data as ProposalComment[]; + return resp; + } + async setSupportState(nodeId: string, state: boolean) { + if (state) { + await this.gqlclient(` + mutation ($data: AddReactionInput!) { + addReaction(input: $data) { + reaction { + content + } + subject { + id + } + } + } + `, { + data: { subjectId: nodeId, content: 'THUMBS_UP' } + }) + } else { + await this.gqlclient(` + mutation ($data: RemoveReactionInput!) { + removeReaction(input: $data) { + reaction { + content + } + subject { + id + } + } + } + `, { data: { subjectId: nodeId, content: 'THUMBS_UP' } }) + } } async getSelfSupported(id: number): Promise { const { repository } = await this.gqlclient(` diff --git a/gatsby-theme-oi-wiki/src/components/Collection/DetailInputDialog.tsx b/gatsby-theme-oi-wiki/src/components/Collection/DetailInputDialog.tsx index 54a1a4df..4f549249 100644 --- a/gatsby-theme-oi-wiki/src/components/Collection/DetailInputDialog.tsx +++ b/gatsby-theme-oi-wiki/src/components/Collection/DetailInputDialog.tsx @@ -1,6 +1,7 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars import { Button, CircularProgress, createStyles, Dialog, DialogActions, DialogContent, DialogTitle, Grid, makeStyles, TextField, Theme } from '@material-ui/core' import React, { useState } from 'react' +// eslint-disable-next-line @typescript-eslint/no-unused-vars import { CollectionClient } from './CollectionClient' const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -9,7 +10,7 @@ const useStyles = makeStyles((theme: Theme) => createStyles({ width: '100%', }, dialogContent: { - width: '400px', + // width: '400px', }, })) @@ -38,18 +39,49 @@ const DetailInputDialog: React.FC<{ open: boolean; onClose: () => void; finishCa finishCallback() } return
- + { + if (reason !== 'backdropClick') onClose() + }} + fullWidth + maxWidth='sm' + > 添加提案 -
- { setName(e.target.value); setNameErrorText('') }} helperText={nameErrorText !== '' ? nameErrorText : '题目名用以同其他提案相区分,请保证唯一性'}> -
-
- setUrl(e.target.value)}> -
-
- setDescription(e.target.value)}> -
+ { + setName(e.target.value) + setNameErrorText('') + }} + helperText={nameErrorText !== '' ? nameErrorText : '题目名用以同其他提案相区分,请保证唯一性'} + > + setUrl(e.target.value)} + > + setDescription(e.target.value)} + > + {loading && diff --git a/gatsby-theme-oi-wiki/src/components/Collection/ProposalCard.tsx b/gatsby-theme-oi-wiki/src/components/Collection/ProposalCard.tsx index c4a1d146..67538003 100644 --- a/gatsby-theme-oi-wiki/src/components/Collection/ProposalCard.tsx +++ b/gatsby-theme-oi-wiki/src/components/Collection/ProposalCard.tsx @@ -1,8 +1,13 @@ -import { Accordion, AccordionDetails, AccordionProps, AccordionSummary, Badge, CircularProgress, Container, createStyles, Grid, makeStyles, Theme, Typography } from '@material-ui/core' -import React, { useEffect, useRef, useState } from 'react' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { Accordion, AccordionDetails, AccordionSummary, Badge, Button, Container, createStyles, Grid, makeStyles, Typography, Theme } from '@material-ui/core' +import React, { useEffect, useState } from 'react' +import { ExpandMore as ExpandMoreIcon, ThumbUp as ThumbUpIcon, Comment as CommentIcon, Delete as DeleteIcon } from '@material-ui/icons' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { ProposalMeta, SortMethod, CollectionUser, GeneralGithubUser } from './types' +// eslint-disable-next-line @typescript-eslint/no-unused-vars import { CollectionClient } from './CollectionClient' -import { CollectionItem, CollectionUser, ProposalMeta, SortMethod } from './types' -import { ExpandMore as ExpandMoreIcon, ThumbUp as ThumbUpIcon, Comment as CommentIcon } from '@material-ui/icons' +import ProposalComments from './ProposalComments' + interface ProposalCardProps extends ProposalMeta { client: CollectionClient; sortMethod: SortMethod; @@ -11,11 +16,21 @@ interface ProposalCardProps extends ProposalMeta { commentCount: number; supportCount: number; nodeId: string; + proposalUser: GeneralGithubUser; + updateSupportCount: (val: number) => void; + removeCallback: () => void; } const useStyles = makeStyles((theme: Theme) => createStyles({ card: { marginBottom: theme.spacing(1), }, + utilsContainer: { + marginTop: theme.spacing(1), + marginBottom: theme.spacing(1), + }, + utilsButton: { + marginRight: theme.spacing(1), + }, })) const ProposalCard: React.FC = (props) => { const { @@ -26,12 +41,19 @@ const ProposalCard: React.FC = (props) => { id, description, user, + nodeId, + supportCount, + proposalUser, + updateSupportCount, + removeCallback, } = props const styles = useStyles() const [supportStateLoaded, setSupportStateLoaded] = useState(false) const [selfSupported, setSelfSupported] = useState(false) - const [supportCount, setSupportCount] = useState(props.supportCount) const [expanding, setExpanding] = useState(false) + const [toggling, setToggling] = useState(false) + const [removing, setRemoving] = useState(false) + const alreadyLogin = user.login useEffect(() => { if (!supportStateLoaded && user.login) { client.getSelfSupported(id).then(ok => { @@ -40,6 +62,24 @@ const ProposalCard: React.FC = (props) => { }) } }, [client, id, supportStateLoaded, user.login]) + const toggleSelfSupportState = async (): Promise => { + setToggling(true) + if (selfSupported) { + await client.setSupportState(nodeId, false) + updateSupportCount(supportCount - 1) + } else { + await client.setSupportState(nodeId, true) + updateSupportCount(supportCount + 1) + } + setToggling(false) + setSelfSupported(b => !b) + } + const removeThisProposal = async (): Promise => { + setRemoving(true) + await client.deleteProposal(nodeId) + setRemoving(false) + removeCallback() + } return
setExpanding(!expanding)}> = (props) => { - {/* {commentLoading && - - - - } */} {url !== '' && 题目链接: {url} } - {description} + + {description} + + + + + + {alreadyLogin && user.id === proposalUser.id && } + } + > diff --git a/gatsby-theme-oi-wiki/src/components/Collection/ProposalComments.tsx b/gatsby-theme-oi-wiki/src/components/Collection/ProposalComments.tsx new file mode 100644 index 00000000..f437bff9 --- /dev/null +++ b/gatsby-theme-oi-wiki/src/components/Collection/ProposalComments.tsx @@ -0,0 +1,163 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { Avatar, Button, CircularProgress, Container, createStyles, Divider, Grid, makeStyles, Theme, Typography } from '@material-ui/core' +import React, { useEffect, useState } from 'react' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { CollectionClient } from './CollectionClient' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { CollectionUser, ProposalComment } from './types' +import { Pagination } from '@material-ui/lab' +import AddCommentDialog from './AddCommentDialog' +import { Add as AddIcon } from '@material-ui/icons' +import { DateTime } from 'luxon' +interface ProposalCommentsProps { + issueId: number; + client: CollectionClient; + user: CollectionUser; + extraButtonSlot: React.ReactNode; + alreadyLogin: boolean; +} +const COMMENTS_PER_PAGE = 5 +const useStyles = makeStyles((theme: Theme) => createStyles({ + pagination: { + marginTop: theme.spacing(1), + }, + commentItem: { + marginTop: theme.spacing(1), + marginBottom: theme.spacing(1), + }, + utilsContainer: { + marginTop: theme.spacing(1), + marginBottom: theme.spacing(1), + paddingLeft: theme.spacing(0), + }, + headerTop: { + marginTop: theme.spacing(2), + }, + utilsButton: { + marginRight: theme.spacing(1), + }, +})) +const ProposalComments: React.FC = (props) => { + const styles = useStyles() + const { + issueId, + client, + user, + extraButtonSlot, + alreadyLogin, + } = props + const [loaded, setLoaded] = useState(false) + const [loading, setLoading] = useState(false) + const [data, setData] = useState([]) + const [pageCount, setPageCount] = useState(0) + const [page, setPage] = useState(-1) + const [showAddDialog, setShowAddDialog] = useState(false) + const loadPage = async (page: number): Promise => { + // -1表示加载最后一页 + setLoading(true) + const commentCount = await client.getIssueCommentCount(issueId) + const pages = Math.ceil(commentCount / COMMENTS_PER_PAGE) + if (page === -1) page = pages + const resp = await client.getComments(issueId, page, COMMENTS_PER_PAGE) + console.debug('Loaded comments for issueId ', issueId, resp) + setData(resp) + setPageCount(pages) + setPage(page) + setLoading(false) + setLoaded(true) + } + useEffect(() => { + if (!loaded) loadPage(1) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [loaded]) + useEffect(() => { + setLoaded(false) + }, [issueId]) + return <> + {alreadyLogin && <> + + <> + {extraButtonSlot} + + + + } + + 评论 + + + + {loading && + + + + } + {loaded && <> + {pageCount === 0 + ? <> + + + 暂无评论... + + + + : <> + {data.map((x, i) =>
+ + + + + + + + + {x.user.login} + + + + + {DateTime.fromISO(x.created_at).toJSDate().toLocaleString()} + + + + + {x.body} + + + + +
)} + + + loadPage(p)} + className={styles.pagination} + > + + + } + } + {showAddDialog && loadPage(-1)} + onClose={() => setShowAddDialog(false)} + issueId={issueId} + open={showAddDialog} + user={user} + >} + +} + +export default ProposalComments diff --git a/gatsby-theme-oi-wiki/src/components/Collection/types.ts b/gatsby-theme-oi-wiki/src/components/Collection/types.ts index 424e57f1..80f4c483 100644 --- a/gatsby-theme-oi-wiki/src/components/Collection/types.ts +++ b/gatsby-theme-oi-wiki/src/components/Collection/types.ts @@ -2,7 +2,14 @@ /* eslint-disable camelcase */ /* eslint-disable spaced-comment */ -type CollectionUser = { login: false; } | { login: true; username: string; avatar: string; homepage: string } +type CollectionUser = { login: false; } | { + login: true; + username: string; + avatar: string; + homepage: string; + id: number; + node_id: string; +} interface GeneralGithubUser { login: string;//用户名 @@ -23,6 +30,7 @@ interface GeneralGithubUser { received_events_url: string; type: 'User'; site_admin: boolean; + avatar_url: string; } interface Response_getIssue { @@ -47,6 +55,17 @@ interface CollectionItem extends ProposalMeta { nodeId: string; } type SortMethod = 'support' | 'comment' +type ButtonClickEvent = React.MouseEventHandler; + +interface ProposalComment { + id: number; + node_id: string; + body: string; + user: GeneralGithubUser; + created_at: string; + updated_at: string; +} + export type { CollectionUser, ReturnValue_getIssue, @@ -55,4 +74,6 @@ export type { ProposalMeta, CollectionItem, SortMethod, + ButtonClickEvent, + ProposalComment, } From d6df8679c7a9ea85e587225081ec7ee1aa5903c7 Mon Sep 17 00:00:00 2001 From: officeyutong Date: Fri, 24 Sep 2021 23:00:24 +0800 Subject: [PATCH 5/7] feat: move proxy url to env file --- gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx b/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx index 4dfa7383..ac982f2d 100644 --- a/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx +++ b/gatsby-theme-oi-wiki/src/components/Collection/Collection.tsx @@ -15,7 +15,7 @@ const useItemsPerPage = createPersistedState('collection-items-per-page') const usePreferredSortMethod = createPersistedState('collection-preferred-sort-method') const REPO_OWNER = process.env.GATSBY_GITHUB_COLLECTION_REPO_OWNER || 'officeyutong' const REPO_NAME = process.env.GATSBY_GITHUB_COLLECTION_REPO_NAME || 'collection-test' - +const PROYX_URL = process.env.GATSBY_GITHUB_COLLECTION_PROXYURL || 'https://sparkling-silence-bf63.officeyutong.workers.dev/?' const apiClient = new GithubV4({ baseURL: 'https://github.com', owner: REPO_OWNER, @@ -24,7 +24,7 @@ const apiClient = new GithubV4({ clientId: process.env.GATSBY_GITHUB_CLIENT_ID, clientSecret: process.env.GATSBY_GITHUB_CLIENT_SECRET, labels: [], - proxy: (url: string) => `https://sparkling-silence-bf63.officeyutong.workers.dev/?${url}`, + proxy: (url: string) => `${PROYX_URL}${url}`, }) const useStyles = makeStyles((theme: Theme) => createStyles({ From 6efb256e5fa405be0470958e65ab38fe54d14d78 Mon Sep 17 00:00:00 2001 From: officeyutong Date: Fri, 24 Sep 2021 23:02:24 +0800 Subject: [PATCH 6/7] feat: more fix --- example/.env.development | 5 ++++- yarn.lock | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/example/.env.development b/example/.env.development index f0c69447..1d440799 100644 --- a/example/.env.development +++ b/example/.env.development @@ -1,3 +1,6 @@ GATSBY_IS_DEV=true GATSBY_GITHUB_CLIENT_ID=23f389d8ae84d868b545 -GATSBY_GITHUB_CLIENT_SECRET=38d366c69a5c7caca8b1f066ebed85ba62fc4141 \ No newline at end of file +GATSBY_GITHUB_CLIENT_SECRET=38d366c69a5c7caca8b1f066ebed85ba62fc4141 + +GATSBY_COLLECTION_CLIENT_ID=f9d100ebbb7404db8b52 +GATSBY_COLLECTION_CLIENT_SECRET=486f19c5abf97a27e2cefe0d5bfef2e15ed332e6 \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index bd529e87..2b13d9d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2284,6 +2284,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.171.tgz#f01b3a5fe3499e34b622c362a46a609fdb23573b" integrity sha512-7eQ2xYLLI/LsicL2nejW9Wyko3lcpN6O/z0ZLHrEQsg280zIdCv1t/0m6UtBjUHokCGBQ3gYTbHzDkZ1xOBwwg== +"@types/luxon@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-2.0.4.tgz#f7b5a86ccd843c0ccaddfaedd9ee1081bc1cde3b" + integrity sha512-l3xuhmyF2kBldy15SeY6d6HbK2BacEcSK1qTF1ISPtPHr29JH0C1fndz9ExXLKpGl0J6pZi+dGp1i5xesMt60Q== + "@types/mathjax@^0.0.36": version "0.0.36" resolved "https://registry.yarnpkg.com/@types/mathjax/-/mathjax-0.0.36.tgz#18cf766f88ac0cd4e7ee8282b1286049bb6aa682" @@ -10046,6 +10051,11 @@ lru-queue@^0.1.0: dependencies: es5-ext "~0.10.2" +luxon@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-2.0.2.tgz#11f2cd4a11655fdf92e076b5782d7ede5bcdd133" + integrity sha512-ZRioYLCgRHrtTORaZX1mx+jtxKtKuI5ZDvHNAmqpUzGqSrR+tL4FVLn/CUGMA3h0+AKD1MAxGI5GnCqR5txNqg== + make-dir@^1.0.0, make-dir@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" From 2ce7a0154ec2167861eb6f680b45854f171c2998 Mon Sep 17 00:00:00 2001 From: officeyutong Date: Fri, 24 Sep 2021 23:29:53 +0800 Subject: [PATCH 7/7] fix: remove invalid merging prompt --- yarn.lock | 7 ------- 1 file changed, 7 deletions(-) diff --git a/yarn.lock b/yarn.lock index 71679097..1bc807fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14897,7 +14897,6 @@ unist-util-visit@^3.0.0: unist-util-is "^5.0.0" unist-util-visit-parents "^4.0.0" -<<<<<<< HEAD unist-util-visit@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-4.0.0.tgz#6e1f7e8e163921d20281354c38bfd3244b64580a" @@ -14906,12 +14905,6 @@ unist-util-visit@^4.0.0: "@types/unist" "^2.0.0" unist-util-is "^5.0.0" unist-util-visit-parents "^5.0.0" -======= -universal-user-agent@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" - integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== ->>>>>>> collection universalify@^0.1.0, universalify@^0.1.2: version "0.1.2"