mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
Merge branch 'develop'
This commit is contained in:
commit
81867ee5fd
63 changed files with 2970 additions and 1389 deletions
5
.babelrc
5
.babelrc
|
|
@ -5,13 +5,14 @@
|
|||
{
|
||||
"targets": {
|
||||
"chrome": "67"
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
"@babel/preset-typescript",
|
||||
"@babel/preset-react",
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-object-rest-spread"
|
||||
]
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -3,5 +3,5 @@
|
|||
node_modules
|
||||
tmp
|
||||
dist
|
||||
src/assets/icons/octicons
|
||||
yarn-error.log
|
||||
vscode-icons
|
||||
|
|
|
|||
27
Makefile
Executable file
27
Makefile
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
RAW_VERSION?=$(shell node scripts/get-version.js)
|
||||
FULL_VERSION=v$(RAW_VERSION)
|
||||
|
||||
build:
|
||||
rm -rf dist
|
||||
yarn build
|
||||
|
||||
upload-for-analytics:
|
||||
# make sure sentry can retrieve current commit on remote
|
||||
git push --tags
|
||||
yarn sentry-cli releases new "$(FULL_VERSION)"
|
||||
yarn sentry-cli releases set-commits "$(FULL_VERSION)" --auto
|
||||
yarn sentry-cli releases files "$(FULL_VERSION)" upload-sourcemaps dist --no-rewrite
|
||||
yarn sentry-cli releases finalize "$(FULL_VERSION)"
|
||||
|
||||
compress:
|
||||
rm -f dist/gitako.zip
|
||||
cd dist && zip -r gitako-$(FULL_VERSION).zip * -x *.map -x *.DS_Store
|
||||
|
||||
release:
|
||||
$(MAKE) build
|
||||
$(MAKE) upload-for-analytics
|
||||
$(MAKE) compress
|
||||
$(MAKE) compress-source
|
||||
|
||||
compress-source:
|
||||
git archive -o dist/source-$(FULL_VERSION).zip HEAD
|
||||
11
README.md
11
README.md
|
|
@ -8,15 +8,12 @@ Yet another extension for GitHub, available on both Chrome and Firefox. Inspired
|
|||
- 🔎 instant file search
|
||||
- 🏎 extremely fast even in gigantic projects
|
||||
- ⌨️ intuitive keyboard accessibility
|
||||
- navigate file using arrow keys
|
||||
- shortcut for toggling sidebar
|
||||
- 🚀 boosts loading speed
|
||||
- 🚀 boosts page performance
|
||||
- ↔️ customizable layout
|
||||
- 📋 shortcuts for copy snippets and file
|
||||
- 📋 copy snippets and file
|
||||
- 🕶️ support private repositories
|
||||
- 🗂 support git submodule
|
||||
- 🎨 GitHub-friendly UI
|
||||
- 🎈 light weight in size and memory usage
|
||||
- 🎨 friendly UI and rich icons
|
||||
|
||||
### Install
|
||||
|
||||
|
|
@ -34,7 +31,7 @@ Any bug report or feature request discussions are welcomed, feel free to draft a
|
|||
|
||||
#### Why named 'Gitako'?
|
||||
|
||||
GitHub's totem is a cute octopus. And octopus in Japanese is `たこ`(tako).
|
||||
GitHub's totem is a cute octopus. And octopus in Japanese is `タコ`(tako).
|
||||
Then link them together:
|
||||
|
||||
git + tako -> gitako
|
||||
|
|
|
|||
29
package.json
29
package.json
|
|
@ -1,30 +1,37 @@
|
|||
{
|
||||
"name": "gitako",
|
||||
"version": "0.5.15",
|
||||
"version": "0.8.2",
|
||||
"description": "The missing part of GitHub.",
|
||||
"repository": "https://github.com/EnixCoda/Gitako",
|
||||
"author": "EnixCoda",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "webpack --watch",
|
||||
"start": "VERSION=dev-v$(node scripts/get-version.js) webpack --watch",
|
||||
"debug-firefox": "web-ext run -s dist",
|
||||
"postversion": "node scripts/version.js && npm run roll",
|
||||
"build": "NODE_ENV=production webpack",
|
||||
"analyse-bundle": "ANALYSE= NODE_ENV=production webpack",
|
||||
"roll": "./scripts/roll.sh"
|
||||
"build": "VERSION=v$(node scripts/get-version.js) NODE_ENV=production webpack",
|
||||
"postversion": "node scripts/version.js",
|
||||
"roll": "make release"
|
||||
},
|
||||
"dependencies": {
|
||||
"@primer/octicons": "^9.1.1",
|
||||
"@primer/octicons": "^9.2.0",
|
||||
"@primer/octicons-react": "^9.2.0",
|
||||
"@sentry/browser": "^5.5.0",
|
||||
"@types/firefox-webext-browser": "^70.0.1",
|
||||
"@types/ini": "^1.3.30",
|
||||
"@types/js-base64": "^2.3.1",
|
||||
"@types/node": "^11.10.4",
|
||||
"@types/nprogress": "^0.0.29",
|
||||
"@types/react": "^16.8.24",
|
||||
"@types/react-dom": "^16.8.5",
|
||||
"@types/react-window": "^1.8.1",
|
||||
"ini": "^1.3.5",
|
||||
"js-base64": "^2.5.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-use": "^13.8.0",
|
||||
"react-window": "^1.8.5",
|
||||
"safe-touch": "^0.2.2",
|
||||
"webextension-polyfill": "^0.5.0"
|
||||
|
|
@ -34,16 +41,11 @@
|
|||
"@babel/core": "^7.3.4",
|
||||
"@babel/plugin-proposal-class-properties": "^7.3.4",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.3.4",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.6.0",
|
||||
"@babel/preset-env": "^7.3.4",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/preset-typescript": "^7.3.3",
|
||||
"@sentry/cli": "^1.47.1",
|
||||
"@types/ini": "^1.3.30",
|
||||
"@types/js-base64": "^2.3.1",
|
||||
"@types/node": "^11.10.4",
|
||||
"@types/nprogress": "^0.0.29",
|
||||
"@types/react": "^16.8.24",
|
||||
"@types/react-dom": "^16.8.5",
|
||||
"babel-loader": "^8.0.5",
|
||||
"copy-webpack-plugin": "^5.0.0",
|
||||
"css-loader": "^2.1.0",
|
||||
|
|
@ -54,8 +56,9 @@
|
|||
"json-loader": "^0.5.7",
|
||||
"less": "^3.9.0",
|
||||
"less-loader": "^4.0.5",
|
||||
"raw-loader": "^4.0.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"typescript": "^3.6.3",
|
||||
"typescript": "^3.7.2",
|
||||
"uglifyjs-webpack-plugin": "^2.1.2",
|
||||
"url-loader": "^1.1.2",
|
||||
"web-ext": "^3.2.0",
|
||||
|
|
|
|||
|
|
@ -75,8 +75,6 @@ Pjax.prototype = {
|
|||
|
||||
attachLink: require("./lib/proto/attach-link.js"),
|
||||
|
||||
attachForm: require("./lib/proto/attach-form.js"),
|
||||
|
||||
forEachSelectors: function(cb, context, DOMContext) {
|
||||
return forEachSelectors.bind(this)(this.options.selectors, cb, context, DOMContext)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -10,10 +10,6 @@ module.exports = function(el) {
|
|||
break
|
||||
|
||||
case "form":
|
||||
// only attach link if el does not already have link attached
|
||||
if (!el.hasAttribute(attrState)) {
|
||||
this.attachForm(el)
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
|
|
|
|||
71
scripts/generate-file-icon-index.js
Normal file
71
scripts/generate-file-icon-index.js
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
const languageIds = require('./language-id-ext.json')
|
||||
|
||||
function generateCSV() {
|
||||
const records = []
|
||||
|
||||
document.body
|
||||
.querySelector('table')
|
||||
.querySelectorAll('tbody tr')
|
||||
.forEach(tr => {
|
||||
const [name, id, dark, light] = Array.from(tr.querySelectorAll('td'))
|
||||
const exts = []
|
||||
const names = []
|
||||
id.innerHTML
|
||||
.replace(/<sub>|<\/sub>/g, '')
|
||||
.split(', ')
|
||||
.map(part => {
|
||||
const tags = part.match(/<(\w+)>(.*?)<\/\1>/g)
|
||||
if (tags) {
|
||||
tags.forEach(subPart => {
|
||||
const match = subPart.match(/<(\w+)>(.*?)<\/\1>/)
|
||||
if (match) {
|
||||
const [, tag, content] = match
|
||||
if (tag === 'strong') {
|
||||
// filenames in bold
|
||||
names.push(content)
|
||||
} else if (tag === 'code') {
|
||||
// language ids in code block
|
||||
const map = Object.values(languageIds).find(({ ids }) =>
|
||||
(Array.isArray(ids) ? ids : [ids]).includes(content),
|
||||
)
|
||||
if (map && map.exts) exts.push(map.exts)
|
||||
} else {
|
||||
console.warn(`Found unrecognized format`, subPart, tag) // unknown
|
||||
}
|
||||
}
|
||||
})
|
||||
} else if (part) {
|
||||
// extensions are in regular fonts
|
||||
exts.push(part.replace(/^\./, ''))
|
||||
}
|
||||
})
|
||||
records.push({
|
||||
name: name.innerText,
|
||||
exts,
|
||||
names,
|
||||
icon: getSrc(dark.querySelector('img')) || getSrc(light.querySelector('img')),
|
||||
})
|
||||
})
|
||||
|
||||
function getSrc(img) {
|
||||
return img && img.src
|
||||
}
|
||||
|
||||
const prepend = 'https://github.com/vscode-icons/vscode-icons/raw/master/icons/file_type_'
|
||||
const append = '.svg?sanitize=true'
|
||||
const separator = ':'
|
||||
const csv = records
|
||||
.map(({ name, names, exts, icon }) =>
|
||||
[
|
||||
name,
|
||||
names.join(separator),
|
||||
exts.join(separator),
|
||||
// icon.replace(prepend, '').replace(append, ''), // assumption: name is equal to this
|
||||
].join(','),
|
||||
)
|
||||
.join('\n')
|
||||
|
||||
return csv
|
||||
}
|
||||
|
||||
console.log(generateCSV())
|
||||
41
scripts/generate-folder-icon-index.js
Normal file
41
scripts/generate-folder-icon-index.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
function parsePageContent() {
|
||||
const records = []
|
||||
document.body
|
||||
.querySelector('table')
|
||||
.querySelectorAll('tbody tr')
|
||||
.forEach(tr => {
|
||||
const [name, folderNames, closed, open] = Array.from(tr.querySelectorAll('td'))
|
||||
const names = folderNames.innerHTML.split(', ')
|
||||
records.push({
|
||||
name: name.innerText,
|
||||
names,
|
||||
icon: {
|
||||
closed: getSrc(closed.querySelector('img')),
|
||||
open: getSrc(open.querySelector('img')),
|
||||
},
|
||||
})
|
||||
})
|
||||
return records
|
||||
}
|
||||
|
||||
function getSrc(img) {
|
||||
return img && img.src
|
||||
}
|
||||
|
||||
function generateCSV(records) {
|
||||
const prepend = 'https://github.com/vscode-icons/vscode-icons/raw/master/icons/folder_type_'
|
||||
const append = '.svg?sanitize=true'
|
||||
const separator = ':'
|
||||
const csv = records
|
||||
.map(({ name, names, icon }) =>
|
||||
[
|
||||
name,
|
||||
names.join(separator),
|
||||
// icon.replace(prepend, '').replace(append, ''), // assumption: name is equal to this
|
||||
].join(','),
|
||||
)
|
||||
.join('\n')
|
||||
return csv
|
||||
}
|
||||
|
||||
console.log(generateCSV(parsePageContent()))
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
const octicons = require('@primer/octicons')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const pathToIconsFolder = path.resolve(__dirname, `../src/assets/icons/octicons`)
|
||||
|
||||
function generateIconSVGFiles() {
|
||||
Object.values(octicons).forEach(icon => {
|
||||
fs.writeFile(
|
||||
`${pathToIconsFolder}/${icon.symbol}.svg`,
|
||||
icon.toSVG({ xmlns: 'http://www.w3.org/2000/svg' }),
|
||||
err => {
|
||||
if (err) throw err
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fs.exists(pathToIconsFolder, exists => {
|
||||
if (exists) {
|
||||
fs.lstat(pathToIconsFolder, (err, stats) => {
|
||||
if (err) throw err
|
||||
if (!stats.isDirectory()) {
|
||||
throw new Error(`${pathToIconsFolder} is not a folder!`)
|
||||
}
|
||||
generateIconSVGFiles()
|
||||
})
|
||||
} else {
|
||||
fs.mkdir(pathToIconsFolder, err => {
|
||||
if (err) throw err
|
||||
generateIconSVGFiles()
|
||||
})
|
||||
}
|
||||
})
|
||||
276
scripts/language-id-ext.json
Normal file
276
scripts/language-id-ext.json
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
{
|
||||
"actionscript": { "ids": "nextgenas", "exts": "as" },
|
||||
"ada": { "ids": "ada", "exts": "ada" },
|
||||
"advpl": { "ids": "advpl", "exts": "prw" },
|
||||
"affectscript": { "ids": "affectscript", "exts": "affect" },
|
||||
"al": { "ids": "al", "exts": "al" },
|
||||
"ansible": { "ids": "ansible", "exts": "ansible" },
|
||||
"antlr": { "ids": "antlr", "exts": "g4" },
|
||||
"anyscript": { "ids": "anyscript", "exts": "any" },
|
||||
"apache": { "ids": "apacheconf", "exts": "htaccess" },
|
||||
"apex": { "ids": "apex", "exts": "cls" },
|
||||
"apib": { "ids": "apiblueprint", "exts": "apib" },
|
||||
"apl": { "ids": "apl", "exts": "apl" },
|
||||
"applescript": { "ids": "applescript", "exts": "applescript" },
|
||||
"asciidoc": { "ids": "asciidoc", "exts": "adoc" },
|
||||
"asp": { "ids": ["asp", "asp (html)"], "exts": "asp" },
|
||||
"assembly": { "ids": ["arm", "asm"], "exts": "asm" },
|
||||
"ats": { "ids": ["ats"], "exts": "ats" },
|
||||
"autohotkey": { "ids": "ahk", "exts": "ahk" },
|
||||
"autoit": { "ids": "autoit", "exts": "au3" },
|
||||
"avro": { "ids": "avro", "exts": "avcs" },
|
||||
"azcli": { "ids": "azcli", "exts": "azcli" },
|
||||
"azurepipelines": {
|
||||
"ids": "azure-pipelines",
|
||||
"exts": "azure-pipelines.yml"
|
||||
},
|
||||
"ballerina": { "ids": "ballerina", "exts": "bal" },
|
||||
"bat": { "ids": "bat", "exts": "bat" },
|
||||
"bazel": { "ids": "bazel", "exts": "bzl" },
|
||||
"befunge": { "ids": ["befunge", "befunge98"], "exts": "bf" },
|
||||
"bibtex": { "ids": "bibtex", "exts": "bib" },
|
||||
"biml": { "ids": "biml", "exts": "biml" },
|
||||
"blade": { "ids": ["blade", "laravel-blade"], "exts": "blade.php" },
|
||||
"bolt": { "ids": "bolt", "exts": "bolt" },
|
||||
"c": { "ids": "c", "exts": "c" },
|
||||
"c_al": { "ids": "c-al", "exts": "cal" },
|
||||
"cabal": { "ids": "cabal", "exts": "cabal" },
|
||||
"caddyfile": { "ids": "caddyfile", "exts": "Caddyfile" },
|
||||
"ceylon": { "ids": "ceylon", "exts": "ceylon" },
|
||||
"cfc": { "ids": "cfc", "exts": "cfc" },
|
||||
"cfm": { "ids": "cfmhtml", "exts": "cfm" },
|
||||
"clojure": { "ids": "clojure", "exts": "clojure" },
|
||||
"clojurescript": { "ids": "clojurescript", "exts": "clojurescript" },
|
||||
"cmake": { "ids": "cmake", "exts": "cmake" },
|
||||
"cmakecache": { "ids": "cmake-cache", "exts": "CMakeCache.txt" },
|
||||
"cobol": { "ids": "cobol", "exts": "cbl" },
|
||||
"coffeescript": { "ids": "coffeescript", "exts": "coffee" },
|
||||
"coldfusion": { "ids": ["cfml", "lang-cfml"], "exts": "cfml" },
|
||||
"confluence": { "ids": ["confluence"], "exts": "confluence" },
|
||||
"cookbook": { "ids": "cookbook", "exts": "ckbk" },
|
||||
"cpp": { "ids": "cpp", "exts": "cpp" },
|
||||
"crystal": { "ids": "crystal", "exts": "cr" },
|
||||
"csharp": { "ids": "csharp", "exts": "cs" },
|
||||
"css": { "ids": "css", "exts": "css" },
|
||||
"cucumber": { "ids": "feature", "exts": "feature" },
|
||||
"cuda": { "ids": "cuda", "exts": "cu" },
|
||||
"cython": { "ids": "cython", "exts": "pyx" },
|
||||
"dal": { "ids": "dal", "exts": "dal" },
|
||||
"dart": { "ids": "dart", "exts": "dart" },
|
||||
"django": { "ids": ["django-html", "django-txt"], "exts": "html" },
|
||||
"diff": { "ids": "diff", "exts": "diff" },
|
||||
"dlang": { "ids": ["d", "dscript", "dml", "diet"], "exts": "d" },
|
||||
"dockerfile": { "ids": "dockerfile", "exts": "dockerfile" },
|
||||
"dockerignore": { "ids": "ignore", "exts": "dockerignore" },
|
||||
"doctex": { "ids": "doctex", "exts": "dtx" },
|
||||
"dotenv": { "ids": "dotenv", "exts": "env" },
|
||||
"dotjs": { "ids": "dotjs", "exts": "dot" },
|
||||
"doxygen": { "ids": "doxygen", "exts": "dox" },
|
||||
"drools": { "ids": "drools", "exts": "drl" },
|
||||
"dylanlang": { "ids": ["dylan", "dylan-lid"], "exts": "dylan" },
|
||||
"dustjs": { "ids": "dustjs", "exts": "dust" },
|
||||
"edge": { "ids": "edge", "exts": "edge" },
|
||||
"eex": { "ids": ["eex", "html-eex"], "exts": "eex" },
|
||||
"elastic": { "ids": "es", "exts": "es" },
|
||||
"elixir": { "ids": "elixir", "exts": "ex" },
|
||||
"elm": { "ids": "elm", "exts": "elm" },
|
||||
"erb": { "ids": "erb", "exts": "erb" },
|
||||
"erlang": { "ids": "erlang", "exts": "erl" },
|
||||
"falcon": { "ids": "falcon", "exts": "falcon" },
|
||||
"fortran": {
|
||||
"ids": ["fortran", "fortran-modern", "FortranFreeForm", "fortran_fixed-form"],
|
||||
"exts": "f"
|
||||
},
|
||||
"freemarker": { "ids": "ftl", "exts": "ftl" },
|
||||
"fsharp": { "ids": "fsharp", "exts": "fs" },
|
||||
"galen": { "ids": "galen", "exts": "gspec" },
|
||||
"gamemaker": { "ids": "gml-gms", "exts": "gml" },
|
||||
"gamemaker2": { "ids": "gml-gms2", "exts": "gml" },
|
||||
"gamemaker81": { "ids": "gml-gm81", "exts": "gml" },
|
||||
"gcode": { "ids": "gcode", "exts": "gcode" },
|
||||
"git": { "ids": ["git-commit", "git-rebase"], "exts": "git" },
|
||||
"glsl": { "ids": "glsl", "exts": "glsl" },
|
||||
"go": { "ids": "go", "exts": "go" },
|
||||
"godot": { "ids": "gdscript", "exts": "gd" },
|
||||
"graphql": { "ids": "graphql", "exts": "gql" },
|
||||
"graphviz": { "ids": "dot", "exts": "gv" },
|
||||
"groovy": { "ids": "groovy", "exts": "groovy" },
|
||||
"haml": { "ids": "haml", "exts": "haml" },
|
||||
"handlebars": { "ids": "handlebars", "exts": "hbs" },
|
||||
"harbour": { "ids": "harbour", "exts": "prg" },
|
||||
"haskell": { "ids": "haskell", "exts": "hs" },
|
||||
"haxe": { "ids": ["haxe", "hxml", "Haxe AST dump"], "exts": "haxe" },
|
||||
"helm": { "ids": "helm", "exts": "helm.tpl" },
|
||||
"hjson": { "ids": "hjson", "exts": "hjson" },
|
||||
"hlsl": { "ids": "hlsl", "exts": "hlsl" },
|
||||
"homeassistant": { "ids": "home-assistant", "exts": "yaml" },
|
||||
"hosts": { "ids": "hosts", "exts": "hosts" },
|
||||
"html": { "ids": "html", "exts": "html" },
|
||||
"http": { "ids": "http", "exts": "http" },
|
||||
"hunspell": { "ids": ["hunspell.aff", "hunspell.dic"], "exts": "aff" },
|
||||
"icl": { "ids": "icl", "exts": "icl" },
|
||||
"imba": { "ids": "imba", "exts": "imba" },
|
||||
"informix": { "ids": "4GL", "exts": "4gl" },
|
||||
"ini": { "ids": "ini", "exts": "ini" },
|
||||
"ink": { "ids": "ink", "exts": "ink" },
|
||||
"innosetup": { "ids": "innosetup", "exts": "iss" },
|
||||
"io": { "ids": "io", "exts": "io" },
|
||||
"latex": { "ids": "latex", "exts": "tex" },
|
||||
"janet": { "ids": "janet", "exts": "janet" },
|
||||
"java": { "ids": "java", "exts": "java" },
|
||||
"javascript": { "ids": "javascript", "exts": "js" },
|
||||
"javascriptreact": { "ids": "javascriptreact", "exts": "jsx" },
|
||||
"jekyll": { "ids": "jekyll", "exts": "jekyll" },
|
||||
"jenkins": {
|
||||
"ids": ["jenkins", "declarative", "jenkinsfile"],
|
||||
"exts": "jenkins"
|
||||
},
|
||||
"jinja": { "ids": "jinja", "exts": "jinja" },
|
||||
"json": { "ids": "json", "exts": "json" },
|
||||
"jsonc": { "ids": "jsonc", "exts": "jsonc" },
|
||||
"jsonnet": { "ids": "jsonnet", "exts": "jsonnet" },
|
||||
"json5": { "ids": "json5", "exts": "json5" },
|
||||
"julia": { "ids": ["julia", "juliamarkdown"], "exts": "jl" },
|
||||
"iodine": { "ids": "iodine", "exts": "id" },
|
||||
"kivy": { "ids": "kivy", "exts": "kv" },
|
||||
"kos": { "ids": "kos", "exts": "ks" },
|
||||
"kotlin": { "ids": "kotlin", "exts": "kt" },
|
||||
"less": { "ids": "less", "exts": "less" },
|
||||
"lisp": { "ids": "lisp", "exts": "lisp" },
|
||||
"literatehaskell": { "ids": ["literate haskell"], "exts": "lhs" },
|
||||
"lolcode": { "ids": "lolcode", "exts": "lol" },
|
||||
"lsl": { "ids": "lsl", "exts": "lsl" },
|
||||
"lua": { "ids": "lua", "exts": "lua" },
|
||||
"makefile": { "ids": "makefile", "exts": "mk" },
|
||||
"markdown": { "ids": "markdown", "exts": "md" },
|
||||
"marko": { "ids": "marko", "exts": "marko" },
|
||||
"matlab": { "ids": "matlab", "exts": "mat" },
|
||||
"maxscript": { "ids": "maxscript", "exts": "ms" },
|
||||
"mediawiki": { "ids": "mediawiki", "exts": "mediawiki" },
|
||||
"mel": { "ids": "mel", "exts": "mel" },
|
||||
"meson": { "ids": "meson", "exts": "meson.build" },
|
||||
"mjml": { "ids": "mjml", "exts": "mjml" },
|
||||
"mlang": { "ids": ["mlang", "powerquerymlanguage"], "exts": "pq" },
|
||||
"mojolicious": { "ids": "mojolicious", "exts": "ep" },
|
||||
"mongo": { "ids": "mongo", "exts": "mongo" },
|
||||
"mson": { "ids": "mson", "exts": "mson" },
|
||||
"nearley": { "ids": "nearley", "exts": "ne" },
|
||||
"nim": { "ids": ["nim", "nimble"], "exts": "nim" },
|
||||
"nsis": { "ids": ["nsis", "nfl", "nsl", "bridlensis"], "exts": "nsi" },
|
||||
"nunjucks": { "ids": "nunjucks", "exts": "nunjucks" },
|
||||
"objectivec": { "ids": "objective-c", "exts": "m" },
|
||||
"objectivecpp": { "ids": "objective-cpp", "exts": "mm" },
|
||||
"ocaml": { "ids": ["ocaml", "ocamllex", "menhir"], "exts": "ml" },
|
||||
"openEdge": { "ids": "abl", "exts": "w" },
|
||||
"openHAB": { "ids": "openhab", "exts": "things" },
|
||||
"pascal": { "ids": ["pascal", "objectpascal"], "exts": "pas" },
|
||||
"pddl": { "ids": "pddl", "exts": "pddl" },
|
||||
"pddlplan": { "ids": "plan", "exts": "plan" },
|
||||
"pddlhappenings": { "ids": "happenings", "exts": "happenings" },
|
||||
"perl": { "ids": "perl", "exts": "pl" },
|
||||
"perl6": { "ids": "perl6", "exts": "pl6" },
|
||||
"pgsql": { "ids": "pgsql", "exts": "pgsql" },
|
||||
"php": { "ids": "php", "exts": "php" },
|
||||
"pine": { "ids": "pine", "exts": "pine" },
|
||||
"pip": { "ids": "pip-requirements", "exts": "requirements.txt" },
|
||||
"plaintext": { "ids": "plaintext", "exts": "txt" },
|
||||
"platformio": {
|
||||
"ids": ["platformio-debug.disassembly", "platformio-debug.memoryview", "platformio-debug.asm"],
|
||||
"exts": "dbgasm"
|
||||
},
|
||||
"plsql": { "ids": ["plsql", "oracle"], "exts": "ddl" },
|
||||
"polymer": { "ids": "polymer", "exts": "polymer" },
|
||||
"pony": { "ids": "pony", "exts": "pony" },
|
||||
"postcss": { "ids": "postcss", "exts": "pcss" },
|
||||
"powershell": { "ids": "powershell", "exts": "ps1" },
|
||||
"prisma": { "ids": "prisma", "exts": "prisma" },
|
||||
"processinglang": { "ids": "pde", "exts": "pde" },
|
||||
"prolog": { "ids": "prolog", "exts": "pro" },
|
||||
"prometheus": { "ids": "prometheus", "exts": "rules" },
|
||||
"properties": { "ids": "properties", "exts": "properties" },
|
||||
"protobuf": { "ids": ["proto3", "proto"], "exts": "proto" },
|
||||
"pug": { "ids": "jade", "exts": "pug" },
|
||||
"puppet": { "ids": "puppet", "exts": "pp" },
|
||||
"purescript": { "ids": "purescript", "exts": "purs" },
|
||||
"pyret": { "ids": "pyret", "exts": "arr" },
|
||||
"python": { "ids": "python", "exts": "py" },
|
||||
"qlik": { "ids": "qlik", "exts": "qvs" },
|
||||
"qml": { "ids": "qml", "exts": "qml" },
|
||||
"qsharp": { "ids": "qsharp", "exts": "qs" },
|
||||
"r": { "ids": "r", "exts": "r" },
|
||||
"racket": { "ids": "racket", "exts": "rkt" },
|
||||
"razor": { "ids": ["razor", "aspnetcorerazor"], "exts": "cshtml" },
|
||||
"raml": { "ids": "raml", "exts": "raml" },
|
||||
"reason": { "ids": "reason", "exts": "re" },
|
||||
"red": { "ids": "red", "exts": "red" },
|
||||
"restructuredtext": { "ids": "restructuredtext", "exts": "rst" },
|
||||
"riot": { "ids": "riot", "exts": "tag" },
|
||||
"robot": { "ids": "robot", "exts": "robot" },
|
||||
"ruby": { "ids": "ruby", "exts": "rb" },
|
||||
"rust": { "ids": "rust", "exts": "rs" },
|
||||
"san": { "ids": "san", "exts": "san" },
|
||||
"sbt": { "ids": "sbt", "exts": "sbt" },
|
||||
"scala": { "ids": "scala", "exts": "scala" },
|
||||
"scilab": { "ids": "scilab", "exts": "sce" },
|
||||
"scss": { "ids": "scss", "exts": "scss" },
|
||||
"sdlang": { "ids": "sdl", "exts": "sdl" },
|
||||
"shaderlab": { "ids": "shaderlab", "exts": "shader" },
|
||||
"shellscript": { "ids": "shellscript", "exts": "sh" },
|
||||
"slang": { "ids": "slang", "exts": "slang" },
|
||||
"slice": { "ids": ["slice"], "exts": "ice" },
|
||||
"slim": { "ids": ["slim"], "exts": "slim" },
|
||||
"silverstripe": { "ids": "silverstripe", "exts": "ss" },
|
||||
"skipper": { "ids": ["eskip"], "exts": "eskip" },
|
||||
"smarty": { "ids": ["smarty"], "exts": "tpl" },
|
||||
"snort": { "ids": ["snort"], "exts": "snort" },
|
||||
"solidity": { "ids": ["solidity"], "exts": "sol" },
|
||||
"sqf": { "ids": "sqf", "exts": "sqf" },
|
||||
"sql": { "ids": "sql", "exts": "sql" },
|
||||
"squirrel": { "ids": "squirrel", "exts": "nut" },
|
||||
"stan": { "ids": "stan", "exts": "stan" },
|
||||
"stata": { "ids": "stata", "exts": "do" },
|
||||
"stencil": { "ids": "stencil", "exts": "stencil" },
|
||||
"stencilhtml": { "ids": "stencil-html", "exts": "html.stencil" },
|
||||
"stylable": { "ids": "stylable", "exts": "st.css" },
|
||||
"styled": { "ids": "source.css.styled", "exts": "styled" },
|
||||
"stylus": { "ids": "stylus", "exts": "styl" },
|
||||
"svelte": { "ids": "svelte", "exts": "svelte" },
|
||||
"swagger": { "ids": ["Swagger", "swagger"], "exts": "swagger" },
|
||||
"swift": { "ids": "swift", "exts": "swift" },
|
||||
"swig": { "ids": "swig", "exts": "swig" },
|
||||
"systemd": { "ids": "systemd-unit-file", "exts": "link" },
|
||||
"systemverilog": { "ids": "systemverilog", "exts": "sv" },
|
||||
"t4": { "ids": "t4", "exts": "tt" },
|
||||
"templatetoolkit": { "ids": "tt", "exts": "tt3" },
|
||||
"tera": { "ids": "tera", "exts": "tera" },
|
||||
"terraform": { "ids": "terraform", "exts": "tf" },
|
||||
"tex": { "ids": "tex", "exts": "sty" },
|
||||
"textile": { "ids": "textile", "exts": "textile" },
|
||||
"textmatejson": { "ids": "json-tmlanguage", "exts": "JSON-tmLanguage" },
|
||||
"textmateyaml": { "ids": "yaml-tmlanguage", "exts": "YAML-tmLanguage" },
|
||||
"toml": { "ids": "toml", "exts": "toml" },
|
||||
"ttcn": { "ids": "ttcn", "exts": "ttcn3" },
|
||||
"twig": { "ids": "twig", "exts": "twig" },
|
||||
"typescript": { "ids": "typescript", "exts": "ts" },
|
||||
"typescriptreact": { "ids": "typescriptreact", "exts": "tsx" },
|
||||
"typo3": { "ids": "typoscript", "exts": "typoscript" },
|
||||
"vb": { "ids": "vb", "exts": "vb" },
|
||||
"vba": { "ids": "vba", "exts": "cls" },
|
||||
"vbscript": { "ids": "vbscript", "exts": "wsf" },
|
||||
"velocity": { "ids": "velocity", "exts": "vm" },
|
||||
"verilog": { "ids": "verilog", "exts": "v" },
|
||||
"vhdl": { "ids": "vhdl", "exts": "vhdl" },
|
||||
"viml": { "ids": "viml", "exts": "vim" },
|
||||
"vlang": { "ids": "v", "exts": "v" },
|
||||
"volt": { "ids": "volt", "exts": "volt" },
|
||||
"vue": { "ids": "vue", "exts": "vue" },
|
||||
"wasm": { "ids": ["wasm", "wat"], "exts": "wasm" },
|
||||
"wolfram": { "ids": "wolfram", "exts": "wl" },
|
||||
"wurst": { "ids": ["wurstlang", "wurst"], "exts": "wurst" },
|
||||
"wxml": { "ids": "wxml", "exts": "wxml" },
|
||||
"xml": { "ids": "xml", "exts": "xml" },
|
||||
"xquery": { "ids": "xquery", "exts": "xquery" },
|
||||
"xsl": { "ids": "xsl", "exts": "xsl" },
|
||||
"yaml": { "ids": "yaml", "exts": "yaml" },
|
||||
"yang": { "ids": "yang", "exts": "yang" }
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
#!/bin/sh
|
||||
rm -rf dist
|
||||
yarn build
|
||||
|
||||
GIT_SHA=$(git rev-parse HEAD)
|
||||
VERSION=v$(node scripts/get-version.js)
|
||||
echo "Got version $VERSION"
|
||||
|
||||
# sentry
|
||||
git push # make sure sentry can retrieve current commit on remote
|
||||
yarn sentry-cli releases new "$VERSION"
|
||||
yarn sentry-cli releases set-commits "$VERSION" --auto
|
||||
yarn sentry-cli releases files "$VERSION" upload-sourcemaps dist --no-rewrite
|
||||
yarn sentry-cli releases finalize "$VERSION"
|
||||
|
||||
cd dist
|
||||
rm -f ./gitako.zip
|
||||
zip -r gitako.zip * -x *.map
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
import * as Sentry from '@sentry/browser'
|
||||
import { Middleware } from 'driver/connect.js'
|
||||
import { IN_PRODUCTION_MODE } from 'env'
|
||||
import { version } from '../package.json'
|
||||
import { IN_PRODUCTION_MODE, VERSION } from 'env'
|
||||
|
||||
const PUBLIC_KEY = 'd22ec5c9cc874539a51c78388c12e3b0'
|
||||
const PROJECT_ID = '1406497'
|
||||
|
||||
const sentryOptions: Sentry.BrowserOptions = {
|
||||
dsn: `https://${PUBLIC_KEY}@sentry.io/${PROJECT_ID}`,
|
||||
release: `v${version}`,
|
||||
release: VERSION,
|
||||
environment: IN_PRODUCTION_MODE ? 'production' : 'development',
|
||||
// Not safe to activate all integrations in non-Chrome environments where Gitako may not run in top context
|
||||
// https://docs.sentry.io/platforms/javascript/#sdk-integrations
|
||||
|
|
|
|||
648
src/assets/icons/file-icons-index.csv
Normal file
648
src/assets/icons/file-icons-index.csv
Normal file
|
|
@ -0,0 +1,648 @@
|
|||
file,,
|
||||
access,,accdb:accdt:mdb:accda:accdc:accde:accdp:accdr:accdu:ade:adp:laccdb:ldb:mam:maq:mdw
|
||||
access2,,accdb:accdt:mdb:accda:accdc:accde:accdp:accdr:accdu:ade:adp:laccdb:ldb:mam:maq:mdw
|
||||
actionscript,,as
|
||||
actionscript2,,as
|
||||
ada,,ada
|
||||
advpl,,prw
|
||||
ai,,ai
|
||||
ai2,,ai
|
||||
al,,al
|
||||
affinitydesigner,,afdesign:affinitydesigner
|
||||
affinityphoto,,afphoto:affinityphoto
|
||||
affinitypublisher,,afpub:affinitypublisher
|
||||
angular,.angular-cli.json:angular-cli.json:angular.json:.angular.json,
|
||||
ng_component_dart,,component.dart
|
||||
ng_component_ts,,component.ts
|
||||
ng_component_js,,component.js
|
||||
ng_controller_ts,,controller.ts
|
||||
ng_controller_js,,controller.js
|
||||
ng_directive_dart,,directive.dart
|
||||
ng_directive_ts,,directive.ts
|
||||
ng_directive_js,,directive.js
|
||||
ng_guard_dart,,guard.dart
|
||||
ng_guard_ts,,guard.ts
|
||||
ng_guard_js,,guard.js
|
||||
ng_module_dart,,module.dart
|
||||
ng_module_ts,,module.ts
|
||||
ng_module_js,,module.js
|
||||
ng_pipe_dart,,pipe.dart
|
||||
ng_pipe_ts,,pipe.ts
|
||||
ng_pipe_js,,pipe.js
|
||||
ng_routing_dart,,routing.dart
|
||||
ng_routing_ts,,routing.ts
|
||||
ng_routing_js,,routing.js
|
||||
ng_routing_dart,app-routing.module.dart,
|
||||
ng_routing_ts,app-routing.module.ts,
|
||||
ng_routing_js,app-routing.module.js,
|
||||
ng_smart_component_dart,,page.dart:container.dart
|
||||
ng_smart_component_ts,,page.ts:container.ts
|
||||
ng_smart_component_js,,page.js:container.js
|
||||
ng_service_dart,,service.dart
|
||||
ng_service_ts,,service.ts
|
||||
ng_service_js,,service.js
|
||||
ng_interceptor_dart,,interceptor.dart
|
||||
ng_interceptor_ts,,interceptor.ts
|
||||
ng_interceptor_js,,interceptor.js
|
||||
ng_component_ts2,,component.ts
|
||||
ng_component_js2,,component.js
|
||||
ng_directive_ts2,,directive.ts
|
||||
ng_directive_js2,,directive.js
|
||||
ng_module_ts2,,module.ts
|
||||
ng_module_js2,,module.js
|
||||
ng_pipe_ts2,,pipe.ts
|
||||
ng_pipe_js2,,pipe.js
|
||||
ng_routing_ts2,,routing.ts
|
||||
ng_routing_js2,,routing.js
|
||||
ng_routing_ts2,app-routing.module.ts,
|
||||
ng_routing_js2,app-routing.module.js,
|
||||
ng_smart_component_ts2,,page.ts:container.ts
|
||||
ng_smart_component_js2,,page.js:container.js
|
||||
ng_service_ts2,,service.ts
|
||||
ng_service_js2,,service.js
|
||||
affectscript,,affect
|
||||
ansible,,ansible
|
||||
antlr,,g4
|
||||
anyscript,,any
|
||||
apache,,htaccess
|
||||
apex,,cls
|
||||
apib,,apib
|
||||
apl,,apl
|
||||
applescript,,applescript
|
||||
appveyor,appveyor.yml:.appveyor.yml,
|
||||
arduino,,ino:pde
|
||||
asciidoc,,adoc
|
||||
asp,,asp:asp
|
||||
aspx,,aspx:ascx
|
||||
assembly,,asm:asm
|
||||
ats,,ats
|
||||
audio,,aac:act:aiff:amr:ape:au:dct:dss:dvf:flac:gsm:iklax:ivs:m4a:m4b:m4p:mmf:mogg:mp3:mpc:msv:oga:ogg:opus:ra:raw:tta:vox:wav:wma
|
||||
aurelia,aurelia.json,
|
||||
autohotkey,,ahk
|
||||
autoit,,au3
|
||||
avro,,avcs
|
||||
aws,,
|
||||
azure,,azcli
|
||||
azurepipelines,azure-pipelines.yml:.vsts-ci.yml,azure-pipelines.yml
|
||||
babel,.babelrc:babelrc.js:.babelrc.js:babelrc.json:babel.config.js:.babelignore,
|
||||
babel2,.babelrc:babelrc.js:.babelrc.js:babelrc.json:babel.config.js:.babelignore,
|
||||
ballerina,,bal
|
||||
bat,,bat
|
||||
bazaar,.bzrignore,
|
||||
bazel,.bazelrc:bazel.rc:bazel.bazelrc,bzl
|
||||
befunge,,bf:bf
|
||||
biml,,biml
|
||||
binary,,a:app:bin:cmo:cmx:cma:cmxa:cmi:dll:exe:hl:ilk:lib:n:ndll:o:obj:pyc:pyd:pyo:pdb:scpt:scptd:so
|
||||
bithound,.bithoundrc,
|
||||
bitbucketpipeline,bitbucket-pipelines.yml,
|
||||
blade,,blade.php:blade.php
|
||||
bolt,,bolt
|
||||
bower,.bowerrc:bower.json,
|
||||
browserslist,.browserslistrc:browserslist,
|
||||
buckbuild,.buckconfig,
|
||||
bundler,,gemfile:gemfile.lock
|
||||
bundler,gemfile:gemfile.lock,
|
||||
c,,c
|
||||
c2,,c
|
||||
c3,,c
|
||||
c_al,,cal
|
||||
cabal,,cabal
|
||||
caddy,,Caddyfile
|
||||
cake,,cake
|
||||
cakephp,,
|
||||
capacitor,capacitor.config.json,
|
||||
cargo,cargo.toml:cargo.lock,
|
||||
cert,,csr:crt:cer:der:pfx:p12:p7b:p7r:src:crl:sst:stl
|
||||
ceylon,,ceylon
|
||||
cf,,lucee:cfml:cfml
|
||||
cf2,,lucee:cfml:cfml
|
||||
cfc,,cfc
|
||||
cfc2,,cfc
|
||||
cfm,,cfm
|
||||
cfm2,,cfm
|
||||
cheader,,h
|
||||
chef,chefignore:berksfile:berksfile.lock:policyfile,
|
||||
class,,class
|
||||
circleci,circle.yml,
|
||||
clojure,,cjm:cljc:clojure
|
||||
clojurescript,,cljs:clojurescript
|
||||
cloudfoundry,.cfignore,
|
||||
cmake,,cmake:CMakeCache.txt
|
||||
cobol,,cbl
|
||||
codacy,.codacy.yml:.codacy.yaml,
|
||||
codeclimate,.codeclimate.yml,
|
||||
codecov,codecov.yml:.codecov.yml,
|
||||
codekit,,kit
|
||||
codekit,config.codekit:config.codekit2:config.codekit3:.config.codekit:.config.codekit2:.config.codekit3,
|
||||
coffeelint,coffeelint.json:.coffeelintignore,
|
||||
coffeescript,,coffee
|
||||
conan,conanfile.txt:conanfile.py,
|
||||
conda,.condarc,
|
||||
config,,plist:properties:env
|
||||
compass,,
|
||||
composer,composer.json:composer.lock,
|
||||
chef_cookbook,,ckbk
|
||||
confluence,,confluence
|
||||
coveralls,.coveralls.yml,
|
||||
cpp,,cpp
|
||||
cpp2,,cpp
|
||||
cpp3,,cpp
|
||||
cppheader,,hpp
|
||||
crowdin,crowdin.yml,
|
||||
crystal,,cr
|
||||
csharp,,csx:cs
|
||||
csharp2,,csx:cs
|
||||
csproj,,csproj
|
||||
css,,css
|
||||
csscomb,.csscomb.json,
|
||||
csslint,.csslintrc,
|
||||
cssmap,,css.map
|
||||
cucumber,,feature
|
||||
cuda,,cu
|
||||
cython,,pyx
|
||||
cypress,cypress.json:cypress.env.json,
|
||||
cvs,.cvsignore,
|
||||
dal,,dal
|
||||
darcs,.boringignore,
|
||||
dartlang,,dart
|
||||
db,,db
|
||||
dependencies,dependencies.yml,
|
||||
delphi,,pas:pas
|
||||
django,,djt:html:html
|
||||
dlang,,d:d:d:d
|
||||
diff,,diff
|
||||
docker,docker-compose.yml:docker-compose.ci-build.yml:docker-compose.override.yml:docker-compose.vs.debug.yml:docker-compose.vs.release.yml:docker-cloud.yml,dockerfile:dockerignore
|
||||
docker2,docker-compose.yml:docker-compose.ci-build.yml:docker-compose.override.yml:docker-compose.vs.debug.yml:docker-compose.vs.release.yml:docker-cloud.yml,dockerfile:dockerignore
|
||||
dockertest,docker-compose.test.yml,
|
||||
dockertest2,docker-compose.test.yml,
|
||||
docpad,,eco
|
||||
docz,.doczrc:docz.js:docz.json:.docz.js:.docz.json:doczrc.js:doczrc.json:docz.config.js:docz.config.json,
|
||||
dojo,.dojorc,
|
||||
doxygen,,dox
|
||||
drone,.drone.yml:.drone.yml.sig,
|
||||
drools,,drl
|
||||
dotjs,,dot
|
||||
dustjs,,dust
|
||||
dylan,,dylan:dylan
|
||||
editorconfig,.editorconfig,
|
||||
edge,,edge
|
||||
edge2,,edge
|
||||
eex,,eex:eex
|
||||
ejs,,ejs
|
||||
elastic,,es
|
||||
elasticbeanstalk,,
|
||||
elixir,,ex
|
||||
elm,elm-package.json,elm
|
||||
elm2,elm-package.json,elm
|
||||
emacs,,el:elc
|
||||
ember,.ember-cli,
|
||||
ensime,,ensime
|
||||
eps,,eps
|
||||
erb,,erb
|
||||
erlang,emakefile:.emakerfile,erl
|
||||
erlang2,emakefile:.emakerfile,erl
|
||||
eslint,.eslintrc:.eslintignore:.eslintcache:.eslintrc.js:.eslintrc.json:.eslintrc.yaml:.eslintrc.yml,
|
||||
eslint2,.eslintrc:.eslintignore:.eslintcache:.eslintrc.js:.eslintrc.json:.eslintrc.yaml:.eslintrc.yml,
|
||||
excel,,xls:xlsx:xlsm:ods:fods
|
||||
excel2,,xls:xlsx:xlsm:ods:fods
|
||||
falcon,,falcon
|
||||
favicon,favicon.ico,
|
||||
fbx,,fbx
|
||||
firebase,.firebaserc,
|
||||
firebasehosting,firebase.json,
|
||||
firestore,firestore.rules:firestore.indexes.json,
|
||||
flash,,swf:swc
|
||||
fla,,fla
|
||||
floobits,.flooignore,
|
||||
flow,,js.flow
|
||||
flow,.flowconfig,
|
||||
flutter,.flutter-plugins:.metadata,
|
||||
flutter_package,pubspec.lock:pubspec.yaml:.packages,
|
||||
font,,woff:woff2:ttf:otf:eot:pfa:pfb:sfd
|
||||
fortran,,f:f:f:f
|
||||
fossa,.fossaignore,
|
||||
fossil,ignore-glob,
|
||||
fsharp,,fs
|
||||
fsproj,,fsproj
|
||||
freemarker,,ftl
|
||||
fusebox,fuse.js,
|
||||
galen,,gspec
|
||||
galen2,,gspec
|
||||
git,.gitattributes:.gitconfig:.gitignore:.gitmodules:.gitkeep:.mailmap,git:git
|
||||
gamemaker,,gmx:gml
|
||||
gamemaker2,,yy:yyp:gml
|
||||
gamemaker81,,gml
|
||||
gatsby,gatsby-config.js:gatsby-config.ts:gatsby-node.js:gatsby-node.ts:gatsby-browser.js:gatsby-browser.ts:gatsby-ssr.js:gatsby-ssr.ts,
|
||||
gcode,,gcode
|
||||
gitlab,.gitlab-ci.yml,
|
||||
glide,glide.yml,
|
||||
glsl,,glsl
|
||||
go,,go
|
||||
go_package,go.sum:go.mod,
|
||||
godot,,gd
|
||||
gradle,,gradle
|
||||
graphql,.gqlconfig,gql
|
||||
graphviz,,gv
|
||||
greenkeeper,greenkeeper.json,
|
||||
gridsome,gridsome.config.js:gridsome.config.ts:gridsome.server.js:gridsome.server.ts:gridsome.client.js:gridsome.client.ts,
|
||||
groovy,,groovy
|
||||
groovy2,,groovy
|
||||
grunt,gruntfile.js:gruntfile.coffee:gruntfile.ts:gruntfile.babel.js:gruntfile.babel.coffee:gruntfile.babel.ts,
|
||||
gulp,gulpfile.js:gulpfile.coffee:gulpfile.ts:gulpfile.babel.js:gulpfile.babel.coffee:gulpfile.babel.ts,
|
||||
haml,,haml
|
||||
handlebars,,hbs
|
||||
handlebars2,,hbs
|
||||
harbour,,prg
|
||||
haskell,,hs:lhs
|
||||
haskell2,,hs:lhs
|
||||
haxe,haxelib.json,haxe:haxe:haxe
|
||||
haxecheckstyle,checkstyle.json,
|
||||
haxedevelop,,hxproj
|
||||
helix,.p4ignore,
|
||||
helm,,helm.tpl
|
||||
hjson,,hjson
|
||||
hlsl,,hlsl
|
||||
homeassistant,,yaml
|
||||
host,,hosts
|
||||
html,,html
|
||||
htmlhint,.htmlhintrc,
|
||||
http,,http
|
||||
hunspell,,aff:aff
|
||||
husky,.huskyrc:.huskyrc.js:.huskyrc.json:.huskyrc.yaml:.huskyrc.yml,
|
||||
icl,,icl
|
||||
idris,,idr:lidr
|
||||
idrisbin,,ibc
|
||||
idrispkg,,ipkg
|
||||
image,,jpeg:jpg:gif:png:bmp:tiff:ico
|
||||
imba,,imba
|
||||
inc,,inc:include
|
||||
infopath,,infopathxml:xsn:xsf:xtp2
|
||||
informix,,4gl
|
||||
ini,,ini
|
||||
ink,,ink
|
||||
innosetup,,iss
|
||||
ionic,ionic.project:ionic.config.json,
|
||||
jake,jakefile:jakefile.js,
|
||||
janet,,janet
|
||||
jar,,jar
|
||||
java,,java
|
||||
jbuilder,,jbuilder
|
||||
jest,jest.config.js:jest.json:jest.config.json:.jestrc:.jestrc.js:.jestrc.json,
|
||||
jest_snapshot,,js.snap:jsx.snap:ts.snap:tsx.snap
|
||||
jekyll,,jekyll
|
||||
jenkins,,jenkins:jenkins:jenkins
|
||||
jinja,,jinja
|
||||
jpm,.jpmignore,
|
||||
js,,js
|
||||
js_official,,js
|
||||
jsbeautify,.jsbeautifyrc:jsbeautifyrc:.jsbeautify:jsbeautify,
|
||||
jsconfig,jsconfig.json,
|
||||
jshint,.jshintrc:.jshintignore,
|
||||
jsmap,,js.map
|
||||
json,,json:JSON-tmLanguage:jsonc
|
||||
json_official,,json:JSON-tmLanguage:jsonc
|
||||
json2,,json:JSON-tmLanguage:jsonc
|
||||
jsonnet,,jsonnet
|
||||
json5,,json5:json5
|
||||
jsonld,,jsonld:json-ld
|
||||
jsp,,jsp
|
||||
jss,,jss
|
||||
julia,,jl:jl
|
||||
julia2,,jl:jl
|
||||
jupyter,,ipynb
|
||||
io,,io
|
||||
iodine,,id
|
||||
karma,karma.conf.js:karma.conf.coffee:karma.conf.ts,
|
||||
key,,key:pem
|
||||
kite,.kiteignore,
|
||||
kitchenci,.kitchen.yml,
|
||||
kivy,,kv
|
||||
kos,,ks
|
||||
kotlin,,kt
|
||||
layout,,master:layout.html:layout.htm
|
||||
layout,layout.html:layout.htm,
|
||||
lerna,lerna.json,
|
||||
less,,less
|
||||
license,,enc
|
||||
license,license:licence:license.md:license.txt:licence.md:licence.txt,
|
||||
lisp,,lisp
|
||||
lime,,hxp
|
||||
lime,include.xml,
|
||||
lintstagedrc,.lintstagedrc:lint-staged.config.js:.lintstagedrc.js:.lintstagedrc.json:.lintstagedrc.yaml:.lintstagedrc.yml,
|
||||
liquid,,liquid
|
||||
livescript,,ls
|
||||
locale,,
|
||||
log,,log:tlg
|
||||
lolcode,,lol
|
||||
lsl,,lsl
|
||||
lua,,lua
|
||||
lync,,crec:ocrec
|
||||
makefile,,makefile:mk
|
||||
manifest,manifest,
|
||||
manifest_skip,manifest.skip,
|
||||
manifest_bak,manifest.bak,
|
||||
map,,map
|
||||
markdown,,mdown:markdown:md
|
||||
markdownlint,.markdownlint.json,
|
||||
marko,,marko
|
||||
markojs,,marko.js
|
||||
matlab,,fig:mex:mexn:mexrs6:mn:mum:mx:mx3:rwd:slx:slddc:smv:xvc:mat
|
||||
maxscript,,ms
|
||||
maven,maven.config:pom.xml:extensions.xml:settings.xml,
|
||||
maya,,mel
|
||||
mdx,,mdx
|
||||
mediawiki,,mediawiki
|
||||
mercurial,.hgignore,
|
||||
meson,,meson.build
|
||||
meteor,,
|
||||
mjml,,mjml
|
||||
mlang,,pq:pq
|
||||
mocha,mocha.opts:.mocharc.js:.mocharc.json:.mocharc.jsonc:.mocharc.yaml:.mocharc.yml,
|
||||
mojolicious,,ep
|
||||
moleculer,moleculer.config.js:moleculer.config.json:moleculer.config.ts,
|
||||
mongo,,mongo
|
||||
monotone,.mtn-ignore,
|
||||
mson,,mson
|
||||
mustache,,mustache:mst
|
||||
nearly,,ne
|
||||
nestjs,.nest-cli.json:nest-cli.json:nestconfig.json:.nestconfig.json,
|
||||
nest_adapter_js,,adapter.js
|
||||
nest_adapter_ts,,adapter.ts
|
||||
nest_controller_js,,controller.js
|
||||
nest_controller_ts,,controller.ts
|
||||
nest_decorator_js,,decorator.js
|
||||
nest_decorator_ts,,decorator.ts
|
||||
nest_filter_js,,filter.js
|
||||
nest_filter_ts,,filter.ts
|
||||
nest_gateway_js,,gateway.js
|
||||
nest_gateway_ts,,gateway.ts
|
||||
nest_guard_js,,guard.js
|
||||
nest_guard_ts,,guard.ts
|
||||
nest_interceptor_js,,interceptor.js
|
||||
nest_interceptor_ts,,interceptor.ts
|
||||
nest_middleware_js,,middleware.js
|
||||
nest_middleware_ts,,middleware.ts
|
||||
nest_module_js,,module.js
|
||||
nest_module_ts,,module.ts
|
||||
nest_pipe_js,,pipe.js
|
||||
nest_pipe_ts,,pipe.ts
|
||||
nest_service_js,,service.js
|
||||
nest_service_ts,,service.ts
|
||||
netlify,netlify.toml,
|
||||
nginx,nginx.conf,
|
||||
nim,,nim:nim
|
||||
ninja,build.ninja,
|
||||
njsproj,,njsproj
|
||||
node,.node-version:.nvmrc,
|
||||
node2,.node-version:.nvmrc,
|
||||
nodemon,nodemon.json,
|
||||
npm,.npmignore:.npmrc:package.json:package-lock.json:npm-shrinkwrap.json,
|
||||
nsi,,nsi:nsi:nsi:nsi
|
||||
nsri,.nsrirc:.nsriignore:.nsrirc.js:.nsrirc.json:.nsrirc.yaml:.nsrirc.yml:.nsrirc.config.js,
|
||||
nsri-integrity,.integrity.json,
|
||||
nuget,,nupkg:nuspec:psmdcp
|
||||
nunjucks,,nunj:njs:nunjucks
|
||||
nuxt,nuxt.config.js:nuxt.config.ts,
|
||||
nyc,.nycrc:.nycrc.json,
|
||||
objectivec,,m
|
||||
objectivecpp,,mm
|
||||
ocaml,.merlin,ml:ml:ml
|
||||
onenote,,one:onepkg:onetoc:onetoc2:sig
|
||||
opencl,,cl:opencl
|
||||
openHAB,,things
|
||||
org,,org
|
||||
outlook,,pst:bcmx:otm:msg:oft
|
||||
ovpn,,ovpn
|
||||
package,,pkg
|
||||
paket,paket.dependencies:paket.lock:paket.references:paket.template:paket.local,
|
||||
patch,,patch
|
||||
pcl,,pcd
|
||||
pddl,,pddl
|
||||
pddl_plan,,plan
|
||||
pddl_happenings,,happenings
|
||||
pdf,,pdf
|
||||
pdf2,,pdf
|
||||
perl,,pl
|
||||
perl2,,pl
|
||||
perl6,,pl6
|
||||
pgsql,,pgsql
|
||||
photoshop,,psd
|
||||
photoshop2,,psd
|
||||
php,,php1:php2:php3:php4:php5:php6:phps:phpsa:phpt:phtml:phar:php
|
||||
php2,,php1:php2:php3:php4:php5:php6:phps:phpsa:phpt:phtml:phar:php
|
||||
php3,,php1:php2:php3:php4:php5:php6:phps:phpsa:phpt:phtml:phar:php
|
||||
phpcsfixer,.php_cs:.php_cs.dist,
|
||||
phpunit,phpunit:phpunit.xml:phpunit.xml.dist,
|
||||
phraseapp,.phraseapp.yml,
|
||||
pine,,pine
|
||||
pip,pipfile:pipfile.lock,requirements.txt
|
||||
platformio,platformio.ini,dbgasm:dbgasm:dbgasm
|
||||
plantuml,,pu:plantuml:iuml:puml
|
||||
plsql,,ddl:ddl
|
||||
plsql_package,,pck
|
||||
plsql_package_body,,pkb
|
||||
plsql_package_header,,pkh
|
||||
plsql_package_spec,,pks
|
||||
poedit,,po:mo
|
||||
polymer,,polymer
|
||||
pony,,pony
|
||||
postcss,,pcss
|
||||
postcssconfig,.postcssrc:.postcssrc.json:.postcssrc.yml:.postcssrc.js:postcss.config.js,
|
||||
powerpoint,,pot:potx:potm:pps:ppsx:ppsm:ppt:pptx:pptm:pa:ppa:ppam:sldm:sldx
|
||||
powerpoint2,,pot:potx:potm:pps:ppsx:ppsm:ppt:pptx:pptm:pa:ppa:ppam:sldm:sldx
|
||||
powershell,,ps1
|
||||
powershell_psm,,psm1
|
||||
powershell_psd,,psd1
|
||||
powershell_format,,format.ps1xml
|
||||
powershell_types,,types.ps1xml
|
||||
powershell2,,ps1
|
||||
powershell_psm2,,psm1
|
||||
powershell_psd2,,psd1
|
||||
precommit,.pre-commit-config.yaml,
|
||||
prettier,.prettierrc:.prettierignore,
|
||||
prettier,prettier.config.js:prettier.config.ts:prettier.config.coffee,
|
||||
prettier,.prettierrc.js:.prettierrc.json:.prettierrc.yml:.prettierrc.yaml,
|
||||
prisma,,prisma
|
||||
processinglang,,pde
|
||||
procfile,procfile,
|
||||
progress,,w
|
||||
prolog,,pro:P:pro
|
||||
prometheus,,rules
|
||||
protobuf,,proto:proto
|
||||
protractor,protractor.conf.js:protractor.conf.coffee:protractor.conf.ts,
|
||||
publisher,,pub:puz
|
||||
puppet,,pp
|
||||
pug,.jade-lintrc:.pug-lintrc:.jade-lint.json:.pug-lintrc.js:.pug-lintrc.json,pug
|
||||
purescript,,purs
|
||||
pyret,,arr
|
||||
python,,py
|
||||
pyup,.pyup:.pyup.yml,
|
||||
q,,q
|
||||
qbs,,qbs
|
||||
qlikview,,qvd:qvw:qvs
|
||||
qml,,qml
|
||||
qmldir,qmldir,
|
||||
qsharp,,qs
|
||||
quasar,quasar.conf.js,
|
||||
r,,r
|
||||
racket,,rkt
|
||||
rails,,
|
||||
rake,,rake
|
||||
rake,rakefile,
|
||||
raml,,raml
|
||||
razor,,cshtml:cshtml
|
||||
razzle,razzle.config.js,
|
||||
reactjs,,jsx
|
||||
reacttemplate,,rt
|
||||
reactts,,tsx
|
||||
reason,,re
|
||||
red,,red
|
||||
registry,,reg
|
||||
rehype,.rehyperc:.rehypeignore:.rehyperc.js:.rehyperc.json:.rehyperc.yml:.rehyperc.yaml,
|
||||
remark,.remarkrc:.remarkignore:.remarkrc.js:.remarkrc.json:.remarkrc.yml:.remarkrc.yaml,
|
||||
renovate,.renovaterc:renovate.json:.renovaterc.json,
|
||||
rest,,rst
|
||||
retext,.retextrc:.retextignore:.retextrc.js:.retextrc.json:.retextrc.yml:.retextrc.yaml,
|
||||
riot,,tag
|
||||
robotframework,,robot
|
||||
robots,robots.txt,
|
||||
rollup,rollup.config.js:rollup.config.mjs:rollup.config.coffee:rollup.config.ts:rollup.config.common.js:rollup.config.common.mjs:rollup.config.common.coffee:rollup.config.common.ts:rollup.config.dev.js:rollup.config.dev.mjs:rollup.config.dev.coffee:rollup.config.dev.ts:rollup.config.prod.js:rollup.config.prod.mjs:rollup.config.prod.coffee:rollup.config.prod.ts,
|
||||
rproj,,rproj
|
||||
rspec,.rspec,
|
||||
rubocop,.rubocop.yml:.rubocop_todo.yml,
|
||||
ruby,,rb
|
||||
rust,,rs
|
||||
saltstack,,sls
|
||||
san,,san
|
||||
sass,,sass
|
||||
sbt,,sbt
|
||||
scala,,scala
|
||||
script,,wsf
|
||||
scss,,scssm:scss
|
||||
scilab,,sce
|
||||
sdlang,,sdl
|
||||
sentry,.sentryclirc,
|
||||
serverless,serverless.yml,
|
||||
sequelize,.sequelizerc:.sequelizerc.js:.sequelizerc.json,
|
||||
shaderlab,,shader
|
||||
shell,,fish:sh
|
||||
sketch,,sketch
|
||||
slang,,slang
|
||||
slice,,ice
|
||||
slim,,slim
|
||||
sln,,sln
|
||||
sln2,,sln
|
||||
silverstripe,,ss
|
||||
skipper,,eskip:eskip
|
||||
smarty,,tpl
|
||||
snapcraft,snapcraft.yaml,
|
||||
snort,,snort
|
||||
snyk,.snyk,
|
||||
solidarity,.solidarity:.solidarity.json,
|
||||
solidity,,sol
|
||||
source,,
|
||||
sqf,,sqf
|
||||
sql,,sql
|
||||
sqlite,,sqlite:sqlite3:db3
|
||||
squirrel,,nut
|
||||
sss,,sss
|
||||
stan,,stan
|
||||
stata,,dta:do
|
||||
stencil,,stencil:html.stencil
|
||||
style,,
|
||||
stylelint,.stylelintrc:.stylelintignore:.stylelintcache:stylelint.config.js:stylelint.config.json:stylelint.config.yaml:stylelint.config.yml:stylelint.config.ts:.stylelintrc.js:.stylelintrc.json:.stylelintrc.yaml:.stylelintrc.yml:.stylelintrc.ts,
|
||||
stylable,,st.css
|
||||
styled,,styled
|
||||
stylus,,styl
|
||||
storyboard,,storyboard
|
||||
storybook,,story.js:story.jsx:story.ts:story.tsx:stories.js:stories.jsx:stories.ts:stories.tsx
|
||||
subversion,.svnignore,
|
||||
svelte,,svelte
|
||||
svg,,svg
|
||||
swagger,,swagger:swagger
|
||||
swift,package.pins,swift
|
||||
swig,,swig
|
||||
symfony,symfony.lock,
|
||||
systemd,,link
|
||||
systemverilog,,sv
|
||||
t4tt,,tt
|
||||
tailwind,tailwind.js:tailwind.coffee:tailwind.ts:tailwind.config.js:tailwind.config.coffee:tailwind.config.ts,
|
||||
tt,,tt2:tt3
|
||||
tcl,,tcl:exp
|
||||
tera,,tera
|
||||
terraform,,tfstate:tf
|
||||
test,,tst
|
||||
testjs,,test.js:test.jsx:test.mjs:spec.js:spec.jsx:spec.mjs
|
||||
testts,,test.ts:test.tsx:spec.ts:spec.tsx:e2e-test.ts:e2e-test.tsx:e2e-spec.ts:e2e-spec.tsx
|
||||
tex,,texi:tikz:sty:tex:bib:dtx
|
||||
text,,csv:txt
|
||||
textile,,textile
|
||||
tfs,.tfignore,
|
||||
todo,,todo
|
||||
toml,,toml
|
||||
tox,.ini,
|
||||
travis,.travis.yml,
|
||||
tsconfig,tsconfig.json:tsconfig.app.json:tsconfig.spec.json:tsconfig.e2e.json:tsconfig.base.json:tsconfig.common.json:tsconfig.dev.json:tsconfig.development.json:tsconfig.staging.json:tsconfig.test.json:tsconfig.prod.json:tsconfig.production.json,
|
||||
tslint,tslint.json:tslint.yaml:tslint.yml,
|
||||
ttcn,,ttcn3
|
||||
twig,,twig
|
||||
typescript,,ts
|
||||
typescript_official,,ts
|
||||
typescriptdef,,d.ts
|
||||
typescriptdef_official,,d.ts
|
||||
typo3,,typoscript
|
||||
unibeautify,.unibeautifyrc:unibeautify.config.js:.unibeautifyrc.js:.unibeautifyrc.json:.unibeautifyrc.yaml:.unibeautifyrc.yml,
|
||||
vagrant,vagrantfile,
|
||||
vala,,vala
|
||||
vapi,,vapi
|
||||
vash,,vash
|
||||
vb,,vb
|
||||
vba,,cls
|
||||
vbhtml,,vbhtml
|
||||
vbproj,,vbproj
|
||||
vcxproj,,vcxproj
|
||||
velocity,,vm
|
||||
verilog,,v
|
||||
vhdl,,vhdl
|
||||
video,,3g2:3gp:asf:amv:avi:divx:qt:f4a:f4b:f4p:f4v:flv:m2v:m4v:mkv:mk3d:mov:mp2:mp4:mpe:mpeg:mpeg2:mpg:mpv:nsv:ogv:rm:rmvb:svi:vob:webm:wmv
|
||||
view,,
|
||||
vim,.vimrc:.gvimrc,vim
|
||||
vlang,,v
|
||||
volt,,volt
|
||||
vscode,.vscodeignore:launch.json:tasks.json:vscodeignore.json,
|
||||
vscode2,.vscodeignore:launch.json:tasks.json:vscodeignore.json,
|
||||
vscode3,.vscodeignore:launch.json:tasks.json:vscodeignore.json,
|
||||
vscode-insiders,.vscodeignore:launch.json:tasks.json:vscodeignore.json,
|
||||
vsix,,vsix
|
||||
vsixmanifest,,vsixmanifest
|
||||
vue,,vue
|
||||
vueconfig,.vuerc:vue.config.js,
|
||||
wallaby,wallaby.json:wallaby.js:wallaby.ts:wallaby.coffee:wallaby.conf.json:wallaby.conf.js:wallaby.conf.ts:wallaby.conf.coffee:.wallaby.json:.wallaby.js:.wallaby.ts:.wallaby.coffee:.wallaby.conf.json:.wallaby.conf.js:.wallaby.conf.ts:.wallaby.conf.coffee,
|
||||
watchmanconfig,.watchmanconfig,
|
||||
wasm,,wasm:wasm:wasm
|
||||
webp,,webp
|
||||
webpack,webpack.base.conf.js:webpack.base.conf.coffee:webpack.base.conf.ts:webpack.common.js:webpack.common.coffee:webpack.common.ts:webpack.config.js:webpack.config.coffee:webpack.config.ts:webpack.config.base.js:webpack.config.base.coffee:webpack.config.base.ts:webpack.config.common.js:webpack.config.common.coffee:webpack.config.common.ts:webpack.config.dev.js:webpack.config.dev.coffee:webpack.config.dev.ts:webpack.config.development.js:webpack.config.development.coffee:webpack.config.development.ts:webpack.config.staging.js:webpack.config.staging.coffee:webpack.config.staging.ts:webpack.config.test.js:webpack.config.test.coffee:webpack.config.test.ts:webpack.config.prod.js:webpack.config.prod.coffee:webpack.config.prod.ts:webpack.config.production.js:webpack.config.production.coffee:webpack.config.production.ts:webpack.config.babel.js:webpack.config.babel.coffee:webpack.config.babel.ts:webpack.config.base.babel.js:webpack.config.base.babel.coffee:webpack.config.base.babel.ts:webpack.config.common.babel.js:webpack.config.common.babel.coffee:webpack.config.common.babel.ts:webpack.config.dev.babel.js:webpack.config.dev.babel.coffee:webpack.config.dev.babel.ts:webpack.config.development.babel.js:webpack.config.development.babel.coffee:webpack.config.development.babel.ts:webpack.config.staging.babel.js:webpack.config.staging.babel.coffee:webpack.config.staging.babel.ts:webpack.config.test.babel.js:webpack.config.test.babel.coffee:webpack.config.test.babel.ts:webpack.config.prod.babel.js:webpack.config.prod.babel.coffee:webpack.config.prod.babel.ts:webpack.config.production.babel.js:webpack.config.production.babel.coffee:webpack.config.production.babel.ts:webpack.dev.js:webpack.dev.coffee:webpack.dev.ts:webpack.dev.conf.js:webpack.dev.conf.coffee:webpack.dev.conf.ts:webpack.prod.js:webpack.prod.coffee:webpack.prod.ts:webpack.prod.conf.js:webpack.prod.conf.coffee:webpack.prod.conf.ts:webpack.mix.js:webpack.mix.coffee:webpack.mix.ts:webpack.test.conf.js:webpack.test.conf.coffee:webpack.test.conf.ts,
|
||||
wercker,wercker.yml,
|
||||
wolfram,,wl
|
||||
word,,doc:docx:docm:dot:dotx:dotm:wll
|
||||
word2,,doc:docx:docm:dot:dotx:dotm:wll
|
||||
wpml,wpml-config.xml,
|
||||
wurst,,wurst:wurst
|
||||
wxml,,wxml
|
||||
wxss,,wxss
|
||||
xcode,,xcodeproj
|
||||
xfl,,xfl
|
||||
xib,,xib
|
||||
xliff,,xliff:xlf
|
||||
xml,,pex:tmlanguage:xml
|
||||
xquery,,xquery
|
||||
xsl,,xsl
|
||||
yaml,,yaml:YAML-tmLanguage
|
||||
yamllint,.yamllint,
|
||||
yandex,.yaspellerrc:.yaspeller.json,
|
||||
yang,,yang
|
||||
yarn,yarn.lock:.yarnrc:.yarnclean:.yarn-integrity:.yarn-metadata.json:.yarnignore,
|
||||
yeoman,.yo-rc.json,
|
||||
zeit,now.json:.nowignore,
|
||||
zip,,zip:rar:7z:tar:gz:bzip2:xz:bz2
|
||||
zip2,,zip:rar:7z:tar:gz:bzip2:xz:bz2
|
||||
|
125
src/assets/icons/folder-icons-index.csv
Normal file
125
src/assets/icons/folder-icons-index.csv
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
folder,
|
||||
root_folder,
|
||||
android,android
|
||||
api,api:.api
|
||||
app,app:.app
|
||||
arangodb,arangodb:arango
|
||||
asset,assets:.assets
|
||||
aurelia,aurelia_project
|
||||
audio,audio:.audio:audios:.audios:sound:.sound:sounds:.sounds
|
||||
aws,aws:.aws
|
||||
azure,azure:.azure
|
||||
azurepipelines,azure-pipelines:.azure-pipelines
|
||||
binary,bin:.bin
|
||||
blueprint,blueprint:.blueprint:blueprints:.blueprints
|
||||
bower,bower_components
|
||||
buildkite,.buildkite
|
||||
cake,cake:.cake
|
||||
certificate,certificates:.certificates:certs:certs.
|
||||
chef,chef:.chef
|
||||
circleci,.circleci
|
||||
controller,controllers:.controllers:handlers:.handlers
|
||||
component,components:.components:widgets
|
||||
composer,composer:.composer
|
||||
cli,cli:cmd:command:commands:commandline:console
|
||||
client,client
|
||||
cmake,.cmake:cmake
|
||||
config,config:.config:configs:.configs:configuration:.configuration:configurations:.configurations:setting:.setting:settings:.settings:ini:.ini:initializers:.initializers
|
||||
coverage,coverage
|
||||
css,css:_css
|
||||
cypress,cypress
|
||||
db,db:database:sql:data:repo:repository:repositories
|
||||
debian,debian
|
||||
dist,dist:dists:out:outs:export:exports:build:builds:release:releases:target:targets
|
||||
docker,docker:.docker
|
||||
docs,docs:doc
|
||||
e2e,e2e
|
||||
elasticbeanstalk,.elasticbeanstalk:.ebextensions
|
||||
electron,electron
|
||||
favicon,favicon:favicons
|
||||
flow,flow:flow-typed
|
||||
fonts,fonts:font:fnt
|
||||
gcp,gcp:.gcp
|
||||
git,.git:submodules:.submodules
|
||||
github,.github
|
||||
gitlab,.gitlab
|
||||
gradle,gradle:.gradle
|
||||
graphql,graphql
|
||||
grunt,grunt
|
||||
gulp,gulp:gulpfile.js:gulpfile.coffee:gulpfile.ts:gulpfile.babel.js:gulpfile.babel.coffee:gulpfile.babel.ts
|
||||
haxelib,.haxelib:haxe_libraries
|
||||
helper,helpers:.helpers
|
||||
idea,.idea
|
||||
images,images:image:img:icons:icon:ico:screenshot:screenshots:svg
|
||||
include,include:includes:incl:inc:.include:.includes:.incl:.inc:_include:_includes:_incl:_inc
|
||||
interfaces,interfaces
|
||||
ios,ios
|
||||
js,js
|
||||
json,json
|
||||
json_official,json
|
||||
kubernetes,kubernetes:k8s:kube:kuber:.kubernetes:.k8s:.kube:.kuber
|
||||
less,less:_less
|
||||
library,lib:.lib:library
|
||||
linux,linux
|
||||
locale,lang:language:languages:locale:locales:_locale:_locales:internationalization:globalization:localization:i18n:g11n:l10n
|
||||
log,log:logs
|
||||
macos,macos:darwin
|
||||
mariadb,mariadb:maria
|
||||
maven,.mvn
|
||||
memcached,memcached:.memcached
|
||||
middleware,middleware
|
||||
mjml,mjml:.mjml
|
||||
minikube,minikube:minik8s:minikuber
|
||||
mock,mocks:.mocks:__mocks__
|
||||
model,models:.models:entities:.entities
|
||||
module,modules
|
||||
mongodb,mongodb:mongo
|
||||
mysql,mysqldb:mysql
|
||||
nginx,nginx:conf.d
|
||||
node,node_modules
|
||||
notification,notification:notifications:event:events
|
||||
nuget,.nuget
|
||||
package,package:packages:.package:.packages:pkg
|
||||
paket,.paket
|
||||
php,php
|
||||
platformio,.pio:.pioenvs
|
||||
plugin,plugin:.plugin:plugins:.plugins:extension:.extension:extensions:.extensions
|
||||
private,private:.private
|
||||
public,public:.public
|
||||
python,.venv:.virtualenv
|
||||
redis,redis
|
||||
ravendb,ravendb
|
||||
route,route:routes:_route:_routes:routers
|
||||
redux,redux
|
||||
meteor,.meteor
|
||||
sass,sass:scss:_sass:_scss
|
||||
script,script:scripts
|
||||
server,server
|
||||
services,services
|
||||
src,src:source:sources
|
||||
sso,sso
|
||||
story,stories:__stories__
|
||||
style,style:styles
|
||||
test,tests:.tests:test:.test:__tests__:__test__:spec:.spec:specs:.specs
|
||||
temp,temp:.temp:tmp:.tmp
|
||||
template,template:.template:templates:.templates
|
||||
theme,theme:themes
|
||||
travis,.travis
|
||||
tools,tools:.tools:util:utils
|
||||
typescript,typescript:ts
|
||||
typings,typings:@types
|
||||
typings2,typings:@types
|
||||
vagrant,vagrant:.vagrant
|
||||
video,video:.video:videos:.videos
|
||||
view,html:view:views:layout:layouts:page:pages:_view:_views:_layout:_layouts:_page:_pages
|
||||
vs,.vs
|
||||
vs2,.vs
|
||||
vscode,.vscode:vscode
|
||||
vscode2,.vscode:vscode
|
||||
vscode3,.vscode:vscode
|
||||
vscode_test,.vscode-test
|
||||
vscode_test2,.vscode-test
|
||||
vscode_test3,.vscode-test
|
||||
webpack,webpack
|
||||
windows,windows:win32
|
||||
www,www:wwwroot
|
||||
|
36
src/components/Clippy.tsx
Normal file
36
src/components/Clippy.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import * as React from 'react'
|
||||
import { cx } from 'utils/cx'
|
||||
import { copyElementContent } from 'utils/DOMHelper'
|
||||
|
||||
type Props = {
|
||||
codeSnippetElement: Element
|
||||
}
|
||||
|
||||
const className = 'clippy-wrapper'
|
||||
export const ClippyClassName = className
|
||||
|
||||
export function Clippy({ codeSnippetElement }: Props) {
|
||||
const [status, setStatus] = React.useState<'normal' | 'success' | 'fail'>('normal')
|
||||
React.useEffect(() => {
|
||||
const timer = window.setTimeout(() => {
|
||||
setStatus('normal')
|
||||
}, 1000)
|
||||
return () => window.clearTimeout(timer)
|
||||
}, [status])
|
||||
|
||||
const onClippyClick = React.useCallback(function onClippyClick() {
|
||||
if (copyElementContent(codeSnippetElement)) {
|
||||
setStatus('success')
|
||||
} else {
|
||||
setStatus('fail')
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<button className="clippy" onClick={onClippyClick}>
|
||||
<i className={cx('icon', status)} />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
42
src/components/CopyFileButton.tsx
Normal file
42
src/components/CopyFileButton.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import * as React from 'react'
|
||||
import { cx } from 'utils/cx'
|
||||
import { copyElementContent, getCodeElement } from 'utils/DOMHelper'
|
||||
|
||||
type Props = {}
|
||||
|
||||
const className = 'gitako-copy-file-button'
|
||||
export const copyFileButtonClassName = className
|
||||
|
||||
export function CopyFileButton(props: React.PropsWithChildren<Props>) {
|
||||
const contents = {
|
||||
success: 'Success!',
|
||||
error: 'Copy failed!',
|
||||
normal: 'Copy file',
|
||||
}
|
||||
const [content, setContent] = React.useState(contents.normal)
|
||||
React.useEffect(() => {
|
||||
if (content !== contents.normal) {
|
||||
const timer = setTimeout(() => {
|
||||
setContent(contents.normal)
|
||||
}, 1000)
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [content])
|
||||
return (
|
||||
<a
|
||||
className={cx('btn btn-sm BtnGroup-item copy-file-btn', className)}
|
||||
onClick={() => {
|
||||
const codeElement = getCodeElement()
|
||||
if (codeElement) {
|
||||
if (copyElementContent(codeElement)) {
|
||||
setContent(contents.success)
|
||||
} else {
|
||||
setContent(contents.error)
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
12
src/components/ErrorBoundary.tsx
Normal file
12
src/components/ErrorBoundary.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { raiseError } from 'analytics'
|
||||
import * as React from 'react'
|
||||
|
||||
export class ErrorBoundary extends React.PureComponent {
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
raiseError(error, errorInfo)
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.props.children
|
||||
}
|
||||
}
|
||||
|
|
@ -1,145 +1,95 @@
|
|||
import LoadingIndicator from 'components/LoadingIndicator'
|
||||
import Node from 'components/Node'
|
||||
import SearchBar from 'components/SearchBar'
|
||||
import connect from 'driver/connect'
|
||||
import { FileExplorer as FileExplorerCore } from 'driver/core'
|
||||
import { ConnectorState } from 'driver/core/FileExplorer'
|
||||
import { LoadingIndicator } from 'components/LoadingIndicator'
|
||||
import { Node } from 'components/Node'
|
||||
import { SearchBar } from 'components/SearchBar'
|
||||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import { connect } from 'driver/connect'
|
||||
import { FileExplorerCore } from 'driver/core'
|
||||
import { ConnectorState, Props } from 'driver/core/FileExplorer'
|
||||
import * as React from 'react'
|
||||
import { FixedSizeList as List, ListChildComponentProps } from 'react-window'
|
||||
import cx from 'utils/cx'
|
||||
import { MetaData, TreeData } from 'utils/GitHubHelper'
|
||||
import { usePrevious } from 'utils/hooks'
|
||||
import { useEvent } from 'react-use'
|
||||
import { FixedSizeList as List, ListChildComponentProps, ListProps } from 'react-window'
|
||||
import { cx } from 'utils/cx'
|
||||
import { useOnLocationChange, usePrevious } from 'utils/hooks'
|
||||
import { getCurrentPath } from 'utils/URLHelper'
|
||||
import { TreeNode, VisibleNodes } from 'utils/VisibleNodesGenerator'
|
||||
import Icon from './Icon'
|
||||
import SizeObserver from './SizeObserver'
|
||||
import { Icon } from './Icon'
|
||||
import { SizeObserver } from './SizeObserver'
|
||||
|
||||
export type Props = {
|
||||
treeData?: TreeData
|
||||
metaData: MetaData
|
||||
freeze: boolean
|
||||
compressSingletonFolder: boolean
|
||||
accessToken: string | undefined
|
||||
toggleShowSettings: React.MouseEventHandler
|
||||
}
|
||||
const VisibleNodesContext = React.createContext<VisibleNodes | null>(null)
|
||||
|
||||
class FileExplorer extends React.Component<Props & ConnectorState> {
|
||||
static defaultProps: Partial<Props & ConnectorState> = {
|
||||
freeze: false,
|
||||
searchKey: '',
|
||||
visibleNodes: null,
|
||||
}
|
||||
const RawFileExplorer: React.FC<Props & ConnectorState> = function RawFileExplorer(props) {
|
||||
const { visibleNodes, freeze, onNodeClick, searchKey } = props
|
||||
const {
|
||||
val: { access_token: accessToken, compressSingletonFolder },
|
||||
} = useConfigs()
|
||||
|
||||
componentWillMount() {
|
||||
const { init, setUpTree, treeData, metaData, compressSingletonFolder, accessToken } = this.props
|
||||
React.useEffect(() => {
|
||||
const { init } = props
|
||||
init()
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
const { setUpTree, treeData, metaData } = props
|
||||
setUpTree({ treeData, metaData, compressSingletonFolder, accessToken })
|
||||
}
|
||||
}, [props.setUpTree, props.treeData, compressSingletonFolder, accessToken])
|
||||
|
||||
componentDidMount() {
|
||||
const { execAfterRender } = this.props
|
||||
React.useEffect(() => {
|
||||
const { execAfterRender } = props
|
||||
execAfterRender()
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: Props & ConnectorState) {
|
||||
if (nextProps.treeData !== this.props.treeData) {
|
||||
const { setUpTree, treeData, metaData, compressSingletonFolder, accessToken } = nextProps
|
||||
setUpTree({ treeData, metaData, compressSingletonFolder, accessToken })
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { execAfterRender } = this.props
|
||||
execAfterRender()
|
||||
}
|
||||
|
||||
renderFiles(visibleNodes: VisibleNodes) {
|
||||
const { nodes, focusedNode } = visibleNodes
|
||||
const { searchKey } = this.props
|
||||
const inSearch = searchKey !== ''
|
||||
if (inSearch && nodes.length === 0) {
|
||||
return <label className={'no-results'}>No results found.</label>
|
||||
}
|
||||
return (
|
||||
<SizeObserver className={'files'}>
|
||||
{({ width = 0, height = 0 }) => (
|
||||
<this.ListV focusedNode={focusedNode} nodes={nodes} height={height} width={width} />
|
||||
)}
|
||||
</SizeObserver>
|
||||
)
|
||||
}
|
||||
|
||||
ListV = React.memo<{
|
||||
nodes: TreeNode[]
|
||||
height: number
|
||||
width: number
|
||||
focusedNode: TreeNode | null
|
||||
}>(({ nodes, width, height, focusedNode }) => {
|
||||
const listRef = React.useRef<List>(null)
|
||||
React.useEffect(() => {
|
||||
if (focusedNode && listRef.current) {
|
||||
listRef.current.scrollToItem(nodes.indexOf(focusedNode), 'smart')
|
||||
}
|
||||
}, [listRef.current, focusedNode])
|
||||
|
||||
const lastNodeLength = usePrevious(nodes.length)
|
||||
React.useEffect(() => {
|
||||
if (listRef.current && !focusedNode && lastNodeLength !== nodes.length) {
|
||||
listRef.current.scrollTo(0)
|
||||
}
|
||||
}, [listRef.current, focusedNode, nodes.length])
|
||||
return (
|
||||
<List
|
||||
ref={listRef}
|
||||
itemKey={(index, { nodes }) => {
|
||||
const node = nodes[index]
|
||||
return node && node.path
|
||||
}}
|
||||
itemData={{ nodes }}
|
||||
itemCount={nodes.length}
|
||||
itemSize={35}
|
||||
height={height}
|
||||
width={width}
|
||||
>
|
||||
{this.VirtualNode}
|
||||
</List>
|
||||
)
|
||||
})
|
||||
|
||||
VirtualNode = React.memo<ListChildComponentProps>(({ index, style }) => {
|
||||
const { visibleNodes, onNodeClick } = this.props
|
||||
if (!visibleNodes) return null
|
||||
const { nodes, depths, focusedNode, expandedNodes } = visibleNodes
|
||||
const node = nodes[index]
|
||||
return (
|
||||
<Node
|
||||
style={style}
|
||||
key={node.path}
|
||||
node={node}
|
||||
depth={depths.get(node) || 0}
|
||||
focused={focusedNode === node}
|
||||
expanded={expandedNodes.has(node)}
|
||||
onClick={onNodeClick}
|
||||
renderActions={this.renderActions}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
private renderActions: Node['props']['renderActions'] = node => {
|
||||
const { searchKey, goTo } = this.props
|
||||
return (
|
||||
searchKey && (
|
||||
const renderActions: React.ComponentProps<typeof Node>['renderActions'] = React.useCallback(
|
||||
node =>
|
||||
searchKey ? (
|
||||
<button
|
||||
title={'Reveal in file tree'}
|
||||
className={'go-to-button'}
|
||||
onClick={this.revealNode(goTo, node)}
|
||||
onClick={revealNode(props.goTo, node)}
|
||||
>
|
||||
<Icon type="go-to" />
|
||||
</button>
|
||||
)
|
||||
)
|
||||
}
|
||||
) : null,
|
||||
[searchKey, props.goTo],
|
||||
)
|
||||
|
||||
revealNode(
|
||||
const renderNode = React.useCallback(
|
||||
({ index, style }: ListChildComponentProps) => (
|
||||
<VirtualNode
|
||||
index={index}
|
||||
style={style}
|
||||
onNodeClick={onNodeClick}
|
||||
renderActions={renderActions}
|
||||
/>
|
||||
),
|
||||
[renderActions, onNodeClick],
|
||||
)
|
||||
|
||||
const renderFiles = React.useCallback(
|
||||
({ nodes, focusedNode }: VisibleNodes) => {
|
||||
const inSearch = searchKey !== ''
|
||||
if (inSearch && nodes.length === 0) {
|
||||
return <label className={'no-results'}>No results found.</label>
|
||||
}
|
||||
return (
|
||||
<SizeObserver className={'files'}>
|
||||
{({ width = 0, height = 0 }) => (
|
||||
<ListView
|
||||
renderNode={renderNode}
|
||||
focusedNode={focusedNode}
|
||||
nodes={nodes}
|
||||
height={height}
|
||||
width={width}
|
||||
expandTo={props.expandTo}
|
||||
metaData={props.metaData}
|
||||
/>
|
||||
)}
|
||||
</SizeObserver>
|
||||
)
|
||||
},
|
||||
[searchKey, ListView, renderNode],
|
||||
)
|
||||
|
||||
const revealNode = React.useCallback(function revealNode(
|
||||
goTo: (path: string[]) => void,
|
||||
node: TreeNode,
|
||||
): (event: React.MouseEvent<HTMLElement, MouseEvent>) => void {
|
||||
|
|
@ -148,39 +98,122 @@ class FileExplorer extends React.Component<Props & ConnectorState> {
|
|||
e.preventDefault()
|
||||
goTo(node.path.split('/'))
|
||||
}
|
||||
}
|
||||
},
|
||||
[])
|
||||
|
||||
render() {
|
||||
const {
|
||||
stateText,
|
||||
visibleNodes,
|
||||
freeze,
|
||||
handleKeyDown,
|
||||
search,
|
||||
toggleShowSettings,
|
||||
onFocusSearchBar,
|
||||
searchKey,
|
||||
} = this.props
|
||||
return (
|
||||
return (
|
||||
<VisibleNodesContext.Provider value={visibleNodes}>
|
||||
<div
|
||||
className={cx(`file-explorer`, { freeze })}
|
||||
tabIndex={-1}
|
||||
onKeyDown={handleKeyDown}
|
||||
onClick={freeze ? toggleShowSettings : undefined}
|
||||
onKeyDown={props.handleKeyDown}
|
||||
onClick={freeze ? props.toggleShowSettings : undefined}
|
||||
>
|
||||
{stateText ? (
|
||||
<LoadingIndicator text={stateText} />
|
||||
{props.stateText ? (
|
||||
<LoadingIndicator text={props.stateText} />
|
||||
) : (
|
||||
visibleNodes && (
|
||||
<React.Fragment>
|
||||
<SearchBar searchKey={searchKey} onSearch={search} onFocus={onFocusSearchBar} />
|
||||
{this.renderFiles(visibleNodes)}
|
||||
</React.Fragment>
|
||||
<>
|
||||
<SearchBar
|
||||
searchKey={searchKey}
|
||||
onSearch={props.search}
|
||||
onFocus={props.onFocusSearchBar}
|
||||
/>
|
||||
{renderFiles(visibleNodes)}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</VisibleNodesContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect<Props, ConnectorState>(FileExplorerCore)(FileExplorer)
|
||||
RawFileExplorer.defaultProps = {
|
||||
freeze: false,
|
||||
searchKey: '',
|
||||
visibleNodes: null,
|
||||
}
|
||||
|
||||
export const FileExplorer = connect(FileExplorerCore)(RawFileExplorer)
|
||||
|
||||
function VirtualNode({
|
||||
index,
|
||||
style,
|
||||
onNodeClick,
|
||||
renderActions,
|
||||
}: {
|
||||
index: number
|
||||
style: React.CSSProperties
|
||||
onNodeClick: (treeNode: TreeNode) => void
|
||||
renderActions: ((node: TreeNode) => React.ReactNode) | undefined
|
||||
}) {
|
||||
const visibleNodes = React.useContext(VisibleNodesContext)
|
||||
if (!visibleNodes) return null
|
||||
const { nodes, depths, focusedNode, expandedNodes } = visibleNodes
|
||||
const node = nodes[index]
|
||||
return (
|
||||
<Node
|
||||
style={style}
|
||||
key={node.path}
|
||||
node={node}
|
||||
depth={depths.get(node) || 0}
|
||||
focused={focusedNode === node}
|
||||
expanded={expandedNodes.has(node)}
|
||||
onClick={onNodeClick}
|
||||
renderActions={renderActions}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function ListView({
|
||||
nodes,
|
||||
width,
|
||||
height,
|
||||
focusedNode,
|
||||
renderNode,
|
||||
metaData,
|
||||
expandTo,
|
||||
}: {
|
||||
nodes: TreeNode[]
|
||||
height: number
|
||||
width: number
|
||||
focusedNode: TreeNode | null
|
||||
renderNode: ListProps['children']
|
||||
} & Pick<Props, 'metaData'> &
|
||||
Pick<ConnectorState, 'expandTo'>) {
|
||||
const listRef = React.useRef<List>(null)
|
||||
React.useEffect(() => {
|
||||
if (focusedNode && listRef.current) {
|
||||
listRef.current.scrollToItem(nodes.indexOf(focusedNode), 'smart')
|
||||
}
|
||||
}, [listRef.current, focusedNode])
|
||||
|
||||
const lastNodeLength = usePrevious(nodes.length)
|
||||
React.useEffect(() => {
|
||||
if (listRef.current && !focusedNode && lastNodeLength !== nodes.length) {
|
||||
listRef.current.scrollTo(0)
|
||||
}
|
||||
}, [listRef.current, focusedNode, nodes.length])
|
||||
|
||||
const goToCurrentItem = React.useCallback(() => {
|
||||
expandTo(getCurrentPath(metaData.branchName))
|
||||
}, [metaData.branchName])
|
||||
useOnLocationChange(goToCurrentItem)
|
||||
useEvent('pjax:complete', goToCurrentItem, window)
|
||||
return (
|
||||
<List
|
||||
ref={listRef}
|
||||
itemKey={(index, { nodes }) => {
|
||||
const node = nodes[index]
|
||||
return node && node.path
|
||||
}}
|
||||
itemData={{ nodes }}
|
||||
itemCount={nodes.length}
|
||||
itemSize={35}
|
||||
height={height}
|
||||
width={width}
|
||||
>
|
||||
{renderNode}
|
||||
</List>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
import { raiseError } from 'analytics'
|
||||
import SideBar from 'components/SideBar'
|
||||
import { SideBar } from 'components/SideBar'
|
||||
import { ConfigsContext, ConfigsContextWrapper } from 'containers/ConfigsContext'
|
||||
import * as React from 'react'
|
||||
import { ErrorBoundary } from './ErrorBoundary'
|
||||
|
||||
export default class Gitako extends React.PureComponent {
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||
raiseError(error, errorInfo)
|
||||
}
|
||||
|
||||
render() {
|
||||
return <SideBar />
|
||||
}
|
||||
export function Gitako() {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<ConfigsContextWrapper>
|
||||
<ConfigsContext.Consumer>
|
||||
{configContext => configContext && <SideBar configContext={configContext} />}
|
||||
</ConfigsContext.Consumer>
|
||||
</ConfigsContextWrapper>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import Octicon, {
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
File,
|
||||
FileCode,
|
||||
FileMedia,
|
||||
|
|
@ -12,11 +13,10 @@ import Octicon, {
|
|||
Markdown,
|
||||
Octoface,
|
||||
Reply,
|
||||
TriangleRight,
|
||||
X,
|
||||
} from '@primer/octicons-react'
|
||||
import * as React from 'react'
|
||||
import cx from 'utils/cx'
|
||||
import { cx } from 'utils/cx'
|
||||
|
||||
function getSVGIconComponent(
|
||||
type: string,
|
||||
|
|
@ -57,8 +57,8 @@ function getSVGIconComponent(
|
|||
}
|
||||
case 'folder':
|
||||
return {
|
||||
IconComponent: TriangleRight,
|
||||
name: 'TriangleRight',
|
||||
IconComponent: ChevronRight,
|
||||
name: 'ChevronRight',
|
||||
}
|
||||
case 'go-to':
|
||||
return {
|
||||
|
|
@ -120,10 +120,12 @@ function getSVGIconComponent(
|
|||
type Props = {
|
||||
type: string
|
||||
className?: string
|
||||
placeholder?: boolean
|
||||
onClick?: (event: React.MouseEvent<HTMLElement>) => void
|
||||
}
|
||||
|
||||
const Icon: React.SFC<Props> = function Icon({ type, className = undefined, ...otherProps }) {
|
||||
export function Icon({ type, className = undefined, placeholder, ...otherProps }: Props) {
|
||||
if (placeholder) return <div className={cx('octicon-wrapper')} />
|
||||
const { name, IconComponent } = getSVGIconComponent(type)
|
||||
const mergedClassName = cx('octicon', name)
|
||||
return (
|
||||
|
|
@ -131,9 +133,8 @@ const Icon: React.SFC<Props> = function Icon({ type, className = undefined, ...o
|
|||
{React.createElement(Octicon, {
|
||||
icon: IconComponent,
|
||||
className: mergedClassName,
|
||||
verticalAlign: 'text-bottom',
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Icon
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import Icon from 'components/Icon'
|
||||
import { Icon } from 'components/Icon'
|
||||
import * as React from 'react'
|
||||
|
||||
type Props = {
|
||||
text: React.ReactNode
|
||||
}
|
||||
export default function LoadingIndicator({ text }: Props) {
|
||||
export function LoadingIndicator({ text }: Props) {
|
||||
return (
|
||||
<div className={'loading-indicator-container'}>
|
||||
<div className={'loading-indicator'}>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import * as React from 'react'
|
||||
import { MetaData } from 'utils/GitHubHelper'
|
||||
import { safeTouch } from 'safe-touch'
|
||||
import { MetaData } from 'utils/GitHubHelper'
|
||||
|
||||
type Props = {
|
||||
metaData: MetaData
|
||||
}
|
||||
|
||||
export default function MetaBar({ metaData }: Props) {
|
||||
export function MetaBar({ metaData }: Props) {
|
||||
const userUrl = safeTouch(metaData).api.owner.html_url()
|
||||
const repoUrl = safeTouch(metaData).api.html_url()
|
||||
return (
|
||||
|
|
@ -15,7 +15,7 @@ export default function MetaBar({ metaData }: Props) {
|
|||
{metaData.userName}
|
||||
</a>
|
||||
/
|
||||
<a className={'repo-name pjax-link'} href={repoUrl}>
|
||||
<a className={'repo-name'} href={repoUrl}>
|
||||
{metaData.repoName}
|
||||
</a>
|
||||
/
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import Icon from 'components/Icon'
|
||||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import * as React from 'react'
|
||||
import cx from 'utils/cx'
|
||||
import { cx } from 'utils/cx'
|
||||
import { OperatingSystems, os } from 'utils/general'
|
||||
import { TreeNode } from 'utils/VisibleNodesGenerator'
|
||||
import { getFileIconSrc, getFolderIconSrc } from '../utils/parseIconMapCSV'
|
||||
import { Icon } from './Icon'
|
||||
|
||||
function getIconType(node: TreeNode) {
|
||||
switch (node.type) {
|
||||
|
|
@ -24,42 +26,70 @@ type Props = {
|
|||
renderActions?(node: TreeNode): React.ReactNode
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
export default class Node extends React.PureComponent<Props> {
|
||||
onClick: React.MouseEventHandler = event => {
|
||||
if (
|
||||
(os === OperatingSystems.macOS && event.metaKey) ||
|
||||
(os === OperatingSystems.Windows && event.ctrlKey)
|
||||
) {
|
||||
// Open in new tab
|
||||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
const { node, onClick } = this.props
|
||||
onClick(node)
|
||||
}
|
||||
export function Node({ node, depth, expanded, focused, renderActions, style, onClick }: Props) {
|
||||
const onClickNode: React.MouseEventHandler = React.useCallback(
|
||||
event => {
|
||||
if (
|
||||
(os === OperatingSystems.macOS && event.metaKey) ||
|
||||
(os === OperatingSystems.Windows && event.ctrlKey)
|
||||
) {
|
||||
// The default behavior, open in new tab
|
||||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
|
||||
render() {
|
||||
const { node, depth, expanded, focused, renderActions, style } = this.props
|
||||
const { name, path } = node
|
||||
return (
|
||||
<div
|
||||
className={cx(`node-item-row`, { focused, disabled: node.accessDenied })}
|
||||
style={style}
|
||||
title={path}
|
||||
>
|
||||
<a href={node.url} onClick={this.onClick}>
|
||||
<div
|
||||
className={cx('node-item', { expanded })}
|
||||
style={{ paddingLeft: `${10 + 20 * depth}px` }}
|
||||
>
|
||||
<div className={'node-item-label'}>
|
||||
<Icon type={getIconType(node)} />
|
||||
<span className={'node-item-name'}>{name}</span>
|
||||
</div>
|
||||
{renderActions && <div>{renderActions(node)}</div>}
|
||||
onClick(node)
|
||||
},
|
||||
[node, onClick],
|
||||
)
|
||||
|
||||
const { name, path } = node
|
||||
return (
|
||||
<div
|
||||
className={cx(`node-item-row`, { focused, disabled: node.accessDenied })}
|
||||
style={style}
|
||||
title={path}
|
||||
>
|
||||
<a href={node.url} onClick={onClickNode}>
|
||||
<div
|
||||
className={cx('node-item', { expanded })}
|
||||
style={{ paddingLeft: `${10 + 20 * depth}px` }}
|
||||
>
|
||||
<div className={'node-item-label'}>
|
||||
<NodeItemIcon node={node} open={expanded} />
|
||||
<span className={'node-item-name'}>{name}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{renderActions && <div>{renderActions(node)}</div>}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const NodeItemIcon = React.memo(function NodeItemIcon({
|
||||
node,
|
||||
open = false,
|
||||
}: {
|
||||
node: TreeNode
|
||||
open?: boolean
|
||||
}) {
|
||||
const {
|
||||
val: { icons },
|
||||
} = useConfigs()
|
||||
|
||||
if (icons === 'native') return <Icon type={getIconType(node)} />
|
||||
const src = React.useMemo(
|
||||
() => (node.type === 'tree' ? getFolderIconSrc(node, open) : getFileIconSrc(node)),
|
||||
[open],
|
||||
)
|
||||
return (
|
||||
<>
|
||||
<Icon placeholder={node.type !== 'tree'} type={getIconType(node)} />
|
||||
{node.type === 'commit' ? (
|
||||
<Icon type={getIconType(node)} />
|
||||
) : (
|
||||
<img alt={node.name} className={cx('node-item-icon', { dim: icons === 'dim' })} src={src} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import * as React from 'react'
|
||||
import DOMHelper from 'utils/DOMHelper'
|
||||
import * as DOMHelper from 'utils/DOMHelper'
|
||||
|
||||
type Props<P> = {
|
||||
to: string
|
||||
children: React.ReactElement<P>
|
||||
}
|
||||
|
||||
export default function PJAXLink<P>({ to, children }: Props<P>) {
|
||||
export function PJAXLink<P>({ to, children }: Props<P>) {
|
||||
return React.cloneElement(children, {
|
||||
...children.props,
|
||||
onClick: () => DOMHelper.loadWithPJAX(to),
|
||||
|
|
|
|||
|
|
@ -5,12 +5,8 @@ type Props = {
|
|||
into: Element | null
|
||||
}
|
||||
|
||||
class Portal extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const { into, children } = this.props
|
||||
if (!(into instanceof Element)) return null
|
||||
return ReactDOM.createPortal(children, into)
|
||||
}
|
||||
export function Portal(props: React.PropsWithChildren<Props>) {
|
||||
const { into, children } = props
|
||||
if (!(into instanceof Element)) return null
|
||||
return ReactDOM.createPortal(children, into)
|
||||
}
|
||||
|
||||
export default Portal
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { HorizontalResizeHandler } from 'components/ResizeHandler'
|
||||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import * as React from 'react'
|
||||
import HorizontalResizeHandler from 'components/ResizeHandler'
|
||||
import cx from 'utils/cx'
|
||||
import { useWindowSize, useMediaStyleSheet } from 'utils/hooks'
|
||||
import { cx } from 'utils/cx'
|
||||
import { bodySpacingClassName } from 'utils/DOMHelper'
|
||||
import configHelper, { configKeys } from 'utils/configHelper'
|
||||
import * as features from 'utils/features'
|
||||
import { useMediaStyleSheet, useWindowSize } from 'utils/hooks'
|
||||
|
||||
export type Size = number
|
||||
type Props = {
|
||||
|
|
@ -15,12 +15,9 @@ type Props = {
|
|||
const MINIMAL_CONTENT_VIEWPORT_WIDTH = 100
|
||||
const GITHUB_WIDTH = 1020
|
||||
|
||||
export default function Resizable({
|
||||
baseSize,
|
||||
className,
|
||||
children,
|
||||
}: React.PropsWithChildren<Props>) {
|
||||
export function Resizable({ baseSize, className, children }: React.PropsWithChildren<Props>) {
|
||||
const [size, setSize] = React.useState(baseSize)
|
||||
const configContext = useConfigs()
|
||||
|
||||
React.useEffect(() => {
|
||||
setSize(baseSize)
|
||||
|
|
@ -36,7 +33,7 @@ export default function Resizable({
|
|||
|
||||
React.useEffect(() => {
|
||||
document.documentElement.style.setProperty('--gitako-width', size + 'px')
|
||||
configHelper.setOne(configKeys.sideBarWidth, size)
|
||||
configContext.set({ sideBarWidth: size })
|
||||
}, [size])
|
||||
|
||||
useMediaStyleSheet(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import Icon from 'components/Icon'
|
||||
import { Icon } from 'components/Icon'
|
||||
import * as React from 'react'
|
||||
import { Size } from './Resizable'
|
||||
|
||||
|
|
@ -8,51 +8,46 @@ type Props = {
|
|||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
export default class HorizontalResizeHandler extends React.PureComponent<Props> {
|
||||
pointerDown = false
|
||||
startX = 0
|
||||
baseSize = this.props.size
|
||||
export function HorizontalResizeHandler({ onResize, size, style }: Props) {
|
||||
const pointerDown = React.useRef(false)
|
||||
const startX = React.useRef(0)
|
||||
const baseSize = React.useRef(size)
|
||||
const latestPropSize = React.useRef(size)
|
||||
|
||||
componentWillReceiveProps(nextProps: Props) {
|
||||
if (!this.pointerDown) {
|
||||
// update baseSize when not resizing
|
||||
this.baseSize = nextProps.size
|
||||
React.useEffect(() => {
|
||||
latestPropSize.current = size
|
||||
}, [size])
|
||||
|
||||
const onPointerDown = React.useCallback(({ clientX }: React.MouseEvent) => {
|
||||
startX.current = clientX
|
||||
pointerDown.current = true
|
||||
baseSize.current = latestPropSize.current
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
const onPointerMove = ({ clientX }: MouseEvent) => {
|
||||
if (!pointerDown.current) return
|
||||
const shift = clientX - startX.current
|
||||
onResize(baseSize.current + shift)
|
||||
}
|
||||
}
|
||||
window.addEventListener('mousemove', onPointerMove)
|
||||
return () => window.removeEventListener('mousemove', onPointerMove)
|
||||
}, [onResize])
|
||||
|
||||
subscribeEvents = () => {
|
||||
window.addEventListener('mousemove', this.onPointerMove)
|
||||
window.addEventListener('mouseup', this.onPointerUp)
|
||||
}
|
||||
React.useEffect(() => {
|
||||
const onPointerUp = () => {
|
||||
if (pointerDown.current) {
|
||||
pointerDown.current = false
|
||||
baseSize.current = latestPropSize.current
|
||||
}
|
||||
}
|
||||
window.addEventListener('mouseup', onPointerUp)
|
||||
return () => window.removeEventListener('mouseup', onPointerUp)
|
||||
}, [])
|
||||
|
||||
unsubscribeEvents = () => {
|
||||
window.removeEventListener('mousemove', this.onPointerMove)
|
||||
window.removeEventListener('mouseup', this.onPointerUp)
|
||||
}
|
||||
|
||||
onPointerDown = ({ clientX }: React.MouseEvent) => {
|
||||
this.startX = clientX
|
||||
this.pointerDown = true
|
||||
this.subscribeEvents()
|
||||
}
|
||||
|
||||
onPointerMove = ({ clientX }: MouseEvent) => {
|
||||
if (!this.pointerDown) return
|
||||
this.props.onResize(clientX - this.startX + this.baseSize)
|
||||
}
|
||||
|
||||
onPointerUp = () => {
|
||||
this.pointerDown = false
|
||||
this.baseSize = this.props.size
|
||||
this.unsubscribeEvents()
|
||||
}
|
||||
|
||||
render() {
|
||||
const { style } = this.props
|
||||
return (
|
||||
<div className={'gitako-resize-handler'} onMouseDown={this.onPointerDown} style={style}>
|
||||
<Icon type={'grabber'} className={'grabber-icon'} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className={'gitako-resize-handler'} onMouseDown={onPointerDown} style={style}>
|
||||
<Icon type={'grabber'} className={'grabber-icon'} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react'
|
||||
import cx from 'utils/cx'
|
||||
import { cx } from 'utils/cx'
|
||||
|
||||
type Props = {
|
||||
onSearch: (searchKey: string) => void
|
||||
|
|
@ -7,7 +7,7 @@ type Props = {
|
|||
searchKey: string
|
||||
}
|
||||
|
||||
export default function SearchBar({ onSearch, onFocus, searchKey }: Props) {
|
||||
export function SearchBar({ onSearch, onFocus, searchKey }: Props) {
|
||||
return (
|
||||
<div className={'search-input-wrapper'}>
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import Icon from 'components/Icon'
|
||||
import { oauth } from 'env'
|
||||
import { Icon } from 'components/Icon'
|
||||
import { VERSION } from 'env'
|
||||
import * as React from 'react'
|
||||
import configHelper, { Config, configKeys } from 'utils/configHelper'
|
||||
import { friendlyFormatShortcut, JSONRequest, parseURLSearch } from 'utils/general'
|
||||
import keyHelper from 'utils/keyHelper'
|
||||
import { version } from '../../package.json'
|
||||
import { Config } from 'utils/configHelper'
|
||||
import { useStates } from 'utils/hooks'
|
||||
import { AccessTokenSettings } from './settings/AccessTokenSettings'
|
||||
import { FileTreeIconSettings } from './settings/FileTreeIconSettings'
|
||||
import { ShortcutSettings } from './settings/ShortcutSettings'
|
||||
import { SimpleToggleField } from './SimpleToggleField'
|
||||
|
||||
const WIKI_HOME_LINK = 'https://github.com/EnixCoda/Gitako/wiki'
|
||||
const wikiLinks = {
|
||||
export const wikiLinks = {
|
||||
compressSingletonFolder: `${WIKI_HOME_LINK}/Compress-Singleton-Folder`,
|
||||
changeLog: `${WIKI_HOME_LINK}/Change-Log`,
|
||||
copyFileButton: `${WIKI_HOME_LINK}/Copy-file-and-snippet`,
|
||||
|
|
@ -15,347 +17,107 @@ const wikiLinks = {
|
|||
createAccessToken: `${WIKI_HOME_LINK}/How-to-create-access-token-for-Gitako%3F`,
|
||||
}
|
||||
|
||||
const ACCESS_TOKEN_REGEXP = /^[0-9a-f]{40}$/
|
||||
|
||||
type Props = {
|
||||
accessToken?: string
|
||||
activated: boolean
|
||||
onAccessTokenChange: (accessToken: string) => void
|
||||
onShortcutChange: (shortcut: string) => void
|
||||
setCopyFile: (copyFileButton: Props['copyFileButton']) => void
|
||||
setCopySnippet: (copySnippetButton: Props['copySnippetButton']) => void
|
||||
setCompressSingleton: (compressSingletonFolder: Props['compressSingletonFolder']) => void
|
||||
setIntelligentToggle: (intelligentToggle: Props['intelligentToggle']) => void
|
||||
toggleShowSettings: () => void
|
||||
toggleShowSideBarShortcut?: string
|
||||
} & Pick<
|
||||
Config,
|
||||
'compressSingletonFolder' | 'copyFileButton' | 'copySnippetButton' | 'intelligentToggle'
|
||||
>
|
||||
|
||||
type State = {
|
||||
accessToken?: string
|
||||
accessTokenHint: React.ReactNode
|
||||
shortcutHint: string
|
||||
toggleShowSideBarShortcut?: string
|
||||
reloadHint: React.ReactNode
|
||||
varyOptions: {
|
||||
key: string
|
||||
label: string
|
||||
onChange: (e: React.FormEvent<HTMLInputElement>) => Promise<void> | void
|
||||
getValue: () => boolean
|
||||
wikiLink?: string
|
||||
description?: string
|
||||
}[]
|
||||
}
|
||||
|
||||
export default class SettingsBar extends React.PureComponent<Props, State> {
|
||||
state = {
|
||||
accessToken: '',
|
||||
accessTokenHint: '',
|
||||
shortcutHint: '',
|
||||
toggleShowSideBarShortcut: this.props.toggleShowSideBarShortcut,
|
||||
reloadHint: '',
|
||||
varyOptions: [
|
||||
{
|
||||
key: 'compress-singleton',
|
||||
label: 'Compress singleton folder',
|
||||
onChange: this.createOnToggleChecked(
|
||||
configKeys.compressSingletonFolder,
|
||||
this.props.setCompressSingleton,
|
||||
),
|
||||
getValue: () => this.props.compressSingletonFolder,
|
||||
wikiLink: wikiLinks.compressSingletonFolder,
|
||||
},
|
||||
{
|
||||
key: 'copy-file',
|
||||
label: 'Copy File Shortcut',
|
||||
onChange: this.createOnToggleChecked(configKeys.copyFileButton, this.props.setCopyFile),
|
||||
getValue: () => this.props.copyFileButton,
|
||||
wikiLink: wikiLinks.copyFileButton,
|
||||
},
|
||||
{
|
||||
key: 'copy-snippet',
|
||||
label: 'Copy Snippet Shortcut',
|
||||
onChange: this.createOnToggleChecked(
|
||||
configKeys.copySnippetButton,
|
||||
this.props.setCopySnippet,
|
||||
),
|
||||
getValue: () => this.props.copySnippetButton,
|
||||
wikiLink: wikiLinks.copySnippet,
|
||||
},
|
||||
{
|
||||
key: 'intelligent-toggle',
|
||||
label: 'Intelligent Toggle',
|
||||
onChange: async (e: React.FormEvent<HTMLInputElement>) => {
|
||||
const { checked } = e.currentTarget
|
||||
const intelligentToggle = checked ? null : true
|
||||
await configHelper.setOne(configKeys.intelligentToggle, intelligentToggle)
|
||||
this.props.setIntelligentToggle(intelligentToggle)
|
||||
},
|
||||
getValue: () => this.props.intelligentToggle === null,
|
||||
description: `Gitako will open/close automatically according to page content when this is enabled.`,
|
||||
},
|
||||
],
|
||||
export type SimpleField = {
|
||||
key: keyof Config
|
||||
label: string
|
||||
wikiLink?: string
|
||||
description?: string
|
||||
overwrite?: {
|
||||
value: <T>(value: T) => boolean
|
||||
onChange: (checked: boolean) => any
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.accessToken) this.trySetUpAccessTokenWithCode()
|
||||
}
|
||||
const moreFields: SimpleField[] = [
|
||||
{
|
||||
key: 'compressSingletonFolder',
|
||||
label: 'Compress singleton folder',
|
||||
wikiLink: wikiLinks.compressSingletonFolder,
|
||||
},
|
||||
{
|
||||
key: 'copyFileButton',
|
||||
label: 'Copy File Shortcut',
|
||||
wikiLink: wikiLinks.copyFileButton,
|
||||
},
|
||||
{
|
||||
key: 'copySnippetButton',
|
||||
label: 'Copy Snippet Shortcut',
|
||||
wikiLink: wikiLinks.copySnippet,
|
||||
},
|
||||
{
|
||||
key: 'intelligentToggle',
|
||||
label: 'Intelligent Toggle',
|
||||
description: `Gitako will open/close automatically according to page content when this is enabled.`,
|
||||
overwrite: {
|
||||
value: enabled => enabled === null,
|
||||
onChange: checked => (checked ? null : true),
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
componentWillReceiveProps({ toggleShowSideBarShortcut }: Props) {
|
||||
if (toggleShowSideBarShortcut !== this.props.toggleShowSideBarShortcut) {
|
||||
this.setState({ toggleShowSideBarShortcut })
|
||||
}
|
||||
}
|
||||
function SettingsBarContent() {
|
||||
const useReloadHint = useStates<React.ReactNode>('')
|
||||
const { val: reloadHint } = useReloadHint
|
||||
|
||||
private async trySetUpAccessTokenWithCode() {
|
||||
const search = parseURLSearch()
|
||||
if ('code' in search) {
|
||||
const res = await JSONRequest('https://github.com/login/oauth/access_token', {
|
||||
code: search.code,
|
||||
client_id: oauth.clientId,
|
||||
client_secret: oauth.clientSecret,
|
||||
})
|
||||
const { access_token: accessToken, scope } = res
|
||||
if (scope !== 'repo' || !accessToken) {
|
||||
throw new Error(`Cannot resolve token response: '${JSON.stringify(res)}'`)
|
||||
}
|
||||
window.history.pushState({}, 'removed code', window.location.pathname.replace(/#.*$/, ''))
|
||||
this.setState({ accessToken }, () => this.saveToken(''))
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<h3 className={'gitako-settings-bar-title'}>Settings</h3>
|
||||
<div className={'gitako-settings-bar-content'}>
|
||||
<div className={'shadow-shelter'} />
|
||||
<AccessTokenSettings />
|
||||
<ShortcutSettings />
|
||||
<FileTreeIconSettings />
|
||||
<div className={'gitako-settings-bar-content-section others'}>
|
||||
<h4>More</h4>
|
||||
{moreFields.map(field => (
|
||||
<React.Fragment key={field.key}>
|
||||
<SimpleToggleField field={field} />
|
||||
<br />
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
||||
onInputAccessToken = (event: React.FormEvent<HTMLInputElement>) => {
|
||||
const { value } = event.currentTarget
|
||||
this.setState({
|
||||
accessToken: value,
|
||||
accessTokenHint: ACCESS_TOKEN_REGEXP.test(value) ? '' : 'This token is in unknown format.',
|
||||
})
|
||||
}
|
||||
|
||||
onPressAccessToken = (event: React.KeyboardEvent) => {
|
||||
const { key } = event
|
||||
if (key === 'Enter') {
|
||||
this.saveToken()
|
||||
}
|
||||
}
|
||||
|
||||
saveToken = async (
|
||||
hint: State['accessTokenHint'] = (
|
||||
<span>
|
||||
<a href="#" onClick={() => window.location.reload()}>
|
||||
Reload
|
||||
</a>{' '}
|
||||
to activate!
|
||||
</span>
|
||||
),
|
||||
) => {
|
||||
const { onAccessTokenChange } = this.props
|
||||
const { accessToken } = this.state
|
||||
if (accessToken) {
|
||||
await configHelper.setOne(configKeys.accessToken, accessToken)
|
||||
onAccessTokenChange(accessToken)
|
||||
this.setState({
|
||||
accessToken: '',
|
||||
accessTokenHint: hint,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
clearToken = async () => {
|
||||
const { onAccessTokenChange } = this.props
|
||||
await configHelper.setOne(configKeys.accessToken, '')
|
||||
onAccessTokenChange('')
|
||||
this.setState({ accessToken: '' })
|
||||
}
|
||||
|
||||
saveShortcut = async () => {
|
||||
const { onShortcutChange } = this.props
|
||||
const { toggleShowSideBarShortcut } = this.state
|
||||
await configHelper.setOne(configKeys.shortcut, toggleShowSideBarShortcut)
|
||||
if (typeof toggleShowSideBarShortcut === 'string') {
|
||||
onShortcutChange(toggleShowSideBarShortcut)
|
||||
this.setState({
|
||||
shortcutHint: 'Shortcut is saved!',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onShortCutInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
e.preventDefault()
|
||||
// Clear shortcut with backspace
|
||||
const shortcut = e.key === 'Backspace' ? '' : keyHelper.parseEvent(e)
|
||||
this.setState({ toggleShowSideBarShortcut: shortcut })
|
||||
}
|
||||
|
||||
showReloadHint = () => {
|
||||
this.setState({
|
||||
reloadHint: (
|
||||
<span>
|
||||
Saved,{' '}
|
||||
<a href="#" onClick={() => window.location.reload()}>
|
||||
reload
|
||||
</a>{' '}
|
||||
to apply.
|
||||
</span>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
createOnToggleChecked(
|
||||
configKey: configKeys,
|
||||
set: (value: boolean) => void,
|
||||
): (e: React.FormEvent<HTMLInputElement>) => Promise<void> {
|
||||
return async e => {
|
||||
const enabled = e.currentTarget.checked
|
||||
await configHelper.setOne(configKey, enabled)
|
||||
set(enabled)
|
||||
this.showReloadHint()
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
accessTokenHint,
|
||||
toggleShowSideBarShortcut,
|
||||
shortcutHint,
|
||||
accessToken,
|
||||
reloadHint,
|
||||
varyOptions,
|
||||
} = this.state
|
||||
const { toggleShowSettings, activated } = this.props
|
||||
const hasAccessToken = Boolean(this.props.accessToken)
|
||||
return (
|
||||
<div className={'gitako-settings-bar'}>
|
||||
{activated && (
|
||||
<React.Fragment>
|
||||
<h3 className={'gitako-settings-bar-title'}>Settings</h3>
|
||||
<div className={'gitako-settings-bar-content'}>
|
||||
<div className={'shadow-shelter'} />
|
||||
<div className={'gitako-settings-bar-content-section access-token'}>
|
||||
<h4>
|
||||
Access Token
|
||||
<a href={wikiLinks.createAccessToken} target="_blank">
|
||||
(?)
|
||||
</a>
|
||||
</h4>
|
||||
{!hasAccessToken && (
|
||||
<a
|
||||
href="#"
|
||||
onClick={() => {
|
||||
// use js here to make sure redirect_uri is latest url
|
||||
const url = `https://github.com/login/oauth/authorize?client_id=${
|
||||
oauth.clientId
|
||||
}&scope=repo&redirect_uri=${encodeURIComponent(window.location.href)}`
|
||||
window.location.href = url
|
||||
}}
|
||||
>
|
||||
Create with OAuth (recommended)
|
||||
</a>
|
||||
)}
|
||||
<div className={'access-token-input-control'}>
|
||||
<input
|
||||
className={'access-token-input form-control'}
|
||||
disabled={hasAccessToken}
|
||||
placeholder={hasAccessToken ? 'Your token is saved' : 'Or input here manually'}
|
||||
value={accessToken}
|
||||
onChange={this.onInputAccessToken}
|
||||
onKeyPress={this.onPressAccessToken}
|
||||
/>
|
||||
{hasAccessToken && !accessToken ? (
|
||||
<button className={'btn'} onClick={this.clearToken}>
|
||||
Clear
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className={'btn'}
|
||||
onClick={() => this.saveToken()}
|
||||
disabled={!accessToken}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{accessTokenHint && <span className={'hint'}>{accessTokenHint}</span>}
|
||||
</div>
|
||||
<div className={'gitako-settings-bar-content-section toggle-shortcut'}>
|
||||
<h4>Toggle Shortcut</h4>
|
||||
<span>Set a combination of keys for toggling Gitako sidebar.</span>
|
||||
<br />
|
||||
<div className={'toggle-shortcut-input-control'}>
|
||||
<input
|
||||
className={'toggle-shortcut-input form-control'}
|
||||
placeholder={'focus here and press the shortcut keys'}
|
||||
value={friendlyFormatShortcut(toggleShowSideBarShortcut)}
|
||||
onKeyDown={this.onShortCutInputKeyDown}
|
||||
readOnly
|
||||
/>
|
||||
<button className={'btn'} onClick={this.saveShortcut}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
{shortcutHint && <span className={'hint'}>{shortcutHint}</span>}
|
||||
</div>
|
||||
<div className={'gitako-settings-bar-content-section others'}>
|
||||
<h4>More Options</h4>
|
||||
{varyOptions.map(option => (
|
||||
<React.Fragment key={option.key}>
|
||||
<label htmlFor={option.key}>
|
||||
<input
|
||||
id={option.key}
|
||||
name={option.key}
|
||||
type={'checkbox'}
|
||||
onChange={option.onChange}
|
||||
checked={option.getValue()}
|
||||
/>
|
||||
{option.label}
|
||||
{option.wikiLink ? (
|
||||
<a href={option.wikiLink} target={'_blank'}>
|
||||
(?)
|
||||
</a>
|
||||
) : (
|
||||
option.description && (
|
||||
<span className={'description'} title={option.description}>
|
||||
(?)
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</label>
|
||||
<br />
|
||||
</React.Fragment>
|
||||
))}
|
||||
{reloadHint && <div className={'hint'}>{reloadHint}</div>}
|
||||
</div>
|
||||
<div className={'gitako-settings-bar-content-section issue'}>
|
||||
<h4>Contact</h4>
|
||||
<a href="https://github.com/EnixCoda/Gitako/issues" target="_blank">
|
||||
Bug report / feature request.
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<div className={'placeholder-row'}>
|
||||
<a
|
||||
className={'version'}
|
||||
href={wikiLinks.changeLog}
|
||||
target={'_blank'}
|
||||
title={'Check out new features!'}
|
||||
>
|
||||
v{version}
|
||||
{reloadHint && <div className={'hint'}>{reloadHint}</div>}
|
||||
</div>
|
||||
<div className={'gitako-settings-bar-content-section issue'}>
|
||||
<h4>Contact</h4>
|
||||
<a href="https://github.com/EnixCoda/Gitako/issues" target="_blank">
|
||||
Report bug / Request feature.
|
||||
</a>
|
||||
{activated ? (
|
||||
<Icon
|
||||
type={'chevron-down'}
|
||||
className={'hide-settings-icon'}
|
||||
onClick={toggleShowSettings}
|
||||
/>
|
||||
) : (
|
||||
<Icon type={'gear'} className={'show-settings-icon'} onClick={toggleShowSettings} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function SettingsBar(props: Props) {
|
||||
const { toggleShowSettings, activated } = props
|
||||
return (
|
||||
<div className={'gitako-settings-bar'}>
|
||||
{activated && <SettingsBarContent />}
|
||||
<div className={'header-row'}>
|
||||
<a
|
||||
className={'version'}
|
||||
href={wikiLinks.changeLog}
|
||||
target={'_blank'}
|
||||
title={'Check out new features!'}
|
||||
>
|
||||
{VERSION}
|
||||
</a>
|
||||
{activated ? (
|
||||
<Icon
|
||||
type={'chevron-down'}
|
||||
className={'hide-settings-icon'}
|
||||
onClick={toggleShowSettings}
|
||||
/>
|
||||
) : (
|
||||
<Icon type={'gear'} className={'show-settings-icon'} onClick={toggleShowSettings} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,150 +1,209 @@
|
|||
import FileExplorer from 'components/FileExplorer'
|
||||
import MetaBar from 'components/MetaBar'
|
||||
import Portal from 'components/Portal'
|
||||
import Resizable from 'components/Resizable'
|
||||
import SettingsBar from 'components/SettingsBar'
|
||||
import ToggleShowButton from 'components/ToggleShowButton'
|
||||
import connect from 'driver/connect'
|
||||
import { SideBar as SideBarCore } from 'driver/core'
|
||||
import { ConnectorState } from 'driver/core/SideBar'
|
||||
import { raiseError } from 'analytics'
|
||||
import { FileExplorer } from 'components/FileExplorer'
|
||||
import { MetaBar } from 'components/MetaBar'
|
||||
import { Portal } from 'components/Portal'
|
||||
import { Resizable } from 'components/Resizable'
|
||||
import { SettingsBar } from 'components/SettingsBar'
|
||||
import { ToggleShowButton } from 'components/ToggleShowButton'
|
||||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import { connect } from 'driver/connect'
|
||||
import { SideBarCore } from 'driver/core'
|
||||
import { ConnectorState, Props } from 'driver/core/SideBar'
|
||||
import { oauth } from 'env'
|
||||
import * as React from 'react'
|
||||
import cx from 'utils/cx'
|
||||
import { useEvent } from 'react-use'
|
||||
import { cx } from 'utils/cx'
|
||||
import * as DOMHelper from 'utils/DOMHelper'
|
||||
import { JSONRequest, parseURLSearch } from 'utils/general'
|
||||
import { useDidUpdate } from 'utils/hooks'
|
||||
import * as keyHelper from 'utils/keyHelper'
|
||||
import * as URLHelper from 'utils/URLHelper'
|
||||
|
||||
export type Props = {}
|
||||
const RawGitako: React.FC<Props & ConnectorState> = function RawGitako(props) {
|
||||
const configContext = useConfigs()
|
||||
const accessToken = props.configContext.val.access_token
|
||||
|
||||
class Gitako extends React.PureComponent<Props & ConnectorState> {
|
||||
static defaultProps: Partial<Props & ConnectorState> = {
|
||||
baseSize: 260,
|
||||
shouldShow: false,
|
||||
showSettings: false,
|
||||
errorDueToAuth: false,
|
||||
accessToken: '',
|
||||
toggleShowSideBarShortcut: '',
|
||||
compressSingletonFolder: true,
|
||||
copyFileButton: true,
|
||||
copySnippetButton: true,
|
||||
disabled: false,
|
||||
}
|
||||
React.useEffect(() => {
|
||||
const { init } = props
|
||||
;(async function() {
|
||||
if (!accessToken) {
|
||||
const accessToken = await trySetUpAccessTokenWithCode()
|
||||
configContext.set({ access_token: accessToken })
|
||||
}
|
||||
init()
|
||||
})()
|
||||
}, [])
|
||||
|
||||
componentWillMount() {
|
||||
const { init } = this.props
|
||||
init()
|
||||
}
|
||||
React.useEffect(
|
||||
function attachKeyDown() {
|
||||
if (props.disabled || !configContext.val.shortcut) return
|
||||
|
||||
componentDidMount() {
|
||||
const { useListeners } = this.props
|
||||
useListeners(true)
|
||||
}
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
const keys = keyHelper.parseEvent(e)
|
||||
if (keys === configContext.val.shortcut) {
|
||||
props.toggleShowSideBar()
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', onKeyDown)
|
||||
return () => window.removeEventListener('keydown', onKeyDown)
|
||||
},
|
||||
[props.disabled, configContext.val.shortcut],
|
||||
)
|
||||
|
||||
componentWillUnmount() {
|
||||
const { useListeners } = this.props
|
||||
useListeners(false)
|
||||
}
|
||||
const updateSideBarVisibility = React.useCallback(
|
||||
function updateSideBarVisibility() {
|
||||
if (configContext.val.intelligentToggle === null) {
|
||||
props.setShouldShow(
|
||||
URLHelper.isInCodePage({
|
||||
branchName: props.metaData?.branchName,
|
||||
}),
|
||||
)
|
||||
}
|
||||
},
|
||||
[props.metaData?.branchName, configContext.val.intelligentToggle],
|
||||
)
|
||||
useEvent('pjax:complete', updateSideBarVisibility, window)
|
||||
|
||||
renderAccessDeniedError() {
|
||||
return (
|
||||
<div className={'description'}>
|
||||
<h5>Access Denied</h5>
|
||||
const attachCopyFileButton = React.useCallback(
|
||||
function attachCopyFileButton() {
|
||||
if (configContext.val.copyFileButton) return DOMHelper.attachCopyFileBtn() || undefined // for the sake of react effect
|
||||
},
|
||||
[configContext.val.copyFileButton],
|
||||
)
|
||||
React.useEffect(attachCopyFileButton, [configContext.val.copyFileButton])
|
||||
useEvent('pjax:complete', attachCopyFileButton, window)
|
||||
|
||||
const attachCopySnippetButton = React.useCallback(
|
||||
function attachCopySnippetButton() {
|
||||
if (configContext.val.copySnippetButton) return DOMHelper.attachCopySnippet() || undefined // for the sake of react effect
|
||||
},
|
||||
[configContext.val.copySnippetButton],
|
||||
)
|
||||
React.useEffect(attachCopySnippetButton, [configContext.val.copySnippetButton])
|
||||
useEvent('pjax:complete', attachCopySnippetButton, window)
|
||||
|
||||
// init again when setting new accessToken
|
||||
useDidUpdate(() => {
|
||||
props.init()
|
||||
}, [accessToken])
|
||||
|
||||
const {
|
||||
errorDueToAuth,
|
||||
metaData,
|
||||
treeData,
|
||||
baseSize,
|
||||
error,
|
||||
shouldShow,
|
||||
showSettings,
|
||||
logoContainerElement,
|
||||
toggleShowSideBar,
|
||||
toggleShowSettings,
|
||||
} = props
|
||||
return (
|
||||
<div className={'gitako-side-bar'}>
|
||||
<Portal into={logoContainerElement}>
|
||||
<ToggleShowButton
|
||||
error={error}
|
||||
shouldShow={shouldShow}
|
||||
toggleShowSideBar={toggleShowSideBar}
|
||||
/>
|
||||
</Portal>
|
||||
<Resizable className={cx({ hidden: error || !shouldShow })} baseSize={baseSize}>
|
||||
<div className={'gitako-side-bar-body'}>
|
||||
<div className={'gitako-side-bar-content'}>
|
||||
{metaData && <MetaBar metaData={metaData} />}
|
||||
{errorDueToAuth
|
||||
? renderAccessDeniedError(Boolean(accessToken))
|
||||
: metaData && (
|
||||
<FileExplorer
|
||||
toggleShowSettings={toggleShowSettings}
|
||||
metaData={metaData}
|
||||
treeData={treeData}
|
||||
freeze={showSettings}
|
||||
accessToken={accessToken}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<SettingsBar toggleShowSettings={toggleShowSettings} activated={showSettings} />
|
||||
</div>
|
||||
</Resizable>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
RawGitako.defaultProps = {
|
||||
baseSize: 260,
|
||||
shouldShow: false,
|
||||
showSettings: false,
|
||||
errorDueToAuth: false,
|
||||
disabled: false,
|
||||
}
|
||||
|
||||
export const SideBar = connect(SideBarCore)(RawGitako)
|
||||
|
||||
function renderAccessDeniedError(hasToken: boolean) {
|
||||
return (
|
||||
<div className={'description'}>
|
||||
<h5>Access Denied</h5>
|
||||
{hasToken ? (
|
||||
<>
|
||||
<p>
|
||||
Current access token is either invalid or not granted with permissions to access this
|
||||
project.
|
||||
</p>
|
||||
<p>
|
||||
You can grant or request access{' '}
|
||||
<a href={`https://github.com/settings/connections/applications/${oauth.clientId}`}>
|
||||
here
|
||||
</a>{' '}
|
||||
if you setup Gitako with OAuth.
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<p>
|
||||
Due to{' '}
|
||||
Gitako needs access token to read this project due to{' '}
|
||||
<a target="_blank" href="https://developer.github.com/v3/#rate-limiting">
|
||||
limitation of GitHub
|
||||
GitHub rate limiting
|
||||
</a>{' '}
|
||||
or{' '}
|
||||
and{' '}
|
||||
<a target="_blank" href="https://developer.github.com/v3/#authentication">
|
||||
auth needs
|
||||
</a>
|
||||
, Gitako needs access token to continue. Please follow the instructions in the settings
|
||||
panel below.
|
||||
. Please setup access token in the settings panel below.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
const {
|
||||
errorDueToAuth,
|
||||
metaData,
|
||||
treeData,
|
||||
showSettings,
|
||||
toggleShowSettings,
|
||||
compressSingletonFolder,
|
||||
accessToken,
|
||||
} = this.props
|
||||
return (
|
||||
<div className={'gitako-side-bar-content'}>
|
||||
{metaData && <MetaBar metaData={metaData} />}
|
||||
{errorDueToAuth
|
||||
? this.renderAccessDeniedError()
|
||||
: metaData && (
|
||||
<FileExplorer
|
||||
compressSingletonFolder={compressSingletonFolder}
|
||||
toggleShowSettings={toggleShowSettings}
|
||||
metaData={metaData}
|
||||
treeData={treeData}
|
||||
freeze={showSettings}
|
||||
accessToken={accessToken}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
baseSize,
|
||||
error,
|
||||
shouldShow,
|
||||
showSettings,
|
||||
accessToken,
|
||||
compressSingletonFolder,
|
||||
copyFileButton,
|
||||
copySnippetButton,
|
||||
intelligentToggle,
|
||||
toggleShowSideBarShortcut,
|
||||
logoContainerElement,
|
||||
toggleShowSideBar,
|
||||
toggleShowSettings,
|
||||
onShortcutChange,
|
||||
onAccessTokenChange,
|
||||
setCompressSingleton,
|
||||
setCopyFile,
|
||||
setCopySnippet,
|
||||
setIntelligentToggle,
|
||||
} = this.props
|
||||
return (
|
||||
<div className={'gitako-side-bar'}>
|
||||
<Portal into={logoContainerElement}>
|
||||
<ToggleShowButton
|
||||
error={error}
|
||||
shouldShow={shouldShow}
|
||||
toggleShowSideBar={toggleShowSideBar}
|
||||
/>
|
||||
</Portal>
|
||||
<Resizable className={cx({ hidden: error || !shouldShow })} baseSize={baseSize}>
|
||||
<div className={'gitako-side-bar-body'}>
|
||||
{this.renderContent()}
|
||||
<SettingsBar
|
||||
toggleShowSettings={toggleShowSettings}
|
||||
onShortcutChange={onShortcutChange}
|
||||
onAccessTokenChange={onAccessTokenChange}
|
||||
activated={showSettings}
|
||||
accessToken={accessToken}
|
||||
toggleShowSideBarShortcut={toggleShowSideBarShortcut}
|
||||
compressSingletonFolder={compressSingletonFolder}
|
||||
copyFileButton={copyFileButton}
|
||||
copySnippetButton={copySnippetButton}
|
||||
intelligentToggle={intelligentToggle}
|
||||
setCompressSingleton={setCompressSingleton}
|
||||
setCopyFile={setCopyFile}
|
||||
setCopySnippet={setCopySnippet}
|
||||
setIntelligentToggle={setIntelligentToggle}
|
||||
/>
|
||||
</div>
|
||||
</Resizable>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect<Props, ConnectorState>(SideBarCore)(Gitako)
|
||||
async function trySetUpAccessTokenWithCode() {
|
||||
try {
|
||||
const search = parseURLSearch()
|
||||
if ('code' in search) {
|
||||
const res = await JSONRequest('https://github.com/login/oauth/access_token', {
|
||||
code: search.code,
|
||||
client_id: oauth.clientId,
|
||||
client_secret: oauth.clientSecret,
|
||||
})
|
||||
const { access_token: accessToken, scope, error_description: errorDescription } = res
|
||||
if (errorDescription) {
|
||||
const TOKEN_EXPIRED_DESCRIPTION = `The code passed is incorrect or expired.`
|
||||
if (errorDescription === TOKEN_EXPIRED_DESCRIPTION) {
|
||||
alert(`Gitako: The OAuth token has expired, please try again.`)
|
||||
} else {
|
||||
throw new Error(errorDescription)
|
||||
}
|
||||
} else if (scope !== 'repo' || !accessToken) {
|
||||
throw new Error(`Cannot resolve token response: '${JSON.stringify(res)}'`)
|
||||
}
|
||||
window.history.pushState(
|
||||
{},
|
||||
'removed search param',
|
||||
window.location.pathname.replace(window.location.search, ''),
|
||||
)
|
||||
return accessToken
|
||||
}
|
||||
} catch (err) {
|
||||
raiseError(err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
41
src/components/SimpleToggleField.tsx
Normal file
41
src/components/SimpleToggleField.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import * as React from 'react'
|
||||
import { SimpleField } from './SettingsBar'
|
||||
|
||||
type Props = {
|
||||
field: SimpleField
|
||||
onChange?(): void
|
||||
}
|
||||
|
||||
export function SimpleToggleField({ field, onChange }: Props) {
|
||||
const { overwrite } = field
|
||||
const configContext = useConfigs()
|
||||
const value = configContext.val[field.key]
|
||||
return (
|
||||
<label htmlFor={field.key}>
|
||||
<input
|
||||
id={field.key}
|
||||
name={field.key}
|
||||
type={'checkbox'}
|
||||
onChange={async e => {
|
||||
const enabled = e.currentTarget.checked
|
||||
configContext.set({ [field.key]: overwrite ? overwrite.onChange(enabled) : enabled })
|
||||
if (onChange) onChange()
|
||||
}}
|
||||
checked={overwrite ? overwrite.value(value) : Boolean(value)}
|
||||
/>
|
||||
{field.label}
|
||||
{field.wikiLink ? (
|
||||
<a href={field.wikiLink} target={'_blank'}>
|
||||
(?)
|
||||
</a>
|
||||
) : (
|
||||
field.description && (
|
||||
<span className={'description'} title={field.description}>
|
||||
(?)
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ type Props = {
|
|||
children(size: Partial<Size>): React.ReactNode
|
||||
} & React.HTMLAttributes<HTMLElement>
|
||||
|
||||
export default function SizeObserver({ type = 'div', children, ...rest }: Props) {
|
||||
export function SizeObserver({ type = 'div', children, ...rest }: Props) {
|
||||
const ref = React.useRef<any>()
|
||||
|
||||
const [size, setSize] = React.useState<Partial<Size>>({
|
||||
|
|
@ -19,6 +19,16 @@ export default function SizeObserver({ type = 'div', children, ...rest }: Props)
|
|||
height: undefined,
|
||||
})
|
||||
|
||||
const safeSetSize = React.useCallback(function safeSetSize(rect: DOMRectReadOnly) {
|
||||
// requestAnimationFrame fixes "ResizeObserver loop limit exceeded" error
|
||||
requestAnimationFrame(() =>
|
||||
setSize({
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
}),
|
||||
)
|
||||
}, [])
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
if (features.resize) {
|
||||
const observer = new window.ResizeObserver(entries => {
|
||||
|
|
@ -43,14 +53,4 @@ export default function SizeObserver({ type = 'div', children, ...rest }: Props)
|
|||
const props: any = { ...rest, ref } // :)
|
||||
|
||||
return React.createElement(type, props, children(size))
|
||||
|
||||
function safeSetSize(rect: DOMRectReadOnly) {
|
||||
// requestAnimationFrame fixes "ResizeObserver loop limit exceeded" error
|
||||
requestAnimationFrame(() =>
|
||||
setSize({
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import { Icon } from 'components/Icon'
|
||||
import * as React from 'react'
|
||||
import Icon from 'components/Icon'
|
||||
import cx from 'utils/cx'
|
||||
import { cx } from 'utils/cx'
|
||||
|
||||
type Props = {
|
||||
error?: string
|
||||
shouldShow: boolean
|
||||
toggleShowSideBar: React.MouseEventHandler
|
||||
}
|
||||
export default function Logo({ error, shouldShow, toggleShowSideBar }: Props) {
|
||||
|
||||
export function ToggleShowButton({ error, shouldShow, toggleShowSideBar }: Props) {
|
||||
return (
|
||||
<div
|
||||
className={cx('gitako-toggle-show-button-wrapper', {
|
||||
|
|
|
|||
103
src/components/settings/AccessTokenSettings.tsx
Normal file
103
src/components/settings/AccessTokenSettings.tsx
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import { wikiLinks } from 'components/SettingsBar'
|
||||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import { oauth } from 'env'
|
||||
import * as React from 'react'
|
||||
import { useStates } from 'utils/hooks'
|
||||
|
||||
const ACCESS_TOKEN_REGEXP = /^[0-9a-f]{40}$/
|
||||
|
||||
type Props = {}
|
||||
|
||||
export function AccessTokenSettings(props: React.PropsWithChildren<Props>) {
|
||||
const configContext = useConfigs()
|
||||
const hasAccessToken = Boolean(configContext.val.access_token)
|
||||
const useAccessToken = useStates('')
|
||||
const useAccessTokenHint = useStates<React.ReactNode>('')
|
||||
|
||||
const { val: accessTokenHint } = useAccessTokenHint
|
||||
const { val: accessToken } = useAccessToken
|
||||
|
||||
React.useEffect(() => {
|
||||
// clear input when access token updates
|
||||
useAccessToken.set('')
|
||||
}, [configContext.val.access_token])
|
||||
|
||||
const onInputAccessToken = React.useCallback(
|
||||
({ currentTarget: { value } }: React.FormEvent<HTMLInputElement>) => {
|
||||
useAccessToken.set(value)
|
||||
useAccessTokenHint.set(
|
||||
ACCESS_TOKEN_REGEXP.test(value) ? '' : 'This token is in unknown format.',
|
||||
)
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
const onPressAccessToken = React.useCallback(({ key }: React.KeyboardEvent) => {
|
||||
if (key === 'Enter') saveToken()
|
||||
}, [])
|
||||
|
||||
const saveToken = React.useCallback(
|
||||
async (hint?: typeof useAccessTokenHint.val) => {
|
||||
if (accessToken) {
|
||||
configContext.set({ access_token: accessToken })
|
||||
useAccessToken.set('')
|
||||
useAccessTokenHint.set(
|
||||
hint || (
|
||||
<span>
|
||||
<a href="#" onClick={() => window.location.reload()}>
|
||||
Reload
|
||||
</a>{' '}
|
||||
to activate!
|
||||
</span>
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
[accessToken],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={'gitako-settings-bar-content-section access-token'}>
|
||||
<h4>
|
||||
Access Token{' '}
|
||||
<a href={wikiLinks.createAccessToken} target="_blank">
|
||||
(?)
|
||||
</a>
|
||||
</h4>
|
||||
{!hasAccessToken && (
|
||||
<a
|
||||
className={'link-button'}
|
||||
onClick={() => {
|
||||
// use js here to make sure redirect_uri is latest url
|
||||
const url = `https://github.com/login/oauth/authorize?client_id=${
|
||||
oauth.clientId
|
||||
}&scope=repo&redirect_uri=${encodeURIComponent(window.location.href)}`
|
||||
window.location.href = url
|
||||
}}
|
||||
>
|
||||
Create with OAuth (recommended)
|
||||
</a>
|
||||
)}
|
||||
<div className={'access-token-input-control'}>
|
||||
<input
|
||||
className={'access-token-input form-control'}
|
||||
disabled={hasAccessToken}
|
||||
placeholder={hasAccessToken ? 'Your token is saved' : 'Or input here manually'}
|
||||
value={accessToken}
|
||||
onChange={onInputAccessToken}
|
||||
onKeyPress={onPressAccessToken}
|
||||
/>
|
||||
{hasAccessToken && !accessToken ? (
|
||||
<button className={'btn'} onClick={() => configContext.set({ access_token: '' })}>
|
||||
Clear
|
||||
</button>
|
||||
) : (
|
||||
<button className={'btn'} onClick={() => saveToken()} disabled={!accessToken}>
|
||||
Save
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{accessTokenHint && <span className={'hint'}>{accessTokenHint}</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
56
src/components/settings/FileTreeIconSettings.tsx
Normal file
56
src/components/settings/FileTreeIconSettings.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import * as React from 'react'
|
||||
import { Config } from 'utils/configHelper'
|
||||
|
||||
const options: {
|
||||
key: Config['icons']
|
||||
value: Config['icons']
|
||||
label: string
|
||||
}[] = [
|
||||
{
|
||||
key: 'rich',
|
||||
value: 'rich',
|
||||
label: `VSCode icons`,
|
||||
},
|
||||
{
|
||||
key: 'dim',
|
||||
value: 'dim',
|
||||
label: `VSCode icons (single color)`,
|
||||
},
|
||||
{
|
||||
key: 'native',
|
||||
value: 'native',
|
||||
label: `Native GitHub icons`,
|
||||
},
|
||||
]
|
||||
|
||||
type Props = {}
|
||||
|
||||
export function FileTreeIconSettings(props: React.PropsWithChildren<Props>) {
|
||||
const configContext = useConfigs()
|
||||
return (
|
||||
<div className={'gitako-settings-bar-content-section toggle-shortcut'}>
|
||||
<h4>File Tree Icons</h4>
|
||||
<span>Icons can make a difference.</span>
|
||||
<br />
|
||||
<div className={'toggle-shortcut-input-control'}>
|
||||
<select
|
||||
onChange={e => {
|
||||
configContext.set({
|
||||
icons: e.target.value as Config['icons'],
|
||||
})
|
||||
}}
|
||||
className={'toggle-shortcut-input form-control'}
|
||||
placeholder={'focus here and press the shortcut keys'}
|
||||
value={configContext.val.icons}
|
||||
>
|
||||
{options.map(option => (
|
||||
<option key={option.key} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
56
src/components/settings/ShortcutSettings.tsx
Normal file
56
src/components/settings/ShortcutSettings.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { useConfigs } from 'containers/ConfigsContext'
|
||||
import * as React from 'react'
|
||||
import { friendlyFormatShortcut } from 'utils/general'
|
||||
import { useStates } from 'utils/hooks'
|
||||
import * as keyHelper from 'utils/keyHelper'
|
||||
|
||||
type Props = {}
|
||||
|
||||
export function ShortcutSettings(props: React.PropsWithChildren<Props>) {
|
||||
const configContext = useConfigs()
|
||||
const useShortcutHint = useStates('')
|
||||
const useToggleShowSideBarShortcut = useStates(configContext.val.shortcut)
|
||||
const { val: toggleShowSideBarShortcut } = useToggleShowSideBarShortcut
|
||||
const { val: shortcutHint } = useShortcutHint
|
||||
|
||||
React.useEffect(() => {
|
||||
useToggleShowSideBarShortcut.set(configContext.val.shortcut)
|
||||
}, [configContext.val.shortcut])
|
||||
|
||||
const saveShortcut = React.useCallback(async () => {
|
||||
const { val: toggleShowSideBarShortcut } = useToggleShowSideBarShortcut
|
||||
configContext.set({ shortcut: toggleShowSideBarShortcut })
|
||||
if (typeof toggleShowSideBarShortcut === 'string') {
|
||||
useShortcutHint.set('Shortcut is saved!')
|
||||
}
|
||||
}, [useToggleShowSideBarShortcut.val])
|
||||
|
||||
const onShortCutInputKeyDown = React.useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
// Clear shortcut with backspace
|
||||
const shortcut = e.key === 'Backspace' ? '' : keyHelper.parseEvent(e)
|
||||
useToggleShowSideBarShortcut.set(shortcut)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={'gitako-settings-bar-content-section toggle-shortcut'}>
|
||||
<h4>Toggle Shortcut</h4>
|
||||
<span>Set a combination of keys for toggling Gitako sidebar.</span>
|
||||
<br />
|
||||
<div className={'toggle-shortcut-input-control'}>
|
||||
<input
|
||||
className={'toggle-shortcut-input form-control'}
|
||||
placeholder={'focus here and press the shortcut keys'}
|
||||
value={friendlyFormatShortcut(toggleShowSideBarShortcut)}
|
||||
onKeyDown={onShortCutInputKeyDown}
|
||||
readOnly
|
||||
/>
|
||||
<button className={'btn'} onClick={saveShortcut}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
{shortcutHint && <span className={'hint'}>{shortcutHint}</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
41
src/containers/ConfigsContext.tsx
Normal file
41
src/containers/ConfigsContext.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import * as React from 'react'
|
||||
import * as configsHelper from 'utils/configHelper'
|
||||
import { Config } from 'utils/configHelper'
|
||||
|
||||
type Props = {}
|
||||
|
||||
type ContextShape = PartialValSet<Config>
|
||||
export type ConfigsContextShape = ContextShape
|
||||
|
||||
export const ConfigsContext = React.createContext<ContextShape | null>(null)
|
||||
|
||||
export function ConfigsContextWrapper(props: React.PropsWithChildren<Props>) {
|
||||
const [configs, setConfigs] = React.useState<Config | null>(null)
|
||||
React.useEffect(() => {
|
||||
configsHelper.get().then(setConfigs)
|
||||
}, [])
|
||||
const set = React.useCallback(
|
||||
(updatedConfigs: Partial<Config>) => {
|
||||
const mergedConfigs = { ...configs, ...updatedConfigs } as Config
|
||||
configsHelper.set(mergedConfigs)
|
||||
setConfigs(mergedConfigs)
|
||||
},
|
||||
[configs, setConfigs],
|
||||
)
|
||||
if (configs === null) return null
|
||||
return (
|
||||
<ConfigsContext.Provider value={{ val: configs, set }}>
|
||||
{props.children}
|
||||
</ConfigsContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useConfigs = useNonNullContext(ConfigsContext)
|
||||
|
||||
function useNonNullContext<T, R extends Exclude<T, null>>(theContext: React.Context<T>): () => R {
|
||||
return () => {
|
||||
const context = React.useContext(theContext)
|
||||
if (context === null) throw new Error(`Empty context`)
|
||||
return context as R
|
||||
}
|
||||
}
|
||||
|
|
@ -56,18 +56,14 @@
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
background-image: url('./assets/icons/octicons/clippy.svg?inline');
|
||||
background-image: url('~@primer/octicons/build/svg/clippy.svg?inline');
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
&.success {
|
||||
.icon {
|
||||
background-image: url('./assets/icons/octicons/check.svg?inline');
|
||||
&.success {
|
||||
background-image: url('~@primer/octicons/build/svg/check.svg?inline');
|
||||
}
|
||||
}
|
||||
&.fail {
|
||||
.icon {
|
||||
background-image: url('./assets/icons/octicons/x.svg?inline');
|
||||
&.fail {
|
||||
background-image: url('~@primer/octicons/build/svg/x.svg?inline');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -92,8 +88,6 @@
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 4px;
|
||||
will-change: transform;
|
||||
border: 1px solid transparent;
|
||||
|
|
@ -141,8 +135,8 @@
|
|||
|
||||
.action-icon {
|
||||
color: #666666;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
transition: all @animation-duration ease;
|
||||
.octicon {
|
||||
|
|
@ -151,21 +145,22 @@
|
|||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.error-message {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
.action-icon {
|
||||
color: #cb2431;
|
||||
color: #d73a49;
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
display: none;
|
||||
margin: 0 4px;
|
||||
margin-left: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.error-message {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -363,8 +358,21 @@
|
|||
transition: all 0.5s ease;
|
||||
white-space: nowrap;
|
||||
|
||||
&-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
object-fit: contain;
|
||||
vertical-align: text-bottom;
|
||||
padding-left: 4px;
|
||||
box-sizing: content-box;
|
||||
|
||||
&.dim {
|
||||
filter: sepia(1) hue-rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
// folder icon rotate when expand
|
||||
&.expanded .octicon.TriangleRight {
|
||||
&.expanded .octicon.ChevronRight {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
|
|
@ -438,8 +446,14 @@
|
|||
min-width: 100px;
|
||||
}
|
||||
}
|
||||
select {
|
||||
-moz-appearance: none;
|
||||
}
|
||||
.access-token {
|
||||
border-bottom: none; // prevent overwrite by github style
|
||||
.link-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
.hint {
|
||||
color: #6a737d;
|
||||
}
|
||||
|
|
@ -474,7 +488,7 @@
|
|||
color: #6a737d;
|
||||
}
|
||||
}
|
||||
.placeholder-row {
|
||||
.header-row {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { withErrorLog } from 'analytics'
|
||||
import { Gitako } from 'components/Gitako'
|
||||
import { addMiddleware } from 'driver/connect'
|
||||
import * as React from 'react'
|
||||
import * as ReactDOM from 'react-dom'
|
||||
import Gitako from 'components/Gitako'
|
||||
import { addMiddleware } from 'driver/connect'
|
||||
import { withErrorLog } from 'analytics'
|
||||
|
||||
import './content.less'
|
||||
|
||||
addMiddleware(withErrorLog)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ function run<M extends Method>([method, args]: [M, Parameters<M>]) {
|
|||
}
|
||||
|
||||
export type DispatchState<Props, State> = React.Component<Props, State>['setState']
|
||||
export type GetState<State> = () => State
|
||||
export type GetState<Props, State> = () => [State, Props]
|
||||
export type TriggerOtherMethod<Props, State> = <Args extends any[]>(
|
||||
methodCreator: MethodCreator<Props, State, Args>,
|
||||
...args: Parameters<ReturnType<MethodCreator<Props, State, Args>>>
|
||||
|
|
@ -39,7 +39,7 @@ export type TriggerOtherMethod<Props, State> = <Args extends any[]>(
|
|||
|
||||
export type Dispatch<Props, State> = {
|
||||
set: DispatchState<Props, State>
|
||||
get: GetState<State>
|
||||
get: GetState<Props, State>
|
||||
call: TriggerOtherMethod<Props, State>
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ export type MethodCreator<Props, State, Args extends any[] = []> = (
|
|||
dispatch: Dispatch<Props, State>,
|
||||
) => Method<Args>
|
||||
|
||||
type Sources<P, S> = {
|
||||
export type Sources<P, S> = {
|
||||
[key: string]: MethodCreator<P, S, any>
|
||||
}
|
||||
type WrappedMethods = {
|
||||
|
|
@ -75,7 +75,7 @@ function link<P, S>(instance: React.Component<P, S>, sources: Sources<P, S>): Wr
|
|||
const dispatchState: DispatchState<P, S> = (updater, callback) => {
|
||||
instance.setState(updater, callback)
|
||||
}
|
||||
const prepareState: GetState<S> = () => instance.state
|
||||
const prepareState: GetState<P, S> = () => [instance.state, instance.props]
|
||||
const dispatch: Dispatch<P, S> = {
|
||||
call: dispatchCall,
|
||||
get: prepareState,
|
||||
|
|
@ -93,20 +93,20 @@ function link<P, S>(instance: React.Component<P, S>, sources: Sources<P, S>): Wr
|
|||
return wrappedMethods
|
||||
}
|
||||
|
||||
export default function connect<BaseP, ExtraP>(mapping: Sources<BaseP, ExtraP>) {
|
||||
return function linkComponent<S>(
|
||||
ComponentClass: React.ComponentClass<BaseP & ExtraP, S>,
|
||||
): React.ComponentClass<BaseP, ExtraP> {
|
||||
return class AwesomeApp extends React.PureComponent<BaseP, ExtraP> {
|
||||
static displayName = `Connected(${ComponentClass.displayName || ComponentClass.name})`
|
||||
static defaultProps = ComponentClass.defaultProps
|
||||
export function connect<BaseP, ExtraP>(mapping: Sources<BaseP, ExtraP>) {
|
||||
return function linkComponent<State, ComponentType extends React.ComponentType<BaseP & ExtraP>>(
|
||||
Component: ComponentType,
|
||||
) {
|
||||
return class ConnectedComponent extends React.PureComponent<BaseP, ExtraP, State> {
|
||||
static displayName = `Connected(${Component.displayName || Component.name})`
|
||||
static defaultProps = Component.defaultProps
|
||||
|
||||
state = {} as ExtraP
|
||||
connectedMethods = link<BaseP, ExtraP>(this, mapping) as WrappedMethods
|
||||
state: ExtraP = {} as ExtraP
|
||||
connectedMethods: WrappedMethods = link<BaseP, ExtraP>(this, mapping)
|
||||
|
||||
render() {
|
||||
const props = Object.assign({}, this.props, this.connectedMethods, this.state)
|
||||
return React.createElement(ComponentClass, props)
|
||||
return React.createElement(Component, props)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,22 @@
|
|||
import { raiseError } from 'analytics'
|
||||
import { Props } from 'components/FileExplorer'
|
||||
import { GetCreatedMethod, MethodCreator } from 'driver/connect'
|
||||
import * as ini from 'ini'
|
||||
import { Base64 } from 'js-base64'
|
||||
import DOMHelper from 'utils/DOMHelper'
|
||||
import { Config } from 'utils/configHelper'
|
||||
import * as DOMHelper from 'utils/DOMHelper'
|
||||
import { findNode, searchKeyToRegexps } from 'utils/general'
|
||||
import GitHubHelper, { BlobData } from 'utils/GitHubHelper'
|
||||
import treeParser from 'utils/treeParser'
|
||||
import URLHelper from 'utils/URLHelper'
|
||||
import VisibleNodesGenerator, { TreeNode, VisibleNodes } from 'utils/VisibleNodesGenerator'
|
||||
import * as GitHubHelper from 'utils/GitHubHelper'
|
||||
import { BlobData } from 'utils/GitHubHelper'
|
||||
import * as treeParser from 'utils/treeParser'
|
||||
import * as URLHelper from 'utils/URLHelper'
|
||||
import { TreeNode, VisibleNodes, VisibleNodesGenerator } from 'utils/VisibleNodesGenerator'
|
||||
|
||||
export type Props = {
|
||||
treeData?: GitHubHelper.TreeData
|
||||
metaData: GitHubHelper.MetaData
|
||||
freeze: boolean
|
||||
accessToken: string | undefined
|
||||
toggleShowSettings: React.MouseEventHandler
|
||||
}
|
||||
|
||||
export type ConnectorState = {
|
||||
stateText: string
|
||||
|
|
@ -24,6 +32,7 @@ export type ConnectorState = {
|
|||
onFocusSearchBar: GetCreatedMethod<typeof onFocusSearchBar>
|
||||
setUpTree: GetCreatedMethod<typeof setUpTree>
|
||||
goTo: GetCreatedMethod<typeof goTo>
|
||||
expandTo: GetCreatedMethod<typeof expandTo>
|
||||
}
|
||||
|
||||
type DepthMap = Map<TreeNode, number>
|
||||
|
|
@ -45,12 +54,12 @@ function getVisibleParentNode(nodes: TreeNode[], focusedNode: TreeNode, depths:
|
|||
}
|
||||
|
||||
type Task = () => void
|
||||
const tasksAfterRender: (Task)[] = []
|
||||
const tasksAfterRender: Task[] = []
|
||||
let visibleNodesGenerator: VisibleNodesGenerator
|
||||
|
||||
type BoundMethodCreator<Args extends any[] = []> = MethodCreator<Props, ConnectorState, Args>
|
||||
|
||||
const init: BoundMethodCreator = dispatch => () =>
|
||||
export const init: BoundMethodCreator = dispatch => () =>
|
||||
dispatch.call(setStateText, 'Fetching File List...')
|
||||
|
||||
const githubSubModuleURLRegex = {
|
||||
|
|
@ -118,7 +127,9 @@ function handleParsed(root: TreeNode, parsed: Parsed) {
|
|||
node.accessDenied = true
|
||||
}
|
||||
} else {
|
||||
raiseError(new Error(`Sub-module node not found`), { path })
|
||||
// It turns out that we did not miss any submodule after a lot of tests.
|
||||
// Turning this off.
|
||||
// raiseError(new Error(`Submodule node not found`), { path })
|
||||
}
|
||||
} else {
|
||||
handleParsed(root, value as Parsed)
|
||||
|
|
@ -126,9 +137,9 @@ function handleParsed(root: TreeNode, parsed: Parsed) {
|
|||
})
|
||||
}
|
||||
|
||||
const setUpTree: BoundMethodCreator<
|
||||
[Pick<Props, 'treeData' | 'metaData' | 'compressSingletonFolder' | 'accessToken'>]
|
||||
> = dispatch => async ({ treeData, metaData, compressSingletonFolder, accessToken }) => {
|
||||
export const setUpTree: BoundMethodCreator<[
|
||||
Pick<Props, 'treeData' | 'metaData' | 'accessToken'> & Pick<Config, 'compressSingletonFolder'>,
|
||||
]> = dispatch => async ({ treeData, metaData, compressSingletonFolder, accessToken }) => {
|
||||
if (!treeData) return
|
||||
dispatch.call(setStateText, 'Rendering File List...')
|
||||
const { root, gitModules } = treeParser.parse(treeData, metaData)
|
||||
|
|
@ -157,22 +168,22 @@ const setUpTree: BoundMethodCreator<
|
|||
dispatch.call(goTo, URLHelper.getCurrentPath(metaData.branchName))
|
||||
}
|
||||
|
||||
const execAfterRender: BoundMethodCreator = dispatch => () => {
|
||||
export const execAfterRender: BoundMethodCreator = dispatch => () => {
|
||||
for (const task of tasksAfterRender) {
|
||||
task()
|
||||
}
|
||||
tasksAfterRender.length = 0
|
||||
}
|
||||
|
||||
const setStateText: BoundMethodCreator<[ConnectorState['stateText']]> = dispatch => (
|
||||
export const setStateText: BoundMethodCreator<[ConnectorState['stateText']]> = dispatch => (
|
||||
text: string,
|
||||
) =>
|
||||
dispatch.set({
|
||||
stateText: text,
|
||||
})
|
||||
|
||||
const handleKeyDown: BoundMethodCreator<[React.KeyboardEvent]> = dispatch => event => {
|
||||
const { searched, visibleNodes } = dispatch.get()
|
||||
export const handleKeyDown: BoundMethodCreator<[React.KeyboardEvent]> = dispatch => event => {
|
||||
const [{ searched, visibleNodes }] = dispatch.get()
|
||||
if (!visibleNodes) return
|
||||
const { nodes, focusedNode, expandedNodes, depths } = visibleNodes
|
||||
function handleVerticalMove(index: number) {
|
||||
|
|
@ -276,33 +287,33 @@ const handleKeyDown: BoundMethodCreator<[React.KeyboardEvent]> = dispatch => eve
|
|||
}
|
||||
}
|
||||
|
||||
const onFocusSearchBar: BoundMethodCreator = dispatch => () => dispatch.call(focusNode, null, false)
|
||||
export const onFocusSearchBar: BoundMethodCreator = dispatch => () =>
|
||||
dispatch.call(focusNode, null, false)
|
||||
|
||||
const search: BoundMethodCreator<[string]> = dispatch => searchKey => {
|
||||
export const search: BoundMethodCreator<[string]> = dispatch => searchKey => {
|
||||
dispatch.set({ searchKey, searched: searchKey !== '' })
|
||||
const regexps = searchKeyToRegexps(searchKey)
|
||||
visibleNodesGenerator.search(regexps)
|
||||
dispatch.call(updateVisibleNodes)
|
||||
}
|
||||
|
||||
const goTo: BoundMethodCreator<[string[]]> = dispatch => async currentPath => {
|
||||
export const goTo: BoundMethodCreator<[string[]]> = dispatch => async currentPath => {
|
||||
visibleNodesGenerator.search([])
|
||||
tasksAfterRender.push(() => {
|
||||
const nodeExpandedTo = visibleNodesGenerator.expandTo(currentPath.join('/'))
|
||||
if (nodeExpandedTo) {
|
||||
visibleNodesGenerator.focusNode(nodeExpandedTo)
|
||||
}
|
||||
dispatch.call(updateVisibleNodes)
|
||||
dispatch.call(expandTo, currentPath)
|
||||
})
|
||||
dispatch.set({ searchKey: '', searched: false })
|
||||
}
|
||||
|
||||
const setExpand: BoundMethodCreator<[TreeNode, boolean]> = dispatch => (node, expand = false) => {
|
||||
export const setExpand: BoundMethodCreator<[TreeNode, boolean]> = dispatch => (
|
||||
node,
|
||||
expand = false,
|
||||
) => {
|
||||
visibleNodesGenerator.setExpand(node, expand)
|
||||
dispatch.call(focusNode, node, false)
|
||||
}
|
||||
|
||||
const toggleNodeExpansion: BoundMethodCreator<[TreeNode, boolean]> = dispatch => (
|
||||
export const toggleNodeExpansion: BoundMethodCreator<[TreeNode, boolean]> = dispatch => (
|
||||
node,
|
||||
skipScrollToNode,
|
||||
) => {
|
||||
|
|
@ -311,17 +322,16 @@ const toggleNodeExpansion: BoundMethodCreator<[TreeNode, boolean]> = dispatch =>
|
|||
tasksAfterRender.push(DOMHelper.focusFileExplorer)
|
||||
}
|
||||
|
||||
const focusNode: BoundMethodCreator<[TreeNode | null, boolean]> = dispatch => (
|
||||
export const focusNode: BoundMethodCreator<[TreeNode | null, boolean]> = dispatch => (
|
||||
node: TreeNode | null,
|
||||
skipScroll = false,
|
||||
) => {
|
||||
const { visibleNodes } = dispatch.get()
|
||||
const [{ visibleNodes }] = dispatch.get()
|
||||
if (!visibleNodes) return
|
||||
visibleNodesGenerator.focusNode(node)
|
||||
dispatch.call(updateVisibleNodes)
|
||||
}
|
||||
|
||||
const onNodeClick: BoundMethodCreator<[TreeNode]> = dispatch => node => {
|
||||
export const onNodeClick: BoundMethodCreator<[TreeNode]> = dispatch => node => {
|
||||
if (node.type === 'tree') {
|
||||
dispatch.call(toggleNodeExpansion, node, true)
|
||||
} else if (node.type === 'blob') {
|
||||
|
|
@ -334,23 +344,15 @@ const onNodeClick: BoundMethodCreator<[TreeNode]> = dispatch => node => {
|
|||
}
|
||||
}
|
||||
|
||||
const updateVisibleNodes: BoundMethodCreator = dispatch => () => {
|
||||
export const expandTo: BoundMethodCreator<[string[]]> = dispatch => currentPath => {
|
||||
const nodeExpandedTo = visibleNodesGenerator.expandTo(currentPath.join('/'))
|
||||
if (nodeExpandedTo) {
|
||||
visibleNodesGenerator.focusNode(nodeExpandedTo)
|
||||
}
|
||||
dispatch.call(updateVisibleNodes)
|
||||
}
|
||||
|
||||
export const updateVisibleNodes: BoundMethodCreator = dispatch => () => {
|
||||
const { visibleNodes } = visibleNodesGenerator
|
||||
dispatch.set({ visibleNodes })
|
||||
}
|
||||
|
||||
export default {
|
||||
init,
|
||||
setUpTree,
|
||||
execAfterRender,
|
||||
setStateText,
|
||||
handleKeyDown,
|
||||
onFocusSearchBar,
|
||||
search,
|
||||
setExpand,
|
||||
goTo,
|
||||
toggleNodeExpansion,
|
||||
focusNode,
|
||||
onNodeClick,
|
||||
updateVisibleNodes,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,13 @@
|
|||
import { Props } from 'components/SideBar'
|
||||
import { ConfigsContextShape } from 'containers/ConfigsContext'
|
||||
import { GetCreatedMethod, MethodCreator } from 'driver/connect'
|
||||
import configHelper, { Config, configKeys } from 'utils/configHelper'
|
||||
import DOMHelper from 'utils/DOMHelper'
|
||||
import GitHubHelper, {
|
||||
API_RATE_LIMIT,
|
||||
BAD_CREDENTIALS,
|
||||
BLOCKED_PROJECT,
|
||||
EMPTY_PROJECT,
|
||||
MetaData,
|
||||
NOT_FOUND,
|
||||
TreeData,
|
||||
} from 'utils/GitHubHelper'
|
||||
import keyHelper from 'utils/keyHelper'
|
||||
import URLHelper from 'utils/URLHelper'
|
||||
import * as DOMHelper from 'utils/DOMHelper'
|
||||
import * as GitHubHelper from 'utils/GitHubHelper'
|
||||
import { MetaData, TreeData } from 'utils/GitHubHelper'
|
||||
import * as URLHelper from 'utils/URLHelper'
|
||||
|
||||
export type Props = {
|
||||
configContext: ConfigsContextShape
|
||||
}
|
||||
|
||||
export type ConnectorState = {
|
||||
// error message
|
||||
|
|
@ -32,30 +27,18 @@ export type ConnectorState = {
|
|||
initializingPromise: Promise<void> | null
|
||||
} & {
|
||||
init: GetCreatedMethod<typeof init>
|
||||
onPJAXEnd: GetCreatedMethod<typeof onPJAXEnd>
|
||||
onKeyDown: GetCreatedMethod<typeof onKeyDown>
|
||||
setMetaData: GetCreatedMethod<typeof setMetaData>
|
||||
setShouldShow: GetCreatedMethod<typeof setShouldShow>
|
||||
toggleShowSideBar: GetCreatedMethod<typeof toggleShowSideBar>
|
||||
toggleShowSettings: GetCreatedMethod<typeof toggleShowSettings>
|
||||
useListeners: GetCreatedMethod<typeof useListeners>
|
||||
onAccessTokenChange: GetCreatedMethod<typeof onAccessTokenChange>
|
||||
onShortcutChange: GetCreatedMethod<typeof onShortcutChange>
|
||||
setCopyFile: GetCreatedMethod<typeof setCopyFile>
|
||||
setCopySnippet: GetCreatedMethod<typeof setCopySnippet>
|
||||
setCompressSingleton: GetCreatedMethod<typeof setCompressSingleton>
|
||||
setIntelligentToggle: GetCreatedMethod<typeof setIntelligentToggle>
|
||||
} & {
|
||||
baseSize: number
|
||||
toggleShowSideBarShortcut?: string
|
||||
accessToken?: string
|
||||
} & Pick<
|
||||
Config,
|
||||
'compressSingletonFolder' | 'copyFileButton' | 'copySnippetButton' | 'intelligentToggle'
|
||||
>
|
||||
}
|
||||
|
||||
type BoundMethodCreator<Args extends any[] = []> = MethodCreator<Props, ConnectorState, Args>
|
||||
|
||||
const init: BoundMethodCreator = dispatch => async () => {
|
||||
const { initializingPromise } = dispatch.get()
|
||||
export const init: BoundMethodCreator = dispatch => async () => {
|
||||
const [{ initializingPromise }] = dispatch.get()
|
||||
if (initializingPromise) await initializingPromise
|
||||
|
||||
let done: any = null // cannot use type `(() => void) | null` here
|
||||
|
|
@ -70,6 +53,7 @@ const init: BoundMethodCreator = dispatch => async () => {
|
|||
dispatch.set({ disabled: true })
|
||||
return
|
||||
}
|
||||
DOMHelper.markGitakoReadyState(true)
|
||||
dispatch.set({
|
||||
errorDueToAuth: false,
|
||||
showSettings: false,
|
||||
|
|
@ -82,24 +66,10 @@ const init: BoundMethodCreator = dispatch => async () => {
|
|||
}
|
||||
metaData.branchName = detectedBranchName || 'master'
|
||||
dispatch.call(setMetaData, metaData)
|
||||
const {
|
||||
sideBarWidth,
|
||||
access_token: accessToken,
|
||||
shortcut,
|
||||
compressSingletonFolder,
|
||||
copyFileButton,
|
||||
copySnippetButton,
|
||||
intelligentToggle,
|
||||
} = await configHelper.getAll()
|
||||
DOMHelper.decorateGitHubPageContent({ copyFileButton, copySnippetButton })
|
||||
const [, { configContext }] = dispatch.get()
|
||||
const { sideBarWidth, access_token: accessToken, intelligentToggle } = configContext.val
|
||||
dispatch.set({
|
||||
baseSize: sideBarWidth,
|
||||
accessToken,
|
||||
toggleShowSideBarShortcut: shortcut,
|
||||
compressSingletonFolder,
|
||||
copyFileButton,
|
||||
copySnippetButton,
|
||||
intelligentToggle,
|
||||
})
|
||||
|
||||
if (!metaData.branchName || !metaData.userName) return
|
||||
|
|
@ -151,7 +121,6 @@ const init: BoundMethodCreator = dispatch => async () => {
|
|||
const shouldShow =
|
||||
intelligentToggle === null ? URLHelper.isInCodePage(metaData) : intelligentToggle
|
||||
dispatch.call(setShouldShow, shouldShow)
|
||||
DOMHelper.markGitakoReadyState()
|
||||
} catch (err) {
|
||||
dispatch.call(handleError, err)
|
||||
} finally {
|
||||
|
|
@ -159,146 +128,57 @@ const init: BoundMethodCreator = dispatch => async () => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleError: BoundMethodCreator<[Error]> = dispatch => async err => {
|
||||
if (err.message === EMPTY_PROJECT) {
|
||||
export const handleError: BoundMethodCreator<[Error]> = dispatch => async err => {
|
||||
if (err.message === GitHubHelper.EMPTY_PROJECT) {
|
||||
dispatch.call(setError, 'This project seems to be empty.')
|
||||
} else if (err.message === BLOCKED_PROJECT) {
|
||||
} else if (err.message === GitHubHelper.BLOCKED_PROJECT) {
|
||||
dispatch.call(setError, 'This project is blocked.')
|
||||
} else if (
|
||||
err.message === NOT_FOUND ||
|
||||
err.message === BAD_CREDENTIALS ||
|
||||
err.message === API_RATE_LIMIT
|
||||
err.message === GitHubHelper.NOT_FOUND ||
|
||||
err.message === GitHubHelper.BAD_CREDENTIALS ||
|
||||
err.message === GitHubHelper.API_RATE_LIMIT
|
||||
) {
|
||||
dispatch.set({ errorDueToAuth: true })
|
||||
dispatch.call(setShowSettings, true)
|
||||
dispatch.call(setShouldShow, true)
|
||||
} else {
|
||||
dispatch.call(useListeners, false)
|
||||
DOMHelper.markGitakoReadyState(false)
|
||||
dispatch.call(setError, 'Gitako ate a bug, but it should recovery soon!')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
const onPJAXEnd: BoundMethodCreator = dispatch => () => {
|
||||
const { metaData, copyFileButton, copySnippetButton, intelligentToggle } = dispatch.get()
|
||||
DOMHelper.unmountTopProgressBar()
|
||||
DOMHelper.decorateGitHubPageContent({ copyFileButton, copySnippetButton })
|
||||
const mergedMetaData = { ...metaData, ...URLHelper.parse() }
|
||||
dispatch.call(setMetaData, mergedMetaData)
|
||||
|
||||
if (intelligentToggle === null) {
|
||||
dispatch.call(setShouldShow, URLHelper.isInCodePage(mergedMetaData))
|
||||
}
|
||||
}
|
||||
|
||||
const onKeyDown: BoundMethodCreator<[KeyboardEvent]> = dispatch => e => {
|
||||
const { toggleShowSideBarShortcut } = dispatch.get()
|
||||
if (toggleShowSideBarShortcut) {
|
||||
const keys = keyHelper.parseEvent(e)
|
||||
if (keys === toggleShowSideBarShortcut) {
|
||||
dispatch.call(toggleShowSideBar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const toggleShowSideBar: BoundMethodCreator = dispatch => () => {
|
||||
const { intelligentToggle } = dispatch.get()
|
||||
const shouldShow = !dispatch.get().shouldShow
|
||||
dispatch.call(setShouldShow, shouldShow)
|
||||
export const toggleShowSideBar: BoundMethodCreator = dispatch => () => {
|
||||
const [{ shouldShow }, { configContext }] = dispatch.get()
|
||||
dispatch.call(setShouldShow, !shouldShow)
|
||||
|
||||
const {
|
||||
val: { intelligentToggle },
|
||||
} = configContext
|
||||
if (intelligentToggle !== null) {
|
||||
dispatch.call(setIntelligentToggle, shouldShow)
|
||||
configContext.set({ intelligentToggle: !shouldShow })
|
||||
}
|
||||
}
|
||||
|
||||
const setShouldShow: BoundMethodCreator<
|
||||
[ConnectorState['shouldShow']]
|
||||
> = dispatch => shouldShow => {
|
||||
export const setShouldShow: BoundMethodCreator<[
|
||||
ConnectorState['shouldShow'],
|
||||
]> = dispatch => shouldShow => {
|
||||
dispatch.set({ shouldShow }, shouldShow ? DOMHelper.focusFileExplorer : undefined)
|
||||
DOMHelper.setBodyIndent(shouldShow)
|
||||
}
|
||||
|
||||
const setError: BoundMethodCreator<[ConnectorState['error']]> = dispatch => error => {
|
||||
export const setError: BoundMethodCreator<[ConnectorState['error']]> = dispatch => error => {
|
||||
dispatch.set({ error })
|
||||
dispatch.call(setShouldShow, false)
|
||||
}
|
||||
|
||||
const toggleShowSettings: BoundMethodCreator = dispatch => () =>
|
||||
export const toggleShowSettings: BoundMethodCreator = dispatch => () =>
|
||||
dispatch.set(({ showSettings }) => ({
|
||||
showSettings: !showSettings,
|
||||
}))
|
||||
|
||||
const setShowSettings: BoundMethodCreator<
|
||||
[ConnectorState['showSettings']]
|
||||
> = dispatch => showSettings => dispatch.set({ showSettings })
|
||||
export const setShowSettings: BoundMethodCreator<[
|
||||
ConnectorState['showSettings'],
|
||||
]> = dispatch => showSettings => dispatch.set({ showSettings })
|
||||
|
||||
const onAccessTokenChange: BoundMethodCreator<
|
||||
[ConnectorState['accessToken']]
|
||||
> = dispatch => accessToken => {
|
||||
dispatch.set({ accessToken })
|
||||
// reload when setting new accessToken
|
||||
if (accessToken) {
|
||||
dispatch.call(init)
|
||||
}
|
||||
}
|
||||
|
||||
const onShortcutChange: BoundMethodCreator<
|
||||
[ConnectorState['toggleShowSideBarShortcut']]
|
||||
> = dispatch => shortcut => dispatch.set({ toggleShowSideBarShortcut: shortcut })
|
||||
|
||||
const setMetaData: BoundMethodCreator<[ConnectorState['metaData']]> = dispatch => metaData =>
|
||||
export const setMetaData: BoundMethodCreator<[ConnectorState['metaData']]> = dispatch => metaData =>
|
||||
dispatch.set({ metaData })
|
||||
|
||||
const setCompressSingleton: BoundMethodCreator<
|
||||
[ConnectorState['compressSingletonFolder']]
|
||||
> = dispatch => compressSingletonFolder => dispatch.set({ compressSingletonFolder })
|
||||
|
||||
const setCopyFile: BoundMethodCreator<
|
||||
[ConnectorState['copyFileButton']]
|
||||
> = dispatch => copyFileButton => dispatch.set({ copyFileButton })
|
||||
|
||||
const setCopySnippet: BoundMethodCreator<
|
||||
[ConnectorState['copySnippetButton']]
|
||||
> = dispatch => copySnippetButton => dispatch.set({ copySnippetButton })
|
||||
|
||||
const setIntelligentToggle: BoundMethodCreator<
|
||||
[ConnectorState['intelligentToggle']]
|
||||
> = dispatch => intelligentToggle => {
|
||||
configHelper.setOne(configKeys.intelligentToggle, intelligentToggle)
|
||||
dispatch.set({ intelligentToggle })
|
||||
}
|
||||
|
||||
const useListeners: BoundMethodCreator<[boolean]> = dispatch => {
|
||||
const $onPJAXEnd = () => dispatch.call(onPJAXEnd)
|
||||
const $onKeyDown = (e: KeyboardEvent) => dispatch.call(onKeyDown, e)
|
||||
return on => {
|
||||
const { disabled } = dispatch.get()
|
||||
if (on && !disabled) {
|
||||
window.addEventListener('pjax:complete', $onPJAXEnd)
|
||||
window.addEventListener('keydown', $onKeyDown)
|
||||
} else {
|
||||
window.removeEventListener('pjax:complete', $onPJAXEnd)
|
||||
window.removeEventListener('keydown', $onKeyDown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
init,
|
||||
onPJAXEnd,
|
||||
onKeyDown,
|
||||
setShouldShow,
|
||||
setShowSettings,
|
||||
toggleShowSideBar,
|
||||
toggleShowSettings,
|
||||
onAccessTokenChange,
|
||||
onShortcutChange,
|
||||
setMetaData,
|
||||
setCompressSingleton,
|
||||
setCopyFile,
|
||||
setCopySnippet,
|
||||
setIntelligentToggle,
|
||||
setError,
|
||||
handleError,
|
||||
useListeners,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,11 @@
|
|||
export { default as SideBar } from './SideBar'
|
||||
export { default as FileExplorer } from './FileExplorer'
|
||||
import { Sources } from 'driver/connect'
|
||||
import * as FileExplorer from './FileExplorer'
|
||||
import {
|
||||
ConnectorState as FileExplorerConnectorState,
|
||||
Props as FileExplorerProps,
|
||||
} from './FileExplorer'
|
||||
import * as SideBar from './SideBar'
|
||||
import { ConnectorState as SideBarConnectorState, Props as SideBarProps } from './SideBar'
|
||||
|
||||
export const FileExplorerCore: Sources<FileExplorerProps, FileExplorerConnectorState> = FileExplorer
|
||||
export const SideBarCore: Sources<SideBarProps, SideBarConnectorState> = SideBar
|
||||
|
|
|
|||
|
|
@ -9,3 +9,5 @@ export const oauth = {
|
|||
clientId: process.env.GITHUB_OAUTH_CLIENT_ID,
|
||||
clientSecret: process.env.GITHUB_OAUTH_CLIENT_SECRET,
|
||||
}
|
||||
|
||||
export const VERSION = process.env.VERSION
|
||||
|
|
|
|||
|
|
@ -1 +1,3 @@
|
|||
window.requestAnimationFrame = window.requestAnimationFrame.bind(window)
|
||||
window.setTimeout = window.setTimeout.bind(window)
|
||||
window.clearTimeout = window.clearTimeout.bind(window)
|
||||
|
|
|
|||
14
src/global.d.ts
vendored
Normal file
14
src/global.d.ts
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
type ValSet<T> = {
|
||||
val: T
|
||||
set: (val: T) => void
|
||||
}
|
||||
|
||||
type PartialValSet<T> = {
|
||||
val: T
|
||||
set: (val: Partial<T>) => void
|
||||
}
|
||||
|
||||
declare module '*.csv' {
|
||||
const content: string
|
||||
export default content
|
||||
}
|
||||
|
|
@ -1,18 +1,31 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Gitako - Github file tree",
|
||||
"version": "0.5.15",
|
||||
"version": "0.8.2",
|
||||
"description": "The missing part of GitHub.",
|
||||
"author": "EnixCoda",
|
||||
"icons": {
|
||||
"128": "icons/Gitako-128x128.png"
|
||||
},
|
||||
"homepage_url": "https://github.com/EnixCoda/Gitako",
|
||||
"permissions": ["storage", "*://*.github.com/*", "*://*.sentry.io/*"],
|
||||
"permissions": [
|
||||
"storage",
|
||||
"*://*.github.com/*",
|
||||
"*://*.sentry.io/*"
|
||||
],
|
||||
"web_accessible_resources": [
|
||||
"icons/vscode/*"
|
||||
],
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["https://github.com/*"],
|
||||
"js": ["firefox-shim.js", "browser-polyfill.js", "content.js"]
|
||||
"matches": [
|
||||
"https://github.com/*"
|
||||
],
|
||||
"js": [
|
||||
"firefox-shim.js",
|
||||
"browser-polyfill.js",
|
||||
"content.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,25 @@
|
|||
/**
|
||||
* this helper helps manipulating DOM
|
||||
*/
|
||||
|
||||
import { raiseError } from 'analytics'
|
||||
import { Clippy, ClippyClassName } from 'components/Clippy'
|
||||
import { CopyFileButton, copyFileButtonClassName } from 'components/CopyFileButton'
|
||||
import * as NProgress from 'nprogress'
|
||||
import * as PJAX from 'pjax'
|
||||
import * as React from 'react'
|
||||
import { renderReact } from './general'
|
||||
|
||||
NProgress.configure({ showSpinner: false })
|
||||
|
||||
/**
|
||||
* when gitako is ready, make page's header narrower
|
||||
* or cancel it
|
||||
*/
|
||||
function markGitakoReadyState() {
|
||||
export function markGitakoReadyState(ready: boolean) {
|
||||
const readyClassName = 'gitako-ready'
|
||||
document.body.classList.add(readyClassName)
|
||||
const classList = document.body.classList
|
||||
if (ready) classList.add(readyClassName)
|
||||
else classList.remove(readyClassName)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -21,7 +27,7 @@ function markGitakoReadyState() {
|
|||
* otherwise, hide the space
|
||||
*/
|
||||
export const bodySpacingClassName = 'with-gitako-spacing'
|
||||
function setBodyIndent(shouldShowGitako: boolean) {
|
||||
export function setBodyIndent(shouldShowGitako: boolean) {
|
||||
if (shouldShowGitako) {
|
||||
document.body.classList.add(bodySpacingClassName)
|
||||
} else {
|
||||
|
|
@ -35,10 +41,10 @@ function $<EE extends Element, E extends (element: EE) => any, O extends () => a
|
|||
otherwise?: O,
|
||||
): E extends never
|
||||
? O extends never
|
||||
? (Element | null)
|
||||
? Element | null
|
||||
: ReturnType<O> | null
|
||||
: O extends never
|
||||
? (ReturnType<E> | null)
|
||||
? ReturnType<E> | null
|
||||
: ReturnType<O> | ReturnType<E> {
|
||||
const element = document.querySelector(selector)
|
||||
if (element) {
|
||||
|
|
@ -47,18 +53,18 @@ function $<EE extends Element, E extends (element: EE) => any, O extends () => a
|
|||
return otherwise ? otherwise() : null
|
||||
}
|
||||
|
||||
function isInCodePage() {
|
||||
export function isInCodePage() {
|
||||
const branchListSelector = '#branch-select-menu.branch-select-menu'
|
||||
return Boolean($(branchListSelector))
|
||||
}
|
||||
|
||||
function getBranches() {
|
||||
export function getBranches() {
|
||||
const branchSelector = '.branch-select-menu .select-menu-list > div .select-menu-item-text'
|
||||
const branchElements = Array.from(document.querySelectorAll(branchSelector))
|
||||
return branchElements.map(element => element.innerHTML.trim())
|
||||
}
|
||||
|
||||
function getCurrentBranch() {
|
||||
export function getCurrentBranch() {
|
||||
const selectedBranchButtonSelector = '.repository-content .branch-select-menu summary'
|
||||
const branchButtonElement: HTMLElement = $(selectedBranchButtonSelector)
|
||||
if (branchButtonElement) {
|
||||
|
|
@ -92,16 +98,15 @@ function getCurrentBranch() {
|
|||
|
||||
/**
|
||||
* add the logo element into DOM
|
||||
*
|
||||
*/
|
||||
function insertLogoMountPoint() {
|
||||
export function insertLogoMountPoint() {
|
||||
const logoSelector = '.gitako .gitako-logo'
|
||||
return $(logoSelector) || createLogoMountPoint()
|
||||
}
|
||||
|
||||
function createLogoMountPoint() {
|
||||
const logoMountElement = document.createElement('div')
|
||||
logoMountElement.setAttribute('class', 'gitako-logo-mount-point')
|
||||
logoMountElement.classList.add('gitako-logo-mount-point')
|
||||
document.body.appendChild(logoMountElement)
|
||||
return logoMountElement
|
||||
}
|
||||
|
|
@ -110,7 +115,7 @@ function createLogoMountPoint() {
|
|||
* content above the file navigation bar is same for all pages of the repo
|
||||
* use this function to scroll down a bit to hide them
|
||||
*/
|
||||
function scrollToRepoContent() {
|
||||
export function scrollToRepoContent() {
|
||||
const repositoryContentSelector = '.repository-content'
|
||||
// do NOT use behavior: smooth here as it will scroll horizontally
|
||||
$(repositoryContentSelector, repositoryContentElement =>
|
||||
|
|
@ -119,16 +124,25 @@ function scrollToRepoContent() {
|
|||
}
|
||||
|
||||
const pjax = new PJAX({
|
||||
elements: '.pjax-link',
|
||||
selectors: ['.repository-content', 'title'],
|
||||
elements: 'match-nothing-selector',
|
||||
selectors: [
|
||||
'.repository-content',
|
||||
'title',
|
||||
'[data-pjax="#js-repo-pjax-container"]',
|
||||
'.page-content',
|
||||
],
|
||||
scrollTo: false,
|
||||
analytics: false,
|
||||
cacheBust: false,
|
||||
forceCache: true, // TODO: merge namespace, add forceCache
|
||||
})
|
||||
|
||||
function loadWithPJAX(URL: string) {
|
||||
NProgress.start()
|
||||
// Note: shall not enable below pjax:send listener as there would be dual bar when GitHub PJAX links are triggered
|
||||
// window.addEventListener('pjax:send', () => mountTopProgressBar())
|
||||
window.addEventListener('pjax:complete', () => unmountTopProgressBar())
|
||||
|
||||
export function loadWithPJAX(URL: string) {
|
||||
mountTopProgressBar()
|
||||
pjax.loadUrl(URL, { scrollTo: 0 })
|
||||
}
|
||||
|
||||
|
|
@ -141,6 +155,7 @@ function loadWithPJAX(URL: string) {
|
|||
const PAGE_TYPES = {
|
||||
RAW_TEXT: 'raw_text',
|
||||
RENDERED: 'rendered',
|
||||
SEARCH: 'search',
|
||||
// PREVIEW: 'preview',
|
||||
OTHERS: 'others',
|
||||
}
|
||||
|
|
@ -153,11 +168,14 @@ const PAGE_TYPES = {
|
|||
*
|
||||
* TODO: distinguish type 'preview'
|
||||
*/
|
||||
function getCurrentPageType() {
|
||||
const blobWrapperSelector = '.repository-content .file .blob-wrapper table'
|
||||
export function getCurrentPageType() {
|
||||
const blobPathSelector = '#blob-path' // path next to branch switcher
|
||||
const blobWrapperSelector = '.repository-content .blob-wrapper table'
|
||||
const readmeSelector = '.repository-content .readme'
|
||||
const searchResultSelector = '.codesearch-results'
|
||||
return (
|
||||
$(blobWrapperSelector, () => PAGE_TYPES.RAW_TEXT) ||
|
||||
$(searchResultSelector, () => PAGE_TYPES.SEARCH) ||
|
||||
$(blobWrapperSelector, () => $(blobPathSelector, () => PAGE_TYPES.RAW_TEXT)) ||
|
||||
$(readmeSelector, () => PAGE_TYPES.RENDERED) ||
|
||||
PAGE_TYPES.OTHERS
|
||||
)
|
||||
|
|
@ -165,7 +183,7 @@ function getCurrentPageType() {
|
|||
|
||||
export const REPO_TYPE_PRIVATE = 'private'
|
||||
export const REPO_TYPE_PUBLIC = 'public'
|
||||
function getRepoPageType() {
|
||||
export function getRepoPageType() {
|
||||
const headerSelector = `#js-repo-pjax-container .pagehead.repohead h1`
|
||||
return $(headerSelector, header => {
|
||||
const repoPageTypes = [REPO_TYPE_PRIVATE, REPO_TYPE_PUBLIC]
|
||||
|
|
@ -178,69 +196,54 @@ function getRepoPageType() {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* get text content of raw text content
|
||||
*/
|
||||
export function getCodeElement() {
|
||||
if (getCurrentPageType() === PAGE_TYPES.RAW_TEXT) {
|
||||
const codeContentSelector = '.repository-content .data table'
|
||||
const codeContentElement = $(codeContentSelector)
|
||||
if (!codeContentElement) {
|
||||
raiseError(new Error('cannot find code content element'))
|
||||
}
|
||||
return codeContentElement
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* add copy file content buttons to button groups
|
||||
* click these buttons will copy file content to clipboard
|
||||
*/
|
||||
function attachCopyFileBtn() {
|
||||
/**
|
||||
* get text content of raw text content
|
||||
*/
|
||||
function getCodeElement() {
|
||||
if (getCurrentPageType() === PAGE_TYPES.RAW_TEXT) {
|
||||
const codeContentSelector = '.repository-content .file .data table'
|
||||
const codeContentElement = $(codeContentSelector)
|
||||
if (!codeContentElement) {
|
||||
raiseError(new Error('cannot find code content element'))
|
||||
}
|
||||
return codeContentElement
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* change inner text of copy file button to give feedback
|
||||
* @param {element} copyFileBtn
|
||||
* @param {string} text
|
||||
*/
|
||||
function setTempCopyFileBtnText(copyFileBtn: HTMLButtonElement, text: string) {
|
||||
copyFileBtn.innerText = text
|
||||
window.setTimeout(() => (copyFileBtn.innerText = 'Copy file'), 1000)
|
||||
}
|
||||
|
||||
export function attachCopyFileBtn() {
|
||||
if (getCurrentPageType() === PAGE_TYPES.RAW_TEXT) {
|
||||
const btnGroupSelector = [
|
||||
// the button group next to navigation bar
|
||||
'.repository-content .file-navigation.js-zeroclipboard-container .BtnGroup',
|
||||
// the button group in file content header
|
||||
'.repository-content .file .file-header .file-actions .BtnGroup',
|
||||
].join(', ')
|
||||
const btnGroups = document.querySelectorAll(btnGroupSelector)
|
||||
// the button group in file content header
|
||||
const buttonGroupSelector = '.repository-content > .Box > .Box-header .BtnGroup'
|
||||
const buttonGroups = document.querySelectorAll(buttonGroupSelector)
|
||||
|
||||
btnGroups.forEach(btnGroup => {
|
||||
const copyFileBtn = document.createElement('button')
|
||||
copyFileBtn.classList.add('btn', 'btn-sm', 'BtnGroup-item', 'copy-file-btn')
|
||||
copyFileBtn.innerText = 'Copy file'
|
||||
copyFileBtn.addEventListener('click', () => {
|
||||
const codeElement = getCodeElement()
|
||||
if (codeElement) {
|
||||
if (copyElementContent(codeElement)) {
|
||||
setTempCopyFileBtnText(copyFileBtn, 'Success!')
|
||||
} else {
|
||||
setTempCopyFileBtnText(copyFileBtn, 'Copy failed!')
|
||||
}
|
||||
}
|
||||
})
|
||||
btnGroup.insertBefore(copyFileBtn, btnGroup.lastChild)
|
||||
if (buttonGroups.length === 0) {
|
||||
raiseError(new Error(`No button groups found`))
|
||||
}
|
||||
|
||||
buttonGroups.forEach(async buttonGroup => {
|
||||
if (!buttonGroup.lastElementChild) return
|
||||
const button = await renderReact(React.createElement(CopyFileButton))
|
||||
if (button instanceof HTMLElement) {
|
||||
buttonGroup.appendChild(button)
|
||||
}
|
||||
})
|
||||
return () => {
|
||||
const buttons = document.querySelectorAll(`.${copyFileButtonClassName}`)
|
||||
buttons.forEach(button => {
|
||||
button.parentElement?.removeChild(button)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* copy content of a DOM element to clipboard
|
||||
* @param {element} element
|
||||
* @returns {boolean} whether copy is successful
|
||||
*/
|
||||
function copyElementContent(element: Element) {
|
||||
export function copyElementContent(element: Element): boolean {
|
||||
let selection = window.getSelection()
|
||||
if (selection) selection.removeAllRanges()
|
||||
const range = document.createRange()
|
||||
|
|
@ -253,67 +256,20 @@ function copyElementContent(element: Element) {
|
|||
return isCopySuccessful
|
||||
}
|
||||
|
||||
/**
|
||||
* create a copy file content button `clippy`
|
||||
* once mouse enters a code snippet of markdown, move clippy into it
|
||||
* user can copy the snippet's content by click it
|
||||
*
|
||||
* TODO: 'reactify' it
|
||||
*/
|
||||
function createClippy() {
|
||||
function setTempClippyIconFeedback(clippy: Element, type: 'success' | 'fail') {
|
||||
const tempIconClassName = type === 'success' ? 'success' : 'fail'
|
||||
clippy.classList.add(tempIconClassName)
|
||||
window.setTimeout(() => {
|
||||
clippy.classList.remove(tempIconClassName)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
/**
|
||||
* <div class="clippy-wrapper">
|
||||
* <button class="clippy">
|
||||
* <i class="octicon octicon-clippy" />
|
||||
* </button>
|
||||
* </div>
|
||||
*/
|
||||
const clippyWrapper = document.createElement('div')
|
||||
clippyWrapper.classList.add('clippy-wrapper')
|
||||
const clippy = document.createElement('button')
|
||||
clippy.classList.add('clippy')
|
||||
const clippyIcon = document.createElement('i')
|
||||
clippyIcon.classList.add('icon')
|
||||
|
||||
clippyWrapper.appendChild(clippy)
|
||||
clippy.appendChild(clippyIcon)
|
||||
|
||||
// set clipboard with current code snippet element's content
|
||||
clippy.addEventListener('click', function onClippyClick() {
|
||||
if (copyElementContent(currentCodeSnippetElement)) {
|
||||
setTempClippyIconFeedback(clippy, 'success')
|
||||
} else {
|
||||
setTempClippyIconFeedback(clippy, 'fail')
|
||||
}
|
||||
})
|
||||
|
||||
return clippyWrapper
|
||||
}
|
||||
|
||||
const clippy = createClippy()
|
||||
|
||||
let currentCodeSnippetElement: Element
|
||||
function attachCopySnippet() {
|
||||
export function attachCopySnippet() {
|
||||
const readmeSelector = '.repository-content div#readme'
|
||||
return $(readmeSelector, () => {
|
||||
const readmeArticleSelector = '.repository-content div#readme article'
|
||||
$(
|
||||
return $(
|
||||
readmeArticleSelector,
|
||||
readmeElement =>
|
||||
readmeElement.addEventListener('mouseover', e => {
|
||||
// only move clippy when mouse is over a new snippet(<pre>)
|
||||
const target = e.target as Element
|
||||
if (target.nodeName === 'PRE') {
|
||||
if (currentCodeSnippetElement !== target) {
|
||||
currentCodeSnippetElement = target
|
||||
readmeElement => {
|
||||
const mouseOverCallback = async ({ target }: Event): Promise<void> => {
|
||||
if (target instanceof Element && target.nodeName === 'PRE') {
|
||||
if (
|
||||
target.previousSibling === null ||
|
||||
!(target.previousSibling instanceof Element) ||
|
||||
!target.previousSibling.classList.contains(ClippyClassName)
|
||||
) {
|
||||
/**
|
||||
* <article>
|
||||
* <pre></pre> <!-- case A -->
|
||||
|
|
@ -322,10 +278,26 @@ function attachCopySnippet() {
|
|||
* </div>
|
||||
* </article>
|
||||
*/
|
||||
if (target.parentNode) target.parentNode.insertBefore(clippy, target)
|
||||
if (target.parentNode) {
|
||||
const clippyElement = await renderReact(
|
||||
React.createElement(Clippy, { codeSnippetElement: target }),
|
||||
)
|
||||
if (clippyElement instanceof HTMLElement) {
|
||||
target.parentNode.insertBefore(clippyElement, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
}
|
||||
readmeElement.addEventListener('mouseover', mouseOverCallback)
|
||||
return () => {
|
||||
readmeElement.removeEventListener('mouseover', mouseOverCallback)
|
||||
const buttons = document.querySelectorAll(`.${ClippyClassName}`)
|
||||
buttons.forEach(button => {
|
||||
button.parentElement?.removeChild(button)
|
||||
})
|
||||
}
|
||||
},
|
||||
() => {
|
||||
const plainReadmeSelector = '.repository-content div#readme .plain'
|
||||
$(plainReadmeSelector, undefined, () =>
|
||||
|
|
@ -341,14 +313,14 @@ function attachCopySnippet() {
|
|||
/**
|
||||
* focus to side bar, user will be able to manipulate it with keyboard
|
||||
*/
|
||||
function focusFileExplorer() {
|
||||
export function focusFileExplorer() {
|
||||
const sideBarContentSelector = '.gitako-side-bar .file-explorer'
|
||||
$(sideBarContentSelector, sideBarElement => {
|
||||
if (sideBarElement instanceof HTMLElement) sideBarElement.focus()
|
||||
})
|
||||
}
|
||||
|
||||
function focusSearchInput() {
|
||||
export function focusSearchInput() {
|
||||
const searchInputSelector = '.search-input'
|
||||
$(searchInputSelector, searchInputElement => {
|
||||
if (
|
||||
|
|
@ -360,44 +332,10 @@ function focusSearchInput() {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* a combination of few above functions
|
||||
*/
|
||||
function decorateGitHubPageContent({
|
||||
copyFileButton,
|
||||
copySnippetButton,
|
||||
}: {
|
||||
copyFileButton: boolean
|
||||
copySnippetButton: boolean
|
||||
}) {
|
||||
if (copyFileButton) attachCopyFileBtn()
|
||||
if (copySnippetButton) attachCopySnippet()
|
||||
}
|
||||
|
||||
function mountTopProgressBar() {
|
||||
export function mountTopProgressBar() {
|
||||
NProgress.start()
|
||||
}
|
||||
|
||||
function unmountTopProgressBar() {
|
||||
export function unmountTopProgressBar() {
|
||||
NProgress.done()
|
||||
}
|
||||
|
||||
export default {
|
||||
loadWithPJAX,
|
||||
attachCopyFileBtn,
|
||||
attachCopySnippet,
|
||||
decorateGitHubPageContent,
|
||||
focusSearchInput,
|
||||
focusFileExplorer,
|
||||
getCurrentPageType,
|
||||
getRepoPageType,
|
||||
insertLogoMountPoint,
|
||||
markGitakoReadyState,
|
||||
setBodyIndent,
|
||||
scrollToRepoContent,
|
||||
mountTopProgressBar,
|
||||
unmountTopProgressBar,
|
||||
isInCodePage,
|
||||
getBranches,
|
||||
getCurrentBranch,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { raiseError } from 'analytics'
|
||||
export const SERVER_FAULT = 'Server Fault'
|
||||
export const NOT_FOUND = 'Repo Not Found'
|
||||
export const BAD_CREDENTIALS = 'Bad credentials'
|
||||
export const API_RATE_LIMIT = `API rate limit`
|
||||
|
|
@ -31,14 +32,18 @@ async function request(url: string, { accessToken }: Options = {}) {
|
|||
headers.Authorization = `token ${accessToken}`
|
||||
}
|
||||
const res = await fetch(url, { headers })
|
||||
const contentType = res.headers.get('Content-Type')
|
||||
if (!contentType || !contentType.includes('application/json')) {
|
||||
throw new Error(`Response content is not JSON`)
|
||||
}
|
||||
// About res.ok:
|
||||
// True if res.status between 200~299
|
||||
// Ref: https://developer.mozilla.org/en-US/docs/Web/API/Response/ok
|
||||
if (res.ok) {
|
||||
return res.json()
|
||||
} else {
|
||||
// for private repo, GitHub api also responses with 404 when unauthorized
|
||||
if (res.status === 404) throw new Error(NOT_FOUND)
|
||||
if (res.status === 404 || res.status === 401) throw new Error(NOT_FOUND)
|
||||
else if (res.status === 500) throw new Error(SERVER_FAULT)
|
||||
else {
|
||||
const content = await res.json()
|
||||
if (apiRateLimitExceeded(content)) throw new Error(API_RATE_LIMIT)
|
||||
|
|
@ -70,7 +75,11 @@ type RepoMetaData = {
|
|||
}
|
||||
}
|
||||
|
||||
async function getRepoMeta({ userName, repoName, accessToken }: MetaData): Promise<RepoMetaData> {
|
||||
export async function getRepoMeta({
|
||||
userName,
|
||||
repoName,
|
||||
accessToken,
|
||||
}: MetaData): Promise<RepoMetaData> {
|
||||
const url = `https://api.github.com/repos/${userName}/${repoName}`
|
||||
return await request(url, { accessToken })
|
||||
}
|
||||
|
|
@ -91,7 +100,7 @@ export type TreeData = {
|
|||
url: string
|
||||
}
|
||||
|
||||
async function getTreeData({
|
||||
export async function getTreeData({
|
||||
userName,
|
||||
repoName,
|
||||
branchName,
|
||||
|
|
@ -109,7 +118,7 @@ export type BlobData = {
|
|||
url: string
|
||||
}
|
||||
|
||||
async function getBlobData({
|
||||
export async function getBlobData({
|
||||
userName,
|
||||
repoName,
|
||||
accessToken,
|
||||
|
|
@ -121,17 +130,10 @@ async function getBlobData({
|
|||
return await request(url, { accessToken })
|
||||
}
|
||||
|
||||
function getUrlForRedirect(
|
||||
export function getUrlForRedirect(
|
||||
{ userName, repoName, branchName }: MetaData,
|
||||
type = 'blob',
|
||||
path?: string,
|
||||
) {
|
||||
return `https://github.com/${userName}/${repoName}/${type}/${branchName}/${path}`
|
||||
}
|
||||
|
||||
export default {
|
||||
getRepoMeta,
|
||||
getTreeData,
|
||||
getBlobData,
|
||||
getUrlForRedirect,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { raiseError } from 'analytics'
|
||||
import { MetaData } from './GitHubHelper'
|
||||
|
||||
function parse(): MetaData & { path: string[] } {
|
||||
export function parse(): MetaData & { path: string[] } {
|
||||
const { pathname } = window.location
|
||||
let [
|
||||
,
|
||||
|
|
@ -19,12 +19,12 @@ function parse(): MetaData & { path: string[] } {
|
|||
}
|
||||
}
|
||||
|
||||
function parseSHA() {
|
||||
export function parseSHA() {
|
||||
const { type, path } = parse()
|
||||
return type === 'blob' || type === 'tree' ? path[0] : undefined
|
||||
}
|
||||
|
||||
function isInRepoPage() {
|
||||
export function isInRepoPage() {
|
||||
const repoHeaderSelector = '.repohead'
|
||||
return Boolean(document.querySelector(repoHeaderSelector))
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ const TYPES = {
|
|||
// TODO: record more types
|
||||
}
|
||||
|
||||
function isInCodePage(metaData: MetaData = {}) {
|
||||
export function isInCodePage(metaData: MetaData = {}) {
|
||||
const mergedRepo = { ...parse(), ...metaData }
|
||||
const { type, branchName } = mergedRepo
|
||||
return Boolean(
|
||||
|
|
@ -57,7 +57,7 @@ function isCompleteCommitSHA(sha?: string) {
|
|||
return typeof sha === 'string' && /^[abcdef0-9]{40}$/i.test(sha)
|
||||
}
|
||||
|
||||
function getCurrentPath(branchName = '') {
|
||||
export function getCurrentPath(branchName = '') {
|
||||
const { path, type } = parse()
|
||||
if (type === 'blob' || type === 'tree') {
|
||||
if (isCommitPath(path)) {
|
||||
|
|
@ -90,11 +90,3 @@ function getCurrentPath(branchName = '') {
|
|||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export default {
|
||||
getCurrentPath,
|
||||
isInRepoPage,
|
||||
isInCodePage,
|
||||
parse,
|
||||
parseSHA,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import storageHelper from 'utils/storageHelper'
|
||||
import * as storageHelper from 'utils/storageHelper'
|
||||
|
||||
export type Config = {
|
||||
sideBarWidth: number
|
||||
|
|
@ -8,6 +8,7 @@ export type Config = {
|
|||
copyFileButton: boolean
|
||||
copySnippetButton: boolean
|
||||
intelligentToggle: boolean | null // `null` stands for intelligent, boolean for sidebar open status
|
||||
icons: 'rich' | 'dim' | 'native'
|
||||
}
|
||||
|
||||
export enum configKeys {
|
||||
|
|
@ -18,9 +19,10 @@ export enum configKeys {
|
|||
copyFileButton = 'copyFileButton',
|
||||
copySnippetButton = 'copySnippetButton',
|
||||
intelligentToggle = 'intelligentToggle',
|
||||
icons = 'icons',
|
||||
}
|
||||
|
||||
const defaultConfigs: Config = {
|
||||
export const defaultConfigs: Config = {
|
||||
sideBarWidth: 260,
|
||||
shortcut: undefined,
|
||||
access_token: undefined,
|
||||
|
|
@ -28,42 +30,23 @@ const defaultConfigs: Config = {
|
|||
copyFileButton: true,
|
||||
copySnippetButton: true,
|
||||
intelligentToggle: null,
|
||||
icons: 'rich',
|
||||
}
|
||||
|
||||
const configKeyArray = Object.values(configKeys)
|
||||
|
||||
function applyDefaultConfigs(configs: Config) {
|
||||
return configKeyArray.reduce(
|
||||
(applied, configKey) => {
|
||||
const key = configKey as keyof Config
|
||||
Object.assign(applied, { [key]: key in configs ? configs[key] : defaultConfigs[key] })
|
||||
return applied
|
||||
},
|
||||
{} as Config,
|
||||
)
|
||||
return configKeyArray.reduce((applied, configKey) => {
|
||||
const key = configKey as keyof Config
|
||||
Object.assign(applied, { [key]: key in configs ? configs[key] : defaultConfigs[key] })
|
||||
return applied
|
||||
}, {} as Config)
|
||||
}
|
||||
|
||||
async function getAll(): Promise<Config> {
|
||||
export async function get(): Promise<Config> {
|
||||
return applyDefaultConfigs(await storageHelper.get(configKeyArray))
|
||||
}
|
||||
|
||||
async function getOne(key: configKeys) {
|
||||
return (await getAll())[key]
|
||||
}
|
||||
|
||||
async function setAll(partialConfig: Partial<Config>) {
|
||||
export async function set(partialConfig: Partial<Config>) {
|
||||
return await storageHelper.set(partialConfig)
|
||||
}
|
||||
|
||||
async function setOne(key: configKeys, value: any) {
|
||||
return await setAll({
|
||||
[key]: value,
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
getAll,
|
||||
getOne,
|
||||
setAll,
|
||||
setOne,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* cx('class1', { class2: true, class3: false }) --> 'class1 class2'
|
||||
*/
|
||||
export default function cx(...classNames: any[]): string {
|
||||
export function cx(...classNames: any[]): string {
|
||||
return classNames
|
||||
.filter(Boolean)
|
||||
.map(className => {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import { ReactElement } from 'react'
|
||||
import * as ReactDOM from 'react-dom'
|
||||
import { TreeNode } from './VisibleNodesGenerator'
|
||||
|
||||
export function pick<T>(source: T, keys: string[]): Partial<T> {
|
||||
if (keys && typeof keys === 'object') {
|
||||
return (Array.isArray(keys) ? keys : Object.keys(keys)).reduce(
|
||||
(copy, key) => {
|
||||
if (key in source) {
|
||||
copy[key as keyof T] = source[key as keyof T]
|
||||
}
|
||||
return copy
|
||||
},
|
||||
{} as Partial<T>,
|
||||
)
|
||||
return (Array.isArray(keys) ? keys : Object.keys(keys)).reduce((copy, key) => {
|
||||
if (key in source) {
|
||||
copy[key as keyof T] = source[key as keyof T]
|
||||
}
|
||||
return copy
|
||||
}, {} as Partial<T>)
|
||||
}
|
||||
return {} as Partial<T>
|
||||
}
|
||||
|
|
@ -113,29 +112,42 @@ export function parseURLSearch(search: string = window.location.search) {
|
|||
return parsed
|
||||
}
|
||||
|
||||
export async function JSONRequest(url: string, data: any, method = 'post') {
|
||||
return (await fetch(url, {
|
||||
method,
|
||||
mode: 'cors',
|
||||
cache: 'no-cache',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
redirect: 'follow',
|
||||
referrer: 'no-referrer',
|
||||
body: JSON.stringify(data),
|
||||
})).json()
|
||||
export async function JSONRequest(url: string, data: any, extra: RequestInit = { method: 'post' }) {
|
||||
return (
|
||||
await fetch(url, {
|
||||
mode: 'cors',
|
||||
cache: 'no-cache',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
redirect: 'follow',
|
||||
referrerPolicy: 'no-referrer',
|
||||
method: extra.method || 'post',
|
||||
body: JSON.stringify(data),
|
||||
...extra,
|
||||
})
|
||||
).json()
|
||||
}
|
||||
|
||||
export function searchKeyToRegexps(searchKey: string) {
|
||||
if (!searchKey) return []
|
||||
|
||||
try {
|
||||
const flags = /[A-Z]/.test(searchKey) ? '' : 'i'
|
||||
// case-sensitive when searchKey contains uppercase char
|
||||
return [new RegExp(searchKey, /[A-Z]/i.test(searchKey) ? '' : 'i')]
|
||||
return [new RegExp(searchKey, flags)]
|
||||
} catch (err) {
|
||||
return [/$^/] // matching nothing if failed transforming regexp
|
||||
}
|
||||
}
|
||||
|
||||
export async function renderReact(element: ReactElement) {
|
||||
return new Promise<Node>(resolve => {
|
||||
const mount = document.createElement('div')
|
||||
ReactDOM.render(element, mount, () => {
|
||||
resolve(mount.childNodes[0])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from 'react'
|
||||
import { useLocation } from 'react-use'
|
||||
import { createStyleSheet, setStyleSheetMedia } from './general'
|
||||
|
||||
export function useWindowSize(
|
||||
|
|
@ -47,3 +48,43 @@ export function usePrevious<T>(newValue: T) {
|
|||
})
|
||||
return previousRef.current
|
||||
}
|
||||
|
||||
export function useStates<S>(
|
||||
initialState: S | (() => S),
|
||||
): { val: S; set: React.Dispatch<React.SetStateAction<S>> } {
|
||||
const [val, set] = React.useState(initialState)
|
||||
return { val, set }
|
||||
}
|
||||
|
||||
export function useAsyncMemo<T, D extends any[] | readonly any[]>(
|
||||
factory: (dependencies: D) => T | Promise<T>,
|
||||
deps: D,
|
||||
initialValue: T,
|
||||
): T {
|
||||
const firstTime = React.useRef(true)
|
||||
const state = useStates<T>(() => initialValue)
|
||||
React.useEffect(() => {
|
||||
if (firstTime.current) firstTime.current = false
|
||||
Promise.resolve(factory(deps)).then(consumed => state.set(() => consumed))
|
||||
}, deps)
|
||||
return state.val
|
||||
}
|
||||
|
||||
export function useDidUpdate(effect: React.EffectCallback, deps?: React.DependencyList) {
|
||||
const firstTime = React.useRef(true)
|
||||
React.useEffect(() => {
|
||||
if (firstTime.current) {
|
||||
firstTime.current = false
|
||||
return
|
||||
}
|
||||
return effect()
|
||||
}, deps)
|
||||
}
|
||||
|
||||
export function useOnLocationChange(
|
||||
callback: React.EffectCallback,
|
||||
extraDeps: React.DependencyList = [],
|
||||
) {
|
||||
const { href, pathname, search } = useLocation()
|
||||
React.useEffect(callback, [href, pathname, search, ...extraDeps])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ function parseKeyCode(code: string) {
|
|||
return code.toLowerCase().replace(/^control$/, 'ctrl')
|
||||
}
|
||||
|
||||
function parseEvent(e: KeyboardEvent | React.KeyboardEvent) {
|
||||
export function parseEvent(e: KeyboardEvent | React.KeyboardEvent) {
|
||||
const { altKey: alt, shiftKey: shift, metaKey: meta, ctrlKey: ctrl } = e
|
||||
try {
|
||||
const code = parseKeyCode(e.key)
|
||||
|
|
@ -57,7 +57,3 @@ function parseEvent(e: KeyboardEvent | React.KeyboardEvent) {
|
|||
throw new Error(`Error parse keyboard event: ${serializedKeyData}`)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
parseEvent,
|
||||
}
|
||||
|
|
|
|||
74
src/utils/parseIconMapCSV.tsx
Normal file
74
src/utils/parseIconMapCSV.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import rawFileIconIndex from 'assets/icons/file-icons-index.csv'
|
||||
import rawFolderIconIndex from 'assets/icons/folder-icons-index.csv'
|
||||
import { TreeNode } from 'utils/VisibleNodesGenerator'
|
||||
|
||||
function parseFileIconMapCSV() {
|
||||
const filenameIndex = new Map<string, string>()
|
||||
const fileExtensionIndex = new Map<string, string>()
|
||||
rawFileIconIndex.split('\n').forEach(line => {
|
||||
if (!line) return
|
||||
const [name, names, exts] = line.split(',')
|
||||
if (names) {
|
||||
names.split(':').forEach(filename => {
|
||||
if (!filename) return
|
||||
filenameIndex.set(filename, name)
|
||||
})
|
||||
}
|
||||
if (exts) {
|
||||
exts.split(':').forEach(ext => {
|
||||
if (!ext) return
|
||||
fileExtensionIndex.set(ext, name)
|
||||
})
|
||||
}
|
||||
})
|
||||
return {
|
||||
filenameIndex,
|
||||
fileExtensionIndex,
|
||||
}
|
||||
}
|
||||
|
||||
function parseFolderIconMapCSV() {
|
||||
const folderNameIndex = new Map<string, string>()
|
||||
rawFolderIconIndex.split('\n').forEach(line => {
|
||||
if (!line) return
|
||||
const [name, names] = line.split(',')
|
||||
names.split(':').forEach(folderName => {
|
||||
if (!folderName) return
|
||||
folderNameIndex.set(folderName, name)
|
||||
})
|
||||
})
|
||||
return {
|
||||
folderNameIndex,
|
||||
}
|
||||
}
|
||||
|
||||
const { folderNameIndex } = parseFolderIconMapCSV()
|
||||
|
||||
export function getFolderIconSrc(node: TreeNode, open: boolean) {
|
||||
const name = folderNameIndex.get(node.name.toLowerCase())
|
||||
return getIconSrc('folder', name, open)
|
||||
}
|
||||
|
||||
const { filenameIndex, fileExtensionIndex } = parseFileIconMapCSV()
|
||||
|
||||
export function getFileIconSrc(node: TreeNode) {
|
||||
const fileName = node.name.toLowerCase()
|
||||
let iconName = filenameIndex.get(fileName)
|
||||
if (!iconName) {
|
||||
const tail = fileName.split('.')
|
||||
tail.shift()
|
||||
while (!iconName && tail.length > 0) {
|
||||
iconName = fileExtensionIndex.get(tail.join('.'))
|
||||
tail.shift()
|
||||
}
|
||||
}
|
||||
return getIconSrc('file', iconName)
|
||||
}
|
||||
|
||||
export function getIconSrc(type: 'folder' | 'file', name: string = 'default', open?: boolean) {
|
||||
const filename =
|
||||
(name === 'default' ? 'default_' + type : type + '_type_' + name) +
|
||||
(open ? '_opened' : '') +
|
||||
'.svg'
|
||||
return browser.runtime.getURL(`icons/vscode/${filename}`)
|
||||
}
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
const localStorage = browser.storage.local
|
||||
|
||||
function get(mapping: string[] | null): Promise<any> {
|
||||
return localStorage.get(mapping || undefined)
|
||||
export function get(mapping: string[] | null): Promise<any> | any {
|
||||
try {
|
||||
return localStorage.get(mapping || undefined)
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
function set(value: any): Promise<void> {
|
||||
return localStorage.set(value)
|
||||
}
|
||||
|
||||
export default {
|
||||
get,
|
||||
set,
|
||||
export function set(value: any): Promise<void> | void {
|
||||
try {
|
||||
return localStorage.set(value)
|
||||
} catch (err) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import GitHubHelper, { MetaData, TreeData } from 'utils/GitHubHelper'
|
||||
import { getUrlForRedirect, MetaData, TreeData } from 'utils/GitHubHelper'
|
||||
import { TreeNode } from './VisibleNodesGenerator'
|
||||
|
||||
interface RawItem {
|
||||
|
|
@ -36,7 +36,7 @@ function findGitModules(root: TreeNode) {
|
|||
return null
|
||||
}
|
||||
|
||||
function parse(treeData: TreeData, metaData: MetaData) {
|
||||
export function parse(treeData: TreeData, metaData: MetaData) {
|
||||
const { tree } = treeData
|
||||
|
||||
// nodes are created from items and put onto tree
|
||||
|
|
@ -70,7 +70,7 @@ function parse(treeData: TreeData, metaData: MetaData) {
|
|||
name: item.path && item.path.replace(/^.*\//, ''),
|
||||
url:
|
||||
item.url && item.type && item.path
|
||||
? GitHubHelper.getUrlForRedirect(metaData, item.type, item.path)
|
||||
? getUrlForRedirect(metaData, item.type, item.path)
|
||||
: null,
|
||||
contents: item.type === 'tree' ? [] : null,
|
||||
} as TreeNode
|
||||
|
|
@ -88,7 +88,3 @@ function parse(treeData: TreeData, metaData: MetaData) {
|
|||
root: sortFoldersToFront(root),
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
parse,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ type Options = {
|
|||
compress?: boolean
|
||||
}
|
||||
|
||||
export default class VisibleNodesGenerator {
|
||||
export class VisibleNodesGenerator {
|
||||
l1: L1
|
||||
l2: L2
|
||||
l3: L3
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"files": ["src/content.tsx"],
|
||||
"compilerOptions": {
|
||||
"target": "es2016",
|
||||
"outDir": "dist",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ const plugins = [
|
|||
from: './src/assets/icons/*',
|
||||
to: 'icons/[name].[ext]',
|
||||
},
|
||||
{
|
||||
from: './vscode-icons/icons/*',
|
||||
to: 'icons/vscode/[name].[ext]',
|
||||
},
|
||||
{
|
||||
from: 'node_modules/webextension-polyfill/dist/browser-polyfill.js',
|
||||
to: 'browser-polyfill.js',
|
||||
|
|
@ -38,15 +42,12 @@ if (analyse) {
|
|||
}
|
||||
|
||||
const IN_PRODUCTION_MODE = process.env.NODE_ENV === 'production'
|
||||
if (IN_PRODUCTION_MODE) {
|
||||
plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify('production'),
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
plugins.push(
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
||||
'process.env.VERSION': JSON.stringify(process.env.VERSION),
|
||||
}),
|
||||
)
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
|
|
@ -81,7 +82,10 @@ module.exports = {
|
|||
test: /\.svg$/,
|
||||
resourceQuery: /inline/,
|
||||
loader: ['url-loader'],
|
||||
include: [srcPath],
|
||||
},
|
||||
{
|
||||
test: /\.csv$/,
|
||||
loader: ['raw-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.json$/,
|
||||
|
|
|
|||
234
yarn.lock
234
yarn.lock
|
|
@ -304,6 +304,14 @@
|
|||
"@babel/helper-plugin-utils" "^7.0.0"
|
||||
"@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
|
||||
|
||||
"@babel/plugin-proposal-optional-chaining@^7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.6.0.tgz#e9bf1f9b9ba10c77c033082da75f068389041af8"
|
||||
integrity sha512-kj4gkZ6qUggkprRq3Uh5KP8XnE1MdIO0J7MhdDX8+rAbB6dJ2UrensGIS+0NPZAaaJ1Vr0PN6oLUgXMU1uMcSg==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.0.0"
|
||||
"@babel/plugin-syntax-optional-chaining" "^7.2.0"
|
||||
|
||||
"@babel/plugin-proposal-unicode-property-regex@^7.4.4":
|
||||
version "7.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz#501ffd9826c0b91da22690720722ac7cb1ca9c78"
|
||||
|
|
@ -355,6 +363,13 @@
|
|||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.0.0"
|
||||
|
||||
"@babel/plugin-syntax-optional-chaining@^7.2.0":
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.2.0.tgz#a59d6ae8c167e7608eaa443fda9fa8fa6bf21dff"
|
||||
integrity sha512-HtGCtvp5Uq/jH/WNUPkK6b7rufnCPLLlDAFN7cmACoIjaOOiXxUt3SswU5loHqrhtqTsa/WoLQ1OQ1AGuZqaWA==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.0.0"
|
||||
|
||||
"@babel/plugin-syntax-typescript@^7.2.0":
|
||||
version "7.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz#a7cc3f66119a9f7ebe2de5383cce193473d65991"
|
||||
|
|
@ -754,6 +769,13 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
"@babel/runtime@^7.1.2":
|
||||
version "7.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.2.tgz#111a78002a5c25fc8e3361bedc9529c696b85a6a"
|
||||
integrity sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
"@babel/template@^7.1.0", "@babel/template@^7.4.4", "@babel/template@^7.6.0":
|
||||
version "7.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6"
|
||||
|
|
@ -810,10 +832,10 @@
|
|||
dependencies:
|
||||
prop-types "^15.6.1"
|
||||
|
||||
"@primer/octicons@^9.1.1":
|
||||
version "9.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@primer/octicons/-/octicons-9.1.1.tgz#a72a59e8ec77521cc2eefb36c345f780a61c79fb"
|
||||
integrity sha512-7EGM0+Kx39bIgaYr9bTCzFvBCxm+fqh/YJIoSns8zfCwss32ZJ2GDP3024UH709VQtM5cKFU4JcIYPHyGdSfIg==
|
||||
"@primer/octicons@^9.2.0":
|
||||
version "9.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@primer/octicons/-/octicons-9.2.0.tgz#705783d57335c4ffbde4650e4d22302947208c6e"
|
||||
integrity sha512-3vv7bBqVUHhU7ChpmQhmzz3WmHEKKSk9z/xEK5KnU8Egh96RoqKIim07geRj825ISa+IWj9cPdOA1ShqW5k3Yw==
|
||||
dependencies:
|
||||
object-assign "^4.1.1"
|
||||
|
||||
|
|
@ -1717,6 +1739,11 @@ boolbase@~1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
||||
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
|
||||
|
||||
bowser@^1.7.3:
|
||||
version "1.9.4"
|
||||
resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a"
|
||||
integrity sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==
|
||||
|
||||
boxen@^3.0.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/boxen/-/boxen-3.2.0.tgz#fbdff0de93636ab4450886b6ff45b92d098f45eb"
|
||||
|
|
@ -2357,6 +2384,13 @@ copy-descriptor@^0.1.0:
|
|||
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
|
||||
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
|
||||
|
||||
copy-to-clipboard@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz#d2724a3ccbfed89706fac8a894872c979ac74467"
|
||||
integrity sha512-eOZERzvCmxS8HWzugj4Uxl8OJxa7T2k1Gi0X5qavwydHIfuSHq2dTD09LOg/XyGq4Zpb5IsR/2OJ5lbOegz78w==
|
||||
dependencies:
|
||||
toggle-selection "^1.0.6"
|
||||
|
||||
copy-webpack-plugin@^5.0.0:
|
||||
version "5.0.4"
|
||||
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.0.4.tgz#c78126f604e24f194c6ec2f43a64e232b5d43655"
|
||||
|
|
@ -2481,6 +2515,14 @@ crypto-random-string@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
|
||||
integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=
|
||||
|
||||
css-in-js-utils@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz#3b472b398787291b47cfe3e44fecfdd9e914ba99"
|
||||
integrity sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==
|
||||
dependencies:
|
||||
hyphenate-style-name "^1.0.2"
|
||||
isobject "^3.0.1"
|
||||
|
||||
css-loader@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-2.1.1.tgz#d8254f72e412bb2238bb44dd674ffbef497333ea"
|
||||
|
|
@ -2508,6 +2550,14 @@ css-select@~1.2.0:
|
|||
domutils "1.5.1"
|
||||
nth-check "~1.0.1"
|
||||
|
||||
css-tree@^1.0.0-alpha.28:
|
||||
version "1.0.0-alpha.37"
|
||||
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22"
|
||||
integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==
|
||||
dependencies:
|
||||
mdn-data "2.0.4"
|
||||
source-map "^0.6.1"
|
||||
|
||||
css-what@2.1:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
|
||||
|
|
@ -2523,6 +2573,11 @@ csstype@^2.2.0:
|
|||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.6.tgz#c34f8226a94bbb10c32cc0d714afdf942291fc41"
|
||||
integrity sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg==
|
||||
|
||||
csstype@^2.5.5:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.7.tgz#20b0024c20b6718f4eda3853a1f5a1cce7f5e4a5"
|
||||
integrity sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==
|
||||
|
||||
cyclist@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||
|
|
@ -2976,6 +3031,13 @@ error-ex@^1.3.1:
|
|||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
error-stack-parser@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.4.tgz#a757397dc5d9de973ac9a5d7d4e8ade7cfae9101"
|
||||
integrity sha512-fZ0KkoxSjLFmhW5lHbUT3tLwy3nX1qEzMYo8koY1vrsAco53CMT1djnBSeC/wUjTEZRhZl9iRw7PaMaxfJ4wzQ==
|
||||
dependencies:
|
||||
stackframe "^1.1.0"
|
||||
|
||||
es-abstract@^1.5.1:
|
||||
version "1.16.0"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.16.0.tgz#d3a26dc9c3283ac9750dca569586e976d9dcc06d"
|
||||
|
|
@ -3505,6 +3567,11 @@ fast-safe-stringify@^2.0.7:
|
|||
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743"
|
||||
integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==
|
||||
|
||||
fastest-stable-stringify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fastest-stable-stringify/-/fastest-stable-stringify-1.0.1.tgz#9122d406d4c9d98bea644a6b6853d5874b87b028"
|
||||
integrity sha1-kSLUBtTJ2YvqZEpraFPVh0uHsCg=
|
||||
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
|
|
@ -4237,6 +4304,11 @@ https-proxy-agent@^2.2.1:
|
|||
agent-base "^4.3.0"
|
||||
debug "^3.1.0"
|
||||
|
||||
hyphenate-style-name@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48"
|
||||
integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==
|
||||
|
||||
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
|
|
@ -4352,6 +4424,14 @@ ini@^1.3.4, ini@^1.3.5, ini@~1.3.0, ini@~1.3.3:
|
|||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
|
||||
|
||||
inline-style-prefixer@^4.0.0:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-4.0.2.tgz#d390957d26f281255fe101da863158ac6eb60911"
|
||||
integrity sha512-N8nVhwfYga9MiV9jWlwfdj1UDIaZlBFu4cJSJkIr7tZX7sHpHhGR5su1qdpW+7KPL8ISTvCIkcaFi/JdBknvPg==
|
||||
dependencies:
|
||||
bowser "^1.7.3"
|
||||
css-in-js-utils "^2.0.0"
|
||||
|
||||
inquirer@^0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e"
|
||||
|
|
@ -5178,6 +5258,11 @@ mdn-browser-compat-data@0.0.94:
|
|||
dependencies:
|
||||
extend "3.0.2"
|
||||
|
||||
mdn-data@2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
|
||||
integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==
|
||||
|
||||
media-typer@0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
|
|
@ -5446,6 +5531,20 @@ nan@^2.12.1, nan@^2.14.0:
|
|||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
|
||||
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
|
||||
|
||||
nano-css@^5.2.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.2.1.tgz#73b8470fa40b028a134d3393ae36bbb34b9fa332"
|
||||
integrity sha512-T54okxMAha0+de+W8o3qFtuWhTxYvqQh2ku1cYEqTTP9mR62nWV2lLK9qRuAGWmoaYWhU7K4evT9Lc1iF65wuw==
|
||||
dependencies:
|
||||
css-tree "^1.0.0-alpha.28"
|
||||
csstype "^2.5.5"
|
||||
fastest-stable-stringify "^1.0.1"
|
||||
inline-style-prefixer "^4.0.0"
|
||||
rtl-css-js "^1.9.0"
|
||||
sourcemap-codec "^1.4.1"
|
||||
stacktrace-js "^2.0.0"
|
||||
stylis "3.5.0"
|
||||
|
||||
nanomatch@^1.2.9:
|
||||
version "1.2.13"
|
||||
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
|
||||
|
|
@ -6351,6 +6450,14 @@ raw-body@2.4.0:
|
|||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
raw-loader@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.0.tgz#d639c40fb9d72b5c7f8abc1fb2ddb25b29d3d540"
|
||||
integrity sha512-iINUOYvl1cGEmfoaLjnZXt4bKfT2LJnZZib5N/LLyAphC+Dd11vNP9CNVb38j+SAJpFI1uo8j9frmih53ASy7Q==
|
||||
dependencies:
|
||||
loader-utils "^1.2.3"
|
||||
schema-utils "^2.5.0"
|
||||
|
||||
rc@^1.2.7, rc@^1.2.8:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||
|
|
@ -6371,11 +6478,31 @@ react-dom@^16.8.6:
|
|||
prop-types "^15.6.2"
|
||||
scheduler "^0.15.0"
|
||||
|
||||
react-fast-compare@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
|
||||
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
|
||||
|
||||
react-is@^16.8.1:
|
||||
version "16.9.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.9.0.tgz#21ca9561399aad0ff1a7701c01683e8ca981edcb"
|
||||
integrity sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==
|
||||
|
||||
react-use@^13.8.0:
|
||||
version "13.8.0"
|
||||
resolved "https://registry.yarnpkg.com/react-use/-/react-use-13.8.0.tgz#5e8badaaa5614a1925fd28ad22d01cc1c52e0ff1"
|
||||
integrity sha512-J4ZWIqC1h2eSS6ObmHJUWDQeJSKZvHdBEINCu51plz7RS+ijrt6JEHfDWbNXx+aAoR5AmbRK+mzYwd3jiu3xpA==
|
||||
dependencies:
|
||||
copy-to-clipboard "^3.2.0"
|
||||
nano-css "^5.2.1"
|
||||
react-fast-compare "^2.0.4"
|
||||
resize-observer-polyfill "^1.5.1"
|
||||
screenfull "^5.0.0"
|
||||
set-harmonic-interval "^1.0.1"
|
||||
throttle-debounce "^2.1.0"
|
||||
ts-easing "^0.2.0"
|
||||
tslib "^1.10.0"
|
||||
|
||||
react-window@^1.8.5:
|
||||
version "1.8.5"
|
||||
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.5.tgz#a56b39307e79979721021f5d06a67742ecca52d1"
|
||||
|
|
@ -6631,6 +6758,11 @@ require-uncached@^1.0.2:
|
|||
caller-path "^0.1.0"
|
||||
resolve-from "^1.0.0"
|
||||
|
||||
resize-observer-polyfill@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
|
||||
|
||||
resolve-cwd@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
||||
|
|
@ -6730,6 +6862,13 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
|
|||
hash-base "^3.0.0"
|
||||
inherits "^2.0.1"
|
||||
|
||||
rtl-css-js@^1.9.0:
|
||||
version "1.13.1"
|
||||
resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.13.1.tgz#80deabf6e8f36d6767d495cd3eb60fecb20c67e1"
|
||||
integrity sha512-jgkIDj6Xi25kAEm5oYM3ZMFiOQhpLEcXi2LY/6bVr91cVz73hciHKneL5AMVPxOcks/JuizSaaNsvNRkeAWe3w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
|
||||
run-async@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389"
|
||||
|
|
@ -6817,6 +6956,19 @@ schema-utils@^1.0.0:
|
|||
ajv-errors "^1.0.0"
|
||||
ajv-keywords "^3.1.0"
|
||||
|
||||
schema-utils@^2.5.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.5.0.tgz#8f254f618d402cc80257486213c8970edfd7c22f"
|
||||
integrity sha512-32ISrwW2scPXHUSusP8qMg5dLUawKkyV+/qIEV9JdXKx+rsM6mi8vZY8khg2M69Qom16rtroWXD3Ybtiws38gQ==
|
||||
dependencies:
|
||||
ajv "^6.10.2"
|
||||
ajv-keywords "^3.4.1"
|
||||
|
||||
screenfull@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.0.0.tgz#5c2010c0e84fd4157bf852877698f90b8cbe96f6"
|
||||
integrity sha512-yShzhaIoE9OtOhWVyBBffA6V98CDCoyHTsp8228blmqYy1Z5bddzE/4FPiJKlr8DVR4VBiiUyfPzIQPIYDkeMA==
|
||||
|
||||
semver-diff@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
|
||||
|
|
@ -6873,6 +7025,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
|
||||
|
||||
set-harmonic-interval@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249"
|
||||
integrity sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==
|
||||
|
||||
set-value@^2.0.0, set-value@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
|
||||
|
|
@ -7072,6 +7229,11 @@ source-map-url@^0.4.0:
|
|||
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
|
||||
integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
|
||||
|
||||
source-map@0.5.6:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
|
||||
integrity sha1-dc449SvwczxafwwRjYEzSiu19BI=
|
||||
|
||||
source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
|
|
@ -7082,6 +7244,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
|
|||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
sourcemap-codec@^1.4.1:
|
||||
version "1.4.6"
|
||||
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz#e30a74f0402bad09807640d39e971090a08ce1e9"
|
||||
integrity sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==
|
||||
|
||||
spawn-sync@1.0.15:
|
||||
version "1.0.15"
|
||||
resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476"
|
||||
|
|
@ -7131,6 +7298,35 @@ ssri@^6.0.1:
|
|||
dependencies:
|
||||
figgy-pudding "^3.5.1"
|
||||
|
||||
stack-generator@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.4.tgz#027513eab2b195bbb43b9c8360ba2dd0ab54de09"
|
||||
integrity sha512-ha1gosTNcgxwzo9uKTQ8zZ49aUp5FIUW58YHFxCqaAHtE0XqBg0chGFYA1MfmW//x1KWq3F4G7Ug7bJh4RiRtg==
|
||||
dependencies:
|
||||
stackframe "^1.1.0"
|
||||
|
||||
stackframe@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.1.0.tgz#e3fc2eb912259479c9822f7d1f1ff365bd5cbc83"
|
||||
integrity sha512-Vx6W1Yvy+AM1R/ckVwcHQHV147pTPBKWCRLrXMuPrFVfvBUc3os7PR1QLIWCMhPpRg5eX9ojzbQIMLGBwyLjqg==
|
||||
|
||||
stacktrace-gps@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.0.3.tgz#b89f84cc13bb925b96607e737b617c8715facf57"
|
||||
integrity sha512-51Rr7dXkyFUKNmhY/vqZWK+EvdsfFSRiQVtgHTFlAdNIYaDD7bVh21yBHXaNWAvTD+w+QSjxHg7/v6Tz4veExA==
|
||||
dependencies:
|
||||
source-map "0.5.6"
|
||||
stackframe "^1.1.0"
|
||||
|
||||
stacktrace-js@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.1.tgz#ebdb0e9a16e6f171f96ca7878404e7f15c3d42ba"
|
||||
integrity sha512-13oDNgBSeWtdGa4/2BycNyKqe+VktCoJ8VLx4pDoJkwGGJVtiHdfMOAj3aW9xTi8oR2v34z9IcvfCvT6XNdNAw==
|
||||
dependencies:
|
||||
error-stack-parser "^2.0.4"
|
||||
stack-generator "^2.0.4"
|
||||
stacktrace-gps "^3.0.3"
|
||||
|
||||
static-extend@^0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
|
||||
|
|
@ -7329,6 +7525,11 @@ style-loader@^0.23.1:
|
|||
loader-utils "^1.1.0"
|
||||
schema-utils "^1.0.0"
|
||||
|
||||
stylis@3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1"
|
||||
integrity sha512-pP7yXN6dwMzAR29Q0mBrabPCe0/mNO1MSr93bhay+hcZondvMMTpeGyd8nbhYJdyperNT2DRxONQuUGcJr5iPw==
|
||||
|
||||
supports-color@6.1.0, supports-color@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
|
||||
|
|
@ -7451,6 +7652,11 @@ thenify-all@^1.0.0:
|
|||
dependencies:
|
||||
any-promise "^1.0.0"
|
||||
|
||||
throttle-debounce@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.1.0.tgz#257e648f0a56bd9e54fe0f132c4ab8611df4e1d5"
|
||||
integrity sha512-AOvyNahXQuU7NN+VVvOOX+uW6FPaWdAOdRP5HfwYxAfCzXTFKRMoIMk+n+po318+ktcChx+F1Dd91G3YHeMKyg==
|
||||
|
||||
through2@^2.0.0:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
|
||||
|
|
@ -7530,6 +7736,11 @@ to-regex@^3.0.1, to-regex@^3.0.2:
|
|||
regex-not "^1.0.2"
|
||||
safe-regex "^1.1.0"
|
||||
|
||||
toggle-selection@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
|
||||
integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
|
||||
|
||||
toidentifier@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||
|
|
@ -7577,7 +7788,12 @@ tryer@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
|
||||
integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
|
||||
|
||||
tslib@^1.9.0, tslib@^1.9.3:
|
||||
ts-easing@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec"
|
||||
integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==
|
||||
|
||||
tslib@^1.10.0, tslib@^1.9.0, tslib@^1.9.3:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
||||
|
|
@ -7634,10 +7850,10 @@ typedarray@^0.0.6:
|
|||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
typescript@^3.6.3:
|
||||
version "3.6.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da"
|
||||
integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==
|
||||
typescript@^3.7.2:
|
||||
version "3.7.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb"
|
||||
integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==
|
||||
|
||||
uglify-js@^3.6.0:
|
||||
version "3.6.0"
|
||||
|
|
|
|||
Loading…
Reference in a new issue