修改cloudIDE

This commit is contained in:
何童崇 2022-07-29 14:14:25 +08:00
parent cc443fa112
commit b1725372b7
43 changed files with 10181 additions and 99 deletions

View File

@ -86,7 +86,7 @@ module.exports = {
externals: {
"react": "React",
"react-dom": "ReactDOM",
'alex': 'Alex',
"alex": "Alex",
},
resolve: {
// This allows you to set a fallback for where Webpack should look for modules.

128
package-lock.json generated
View File

@ -4,6 +4,15 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@ahooksjs/use-request": {
"version": "2.8.15",
"resolved": "https://registry.npmjs.org/@ahooksjs/use-request/-/use-request-2.8.15.tgz",
"integrity": "sha512-xhVaM4fyIiAMdVFuuU5i3CFUdFa/IblF+fvITVMFaUEO3w/V5tVCAF6WIA3T03n1/RPuzRkA7Ao1PFtSGtGelw==",
"requires": {
"lodash.debounce": "^4.0.8",
"lodash.throttle": "^4.1.1"
}
},
"@ant-design/colors": {
"version": "3.2.2",
"resolved": "https://registry.npm.taobao.org/@ant-design/colors/download/@ant-design/colors-3.2.2.tgz?cache=0&sync_timestamp=1612935637470&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40ant-design%2Fcolors%2Fdownload%2F%40ant-design%2Fcolors-3.2.2.tgz",
@ -415,6 +424,11 @@
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.0.0.tgz",
"integrity": "sha512-q95SP4FdkmF0CwO0F2q0H6ZgudsApaY/yCtAQNRn1gduef5fGpyEphzy0YCq/N0UFvDSnLg5V8jFK/YGXlDiCw=="
},
"@types/js-cookie": {
"version": "2.2.7",
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.7.tgz",
"integrity": "sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA=="
},
"@types/jss": {
"version": "9.5.8",
"resolved": "https://registry.npmjs.org/@types/jss/-/jss-9.5.8.tgz",
@ -817,6 +831,23 @@
}
}
},
"ahooks": {
"version": "2.10.14",
"resolved": "https://registry.npmjs.org/ahooks/-/ahooks-2.10.14.tgz",
"integrity": "sha512-axWa7VoAgu7bxA56dDl0CXW4rvaQmDBiov/d3tAy0x1YNYywYMKokL8TdLgJ5zO/oXGiWmG7BxlGOQGkqE/zkQ==",
"requires": {
"@ahooksjs/use-request": "^2.8.14",
"@types/js-cookie": "^2.2.6",
"dayjs": "^1.9.1",
"intersection-observer": "^0.7.0",
"js-cookie": "^2.2.1",
"lodash.debounce": "^4.0.8",
"lodash.isequal": "^4.5.0",
"lodash.throttle": "^4.1.1",
"resize-observer-polyfill": "^1.5.1",
"screenfull": "^5.0.0"
}
},
"ajv": {
"version": "6.12.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
@ -4501,6 +4532,11 @@
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.12.0.tgz",
"integrity": "sha512-qJgn99xxKnFgB1qL4jpxU7Q2t0LOn1p8KMIveef3UZD7kqjT3tpFNNdXJelEHhE+rUgffriXriw/sOSU+cS1Hw=="
},
"dayjs": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz",
"integrity": "sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A=="
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
@ -4839,7 +4875,7 @@
},
"dom-closest": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/dom-closest/-/dom-closest-0.2.0.tgz",
"resolved": "https://registry.npm.taobao.org/dom-closest/download/dom-closest-0.2.0.tgz",
"integrity": "sha1-69n5HRvyLo1vR3h2u80+yQIWwM8=",
"requires": {
"dom-matches": ">=1.0.1"
@ -4883,7 +4919,7 @@
},
"dom-matches": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-matches/-/dom-matches-2.0.0.tgz",
"resolved": "https://registry.npm.taobao.org/dom-matches/download/dom-matches-2.0.0.tgz",
"integrity": "sha1-0nKLQWqHUzmA6wibhI0lPPI6dYw="
},
"dom-scroll-into-view": {
@ -5135,7 +5171,7 @@
},
"enquire.js": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/enquire.js/-/enquire.js-2.1.6.tgz",
"resolved": "https://registry.npm.taobao.org/enquire.js/download/enquire.js-2.1.6.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fenquire.js%2Fdownload%2Fenquire.js-2.1.6.tgz",
"integrity": "sha1-PoeAybi4NQhMP2DhZtvDwqPImBQ="
},
"entities": {
@ -5660,7 +5696,7 @@
},
"eventlistener": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/eventlistener/-/eventlistener-0.0.1.tgz",
"resolved": "https://registry.npm.taobao.org/eventlistener/download/eventlistener-0.0.1.tgz",
"integrity": "sha1-7Suqu4UiJ68rz4iRUscsY8pTLrg="
},
"events": {
@ -7194,7 +7230,8 @@
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -7215,12 +7252,14 @@
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -7235,17 +7274,20 @@
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"optional": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -7362,7 +7404,8 @@
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"optional": true
},
"ini": {
"version": "1.3.5",
@ -7374,6 +7417,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -7388,6 +7432,7 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -7395,12 +7440,14 @@
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"optional": true
},
"minipass": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -7419,6 +7466,7 @@
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz",
"integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==",
"optional": true,
"requires": {
"minimist": "^1.2.5"
}
@ -7480,7 +7528,8 @@
"npm-normalize-package-bin": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA=="
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
"optional": true
},
"npm-packlist": {
"version": "1.4.8",
@ -7508,7 +7557,8 @@
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -7520,6 +7570,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"optional": true,
"requires": {
"wrappy": "1"
}
@ -7597,7 +7648,8 @@
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -7633,6 +7685,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -7652,6 +7705,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -7695,12 +7749,14 @@
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"optional": true
},
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"optional": true
}
}
},
@ -7974,7 +8030,7 @@
},
"hammerjs": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz",
"resolved": "https://registry.npm.taobao.org/hammerjs/download/hammerjs-2.0.8.tgz",
"integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE="
},
"handle-thing": {
@ -8788,7 +8844,7 @@
},
"image-size": {
"version": "0.5.5",
"resolved": "http://173.15.15.82:8081/repository/npm-all/image-size/-/image-size-0.5.5.tgz",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
"integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
"optional": true
},
@ -8807,7 +8863,7 @@
},
"immutable": {
"version": "3.7.6",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz",
"resolved": "https://registry.npm.taobao.org/immutable/download/immutable-3.7.6.tgz",
"integrity": "sha1-E7TTyxK++hVIKib+Gy665kAHHks="
},
"import-fresh": {
@ -9021,6 +9077,11 @@
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
"integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw=="
},
"intersection-observer": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.7.0.tgz",
"integrity": "sha512-Id0Fij0HsB/vKWGeBe9PxeY45ttRiBmhFyyt/geBdDHBYNctMRTE3dC1U3ujzz3lap+hVXlEcVaB56kZP/eEUg=="
},
"invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
@ -9896,6 +9957,11 @@
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz",
"integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ=="
},
"js-cookie": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz",
"integrity": "sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ=="
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -10253,7 +10319,7 @@
},
"less": {
"version": "3.9.0",
"resolved": "http://173.15.15.82:8081/repository/npm-all/less/-/less-3.9.0.tgz",
"resolved": "https://registry.npmjs.org/less/-/less-3.9.0.tgz",
"integrity": "sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w==",
"requires": {
"clone": "^2.1.2",
@ -10269,7 +10335,7 @@
"dependencies": {
"promise": {
"version": "7.3.1",
"resolved": "http://173.15.15.82:8081/repository/npm-all/promise/-/promise-7.3.1.tgz",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
"optional": true,
"requires": {
@ -10280,7 +10346,7 @@
},
"less-loader": {
"version": "4.1.0",
"resolved": "http://173.15.15.82:8081/repository/npm-all/less-loader/-/less-loader-4.1.0.tgz",
"resolved": "https://registry.npmjs.org/less-loader/-/less-loader-4.1.0.tgz",
"integrity": "sha512-KNTsgCE9tMOM70+ddxp9yyt9iHqgmSs0yTZc5XH5Wo+g80RWRIYNqE58QJKm/yMud5wZEvz50ugRDuzVIkyahg==",
"requires": {
"clone": "^2.1.1",
@ -10290,7 +10356,7 @@
"dependencies": {
"pify": {
"version": "3.0.0",
"resolved": "http://173.15.15.82:8081/repository/npm-all/pify/-/pify-3.0.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
"integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="
}
}
@ -10437,7 +10503,7 @@
},
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"resolved": "https://registry.npm.taobao.org/lodash.throttle/download/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"lodash.uniq": {
@ -16030,6 +16096,11 @@
"ajv-keywords": "^3.4.1"
}
},
"screenfull": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz",
"integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA=="
},
"scroll": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/scroll/-/scroll-2.0.3.tgz",
@ -16263,6 +16334,15 @@
"safe-buffer": "^5.0.1"
}
},
"sha1": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz",
"integrity": "sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==",
"requires": {
"charenc": ">= 0.0.1",
"crypt": ">= 0.0.1"
}
},
"shallow-clone": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",

View File

@ -6,6 +6,7 @@
"@monaco-editor/react": "^2.3.0",
"@novnc/novnc": "^1.1.0",
"actioncable": "^5.2.4-3",
"ahooks": "^2.10.14",
"antd": "^3.26.15",
"array-flatten": "^2.1.2",
"autoprefixer": "7.1.6",
@ -110,6 +111,7 @@
"sass-loader": "7.3.1",
"save-dev": "0.0.1-security",
"scroll-into-view": "^1.14.2",
"sha1": "^1.1.1",
"showdown": "^1.9.1",
"showdown-katex": "^0.8.0",
"slick-carousel": "^1.8.1",

View File

@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="zh-CN" class="notranslate translated-ltr" translate="no">
<head>
<meta charset="utf-8">
<meta name="Keywords" Content="gitLink,GitLink,gitlink,trustie,trustieforge,forge,确实让创建更美好,协同开发平台">
@ -9,13 +10,14 @@
<meta name="theme-color" content="#000000">
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link href="https://gw.alipayobjects.com/os/lib/alipay/alex/1.9.10/bundle/alex.editor.all.global.min.css" rel="stylesheet">
<link href="https://gw.alipayobjects.com/os/lib/alipay/alex-acr/2.0.7/bundle/acr.global.min.css" rel="stylesheet"/>
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%css/iconfont.css">
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%css/edu-purge.css">
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%css/editormd.min.css">
<link rel="stylesheet" type="text/css" href="%PUBLIC_URL%css/merge.css">
<%= htmlWebpackPlugin.tags.headTags %>
</head>
<body>
<!--用于markdown转html -->
<div id="md_div" style="display: none;"></div>
@ -28,10 +30,9 @@
<script src="%PUBLIC_URL%js/codemirror/codemirror.js"></script>
<script src="%PUBLIC_URL%js/editormd/editormd.min.js"></script>
<script src="%PUBLIC_URL%js/codemirror/merge/merge.js"></script>
<script src="%PUBLIC_URL%js/alex/alex.editor.all.global.min.js" defer ></script>
<script src="%PUBLIC_URL%js/alex/languages.global.min.js" defer ></script>
<!-- <script src="https://gw.alipayobjects.com/os/lib/alipay/alex/1.9.12/bundle/alex.editor.all.global.min.js" defer ></script>
<script src="https://gw.alipayobjects.com/os/lib/alipay/alex/1.9.12/languages/languages.global.min.js" defer ></script> -->
<script src="https://gw.alipayobjects.com/os/lib/moment/2.29.4/moment.js"></script>
<script src="https://gw.alipayobjects.com/os/lib/alipay/alex/2.0.7/bundle/alex.all.global.min.js"></script>
<%= htmlWebpackPlugin.tags.bodyTags %>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -89,8 +89,13 @@ export function initAxiosInterceptors(props) {
if (response.data.status === 404) {
let responseURL = response.request ? response.request.responseURL:'';
// 组织和个人的拥有情况404不跳转
if (responseURL.indexOf('/api/users/') === -1 && responseURL.indexOf('/api/organizations/') === -1 ) {
locationurl('/nopage');
// 邀请页面不进行404跳转
if( window.location.pathname.includes('/invite') && (responseURL.includes('/simple.json')||responseURL.includes('/detail.json')||responseURL.includes('/menu_list.json'))){
}else{
locationurl('/nopage');
}
}
}

View File

@ -11,6 +11,7 @@ export function getImageUrl(path) {
// https://www.educoder.net
// https://testbdweb.trustie.net
// const local = 'http://localhost:3000'
path && !path.startsWith('/') && !path.startsWith('http') && (path = '/'.concat(path));
const local = 'https://testforgeplus.trustie.net';
if (isDev) {
return `${local}/${path}`

View File

@ -80,7 +80,7 @@ function AddMember({getID,login,showNotification}){
<AutoComplete
dataSource={source}
value={searchKey}
style={{ width: 300 }}
style={{ width: 250 }}
onChange={changeInputUser}
onSelect={selectInputUser}
placeholder="搜索需要添加的用户..."

92
src/forge/Invite/Index.js Normal file
View File

@ -0,0 +1,92 @@
import React, { useState, useEffect } from 'react';
import { Modal, Button, } from 'antd';
import { Base64 } from 'js-base64';
import './index.scss';
import { Link } from 'react-router-dom';
import { getImageUrl } from 'educoder'
import axios from 'axios';
const identify = {
manager: "管理员",
developer: "开发者",
reporter: "报告者",
owner: "所有者",
}
function Invite({ history, current_user, match, projectDetail, showNotification }) {
if (!current_user.login) {
let { pathname, search } = window.location;
window.location.href = `/login?go_page=${pathname}${search}`;
}
let permission = projectDetail && projectDetail.permission;
const { projectsId, owner } = match.params;
let inviteString = window.location.search && window.location.search.split('?invite=')[1];
let data = inviteString && JSON.parse(Base64.decode(inviteString));
const [detail, setDetail] = useState({});
const [visible, setVisible] = useState(true);
useEffect(() => {
if (permission && detail.role && detail.role == permission.toLocaleLowerCase() || permission == 'Owner') {
showNotification(`您已经是${identify[detail.role]}`)
setTimeout(() => {
history.push(`/${owner}/${projectsId}`);
}, 2000)
} else {
setVisible(true)
}
}, [permission, detail.role]);
useEffect(() => {
const url = `/${owner}/${projectsId}/project_invite_links/show_link.json?invite_sign=${data.sign}`;
axios.get(url).then(res => {
if (res && res.data) {
setDetail(res.data);
} else {
showNotification('查询邀请链接失败');
}
})
}, [])
function accept() {
const url = `/${owner}/${projectsId}/project_invite_links/redirect_link.json?invite_sign=${data.sign}`;
axios.post(url).then(res => {
if (res && res.data.message == 'success') {
if (detail.is_apply) {
setVisible(false);
Modal.success({ content: '提交申请成功,请等待该仓库管理员审核' });
} else {
history.push(`/${owner}/${projectsId}`);
}
}
}).catch(error => { })
}
return (
<div className="">
{data && <Modal
visible={visible}
className="invite_development"
title={<div className="ownerImage"><img src={detail.project && getImageUrl(detail.project.owner.image_url)} /></div>}
width="548px"
closable={true}
onCancel={() => { setVisible(false); history.push(`/${current_user.login}`) }}
centered
okText={'接受'}
cancelText={'拒绝'}
onOk={accept}
maskClosable={false}
>
<Link className="invite_project link" target="_blank" to={`/${data.ownerLogin}/${data.projectId}`}>{detail.project && detail.project.owner.name}/{detail.project && detail.project.name}</Link>
<div className="invite_content">
<Link className="link" to={`/${detail.user && detail.user.login}`}>{detail.user && detail.user.name}</Link> {identify[detail.role]}
是否接受邀请
</div>
</Modal>}
</div>
)
}
export default Invite;

100
src/forge/Invite/index.scss Normal file
View File

@ -0,0 +1,100 @@
.invite_development {
font-family: PingFang SC;
height: 366px;
background-image: linear-gradient(
359.37deg,
#ebf3ff 0%,
#eff5ff 55.01%,
#cfdeff 100%
);
border: 1.5px solid #ffffff;
border-radius: 4px;
.ant-modal-close{
top:0 !important;
}
.ant-modal-close-x{
font-size: 30px;
color: #666;
width: 48px;
height: 48px;
line-height: 48px;
transform: scaleX(1.05);
}
.ant-modal-content {
background-color: inherit;
}
.ant-modal-header {
background: inherit;
border: 0;
padding-bottom: 0;
}
.ownerImage {
position: relative;
top:-66px;
width: 100px;
height: 100px;
background-color: #e9f1ff;
border: 1px solid #ffffff;
border-radius: 50%;
margin:0 auto;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
img{
width: 100%;
}
}
.invite_project {
display: block;
margin: 0 auto;
font-weight: 500;
font-size: 20px;
line-height: 22px;
text-align: center;
margin-bottom: 30px;
}
.invite_content {
width: 422px;
padding: 24px 48px;
background-color: rgba(225, 231, 255, 0.71);
border-radius: 4px 4px 0px 0px;
margin: 10px auto;
font-weight: 500;
font-size: 17px;
line-height: 32px;
text-align: center;
color: #151d40;
}
.link {
cursor: pointer;
color: $primary-color;
&:hover {
color: $primary-color-hover;
}
}
.ant-modal-footer {
text-align: center;
border: 0;
padding-bottom: 55px;
button {
width: 150px;
height: 42px;
border-radius: 5px;
font-size: 15px;
&:first-child {
background-color: rgba(196, 0, 14, 0.07);
border: 1px solid;
border-color: #f60011;
color: #f60011;
&:hover {
opacity: 0.75;
}
}
& + button {
margin-left: 30px;
}
}
}
}

View File

@ -333,6 +333,8 @@ function CoderDepot(props){
}
}
let n = fileInfo && fileInfo.name;
const mdFlag = n && n.substring(n.length-3,n.length) === ".md";
@ -443,6 +445,7 @@ function CoderDepot(props){
issuesFlag &&
<a onClick={createIssue}>+ 疑修</a>
}
</div>
{ fileOperate &&
<Dropdown

View File

@ -4,6 +4,7 @@ import { Link, Route, Switch } from 'react-router-dom';
import { Content, AlignTop } from '../Component/layout';
import DetailBanner from './sub/DetailBanner';
import cookie from 'react-cookies';
import { Base64 } from 'js-base64';
import '../css/index.scss'
import './list.scss';
@ -145,6 +146,14 @@ const WikiEdit = Loadable({
loader: () => import('../Wiki/EditWiki'),
loading: Loading,
});
const Invite = Loadable({
loader: () => import('../Invite/Index'),
loading: Loading,
});
const Review = Loadable({
loader: () => import('../Newfile/codeReview'),
loading: Loading,
});
/**
* permissionManager:管理员Reporter报告人员(只有读取权限)Developer开发人员除不能设置仓库信息外
*/
@ -227,12 +236,12 @@ class Detail extends Component {
})
}
clearIssueCookies=(history)=>{
clearIssueCookies = (history) => {
const { pathname } = history;
const { projectsId , owner } = this.props.match.params;
const { projectsId, owner } = this.props.match.params;
let currentIssue = pathname.indexOf(`/${owner}/${projectsId}/issues`) === -1;
if (currentIssue) {
cookie.save('states', undefined,{ expires: 0,path:`/` });
cookie.save('states', undefined, { expires: 0, path: `/` });
}
}
@ -304,15 +313,15 @@ class Detail extends Component {
},
disconnected: () => {
console.log("###### cannot connected! ######");
},
},
received: data => {
console.log(`###### ---received data--- ######`);
console.log(data);
if (data) {
if(deleteFlag){
if (deleteFlag) {
this.props.showNotification("镜像同步成功!");
window.location.reload();
}else{
} else {
if (data.project && data.project.mirror_status === 2) {
this.deleteProjectBack();
}
@ -326,20 +335,20 @@ class Detail extends Component {
cable.subscriptions.consumer.disconnect();
}
},
onerror:()=>{
onerror: () => {
console.log("###### cannot connected! ######");
}
});
this.timerChannel = setTimeout(this.reloadDetail,5000);
this.timerChannel = setTimeout(this.reloadDetail, 5000);
}
reloadDetail=()=>{
if(this.state.firstSync||this.state.secondSync){
reloadDetail = () => {
if (this.state.firstSync || this.state.secondSync) {
window.location.reload();
}
}
deleteProjectBack = () => {
const { history } = this.props;
@ -364,22 +373,33 @@ class Detail extends Component {
axios.get(url).then((result) => {
if (result && result.data) {
if (result.data.status === 404) {
this.props.history.push('/nopage');
if (window.location.pathname.includes('/invite')) {
let inviteString = window.location.search && window.location.search.split('?invite=')[1];
let data = inviteString && JSON.parse(Base64.decode(inviteString));
const { project = {}, projectDetail = {} } = this.state
this.setState({
project: Object.assign(project, { author: { name: data.ownerName } }),
projectDetail: Object.assign(projectDetail, { name: data.projectName })
});
} else {
this.props.history.push('/nopage');
}
} else {
this.setState({
projectDetail: result.data,
project_id: result.data.project_id,
isManager: result.data.permission && (result.data.permission === "Manager" || result.data.permission === "Admin" || result.data.permission === "Owner"),
isReporter: result.data.permission && result.data.permission === "Reporter",
isDeveloper: result.data.permission && result.data.permission === "Developer",
http_url: result.data.clone_url,
praised: result.data.praised,
watched: result.data.watched,
watchers_count: result.data.watchers_count,
praises_count: result.data.praises_count,
forked_count: result.data.forked_count,
defaultBranch: result.data.default_branch
})
}
this.setState({
projectDetail: result.data,
project_id: result.data.project_id,
isManager: result.data.permission && (result.data.permission === "Manager" || result.data.permission === "Admin" || result.data.permission === "Owner"),
isReporter: result.data.permission && result.data.permission === "Reporter",
isDeveloper: result.data.permission && result.data.permission === "Developer",
http_url: result.data.clone_url,
praised: result.data.praised,
watched: result.data.watched,
watchers_count: result.data.watchers_count,
praises_count: result.data.praises_count,
forked_count: result.data.forked_count,
defaultBranch: result.data.default_branch
})
}
}).catch((error) => { })
}
@ -464,7 +484,7 @@ class Detail extends Component {
const url = `/${owner}/${projectsId}/forks.json`;
axios.post(url).then(result => {
if (result && result.data.status === 0) {
if(result.data.message === "fork失败你已拥有了这个项目"){
if (result.data.message === "fork失败你已拥有了这个项目") {
this.props.history.push(`/${current_user && current_user.login}/${projectsId}`);
return;
}
@ -490,7 +510,7 @@ class Detail extends Component {
axios.post(url).then(result => {
if (result && result.data && result.data.status === 0) {
this.setState({
secondSync:true
secondSync: true
})
this.canvasChannel(true);
} else {
@ -527,10 +547,9 @@ class Detail extends Component {
let pathname = checkPathname(projectsId, owner, url);
const { state } = this.props.history.location;
const common = {
getDetail: this.getDetail,
getBanner:this.getBanner,
getBanner: this.getBanner,
changeOpenDevops: this.changeOpenDevops,
defaultBranch
}
@ -549,7 +568,7 @@ class Detail extends Component {
<Link to={`/${owner}/${projectsId}`} className="projectN mt6">{projectDetail && projectDetail.name}</Link>
</div>
{projectDetail && projectDetail.private && <span className="privateTag mt6">私有</span>}
{ !platform && <span className="privateTag red mt6">只读</span> }
{!platform && <span className="privateTag red mt6">只读</span>}
</AlignTop>
<div className="mt8">
{
@ -718,7 +737,7 @@ class Detail extends Component {
(props) => (<Setting {...this.props} {...props} {...this.state} {...common} />)
}
></Route>
{/*修改里程碑*/}
<Route path="/:owner/:projectsId/milestones/:meilid/edit"
render={
@ -758,18 +777,18 @@ class Detail extends Component {
{/* 修改详情 edit*/}
<Route path="/:owner/:projectsId/issues/:orderId/edit"
render={
(props) => (<OrderupdateDetail {...this.props} {...props} {...this.state} {...common} form_type={"edit"}/>)
(props) => (<OrderupdateDetail {...this.props} {...props} {...this.state} {...common} form_type={"edit"} />)
}
></Route>
{/* 复制详情 copyetail*/}
<Route path="/:owner/:projectsId/issues/:orderId/copyetail"
render={
(props) => (<OrderupdateDetail {...this.props} {...props} {...this.state} {...common} form_type={"copy"}/>)
(props) => (<OrderupdateDetail {...this.props} {...props} {...this.state} {...common} form_type={"copy"} />)
}
></Route>
{/* 任务详情 */}
<Route path="/:owner/:projectsId/issues/:orderId"
{/* 任务详情 */}
<Route path="/:owner/:projectsId/issues/:orderId"
render={
(props) => (<OrderDetail {...this.props} {...this.state} {...props} {...common} />)
}
@ -802,6 +821,12 @@ class Detail extends Component {
(props) => (<UpdateMerge {...this.props} {...props} {...this.state} {...common} />)
}
></Route>
{/* review */}
<Route path="/:owner/:projectsId/pulls/:mergeId/review"
render={
(props) => (<Review {...this.props} {...props} {...this.state} {...common} />)
}
></Route>
<Route path="/:owner/:projectsId/pulls/:mergeId"
render={
(props) => (<MessageCount {...this.props} {...props} {...this.state} {...common} />)
@ -861,6 +886,13 @@ class Detail extends Component {
(props) => (<CoderDepot {...this.props} {...props} {...this.state} {...common} />)
}
></Route>
{/* 邀请 */}
<Route path="/:owner/:projectsId/invite"
render={
(props) => (<Invite {...this.props} {...props} {...this.state} {...common} />)
}
></Route>
<Route path="/:owner/:projectsId/:subIndex"
render={
(props) => (<CoderRootIndex {...this.props} {...props} {...this.state} {...common} />)

View File

@ -293,6 +293,12 @@ class MessageCount extends Component {
)
}
codeReview=()=>{
const {history,match}=this.props;
const { projectsId, mergeId , owner } =match.params;
history.push(`/${owner}/${projectsId}/pulls/${mergeId}/review`);
}
render() {
const { projectsId, mergeId , owner } = this.props.match.params;
@ -449,6 +455,18 @@ class MessageCount extends Component {
编辑
</Button>
)}
{operate && (
<Button
type="blue"
ghost
className="ml20"
onClick={this.codeReview}
>
代码评审
</Button>
)}
{operate && (
<Button
type="danger"

View File

@ -117,6 +117,11 @@ form .ant-cascader-picker, form .ant-select {
border:1px solid #28BD6C;
color: #28BD6C;
}
/* 绿色按钮-type="blue" */
.ant-btn.ant-btn-blue{
border:1px solid #466aff;
color: #466aff;
}
.copyTab{
min-width: 370px;
padding:0px 18px 22px 18px;

View File

@ -19,7 +19,7 @@ function CloudIDE({download_url}) {
if (commands) {
await commands.executeCommand(
'ChangeThemePlugin.changeTheme',
v ? 'ide-light' : 'ide-dark'
v ? 'opensumi-light' : 'opensumi-dark'
);
}
}
@ -34,9 +34,9 @@ function CloudIDE({download_url}) {
plugins: [changeThemePlugin],
workspaceDir: `${group}/${repo}`,
defaultPreferences: {
// ide-dark
// 'general.theme': 'ide-light',
'general.theme': 'ide-light',
// opensumi-dark
// 'general.theme': 'opensumi-light',
'general.theme': 'opensumi-light',
//
'editor.forceReadOnly': true,
//

View File

@ -0,0 +1,55 @@
module.exports = {
extension: {
publisher: 'cloud-ide-ext',
name: 'editor-plugin-blame',
version: '0.2.5',
},
packageJSON: {
name: 'editor-plugin-blame',
publisher: 'cloud-ide-ext',
version: '0.2.5',
repository: {
type: 'git',
url: 'http://code.alipay.com/yxy167584/editor-plugin-blame.git',
},
displayName: 'editor-plugin-blame',
description: ' ',
activationEvents: ['*'],
kaitianContributes: {
workerMain: './out/worker/index.js',
},
contributes: {
commands: [
{
command: 'code.blame.toggleBlame',
title: '查看blame',
},
{
command: 'code.blame.acrToggleBlame',
title: 'blame',
},
{
command: 'code.blame.linktocommit',
title: 'hover详情跳转',
},
],
views: {},
menus: {
'editor/title': [
{
command: 'code.blame.acrToggleBlame',
type: 'checkbox',
group: 'navigation',
toggledWhen: 'acr_blame_context',
when: 'resourceScheme =~ /^git$|^diff$/',
},
],
},
workerMain: './out/worker/index.js',
},
},
pkgNlsJSON: {},
nlsList: [],
extendConfig: {},
webAssets: ['package.json', 'out/worker/index.js'],
};

View File

@ -0,0 +1,72 @@
import { DependencyList, useEffect, useState, useMemo } from 'react';
// @ts-ignore
import { useEventEmitter, usePrevious } from 'ahooks';
// @ts-ignore
import sha1 from 'sha1';
// @ts-ignore
import differenceBy from 'lodash/differenceBy';
export function useRequest(
factory,
options
) {
const { deps = [], initial, ready = true } = options || {};
const [val, setVal] = useState(initial);
useEffect(() => {
if (!ready) return;
let cancel = false;
const promise = factory();
if (promise === undefined || promise === null) return;
promise
.then((val) => {
if (!cancel) {
setVal(val);
}
})
.catch((err) => {
console.error(err);
});
return () => {
cancel = true;
};
}, [...deps, ready]);
return val;
}
export function useFileReadMarkChange$(
diffs,
readMarks
) {
const fileReadMarkChange$ = useEventEmitter();
const filePathShaMap = useMemo(() => {
const map = new Map();
for (const diff of diffs) {
map.set(sha1(diff.name||diff.newPath), diff.name||diff.newPath);
}
return map;
}, [diffs]);
const prevReadMarks = usePrevious(readMarks);
useEffect(() => {
const changesA = differenceBy(
prevReadMarks,
readMarks,
(readMark) => readMark.filePathSha
);
const changesB = differenceBy(
readMarks,
prevReadMarks,
(readMark) => readMark.filePathSha
);
for (const change of [...changesA, ...changesB]) {
const filePath = filePathShaMap.get(change.filePathSha);
if (filePath) {
fileReadMarkChange$.emit(filePath);
}
}
}, [readMarks, filePathShaMap]);
return fileReadMarkChange$;
}

View File

@ -0,0 +1,273 @@
import React, { useEffect, useState, useMemo } from 'react';
// import { default as AntcodeCR } from 'acr';
import { ACR } from 'alex';
import { Button, Switch } from 'antd';
// blame worker
import CodeBlame from './extensions/alex-ext-public.editor-plugin-blame.js';
// plugin
import CodeBlamePlugin, { ExtensionCommand } from './plugins/code-blame.plugin';
import {
Provider,
useGlobal,
usePr,
useNote,
useReadMark,
useAcr,
useSetting,
} from './model';
import {
DiscussionItem,
Commenting,
Menubar,
AnnotationEntry,
PRMoreActionLinks,
} from './mock/component/index';
import { projectService } from './mock/project.service';
import { lsifService } from './mock/lsif.service';
import { useFileReadMarkChange$ } from './hooks';
import { repoService } from './mock/repo.service';
function findKey(obj) {
// console.log('...............')
for (const key in obj) {
if (key == 'path') {
console.log('path');
console.log(obj);
}
let type = Object.prototype.toString.call(obj[key]);
if (type.includes('Array')) {
for (const item of obj[key]) {
findKey(obj[key]);
}
} else if (type.includes('Object')) {
findKey(obj[key]);
}
}
}
const CodeReview = (props) => {
const { match: { params: { owner, projectsId, mergeId } }, projectDetail,current_user } = props;
const [visible, setVisible] = React.useState(true);
const [count, setCount] = React.useState(0);
const [isFullscreen, setFullscreen] = React.useState(false);
const { locale, setLocale, gbk, setGBK } = useSetting();
const { commentPack } = useNote();
const { project, user, setCurrent_user, setProjectDetail } = useGlobal();
const { pr } = usePr();
console.log('projectDetail');
console.log(projectDetail);
console.log(current_user);
console.log('project');
console.log(project);
console.log(user);
console.log('user');
const {
getFileReadStatus: _getFileReadStatus,
markFileAsRead,
markFileAsUnread,
readMarks,
} = useReadMark();
// todo
// const getFileReadStatus = usePersistFn(_getFileReadStatus);
const getFileReadStatus = _getFileReadStatus;
const {
diffsPack,
getDiffById,
getFileContent,
IDEMode,
toggleViewerType,
annotationPacks,
} = useAcr();
const fileReadMarkChange$ = useFileReadMarkChange$(
diffsPack && diffsPack.diffs || [],
readMarks
);
//
const [pluginActivated, setPluginActivated] = useState(false);
const blamePlugin = useMemo(() => {
return new CodeBlamePlugin(
() => setPluginActivated(true),
(commitId) =>
window.open(
`/${owner}/${projectsId}/commit/${commitId}`
),
// blame
(projectId, commitId, path) =>
repoService.getCodeBlame(projectId, commitId, path)
);
}, []);
useEffect(() => {
if (!pluginActivated) {
return;
}
const projectData = {
projectId: projectsId,
prevSha: diffsPack.fromVersion && diffsPack.fromVersion.headCommitSha || diffsPack.toVersion.baseCommitSha,
nextSha: diffsPack.toVersion.headCommitSha,
};
blamePlugin.commands && blamePlugin.commands.executeCommand(
ExtensionCommand.setProjectData,
projectData
);
}, [pluginActivated, diffsPack]);
if (!diffsPack) {
return null;
}
const propsMock = {
//
noteIdToReplyIdSet: commentPack.noteIdToReplyIdSet,
//
addLineNum: diffsPack.addLineNum,
//
deleteLineNum: diffsPack.delLineNum,
// sha
prevSha:
diffsPack.fromVersion && diffsPack.fromVersion.headCommitSha || diffsPack.toVersion.baseCommitSha,
// sha
nextSha: diffsPack.toVersion.headCommitSha,
toggleViewerType,
DiscussionItem,
Commenting,
getFileContent,
lineToNoteIdSet: commentPack.lineToNoteIdSet,
noteIdToNote: commentPack.noteIdToNote,
noteUpdateFlag: commentPack.updateFlag,
getDiffById,
diffs: diffsPack.diffs,
latestCommitSha: pr.diff.headCommitSha,
projectId: project.id,
projectPath: project.pathWithNamespace,
pullRequestId: pr.id,
pr,
//
getLanguages: () =>
projectService
.getLanguages(project.id, {
aggBy: 'file_extension',
//
orderBy: 'count',
size: 20,
})
.then((res) => res && Object.keys(res)),
getFileReadStatus,
fileReadMarkChange$,
markFileAsRead,
markFileAsUnread,
bulkChangeFiles: (actions, header) => {
let files = [];
for (const item of actions) {
files.push({
action_type: item.actionType,
content: item.content,
encoding: item.encoding,
file_path: item.filePath,
});
}
let data = {
files: files,
// author_email: header.authorEmail,
// author_name: header.authorName,
// author_timeunix: 1658214400,
committer_email: header.authorEmail,
committer_name: header.authorName,
// committer_timeunix: 1658214400,
branch: header.branch,
message: header.commitMessage,
};
console.log('bulkChangeFiles');
console.log(actions);
console.log(header);
console.log(data);
projectService.bulkChangeFiles(projectsId, owner, data)
},
//
Menubar: () => (
<Menubar
initialFullscreen={isFullscreen}
handleFullscreenChange={setFullscreen}
toggleViewerType={toggleViewerType}
logFullScreen={(bool) => console.log('>>>logFullScreen', bool)}
/>
),
user,
lsifService,
defaultEncoding: project.encoding,
//
encoding: gbk ? 'gbk' : 'utf-8',
//
setEncoding: (val) => {
setGBK(val === 'gbk');
},
locale,
annotations: annotationPacks,
AnnotationEntry,
PRMoreActionLinks,
//
isFullscreen,
appConfig: {
//
plugins: [blamePlugin],
//
extensionMetadata: [CodeBlame],
},
};
const IDEContainerStyle = {
position: isFullscreen ? 'fixed' : 'static',
left: 0,
top: 0,
width: '100%',
height: isFullscreen ? '100vh' : 'calc(100vh - 72px)',
zIndex: 1100,
};
console.log('propsMock:');
console.log(propsMock);
// findKey(propsMock);
return (
<div style={{ height: '100%' }}>
{/* <div className="controller">
{!IDEMode && (
<>
IDE 模式: <Switch checked={IDEMode} onChange={toggleViewerType} />
</>
)}
</div> */}
{/* <div className="pr-head">
<div>{pr.description}</div>
<div>
评审人
{pr.review && pr.review.reviewers && pr.review.reviewers.map((r) => (
<span style={{ marginRight: 4 }} key={r.id}>
{r.name}
</span>
))}
<span style={{ marginRight: 24 }}></span>
合并人{pr.assignee && pr.assignee.name}
</div>
</div> */}
{IDEMode && (
<div style={IDEContainerStyle}>
{visible && <ACR {...propsMock} key={count} />}
</div>
)}
</div>
);
};
const CodeReviewP = (props) => {
return (
<Provider {...props}><CodeReview {...props} /></Provider>
)
}
export default CodeReviewP;

View File

@ -0,0 +1,5 @@
export const group = 'Gitlink';
export const repo = 'forgeplus';
export const prIid = '2';
export const project = `${group}/${repo}`;

View File

@ -0,0 +1,67 @@
import { request } from './request/index';
export const apiService = {
get(
url,
query,
options = {}
) {
return request(
url,
{
method: 'GET',
params: query,
},
options
);
},
post(
url,
query,
body,
options= {}
) {
return request(
url,
{
method: 'POST',
params: query,
data: body,
},
options
);
},
put(
url,
query,
body,
options= {}
) {
return request(
url,
{
method: 'PUT',
params: query,
data: body,
},
options
);
},
delete(
url,
query,
options = {}
) {
return request(
url,
{
method: 'DELETE',
params: query,
},
options
);
},
};

View File

@ -0,0 +1,538 @@
import React, {
FC,
MutableRefObject,
memo,
useEffect,
useRef,
useState,
useCallback,
Fragment,
} from "react";
import {
message,
Dropdown,
Icon,
Menu,
Button,
Tooltip,
Avatar,
Modal,
Switch,
Input,
Popconfirm,
Checkbox,
} from "antd";
// @ts-ignore
import { usePersistFn } from "ahooks";
import "./style.module.less";
import { useAcr, useNote, useGlobal } from "../../model";
const AnnotationStatus ={
Init : 'Init',
Ignore : 'Ignore',
Confirm : 'Confirm',
FalsePositive : 'FalsePositive',
}
const noteType= {
comment : 'Comment',
problem : 'Problem',
}
export const Commenting = (props) => {
const { user } = useGlobal();
const { addComment } = useNote();
const { toVersion } = useAcr();
console.log('Commenting--props');
console.log(props);
const submit = usePersistFn(async (note, type) => {
try {
await addComment({
note,
diffId: toVersion.id,
lineCode: props.lineCode,
discussionId: props.discussionId,
path: props.path,
type,
});
props.onClose&&props.onClose();
} catch (e) {
console.error(e);
}
});
const { replyNote } = props;
const [note, setNote] = useState("");
const [isProblem, setIsProblem] = useState(false);
const onSubmit = useCallback(() => {
const submitNote = replyNote ? insertReplyNote(note, replyNote) : note;
submit(submitNote, isProblem ? noteType.problem : noteType.comment);
}, [note, replyNote, isProblem]);
function insertReplyNote(baseNote, replyNote) {
if (replyNote&&replyNote.note) {
const replyLine = replyNote.note.split("\n");
const blockquoteNote = [
`<!-- reply id="${replyNote.id}" -->`,
...replyLine,
]
.map((note) => `> ${note}`)
.join("\n");
return blockquoteNote + "\n\n" + baseNote;
} else {
return baseNote;
}
}
return (
<div className="arc-container">
<div
className={`arc-commenting-container ${props.noPadding ? "no-padding" : ""
}`}
>
<div className="avatar">
<Tooltip title={user.name}>
<Avatar src={user.avatarUrl} size={24} />
</Tooltip>
</div>
<Input.TextArea
autoFocus
placeholder="请输入描述信息"
value={note}
onChange={(e) => setNote(e.target.value)}
style={{ height: 150 }}
/>
<div className="action">
<div>
<Button
type="primary"
onClick={onSubmit}
disabled={!note.trim()}
style={{ marginRight: 8 }}
>
开始评审
</Button>
{note.trim() ? (
<Popconfirm
title="你确定要取消?"
okText="确定"
cancelText="取消"
onConfirm={props.onClose}
>
<Button>取消</Button>
</Popconfirm>
) : (
<Button onClick={props.onClose}>取消</Button>
)}
</div>
<div>
{replyNote && (
<div className="reply-note">Reply to {replyNote.author.name}</div>
)}
</div>
</div>
</div>
</div>
);
};
export const DiscussionItem = memo((props) => {
const { noteId } = props;
const { commentPack } = useNote();
const [commenting, setCommenting] = useState(false);
const [commentReply, setCommentReply] = useState();
const replyIdSet = commentPack.noteIdToReplyIdSet.get(noteId);
const replyIds = replyIdSet
? Array.from(replyIdSet).sort((a, b) => a - b)
: null;
const pending = (function () {
if (!commentPack.hasPendingReview) {
return false;
}
const reviewId = commentPack.noteIdToReviewId.get(noteId);
if (!reviewId) return false;
const review = reviewId ? commentPack.reviewIdToReview.get(reviewId) : null;
if (!review) return false;
return review.pending;
})();
const hasPendingNote = (function () {
if (pending) return true;
if (replyIds) {
for (const replyId of replyIds) {
const reviewId = commentPack.noteIdToReviewId.get(replyId);
if (reviewId && commentPack.reviewIdToReview.get(reviewId)&&commentPack.reviewIdToReview.get(reviewId).pending) {
return true;
}
}
}
return false;
})();
const handleReply = (replyNote) => {
if (replyNote) {
setCommentReply(replyNote);
}
setCommenting(true);
};
useEffect(() => {
if (!commenting) {
setCommentReply(undefined);
}
}, [commenting]);
const note = commentPack.noteIdToNote.get(noteId);
if (!note) return null;
return (
<div className="arc-container">
<div>
{note.type === noteType.problem && (
<Fragment>
{note.state === "resolved" && !pending && "已解决"}
{note.state === "opened" && !pending && "待解决"}
{pending && "需要回应"}
</Fragment>
)}
</div>
<div style={{ paddingLeft: 16 }}>
<div>{note.author.name} 发表评论</div>
<div>{note.note}</div>
</div>
{replyIds && (
<div style={{ paddingLeft: 32 }}>
{replyIds.map((replyId) => {
const note = commentPack.noteIdToNote.get(replyId);
if (!note) return null;
return (
<div key={replyId}>
<div>{note.author.name} 发表评论</div>
<div>{note.note}</div>
</div>
);
})}
</div>
)}
</div>
);
});
export const Menubar = memo((props) => {
const [isFullscreen, setFullscreen] = React.useState(
props.initialFullscreen || false
);
const handleChangeFullscreen = (isFullscreen) => {
setFullscreen(isFullscreen);
if (isFullscreen) {
message.info("全屏模式支持直接使用 ↑/↓ 切换变更文件");
}
props.handleFullscreenChange(isFullscreen);
props.logFullScreen(isFullscreen);
};
useEffect(() => {
if (isFullscreen) {
document.body.style.overflow = "hidden";
}
return () => {
document.body.style.overflow = "";
};
}, [isFullscreen]);
const { project, pr } = useGlobal();
const { versions, fromVersion, toVersion, updateQuery } = useAcr();
const idToVerbose = new Map();
idToVerbose.set(0, "Base Version");
versions.forEach((version, index) => {
if (index === 0) {
idToVerbose.set(version.id, `Latest Version`);
} else {
idToVerbose.set(version.id, `Version ${versions.length - index}`);
}
});
const fromId = fromVersion&&fromVersion.id || 0;
const toId = toVersion&&toVersion.id || 0;
const filterCleared = fromId === 0 && toId === versions[0]&&versions[0].id;
function renderMenuItem(version, selectedId) {
const verbose = idToVerbose.get(version.id);
return (
<Menu.Item
className={`arc-menuItem ${version.id === selectedId ? "selected" : ""
}`}
key={version.id}
>
<div className='headRow'>
<span>{verbose}</span>
<span>{version.headCommitSha.slice(0, 8)}</span>
</div>
<div className='desc'>
{ }提交包含 {version.commitsCount}{version.filesCount} 份文件变更
</div>
</Menu.Item>
);
}
const menuFrom = (
<Menu
onClick={(e) => {
const val = parseInt(e.key);
updateQuery({
from: val === 0 ? undefined : val,
});
}}
>
{versions.slice(1).map((version) => renderMenuItem(version, fromId))}
<Menu.Item
key={0}
className={`${'menuItem'} ${fromId === 0 ? 'selected' : ""}`}
>
Base
</Menu.Item>
</Menu>
);
const menuTo = (
<Menu
onClick={(e) => {
updateQuery({
to: parseInt(e.key),
});
}}
>
{versions
.slice(0, versions.length)
.map((version) => renderMenuItem(version, toId))}
</Menu>
);
return (
<div className="arc-menubar">
<div className={'group'}>
{versions.length !== 0 && (
<div className={'versionContainer'}>
<Dropdown overlay={menuFrom} trigger={["click"]}>
<div className={'versionItem'}>
<span>{idToVerbose.get(fromId)}</span>
<Icon type="down" />
</div>
</Dropdown>
<div>
<Icon type="arrow-right" />
</div>
<Dropdown overlay={menuTo} trigger={["click"]}>
<div className={'versionItem'}>
<span>{idToVerbose.get(toId)}</span>
<Icon type="down" />
</div>
</Dropdown>
{!filterCleared && (
<div>
<a
onClick={() => {
updateQuery({
from: undefined,
to: undefined,
});
}}
>
重置
</a>
</div>
)}
</div>
)}
</div>
<div className={'group'}>
{/* {isFullscreen ? null : (
<div className={'switchContainer'}>
<span className={'ideLabel'}>IDE 模式</span>
<Tooltip
placement="top"
title="提供 IDE 代码浏览体验、完善的语言服务和高效的代码编辑"
>
<Switch checked onChange={props.toggleViewerType} />
</Tooltip>
</div>
)} */}
{isFullscreen ? (
<Icon
type="fullscreen-exit"
onClick={() => handleChangeFullscreen(false)}
/>
) : (
<Tooltip title="全屏">
<Icon
type="fullscreen"
onClick={() => handleChangeFullscreen(true)}
/>
</Tooltip>
)}
</div>
</div>
);
});
export const AnnotationEntry = memo((props) => {
const { annotation, checkSuite } = props;
//
const anchorOffsetRef = useRef(null);
const hash = `#annotation_${annotation.id}`;
const hashActive = hash === location.hash;
useEffect(() => {
setTimeout(() => {
if (hashActive) {
anchorOffsetRef.current&&anchorOffsetRef.current.scrollIntoView(true);
}
}, 500);
});
const [isIgnore, setIsIgnore] = useState(
annotation.feedBackStatus === AnnotationStatus.Ignore
);
return (
<div className="arc-container">
<div
ref={anchorOffsetRef}
id={`annotation_${annotation.id}`}
className="arc-annotationContainer"
>
<div className={'codeLine'}>
<div>{annotation.level}</div>
<div className={'line'}>
{annotation.startLine === annotation.endLine ? (
<span>
{annotation.endLine}
行代码分析
</span>
) : (
<span>
{annotation.startLine}{annotation.endLine}
行代码分析
</span>
)}
</div>
</div>
<div className={'botRow'}>
<Avatar
alt={checkSuite.service.nameShow}
size={20}
src="https://gw-office.alipayobjects.com/bmw-prod/a9596840-928c-4603-a865-b24373ab4b4d.png"
/>
<div>
<div className={'strong'}>{checkSuite.service.nameShow}</div>
</div>
</div>
<div className={'content'}>
<p>{annotation.title}</p>
<div>{annotation.message}</div>
</div>
<div className={'detail'}>
<div>
{annotation.bugId ? (
isIgnore ? (
<Button type="primary" style={{ marginRight: 8 }}>
取消忽略
</Button>
) : (
<Fragment>
<Button style={{ marginRight: 8 }}>忽略</Button>
<Button style={{ marginRight: 8 }}>误报</Button>
<Button>确认</Button>
</Fragment>
)
) : null}
</div>
<div>
<a
className={'halfPlainA'}
href={`https://codeinsightapi.alipay.com/api/v1/describe?bug_type=${annotation.bugType}&bug_id=${annotation.bugId}`}
target="_blank"
>
查看问题详情 <Icon type="double-right" />
</a>
</div>
</div>
</div>
</div>
);
});
export const PRMoreActionLinks = ({
setVisible,
}) => {
const { project, pr } = useGlobal();
const [checkoutBranchVisible, setCheckoutBranchVisible] = useState(false);
return (
<Fragment>
<div
className="arc-checkoutBranch"
onClick={() => {
setCheckoutBranchVisible(true);
setVisible(false);
}}
>
检出分支
</div>
<a
className="arc-flexA"
download
href={`http://code.test.alipay.net/${project.pathWithNamespace}/pull_requests/${pr.iid}.patch`}
>
<span onClick={() => setVisible(false)}>下载 patch 文件</span>
</a>
<a
className="arc-flexA"
download
href={`http://code.test.alipay.net/${project.pathWithNamespace}/pull_requests/${pr.iid}.diff`}
>
<span onClick={() => setVisible(false)}>下载 diff 文件</span>
</a>
<Modal
title="检出,评审,并在本地执行合并"
visible={checkoutBranchVisible}
footer={null}
onCancel={() => setCheckoutBranchVisible(false)}
>
<div className="arc-checkout">
<p>
<span className="fw">Step 1.</span> Fetch and check out the branch
for this pull request
</p>
<pre>
git fetch origin
<br />
{`git checkout -b ${pr.sourceBranch} origin/${pr.sourceBranch}`}
</pre>
<p>
<span className="fw">Step 2.</span> Review the changes locally
</p>
<p>
<span className="fw">Step 3.</span> Merge the branch and fix any
conflicts that come up
</p>
<pre>
git fetch origin
<br />
{`git checkout origin/${pr.targetBranch}`}
<br />
git merge --no-ff master
</pre>
<p>
<span className="fw">Step 4.</span> Push the result of the merge
</p>
<pre>{`git push origin ${pr.targetBranch}`}</pre>
</div>
</Modal>
</Fragment>
);
};

View File

@ -0,0 +1,197 @@
.arc-container {
border: solid 1px #eee;
border-left: none;
border-right: none;
}
.arc-menubar {
display: flex;
align-items: center;
justify-content: space-between;
.group {
display: flex;
align-items: center;
> * {
margin-right: 12px;
transition: 300ms all ease-in-out;
&:last-child {
margin-right: 0;
}
}
}
.versionContainer {
display: flex;
align-items: center;
> *:not(:last-child) {
margin-right: 2px;
}
}
.versionItem {
cursor: pointer;
padding: 4px 6px;
.anticon {
margin-left: 4px;
}
}
.switchContainer {
display: flex;
align-items: center;
}
.ideLabel {
line-height: 1;
margin-right: 8px;
}
}
.arc-menuItem {
background: #ffffff;
width: 350px;
padding-top: 8px;
padding-bottom: 8px;
&.selected {
background: #f0f5ff;
}
.headRow {
display: flex;
justify-content: space-between;
}
.desc {
margin-top: 2px;
color: rgba(0, 0, 0, 0.45);
}
}
.arc-annotationContainer {
border-bottom: solid 1px #eee;
&:last-child {
border-bottom: none;
}
.codeLine {
display: flex;
align-items: center;
}
.line {
padding-top: 8px;
padding-left: 8px;
font-size: 12px;
&.active {
color: rgba(0, 0, 0, 0.45);
}
}
.botRow {
padding: 8px 12px 0 16px;
display: flex;
align-items: center;
> *:not(:first-child) {
margin-left: 8px;
}
}
.content {
padding: 8px 12px 12px 48px;
p {
margin-bottom: 8px;
}
}
.strong {
color: rgba(0, 0, 0, 0.85);
}
.detail {
height: 50px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
padding: 0 21px;
border-top: 1px solid #ebedf0;
}
.halfPlainA {
color: inherit;
&:hover {
color: #2f54eb;
}
}
}
.arc-checkoutBranch {
font-size: 14px;
color: #2f54eb;
cursor: pointer;
line-height: 32px;
}
.arc-flexA {
display: flex !important;
justify-content: space-between;
align-items: center;
line-height: 32px;
color: #2f54eb;
i {
color: #2f54eb;
}
&:hover {
color: #2f54eb;
}
}
.arc-checkout {
pre {
margin-bottom: 8px;
padding: 8px 12px;
background-color: #fbfbfb;
border: 1px solid #ebedf0;
border-radius: 4px;
}
}
.arc-commenting-container {
position: relative;
padding: 8px 12px 0 44px;
&.no-padding {
padding: 0 0 0 32px;
.avatar {
top: 2px;
left: 0;
}
}
.avatar {
position: absolute;
top: 8px;
left: 12px;
}
.action {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 0;
.reply-note {
padding: 1px 5px;
color: #8c8c8c;
line-height: 20px;
background-color: #fafafa;
}
}
}

View File

@ -0,0 +1,68 @@
import { apiService } from './api.service';
// 语言服务接口
export const lsifService = {
async lsifExists(projectPath, sha) {
return !!(await apiService.post(
`/webapi/projects/${encodeURIComponent(
projectPath
)}/repository/lsif/exists`,
{
sha,
}
)) ;
},
async lsifHover(
projectId,
data
) {
return (await apiService.post(
`/webapi/projects/42422/repository/lsif/hover`,
undefined,
{
...data,
method: 'hover',
}
))
},
async lsifDefinitions(
projectId,
data
) {
return (await apiService.post(
`/webapi/projects/42422/repository/lsif/definitions`,
undefined,
data
))
},
async lsifReferences(
projectId,
data
) {
return (await apiService.post(
`/webapi/projects/42422/repository/lsif/references`,
undefined,
data,
{
disableBodyConvert: true,
}
))
},
async lsifReferencesV2(
projectId,
data
) {
return (await apiService.post(
`/webapi/projects/42422/repository/lsif/reference/v2`,
undefined,
data,
{
disableBodyConvert: true,
}
))
},
};

View File

@ -0,0 +1,186 @@
import { apiService } from './api.service';
import { calcChangeLineNum } from './utils/calc-change-line-num';
import { underscoreToCamelcase } from './utils/camelcase-convert';
import axios from 'axios';
export const prService = {
async getPRByIid(projectsId, owner, mergeId) {
const res = await axios.get(`/v1/${owner}/${projectsId}/pulls/${mergeId}.json`);
let data = underscoreToCamelcase(res.data);
let newData = {
...data,
checkSuites: [],
iid: data.index,
sourceBranch: data.base,
sourceProjectId: projectsId,
// state: "opened",
// targetBranch: "master",
// targetProjectId: 42422,
diff: {
baseCommitSha: data.baseCommitSha,
commitsCount: data.commitNum,
headCommitSha: data.headCommitSha,
startCommitSha: data.mergeBase,
// delLineNum: 379,
// addLineNum: 753,
// createdAt: "2021-05-20T14:27:46+0800",
// filesCount: 22,
// overflow: false,
// startCommitSha: "2566c6dec7756e51f7f16267d9a2a63116ac015b",
// updatedAt: "2021-05-20T14:27:46+0800",
},
};
console.log('getPRByIid');
console.log(newData);
return newData;
return (await apiService.get(
`/webapi/projects/42422/get_pull_request_by_iid`,
));
},
async getDiffVersions(projectsId, owner, mergeId) {
const res = await axios.get(`/v1/${owner}/${projectsId}/pulls/${mergeId}/versions.json`);
return Array.isArray(res.data.versions) ? underscoreToCamelcase(res.data.versions) : []
return (await apiService.get(
`/api/v3/projects/42422/pull_requests/13055/diffs`
))
},
async getDiffs(
projectId,
owner,
from,
to,
) {
const res = await axios.get(`/v1/${owner}/42422/compare.json`, { params: { from, to } })
console.log(res);
const changeLineNum = calcChangeLineNum(res.diffs);
return {
...res,
...changeLineNum,
};
},
async getDiffOverviews(projectsId, owner, mergeId, versionId) {
const res = await axios.get(`/v1/${owner}/${projectsId}/pulls/${mergeId}/versions/${versionId}/diff.json`);
let files = res.data.files;
console.log('getDiffOverviews');
console.log(res.data.files);
for (const [i, item] of files.entries()) {
// item.aMode= "100644";
// item.bMode= "0";
// item.markAsRead= true;
// item.tooLarge= false;
// item.updatedAfterRead= false;
item.compareDiffId = versionId;
item.id = item.name;
item.addLineNum = item.addition;
item.binaryFile = item.is_bin;
item.delLineNum = item.deletion;
item.deletedFile = item.is_deleted;
item.newFile = item.is_created;
item.newPath = item.name;
item.oldPath = item.oldname;
item.renamedFile = item.is_renamed;
}
return Array.isArray(res.data.files) ? underscoreToCamelcase(res.data.files) : []
return (await apiService.get(
`/webapi/projects/42422/pull_requests/13055/diffs/31918/changes_overview`
))
},
async getCommentPack(
projectId,
prId,
lastFetchedAt
) {
return (await apiService.get(
'/webapi/projects/42422/pull_requests/13055/comments',
{
lastFetchedAt,
}
))
},
async editPRComment(
projectId,
prId,
noteId,
payload
) {
return (await apiService.put(
`/api/v3/projects/42422/pull_requests/13055/comments/${noteId}`,
undefined,
payload
))
},
async getDiffById(
projectsId, owner, mergeId, versionId,
options = {}
) {
const res = await axios.get(`/v1/${owner}/${projectsId}/pulls/${mergeId}/versions/${versionId}/diff.json`, { params: options });
// console.log('getDiffById');
// console.log(res.data);
// return underscoreToCamelcase(res.data);
return (await apiService.get(
`/webapi/projects/42422/pull_requests/13055/diffs/31918/changes/1079`,
options
));
// return (await apiService.get(
// `/webapi/projects/42422/pull_requests/13055/diffs/${versionId}/changes/${diffId}`,
// options
// ))
},
async getFileReadMarks(projectId, prId) {
return (await apiService.get(
`/api/v3/projects/42422/pull_requests/13055/diffs/mark_files`
))
},
async markFileAsRead(projectId, prId, filePathSha) {
return (await apiService.put(
`/webapi/projects/42422/pull_requests/13055/diffs/mark_file_as_read`,
{
filePathSha,
}
))
},
async markFileAsUnread(projectId, prId, filePathSha) {
return (await apiService.put(
`/webapi/projects/42422/pull_requests/13055/diffs/mark_file_as_unread`,
{
filePathSha,
}
))
},
async addComment(projectId, prId, data) {
return (await apiService.post(
`/webapi/projects/42422/pull_requests/13055/comments`,
undefined,
data
))
},
async createReview(projectId, prId) {
return (await apiService.post(
`/api/v3/projects/42422/pull_requests/13055/reviews`
))
},
async commitReview(projectId, prId, body) {
return await apiService.put(
`/api/v3/projects/42422/pull_requests/13055/reviews`,
undefined,
{
body,
}
);
},
};

View File

@ -0,0 +1,76 @@
import { apiService } from './api.service';
import { underscoreToCamelcase } from './utils/camelcase-convert';
import axios from 'axios';
export const projectService = {
async getUser() {
return apiService.get(`/api/v3/user`);
},
async getProject(namespace, projectName) {
return (await apiService.get(
`/webapi/projects/${namespace}/${projectName}/`
))
},
async getFileBlob(projectsId, owner,options={}) {
const res = await axios.get(`/${owner}/${projectsId}/sub_entries.json`,{params:options});
console.log(res.data);
return res.data.entries.content;
},
// async getFileBlob(
// projectId,
// sha,
// filepath,
// options= {}
// ) {
// return (await apiService.get(
// `/api/v3/projects/42422/repository/blobs/ab32441adfd6c3c381457717a42f19a7fdd6d59b`,
// {
// filepath,
// ...options,
// }
// ))
// // return (await apiService.get(
// // `/api/v3/projects/42422/repository/blobs/${encodeURIComponent(
// // sha
// // )}`,
// // {
// // filepath,
// // ...options,
// // }
// // ))
// },
async getLanguages(
projectId,
params
) {
return (await apiService.get(
`/api/v4/projects/42422/languages`,
params,
{
disableResponseConvert: true,
}
))
},
async bulkChangeFiles(
projectsId, owner,data
) {
const res = await axios.post(`/v1/${owner}/${projectsId}/contents/batc`,data);
console.log('bulkChangeFiles--data');
console.log(res);
// return (await apiService.post(
// `/api/v4/projects/42422/repository/files`,
// undefined,
// {
// actions,
// header,
// }
// ))
},
};

View File

@ -0,0 +1,88 @@
import { apiService } from './api.service';
import { underscoreToCamelcase } from './utils/camelcase-convert';
import axios from 'axios';
export const repoService = {
async getFileList(
projectId,
refName,
path,
withCommit
) {
return (await apiService.get(
`/webapi/projects/42422/repository/tree`,
{
refName,
path,
withCommit,
}
))
},
async getTreeEntry(projectId, refName, path) {
return (await apiService.get(
`/api/v3/projects/42422/repository/tree_entry`,
{
refName,
path,
}
))
},
// async getFileDetail(
// projectId,
// ref,
// filepath,
// charsetName
// ) {
// return await apiService.get(
// '/api/v3/projects/42422/repository/blobs/ab32441adfd6c3c381457717a42f19a7fdd6d59b',
// {
// filepath,
// charsetName,
// },
// {
// disableResponseConvert: true,
// getOriginalResponse: true,
// }
// );
// // return await apiService.get(
// // `/api/v3/projects/42422/repository/blobs/${encodeURIComponent(
// // ref
// // )}`,
// // {
// // filepath,
// // charsetName,
// // },
// // {
// // disableResponseConvert: true,
// // getOriginalResponse: true,
// // }
// // );
// },
async getCodeSymbols(projectId, ref, filepath) {
return await apiService.get(
`/api/v3/projects/42422/repository/file_symbols/${encodeURIComponent(
ref
)}/`,
{
filepath,
}
);
},
async getCodeBlame(projectId, sha, filePath) {
const res = await axios.get(`/v1/${owner}/${projectsId}/blame.json`);
console.log(res);
// return res.
return (await apiService.get(
`/api/v3/projects/42422/repository/blame`,
{
sha,
filePath,
}
))
},
};

View File

@ -0,0 +1,22 @@
import { mockService } from './mock';
import { underscoreToCamelcase } from '../utils/camelcase-convert';
export async function request(
url,
options,
extraOptions
) {
// @ts-ignore
const data = underscoreToCamelcase(mockService[url]);
console.log('request ==== ', url);
console.log(data)
if (data) {
return data;
} else {
return '文件展示内容只mock了 aaa/package.json';
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,32 @@
import { message as messageTip } from 'antd';
export function createApiError(message = '请求出错', response) {
const err = new Error(message);
err.isApiError = true;
err.response = response;
return err;
}
export function isApiError(e) {
return e&&e.isApiError;
}
/**
* 目前存在一些请求成功也报错的情况
* 返回值可能情况
* 1. success === true 此时只提示有 errorMessage 的情况
* 2. success === false 此时提示包括 message 的信息
* todo: 目前由于intl使用的是react component注入的方式这里暂时无法做到国际化后续等移除 konjac 之后采用全局api方式即可支持国际化
* @param responseData 返回值各种可能
*/
export function successErrorMessage(responseData) {
if (responseData) {
const errorMessage = responseData.error || responseData.errorMessage;
const message = errorMessage || responseData.message;
if (responseData.success === false && message) {
messageTip.error(message);
} else if (errorMessage) {
messageTip.error(errorMessage);
}
}
}

View File

@ -0,0 +1,20 @@
export function calcChangeLineNum(diffs) {
let addLineNum = 0;
let delLineNum = 0;
for (const diff of diffs) {
if (diff.tooLarge) {
continue;
}
const addition = diff.diff.match(/^\+/gm)&&diff.diff.match(/^\+/gm).length || 0;
const deletion = diff.diff.match(/^-/gm)&&diff.diff.match(/^-/gm).length || 0;
diff.addLineNum = addition;
diff.delLineNum = deletion;
addLineNum += addition;
delLineNum += deletion;
}
return {
addLineNum,
delLineNum,
};
}

View File

@ -0,0 +1,35 @@
export function underscoreToCamelcase(obj){
if (Array.isArray(obj)) {
return obj.map((item) => underscoreToCamelcase(item))
}
if (typeof obj !== 'object' || obj === null || obj === undefined) {
return obj;
}
const ret = {};
for (const key in obj) {
if (obj.hasOwnProperty && !obj.hasOwnProperty(key)) {
continue;
}
const newKey = key.replace(/_([a-z])/g, (m) => m[1].toUpperCase());
ret[newKey] = underscoreToCamelcase(obj[key]);
}
return ret
}
export function camelcaseToUnderscore(obj) {
if (Array.isArray(obj)) {
return obj.map((item) => camelcaseToUnderscore(item))
}
if (typeof obj !== 'object' || obj === null || obj === undefined) {
return obj;
}
const ret= {};
for (const key in obj) {
if (obj.hasOwnProperty && !obj.hasOwnProperty(key)) {
continue;
}
const newKey = key.replace(/([a-z][A-Z])/g, (m) => m[0] + '_' + m[1].toLowerCase());
ret[newKey] = camelcaseToUnderscore(obj[key]);
}
return ret
}

View File

@ -0,0 +1,2 @@
export const getLocale = () =>
document.cookie.indexOf('LOCALE=en_US') > -1 ? 'en-US' : 'zh-CN';

View File

@ -0,0 +1,8 @@
export function mapGetSet(map, key, fallback) {
let ret = map.get(key);
if (!ret) {
ret = fallback();
map.set(key, ret);
}
return ret;
}

View File

@ -0,0 +1,743 @@
import React, {
useState,
useMemo,
useRef,
useContext,
useEffect,
useCallback,
createContext,
createElement,
FC,
} from 'react';
import { getLocale } from './mock/utils/locale';
// @ts-ignore
import sha1 from 'sha1';
// @ts-ignore
import memoize from 'lodash/memoize';
import jCookie from 'js-cookie';
import { useRequest } from './hooks';
import { group, repo, prIid, project } from './meta';
import { prService } from './mock/pr.service.js';
import { projectService } from './mock/project.service.js';
import { mapGetSet } from './mock/utils/map-get-set.js';
import { isApiError } from './mock/utils/api-error.js';
import { mockService } from './mock/request/mock';
import './style.less';
const NoteType = {
comment: 'Comment',
problem: 'Problem',
}
const NoteState = {
opened: 'opened',
resolved: 'resolved',
invalid: 'invalid',
}
const CharsetName = {
gbk: 'GBK',
utf8: 'UTF-8',
};
function createContainer(useHook, checkNull = false) {
let Context = createContext(null);
const Provider = ({ children }) => {
const value = useHook();
if (checkNull && !value) return null;
return <Context.Provider value={value}>{children}</Context.Provider>;
};
const useContainer = () => React.useContext(Context);
return [Provider, useContainer];
}
let ContextProps = createContext(null);
const useGlobal = () => React.useContext(ContextProps);
// const [GlobalProvider, useGlobal] = createContainer(() => {
// const [current_user, setCurrent_user] = useState({});
// const [projectDetail, setProjectDetail] = useState({});
// // let project=mockService['/webapi/projects/Gitlink/forgeplus/'];
// let user=mockService['/api/v3/user'];
// return {
// user: {...current_user,...user, avatar_url: current_user.image_url, name: current_user.username },
// project:{...projectDetail,...project,id:42422},
// setProjectDetail,
// setCurrent_user,
// };
// // return {
// // user: { ...current_user, avatar_url: current_user.image_url, name: current_user.username },
// // project:{...projectDetail,id:42422},
// // setProjectDetail,
// // setCurrent_user,
// // };
// // let data = useRequest(async () => {
// // // const user = await projectService.getUser();
// // // const project = await projectService.getProject(group, repo);
// // // console.log(JSON.stringify(project)==JSON.stringify(project1))
// // console.log('user');
// // console.log(project);
// // return {
// // user: { ...user, ...current_user, avatar_url: current_user.image_url, name: current_user.username },
// // project,
// // setProjectDetail,
// // setCurrent_user,
// // };
// // });
// // return data;
// },);
const [PrProvider, usePr] = createContainer(() => {
const { params } = useGlobal();
const { owner, projectsId ,mergeId} = params;
const data = useRequest(async () => {
const pr = await prService.getPRByIid(projectsId, owner, mergeId);
console.log('getPRByIidpr');
console.log(pr);
return { pr }
})
return data;
}, true);
// const [GlobalProvider, useGlobal] = createContainer((props) => {
// // let gitProps = props.children.props.children.props.children.props.children.props;
// // let match = gitProps.match;
// // const { owner, projectsId } = match.params;
// // const {current_user,projectDetail}=gitProps;
// const [current_user,setCurrent_user]=useState();
// const [projectDetail,setProjectDetail]=useState({});
// console.log('projectDetail');
// console.log(projectDetail);
// let data = useRequest(async () => {
// const user = await projectService.getUser();
// const project = await projectService.getProject(group, repo);
// const pr = await prService.getPRByIid(project.id, prIid);
// console.log('pr:');
// console.log(user);
// console.log('project');
// console.log(project);
// console.log(pr);
// // console.log(gitProps);
// console.log('projectDetail');
// console.log(projectDetail);
// return {
// user:{...user,...current_user,avatar_url:current_user.image_url,name:current_user.username},
// project,
// pr,
// setProjectDetail,
// setCurrent_user,
// // updateProject,
// };
// });
// return data;
// }, true);
const [SettingProvider, useSetting] = createContainer(() => {
const { project } = useGlobal();
const [gbk, setGBK] = useState(() => project.encoding === 'GBK');
const charsetName = gbk ? CharsetName.gbk : CharsetName.utf8;
const [locale, setLocal] = useState(() => getLocale());
const setLocale = React.useCallback(() => {
jCookie.set('LOCALE', locale === 'en-US' ? 'zh_CN' : 'en_US');
window.location.reload();
}, [locale]);
return {
gbk,
setGBK,
charsetName,
locale,
setLocale,
};
});
const useCommentPack = (projectId, prId) => {
const lineToNoteIdSet = useMemo(() => new Map(), []);
const noteIdToNote = useMemo(() => new Map(), []);
const noteIdToReviewId = useMemo(() => new Map(), []);
const reviewIdToReview = useMemo(() => new Map(), []);
const noteIdToReplyIdSet = useMemo(() => new Map(), []);
const pendingNoteIdSet = useMemo(() => new Set(), []);
const recordNoteIdSet = useMemo(() => new Set(), []);
const topLevelCommentNoteIdSet = useMemo(() => new Set(), []);
const commentNoteIdSet = useMemo(() => new Set(), []);
const openedProblemNoteIdSet = useMemo(() => new Set(), []);
const [updateFlag, setUpdateFlag] = useState({});
const fetchingRef = useRef(false);
const lastFetchAtRef = useRef();
const pendingReviewRef = useRef();
const [hasPendingReview, setHasPendingReview] = useState(false); // TODO: state
function processNote(note, review = null) {
noteIdToNote.set(note.id, note);
noteIdToReviewId.set(note.id, review && review.id);
if (note.type === NoteType.problem && note.state === NoteState.opened) {
openedProblemNoteIdSet.add(note.id);
} else {
openedProblemNoteIdSet.delete(note.id);
}
if (!note.discussionId) {
if (!note.lineCode && note.system) {
recordNoteIdSet.add(note.id);
} else {
topLevelCommentNoteIdSet.add(note.id);
}
}
if (note.discussionId || note.lineCode) {
commentNoteIdSet.add(note.id);
}
if (note.lineCode) {
const noteIdSet = mapGetSet(
lineToNoteIdSet,
note.lineCode,
() => new Set()
);
noteIdSet.add(note.id);
}
if (note.discussionId) {
const replyIdSet = mapGetSet(
noteIdToReplyIdSet,
note.discussionId,
() => new Set()
);
replyIdSet.add(note.id);
}
if (review && review.pending) {
pendingNoteIdSet.add(note.id);
} else {
pendingNoteIdSet.delete(note.id);
}
}
async function doRefresh(force = false) {
if (fetchingRef.current) {
return;
}
fetchingRef.current = true;
const pack = await prService.getCommentPack(
projectId,
prId,
force ? undefined : lastFetchAtRef.current
);
console.log('pack');
console.log(pack);
lastFetchAtRef.current = pack.currentFetchedAt;
let updated = false;
if (force) {
lineToNoteIdSet.clear();
noteIdToNote.clear();
noteIdToReviewId.clear();
reviewIdToReview.clear();
noteIdToReplyIdSet.clear();
pendingNoteIdSet.clear();
recordNoteIdSet.clear();
topLevelCommentNoteIdSet.clear();
commentNoteIdSet.clear();
openedProblemNoteIdSet.clear();
updated = true;
}
for (const note of pack.notes) {
processNote(note);
updated = true;
}
for (const review of pack.committedReviews) {
reviewIdToReview.set(review.id, review);
for (const note of review.reviewNotes) {
processNote(note, review);
}
updated = true;
}
if (pack.pendingReview) {
reviewIdToReview.set(pack.pendingReview.id, pack.pendingReview);
pendingReviewRef.current = pack.pendingReview;
setHasPendingReview(true);
for (const note of pack.pendingReview.reviewNotes) {
processNote(note, pack.pendingReview);
updated = true;
}
} else {
setHasPendingReview(false);
}
if (updated) {
setUpdateFlag({});
}
fetchingRef.current = false;
}
function manualUpdateReview(review) {
reviewIdToReview.set(review.id, review);
if (review.pending) {
pendingReviewRef.current = review;
setHasPendingReview(!!review);
}
setUpdateFlag({});
}
function manualAddNote(note, review = null) {
processNote(note, review);
setUpdateFlag({});
}
function manualUpdateNote(note) {
const reviewId = noteIdToReviewId.get(note.id);
const review = reviewId ? reviewIdToReview.get(reviewId) : null;
processNote(note, review);
setUpdateFlag({});
}
function getRelatedFilePathByNoteId(noteId) {
const note = noteIdToNote.get(noteId);
if (!note) return;
let path = note.path;
if (note.discussionId) {
const parent = noteIdToNote.get(note.discussionId);
path = parent && parent.path;
}
return path;
}
async function editNote(
noteId, data
) {
const newNote = await prService.editPRComment(
projectId,
prId,
noteId,
data
);
manualUpdateNote(newNote);
newNote.discussions && newNote.discussions.forEach((note) => manualUpdateNote(note));
}
const pathShaToNoteCount = (function () {
const ret = new Map();
function handleNote(note) {
if (!note || !note.lineCode) return;
const key = note.lineCode.split('_')[0];
ret.set(key, (ret.get(key) || 0) + 1);
}
topLevelCommentNoteIdSet.forEach((noteId) => {
const note = noteIdToNote.get(noteId);
handleNote(note);
});
return ret;
})();
return {
lineToNoteIdSet,
noteIdToNote,
noteIdToReviewId,
reviewIdToReview,
noteIdToReplyIdSet,
pendingNoteIdSet,
recordNoteIdSet,
topLevelCommentNoteIdSet,
openedProblemNoteIdSet,
hasPendingReview,
hasOpenedProblem: openedProblemNoteIdSet.size > 0,
doRefresh,
manualUpdateReview,
manualAddNote,
manualUpdateNote,
pendingReviewRef,
getRelatedFilePathByNoteId,
editNote,
updateFlag,
pathShaToNoteCount,
setUpdateFlag,
};
};
const [NoteProvider, useNote] = createContainer(() => {
const {
project: { id: projectId },
// pr: { id: prId },
} = useGlobal();
const { pr: { id: prId } } = usePr();
const commentPack = useCommentPack(projectId, prId);
useEffect(() => {
commentPack.doRefresh();
}, []);
async function addComment(data) {
if (!commentPack.hasPendingReview) {
const review = await prService.createReview(projectId, prId);
commentPack.manualUpdateReview(review);
}
const note = await prService.addComment(projectId, prId, data);
commentPack.manualAddNote(note, commentPack.pendingReviewRef.current);
}
async function commitReview(body) {
if (!commentPack.hasPendingReview) {
await prService.createReview(projectId, prId);
}
await prService.commitReview(projectId, prId, body);
await commentPack.doRefresh(true);
}
const activateRef = useRef();
return {
commentPack,
...commentPack,
addComment,
commitReview,
prId,
activateRef,
};
});
const [ReadMarkProvider, useReadMark] = createContainer(() => {
const {
project: { id: projectId },
// pr: { id: prId },
} = useGlobal();
const { pr: { id: prId } } = usePr();
const [flag, updateFlag] = useState({});
const readMarks = useRequest(
() => prService.getFileReadMarks(projectId, prId),
{
deps: [projectId, projectId, flag],
}
);
const readMarkMap = useMemo(() => {
const map = new Map();
if (!readMarks) return map;
for (const mark of readMarks) {
map.set(mark.filePathSha, mark);
}
return map;
}, [readMarks]);
const memoizedSha1 = useMemo(() => memoize(sha1), []);
async function markFileAsRead(filePath) {
const data = await prService.markFileAsRead(
projectId,
prId,
memoizedSha1(filePath)
);
updateFlag({});
return data;
}
async function markFileAsUnread(filePath) {
const data = await prService.markFileAsUnread(
projectId,
prId,
memoizedSha1(filePath)
);
updateFlag({});
return data;
}
const getFileReadMark = useCallback(
(filePath) => {
const filePathSha = memoizedSha1(filePath);
return readMarkMap.get(filePathSha);
},
[readMarkMap]
);
const getFileReadStatus = useCallback(
(filePath) => {
const readMark = getFileReadMark(filePath);
return readMark && readMark.markAsRead && !readMark.updatedAfterRead;
},
[getFileReadMark]
);
return {
readMarks,
readMarkMap,
getFileReadMark,
getFileReadStatus,
markFileAsRead,
markFileAsUnread,
};
});
const [AcrProvider, useAcr] = createContainer(() => {
const { project,params } = useGlobal();
const { pr } = usePr();
const { charsetName } = useSetting();
const { commentPack } = useNote();
const { owner, projectsId ,mergeId} = params;
const [IDEMode, setIDEMode] = useState(true);
const toggleViewerType = useCallback(() => {
setIDEMode((v) => !v);
}, [setIDEMode]);
const [search, setSearch] = useState(location.search);
const query = useMemo(() => {
const searchParams = new URLSearchParams(search);
const ret = {};
if (searchParams.has('from')) {
ret.from = parseInt(searchParams.get('from'));
}
if (searchParams.has('to')) {
ret.to = parseInt(searchParams.get('to'));
}
return ret;
}, [search]);
function updateQuery(data) {
const searchParam = new URLSearchParams();
if (data.from) {
searchParam.set('from', String(data.from));
}
if (data.to) {
searchParam.set('to', String(data.to));
}
const search = searchParam.toString();
history.replaceState(
null,
'',
`${location.pathname}${search ? '?' : ''}${search}`
);
setSearch(search);
}
const versions = useRequest(
async () => await prService.getDiffVersions(projectsId , owner, mergeId),
{ initial: [] }
);
const versionMap = useMemo(() => {
const map = new Map();
for (const version of versions) {
map.set(version.id, version);
}
return map;
}, [versions]);
const latestVersionId = versions[0] && versions[0].id || pr.diff.id;
const from = query.from;
const to = query.to || latestVersionId;
const fromVersion = from ? versionMap.get(from) : null;
const toVersion = versionMap.get(to);
console.log('diffpack', from, to, fromVersion, toVersion);
const ignoreWhiteSpace = false;
// @ts-ignore
const diffsPack = useRequest(
async () => {
if (!toVersion) return null;
const isPartial = !!fromVersion || toVersion.id !== latestVersionId;
const options = {
ignoreWhiteSpaceChange: ignoreWhiteSpace,
charsetName,
};
let res;
if (fromVersion) {
res = await prService.getDiffs(
projectsId,owner,
fromVersion.headCommitSha,
toVersion.headCommitSha,
options
);
console.log('getDiffs1');
console.log(res);
} else {
if (ignoreWhiteSpace) {
res = await prService.getDiffs(
projectsId,owner,
toVersion.baseCommitSha,
toVersion.headCommitSha,
options
);
console.log('getDiffs2');
console.log(res);
} else {
const diffs = await prService.getDiffOverviews(
projectsId,owner,
mergeId,
toVersion.id
);
res = {
diffs,
overflow: false,
addLineNum: toVersion.addLineNum,
delLineNum: toVersion.delLineNum,
};
}
}
return {
...res,
fromVersion,
toVersion,
isPartial,
};
},
{
deps: [fromVersion, toVersion, charsetName],
}
);
useEffect(() => {
commentPack.doRefresh();
}, []);
async function addComment(data) {
console.log('>>>addComment', data);
}
async function commitReview(body) {
console.log('>>>commitReview', body);
}
function getDiffById(diffId) {
return prService.getDiffById(projectsId,owner,mergeId,toVersion.id,
{
filepath:diffId,
});
// return prService.getDiffById(project.id, pr.id, toVersion.id, diffId, {
// charsetName,
// });
}
async function getFileContent(path, sha, maxSize) {
console.log('getFileContent--path');
console.log(path);
console.log('getFileContent--sha');
console.log(sha);
try {
return await projectService.getFileBlob(projectsId, owner, {
filepath:path,
ref:sha,
});
// return await projectService.getFileBlob(project.id, sha, path, {
// maxSize: maxSize || 250000,
// charsetName,
// });
} catch (e) {
if (isApiError(e) && e.response.status === 413) {
return null;
}
throw e;
}
}
const annotationPacks = [];
let countMap = new Map();
for (const checkSuite of pr.checkSuites) {
if (checkSuite.checkRuns) {
for (const checkRun of checkSuite.checkRuns) {
if (checkRun.annotations) {
for (const annotation of checkRun.annotations) {
const pack = {
annotation,
checkSuite,
};
annotationPacks.push(pack);
countMap.set(
annotation.path,
(countMap.get(annotation.path) || 0) + 1
);
}
}
}
}
}
return {
diffsPack,
versions,
fromVersion,
toVersion,
getDiffById,
getFileContent,
IDEMode,
toggleViewerType,
updateQuery,
annotationPacks,
};
});
export const Provider = (props) => {
const match = props.match;
let project = {
// "encoding": "UTF-8",
"id": 42422,
"importStatus": "none",
"namespace": {
"avatar": {
"url": null
},
"createdAt": "2020-07-02T10:34:49+0800",
"description": "",
"id": 23159,
"name": "ide-s",
"owner": null,
"ownerId": null,
"path": "ide-s",
"permission": null,
"public": false,
"state": null,
"tenant": {
"createdAt": null,
"id": 0,
"name": "git",
"path": "git",
"updatedAt": null,
"whileListIp": null
},
"type": "Group",
"updatedAt": "2020-07-02T10:34:49+0800",
"webUrl": "http://gitlab-test.alipay.net/groups/ide-s"
},
"path": "TypeScript-Node-Starter",
"pathWithNamespace": "ide-s/TypeScript-Node-Starter"
};
let user = mockService['/api/v3/user'];
if (props.projectDetail) {
let details = props.projectDetail;
project = {
...project,
pathWithNamespace: details.full_name,
}
}
if (props.current_user) {
let current_user = props.current_user;
user = { ...current_user, ...user, avatar_url: current_user.image_url, name: current_user.username, };
}
return (
<ContextProps.Provider value={{
project, user,params:match.params
}}>
{/* <GlobalProvider> */}
<PrProvider>
<SettingProvider>
<NoteProvider>
<ReadMarkProvider>
<AcrProvider>{props.children}</AcrProvider>
</ReadMarkProvider>
</NoteProvider>
</SettingProvider>
</PrProvider>
{/* </GlobalProvider> */}
</ContextProps.Provider>
);
};
export { useGlobal, usePr, useNote, useReadMark, useAcr, useSetting };

View File

@ -0,0 +1,62 @@
export const ExtensionCommand ={
acrToggleBlame : 'code.blame.acrToggleBlame',
linkToCommit : 'code.blame.linktocommit',
onActive : 'code.blame.extension.active',
setProjectData : 'code.blame.setProjectData',
getBlameData : 'code.blame.getBlameData',
}
export default class IDEPlugin {
/**
* 插件 ID用于唯一标识插件
*/
PLUGIN_ID = 'ACR_BLAME_PLUGIN';
constructor(
onActive,
linkToCommit,
getBlame
) {
this.onActivate = onActive;
// 跳转
this.linkToCommit = linkToCommit;
// 传递blame数据
this.getBlame = getBlame;
}
/**
* 激活插件
*/
activate = (ctx) => {
const { commands, context } = ctx;
this.commands = commands;
context.subscriptions.push(
commands.registerCommand(ExtensionCommand.onActive, () => {
this.onActivate();
}),
commands.registerCommand(ExtensionCommand.linkToCommit, (params) => {
const { commitId } = params;
this.linkToCommit(commitId);
}),
commands.registerCommand(
ExtensionCommand.getBlameData,
async (sendData) => {
const { projectId, prevSha, nextSha, filePath } = sendData;
// kaitian内uri 和 antcode内不同 多了一个 /
const path = filePath.startsWith('/') ? filePath.slice(1) : filePath;
const response = await this.getBlame(projectId, nextSha, path).then(
(res) => {
return res;
}
);
return response;
}
)
);
};
/**
* 注销插件可在此时机清理副作用
*/
deactivate() {}
}

View File

@ -0,0 +1,49 @@
@c45: rgba(0, 0, 0, 0.45);
@c85: rgba(0, 0, 0, 0.85);
html,
body,
#main {
overflow: visible;
}
#main {
padding: 8px 24px 24px;
width: auto;
height: auto;
background-color: #f7f8fa;
}
.pr-head {
padding: 24px;
background-color: #fff;
margin-bottom: 24px;
}
.controller {
margin-bottom: 24px;
> * {
margin-right: 8px;
}
}
.monaco-editor-hover {
border: none !important;
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
transform: translate(-20px, 0px);
}
.monaco-editor-hover-content .markdown-hover .hover-contents {
padding: 16px;
background: #fff;
h1 {
color: @c85;
font-size: 14px;
line-height: 22px;
}
p {
color: @c45;
}
}

View File

@ -4,6 +4,7 @@ import AddMember from '../Component/AddMember';
import AddGroup from '../Component/AddGroup';
import Member from './CollaboratorMember';
import Group from './CollaboratorGroup';
import MemberByLink from './CollaboratorMemberByLink';
function Collaborator(props){
const [ nav , setNav] = useState("1");
@ -23,11 +24,10 @@ function Collaborator(props){
setNewGroupId(id);
}
return (
<WhiteBack>
<div className="flex-a-center baseForm bbr">
{
{/* {
author && author.type === "Organization" ?
<span>
<span style={{cursor:"pointer"}} className={nav === "1" ? "font-18 text-black color-blue":"font-18 text-black"} onClick={()=>{setNav("1");setNewId(undefined)}}>协作者管理</span>
@ -35,22 +35,26 @@ function Collaborator(props){
</span>
:
<span className="font-18 text-black">协作者管理</span>
}
} */}
<span>
<span style={{cursor:"pointer"}} className={nav === "1" ? "font-15 text-black color-blue":"font-15 text-black"} onClick={()=>{setNav("1");setNewId(undefined)}}>协作者管理</span>
<span style={{cursor:"pointer"}} className={nav === "3" ? "font-15 text-black color-blue ml30":"font-15 text-black ml30"} onClick={()=>{setNav("3");}}>邀请成员</span>
{author && author.type === "Organization" && <span style={{cursor:"pointer"}} className={nav === "2" ? "font-15 text-black ml30 color-blue":"font-15 text-black ml30"} onClick={()=>{setNav("2");setNewId(undefined);setNewGroupId(undefined)}}>团队管理</span>}
</span>
{
nav === "1" &&
<AddMember getID={getID} login showNotification={props.showNotification}/>
}
{
(nav !== "1" && addOperation) &&
(nav === "2" && addOperation) &&
<AddGroup getGroupID={getGroupID} organizeId={owner}/>
}
</div>
<div>
{
nav === "1" ?
<Member newId={newId} flag={newIdFlag} projectsId={projectsId} owner={owner} project_id={props.project_id} author={props.projectDetail && props.projectDetail.author} showNotification={props.showNotification}/>
:
<Group setAddOperation={setAddOperation} owner={owner} projectsId={projectsId} newGroupId={newGroupId}/>
nav === "1" ? <Member newId={newId} flag={newIdFlag} projectsId={projectsId} owner={owner} project_id={props.project_id} author={props.projectDetail && props.projectDetail.author} showNotification={props.showNotification}/>
: nav === "2" ? <Group setAddOperation={setAddOperation} owner={owner} projectsId={projectsId} newGroupId={newGroupId}/>
: <MemberByLink newId={newId} flag={newIdFlag} projectsId={projectsId} owner={owner} project_id={props.project_id} author={props.projectDetail && props.projectDetail.author} showNotification={props.showNotification}/>
}
</div>
</WhiteBack>

View File

@ -0,0 +1,92 @@
import React, { useEffect, useState, useImperativeHandle, forwardRef } from 'react';
import { Select, Checkbox, Button, Input, Spin, Table, Tooltip, Pagination, Popconfirm } from "antd";
import axios from 'axios';
import { Base64 } from 'js-base64';
import NoneData from "../Nodata";
import { Link } from "react-router-dom";
import { getImageUrl } from "educoder";
const { Search } = Input;
const LIMIT = 15;
const optionList = [
{ value: "manager", name: "管理员 - 拥有仓库设置功能、代码库读、写操作权限" },
{ value: "developer", name: "开发人员 - 拥有代码库读、写操作权限" },
{ value: "reporter", name: "报告者 - 拥有代码库读操作权限" }
];
function CollaboratorMemberByLink({ projectsId, owner, project_id, author, showNotification, newId, flag }) {
const [role, setRole] = useState('developer');
const [is_apply, setIs_apply] = useState(true);
const [inviteUrl, setinviteUrl] = useState('');
const [copy, setCopy] = useState(false);
useEffect(() => {
const url = `/${owner}/${projectsId}/project_invite_links/current_link.json`;
axios.get(url, {
params: {
role,
is_apply
}
}).then(res => {
if (res && res.data) {
let params = {
projectName: res.data.project.name,
projectId: res.data.project.identifier,
// role: res.data.role,
ownerLogin: res.data.project.owner.login,
ownerName: res.data.project.owner.name,
// userName: res.data.user.name,
// userLogin: res.data.user.login,
sign:res.data.sign,
};
let urlParams = JSON.stringify(params);
let content = Base64.encode(urlParams);
setinviteUrl(`${window.location.origin}/${owner}/${projectsId}/invite?invite=${content}`);
setCopy(false);
}
}).catch(error => { })
}, [role, is_apply]);
function inviteClick() {
const copyEle = document.querySelector('#inviteUrl'); //
if (!copyEle) {
console.error("您的CopyTool未设置正确的inputId");
return;
}
copyEle.select(); //
if (document.execCommand('copy')) {
document.execCommand('copy');
setCopy(true);
}
}
return (
<div className='addMemByLinkBox'>
<div className='font-16 mt20 mb10'>请选择邀请用户权限</div>
<Select className='selectBox' defaultValue="developer" onChange={(v) => { setRole(v) }}>
{optionList.map(item => {
return <Select.Option value={item.value} key={item.value}>{item.name}</Select.Option>
})}
</Select>
<Checkbox checked={is_apply} className='font-15 checkBox' onChange={e => { setIs_apply(e.target.checked) }}>需要管理员审核</Checkbox>
<div className='font-16 mt25 mb10'>邀请链接</div>
<Input
id="inviteUrl"
value={inviteUrl}
readOnly
addonAfter={<Button type='primary' onClick={inviteClick}>{copy ? '复制成功' : '复制链接'}</Button>} className='linkBox'
/>
<div className='tipBox mt25'>
<div>: </div>
<div className='ml5'>
1管理员可通过分享邀请链接的方式邀请其他成员加入项目<br />
2若已勾选管理员审核选项用户接收邀请后管理员可在个人主页中待办事项窗口审核成员审核信息若不需要管理员审核成员接收邀请后将直接加入项目
</div>
</div>
</div>
)
}
export default forwardRef(CollaboratorMemberByLink);

View File

@ -57,7 +57,7 @@ class Index extends Component {
<li className={flag ? "active" : ""}>
<p>
<Link to={`/${owner}/${projectsId}/settings`} className="w-100">
<i className="iconfont icon-huabanfuben font-18 mr10"></i>
<i className="iconfont icon-huabanfuben font-15 mr10"></i>
</Link>
</p>
</li>
@ -68,7 +68,7 @@ class Index extends Component {
>
<p>
<Link to={`/${owner}/${projectsId}/settings/collaborators`} className="w-100">
<i className="iconfont icon-chengyuan font-18 mr10"></i>
<i className="iconfont icon-chengyuan font-15 mr10"></i>
协作者管理
</Link>
</p>
@ -80,7 +80,7 @@ class Index extends Component {
>
<p>
<Link to={`/${owner}/${projectsId}/settings/webhooks`} className="w-100">
<i className="iconfont icon-a-xuanzhongwebhookicon font-18 mr10 color-grey-9"></i>
<i className="iconfont icon-a-xuanzhongwebhookicon font-15 mr10 color-grey-9"></i>
网络钩子
</Link>
</p>
@ -92,7 +92,7 @@ class Index extends Component {
>
<p>
<Link to={`/${owner}/${projectsId}/settings/branches`} className="w-100">
<i className="iconfont icon-fenzhi font-20 mr10"></i>
<i className="iconfont icon-fenzhi font-15 mr10"></i>
分支设置
</Link>
</p>
@ -102,7 +102,7 @@ class Index extends Component {
>
<p>
<Link to={`/${owner}/${projectsId}/settings/labels`} className="w-100">
<i className="iconfont icon-xiangmubiaoqian font-18 mr10 color-grey-6"></i>
<i className="iconfont icon-xiangmubiaoqian font-15 mr10 color-grey-6"></i>
项目标记
</Link>
</p>

View File

@ -23,7 +23,7 @@
content: '';
}
.baseForm{
padding:15px 30px!important;
padding:15px 0!important;
}
.collaboratorList{
min-height: 400px;
@ -243,4 +243,31 @@
&>div:last-child{
border-bottom: none;
}
}
.addMemByLinkBox{
color:#202d40;
.selectBox {
width: 55%;
display: block;
margin-bottom: 18px;
}
.checkBox{
color:#151d40;
}
.tipBox{
background-color:rgba(199, 209, 255, 0.17);
color:#7e849e;
padding: 20px 150px 20px 25px;
display: flex;
}
.linkBox{
width: 55%;
.ant-input-group-addon{
padding: 0;
}
.ant-btn{
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
}

View File

@ -225,24 +225,24 @@ form{
margin-bottom: 12px;
border-radius:2px;
background-color: #fff;
// box-shadow: 0px 0px 2px rgba(0,0,0,0.2);
padding: 20px 0;
border:1px solid rgba(79, 108, 188, 0.21);
&>li{
font-size: 1rem;
padding:0px 0px 0px 20px;
font-size: 15px;
padding:0px 0px 0px 25px;
box-sizing: border-box;
color: #333;
position: relative;
& > p{
height: 62px;
line-height: 62px;
height: 49px;
width: 100%;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
padding-right: 20px;
margin:0px;
border-bottom: 1px solid #eee;
a{
width:100%;
color: #202d40;
@ -264,10 +264,9 @@ form{
& li.active::before{
position: absolute;
left: 0px;
top: 15px;
width: 6px;
content: '';
height: 33px;
height: 100%;
// background: #4CACFF;
background: $primary-color;
}

View File

@ -180,7 +180,7 @@ class InfosUser extends Component {
placeholder="输入项目名称关键字进行搜索"
enterButton="搜索"
size="large"
onSearch={this.get_projects}
onSearch={()=>{this.get_projects()}}
className="list-r-Search"
value={search}
onChange={this.changeSearchValue}