Merge branch 'develop'

This commit is contained in:
EnixCoda 2019-12-19 21:57:06 +08:00
commit 81867ee5fd
No known key found for this signature in database
GPG key ID: 0C1A07377913A1DD
63 changed files with 2970 additions and 1389 deletions

View file

@ -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
View file

@ -3,5 +3,5 @@
node_modules
tmp
dist
src/assets/icons/octicons
yarn-error.log
vscode-icons

27
Makefile Executable file
View 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

View file

@ -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

View file

@ -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",

View file

@ -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)
},

View file

@ -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:

View 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())

View 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()))

View file

@ -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()
})
}
})

View 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" }
}

View file

@ -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

View file

@ -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

View 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
1 file
2 access accdb:accdt:mdb:accda:accdc:accde:accdp:accdr:accdu:ade:adp:laccdb:ldb:mam:maq:mdw
3 access2 accdb:accdt:mdb:accda:accdc:accde:accdp:accdr:accdu:ade:adp:laccdb:ldb:mam:maq:mdw
4 actionscript as
5 actionscript2 as
6 ada ada
7 advpl prw
8 ai ai
9 ai2 ai
10 al al
11 affinitydesigner afdesign:affinitydesigner
12 affinityphoto afphoto:affinityphoto
13 affinitypublisher afpub:affinitypublisher
14 angular .angular-cli.json:angular-cli.json:angular.json:.angular.json
15 ng_component_dart component.dart
16 ng_component_ts component.ts
17 ng_component_js component.js
18 ng_controller_ts controller.ts
19 ng_controller_js controller.js
20 ng_directive_dart directive.dart
21 ng_directive_ts directive.ts
22 ng_directive_js directive.js
23 ng_guard_dart guard.dart
24 ng_guard_ts guard.ts
25 ng_guard_js guard.js
26 ng_module_dart module.dart
27 ng_module_ts module.ts
28 ng_module_js module.js
29 ng_pipe_dart pipe.dart
30 ng_pipe_ts pipe.ts
31 ng_pipe_js pipe.js
32 ng_routing_dart routing.dart
33 ng_routing_ts routing.ts
34 ng_routing_js routing.js
35 ng_routing_dart app-routing.module.dart
36 ng_routing_ts app-routing.module.ts
37 ng_routing_js app-routing.module.js
38 ng_smart_component_dart page.dart:container.dart
39 ng_smart_component_ts page.ts:container.ts
40 ng_smart_component_js page.js:container.js
41 ng_service_dart service.dart
42 ng_service_ts service.ts
43 ng_service_js service.js
44 ng_interceptor_dart interceptor.dart
45 ng_interceptor_ts interceptor.ts
46 ng_interceptor_js interceptor.js
47 ng_component_ts2 component.ts
48 ng_component_js2 component.js
49 ng_directive_ts2 directive.ts
50 ng_directive_js2 directive.js
51 ng_module_ts2 module.ts
52 ng_module_js2 module.js
53 ng_pipe_ts2 pipe.ts
54 ng_pipe_js2 pipe.js
55 ng_routing_ts2 routing.ts
56 ng_routing_js2 routing.js
57 ng_routing_ts2 app-routing.module.ts
58 ng_routing_js2 app-routing.module.js
59 ng_smart_component_ts2 page.ts:container.ts
60 ng_smart_component_js2 page.js:container.js
61 ng_service_ts2 service.ts
62 ng_service_js2 service.js
63 affectscript affect
64 ansible ansible
65 antlr g4
66 anyscript any
67 apache htaccess
68 apex cls
69 apib apib
70 apl apl
71 applescript applescript
72 appveyor appveyor.yml:.appveyor.yml
73 arduino ino:pde
74 asciidoc adoc
75 asp asp:asp
76 aspx aspx:ascx
77 assembly asm:asm
78 ats ats
79 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
80 aurelia aurelia.json
81 autohotkey ahk
82 autoit au3
83 avro avcs
84 aws
85 azure azcli
86 azurepipelines azure-pipelines.yml:.vsts-ci.yml azure-pipelines.yml
87 babel .babelrc:babelrc.js:.babelrc.js:babelrc.json:babel.config.js:.babelignore
88 babel2 .babelrc:babelrc.js:.babelrc.js:babelrc.json:babel.config.js:.babelignore
89 ballerina bal
90 bat bat
91 bazaar .bzrignore
92 bazel .bazelrc:bazel.rc:bazel.bazelrc bzl
93 befunge bf:bf
94 biml biml
95 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
96 bithound .bithoundrc
97 bitbucketpipeline bitbucket-pipelines.yml
98 blade blade.php:blade.php
99 bolt bolt
100 bower .bowerrc:bower.json
101 browserslist .browserslistrc:browserslist
102 buckbuild .buckconfig
103 bundler gemfile:gemfile.lock
104 bundler gemfile:gemfile.lock
105 c c
106 c2 c
107 c3 c
108 c_al cal
109 cabal cabal
110 caddy Caddyfile
111 cake cake
112 cakephp
113 capacitor capacitor.config.json
114 cargo cargo.toml:cargo.lock
115 cert csr:crt:cer:der:pfx:p12:p7b:p7r:src:crl:sst:stl
116 ceylon ceylon
117 cf lucee:cfml:cfml
118 cf2 lucee:cfml:cfml
119 cfc cfc
120 cfc2 cfc
121 cfm cfm
122 cfm2 cfm
123 cheader h
124 chef chefignore:berksfile:berksfile.lock:policyfile
125 class class
126 circleci circle.yml
127 clojure cjm:cljc:clojure
128 clojurescript cljs:clojurescript
129 cloudfoundry .cfignore
130 cmake cmake:CMakeCache.txt
131 cobol cbl
132 codacy .codacy.yml:.codacy.yaml
133 codeclimate .codeclimate.yml
134 codecov codecov.yml:.codecov.yml
135 codekit kit
136 codekit config.codekit:config.codekit2:config.codekit3:.config.codekit:.config.codekit2:.config.codekit3
137 coffeelint coffeelint.json:.coffeelintignore
138 coffeescript coffee
139 conan conanfile.txt:conanfile.py
140 conda .condarc
141 config plist:properties:env
142 compass
143 composer composer.json:composer.lock
144 chef_cookbook ckbk
145 confluence confluence
146 coveralls .coveralls.yml
147 cpp cpp
148 cpp2 cpp
149 cpp3 cpp
150 cppheader hpp
151 crowdin crowdin.yml
152 crystal cr
153 csharp csx:cs
154 csharp2 csx:cs
155 csproj csproj
156 css css
157 csscomb .csscomb.json
158 csslint .csslintrc
159 cssmap css.map
160 cucumber feature
161 cuda cu
162 cython pyx
163 cypress cypress.json:cypress.env.json
164 cvs .cvsignore
165 dal dal
166 darcs .boringignore
167 dartlang dart
168 db db
169 dependencies dependencies.yml
170 delphi pas:pas
171 django djt:html:html
172 dlang d:d:d:d
173 diff diff
174 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
175 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
176 dockertest docker-compose.test.yml
177 dockertest2 docker-compose.test.yml
178 docpad eco
179 docz .doczrc:docz.js:docz.json:.docz.js:.docz.json:doczrc.js:doczrc.json:docz.config.js:docz.config.json
180 dojo .dojorc
181 doxygen dox
182 drone .drone.yml:.drone.yml.sig
183 drools drl
184 dotjs dot
185 dustjs dust
186 dylan dylan:dylan
187 editorconfig .editorconfig
188 edge edge
189 edge2 edge
190 eex eex:eex
191 ejs ejs
192 elastic es
193 elasticbeanstalk
194 elixir ex
195 elm elm-package.json elm
196 elm2 elm-package.json elm
197 emacs el:elc
198 ember .ember-cli
199 ensime ensime
200 eps eps
201 erb erb
202 erlang emakefile:.emakerfile erl
203 erlang2 emakefile:.emakerfile erl
204 eslint .eslintrc:.eslintignore:.eslintcache:.eslintrc.js:.eslintrc.json:.eslintrc.yaml:.eslintrc.yml
205 eslint2 .eslintrc:.eslintignore:.eslintcache:.eslintrc.js:.eslintrc.json:.eslintrc.yaml:.eslintrc.yml
206 excel xls:xlsx:xlsm:ods:fods
207 excel2 xls:xlsx:xlsm:ods:fods
208 falcon falcon
209 favicon favicon.ico
210 fbx fbx
211 firebase .firebaserc
212 firebasehosting firebase.json
213 firestore firestore.rules:firestore.indexes.json
214 flash swf:swc
215 fla fla
216 floobits .flooignore
217 flow js.flow
218 flow .flowconfig
219 flutter .flutter-plugins:.metadata
220 flutter_package pubspec.lock:pubspec.yaml:.packages
221 font woff:woff2:ttf:otf:eot:pfa:pfb:sfd
222 fortran f:f:f:f
223 fossa .fossaignore
224 fossil ignore-glob
225 fsharp fs
226 fsproj fsproj
227 freemarker ftl
228 fusebox fuse.js
229 galen gspec
230 galen2 gspec
231 git .gitattributes:.gitconfig:.gitignore:.gitmodules:.gitkeep:.mailmap git:git
232 gamemaker gmx:gml
233 gamemaker2 yy:yyp:gml
234 gamemaker81 gml
235 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
236 gcode gcode
237 gitlab .gitlab-ci.yml
238 glide glide.yml
239 glsl glsl
240 go go
241 go_package go.sum:go.mod
242 godot gd
243 gradle gradle
244 graphql .gqlconfig gql
245 graphviz gv
246 greenkeeper greenkeeper.json
247 gridsome gridsome.config.js:gridsome.config.ts:gridsome.server.js:gridsome.server.ts:gridsome.client.js:gridsome.client.ts
248 groovy groovy
249 groovy2 groovy
250 grunt gruntfile.js:gruntfile.coffee:gruntfile.ts:gruntfile.babel.js:gruntfile.babel.coffee:gruntfile.babel.ts
251 gulp gulpfile.js:gulpfile.coffee:gulpfile.ts:gulpfile.babel.js:gulpfile.babel.coffee:gulpfile.babel.ts
252 haml haml
253 handlebars hbs
254 handlebars2 hbs
255 harbour prg
256 haskell hs:lhs
257 haskell2 hs:lhs
258 haxe haxelib.json haxe:haxe:haxe
259 haxecheckstyle checkstyle.json
260 haxedevelop hxproj
261 helix .p4ignore
262 helm helm.tpl
263 hjson hjson
264 hlsl hlsl
265 homeassistant yaml
266 host hosts
267 html html
268 htmlhint .htmlhintrc
269 http http
270 hunspell aff:aff
271 husky .huskyrc:.huskyrc.js:.huskyrc.json:.huskyrc.yaml:.huskyrc.yml
272 icl icl
273 idris idr:lidr
274 idrisbin ibc
275 idrispkg ipkg
276 image jpeg:jpg:gif:png:bmp:tiff:ico
277 imba imba
278 inc inc:include
279 infopath infopathxml:xsn:xsf:xtp2
280 informix 4gl
281 ini ini
282 ink ink
283 innosetup iss
284 ionic ionic.project:ionic.config.json
285 jake jakefile:jakefile.js
286 janet janet
287 jar jar
288 java java
289 jbuilder jbuilder
290 jest jest.config.js:jest.json:jest.config.json:.jestrc:.jestrc.js:.jestrc.json
291 jest_snapshot js.snap:jsx.snap:ts.snap:tsx.snap
292 jekyll jekyll
293 jenkins jenkins:jenkins:jenkins
294 jinja jinja
295 jpm .jpmignore
296 js js
297 js_official js
298 jsbeautify .jsbeautifyrc:jsbeautifyrc:.jsbeautify:jsbeautify
299 jsconfig jsconfig.json
300 jshint .jshintrc:.jshintignore
301 jsmap js.map
302 json json:JSON-tmLanguage:jsonc
303 json_official json:JSON-tmLanguage:jsonc
304 json2 json:JSON-tmLanguage:jsonc
305 jsonnet jsonnet
306 json5 json5:json5
307 jsonld jsonld:json-ld
308 jsp jsp
309 jss jss
310 julia jl:jl
311 julia2 jl:jl
312 jupyter ipynb
313 io io
314 iodine id
315 karma karma.conf.js:karma.conf.coffee:karma.conf.ts
316 key key:pem
317 kite .kiteignore
318 kitchenci .kitchen.yml
319 kivy kv
320 kos ks
321 kotlin kt
322 layout master:layout.html:layout.htm
323 layout layout.html:layout.htm
324 lerna lerna.json
325 less less
326 license enc
327 license license:licence:license.md:license.txt:licence.md:licence.txt
328 lisp lisp
329 lime hxp
330 lime include.xml
331 lintstagedrc .lintstagedrc:lint-staged.config.js:.lintstagedrc.js:.lintstagedrc.json:.lintstagedrc.yaml:.lintstagedrc.yml
332 liquid liquid
333 livescript ls
334 locale
335 log log:tlg
336 lolcode lol
337 lsl lsl
338 lua lua
339 lync crec:ocrec
340 makefile makefile:mk
341 manifest manifest
342 manifest_skip manifest.skip
343 manifest_bak manifest.bak
344 map map
345 markdown mdown:markdown:md
346 markdownlint .markdownlint.json
347 marko marko
348 markojs marko.js
349 matlab fig:mex:mexn:mexrs6:mn:mum:mx:mx3:rwd:slx:slddc:smv:xvc:mat
350 maxscript ms
351 maven maven.config:pom.xml:extensions.xml:settings.xml
352 maya mel
353 mdx mdx
354 mediawiki mediawiki
355 mercurial .hgignore
356 meson meson.build
357 meteor
358 mjml mjml
359 mlang pq:pq
360 mocha mocha.opts:.mocharc.js:.mocharc.json:.mocharc.jsonc:.mocharc.yaml:.mocharc.yml
361 mojolicious ep
362 moleculer moleculer.config.js:moleculer.config.json:moleculer.config.ts
363 mongo mongo
364 monotone .mtn-ignore
365 mson mson
366 mustache mustache:mst
367 nearly ne
368 nestjs .nest-cli.json:nest-cli.json:nestconfig.json:.nestconfig.json
369 nest_adapter_js adapter.js
370 nest_adapter_ts adapter.ts
371 nest_controller_js controller.js
372 nest_controller_ts controller.ts
373 nest_decorator_js decorator.js
374 nest_decorator_ts decorator.ts
375 nest_filter_js filter.js
376 nest_filter_ts filter.ts
377 nest_gateway_js gateway.js
378 nest_gateway_ts gateway.ts
379 nest_guard_js guard.js
380 nest_guard_ts guard.ts
381 nest_interceptor_js interceptor.js
382 nest_interceptor_ts interceptor.ts
383 nest_middleware_js middleware.js
384 nest_middleware_ts middleware.ts
385 nest_module_js module.js
386 nest_module_ts module.ts
387 nest_pipe_js pipe.js
388 nest_pipe_ts pipe.ts
389 nest_service_js service.js
390 nest_service_ts service.ts
391 netlify netlify.toml
392 nginx nginx.conf
393 nim nim:nim
394 ninja build.ninja
395 njsproj njsproj
396 node .node-version:.nvmrc
397 node2 .node-version:.nvmrc
398 nodemon nodemon.json
399 npm .npmignore:.npmrc:package.json:package-lock.json:npm-shrinkwrap.json
400 nsi nsi:nsi:nsi:nsi
401 nsri .nsrirc:.nsriignore:.nsrirc.js:.nsrirc.json:.nsrirc.yaml:.nsrirc.yml:.nsrirc.config.js
402 nsri-integrity .integrity.json
403 nuget nupkg:nuspec:psmdcp
404 nunjucks nunj:njs:nunjucks
405 nuxt nuxt.config.js:nuxt.config.ts
406 nyc .nycrc:.nycrc.json
407 objectivec m
408 objectivecpp mm
409 ocaml .merlin ml:ml:ml
410 onenote one:onepkg:onetoc:onetoc2:sig
411 opencl cl:opencl
412 openHAB things
413 org org
414 outlook pst:bcmx:otm:msg:oft
415 ovpn ovpn
416 package pkg
417 paket paket.dependencies:paket.lock:paket.references:paket.template:paket.local
418 patch patch
419 pcl pcd
420 pddl pddl
421 pddl_plan plan
422 pddl_happenings happenings
423 pdf pdf
424 pdf2 pdf
425 perl pl
426 perl2 pl
427 perl6 pl6
428 pgsql pgsql
429 photoshop psd
430 photoshop2 psd
431 php php1:php2:php3:php4:php5:php6:phps:phpsa:phpt:phtml:phar:php
432 php2 php1:php2:php3:php4:php5:php6:phps:phpsa:phpt:phtml:phar:php
433 php3 php1:php2:php3:php4:php5:php6:phps:phpsa:phpt:phtml:phar:php
434 phpcsfixer .php_cs:.php_cs.dist
435 phpunit phpunit:phpunit.xml:phpunit.xml.dist
436 phraseapp .phraseapp.yml
437 pine pine
438 pip pipfile:pipfile.lock requirements.txt
439 platformio platformio.ini dbgasm:dbgasm:dbgasm
440 plantuml pu:plantuml:iuml:puml
441 plsql ddl:ddl
442 plsql_package pck
443 plsql_package_body pkb
444 plsql_package_header pkh
445 plsql_package_spec pks
446 poedit po:mo
447 polymer polymer
448 pony pony
449 postcss pcss
450 postcssconfig .postcssrc:.postcssrc.json:.postcssrc.yml:.postcssrc.js:postcss.config.js
451 powerpoint pot:potx:potm:pps:ppsx:ppsm:ppt:pptx:pptm:pa:ppa:ppam:sldm:sldx
452 powerpoint2 pot:potx:potm:pps:ppsx:ppsm:ppt:pptx:pptm:pa:ppa:ppam:sldm:sldx
453 powershell ps1
454 powershell_psm psm1
455 powershell_psd psd1
456 powershell_format format.ps1xml
457 powershell_types types.ps1xml
458 powershell2 ps1
459 powershell_psm2 psm1
460 powershell_psd2 psd1
461 precommit .pre-commit-config.yaml
462 prettier .prettierrc:.prettierignore
463 prettier prettier.config.js:prettier.config.ts:prettier.config.coffee
464 prettier .prettierrc.js:.prettierrc.json:.prettierrc.yml:.prettierrc.yaml
465 prisma prisma
466 processinglang pde
467 procfile procfile
468 progress w
469 prolog pro:P:pro
470 prometheus rules
471 protobuf proto:proto
472 protractor protractor.conf.js:protractor.conf.coffee:protractor.conf.ts
473 publisher pub:puz
474 puppet pp
475 pug .jade-lintrc:.pug-lintrc:.jade-lint.json:.pug-lintrc.js:.pug-lintrc.json pug
476 purescript purs
477 pyret arr
478 python py
479 pyup .pyup:.pyup.yml
480 q q
481 qbs qbs
482 qlikview qvd:qvw:qvs
483 qml qml
484 qmldir qmldir
485 qsharp qs
486 quasar quasar.conf.js
487 r r
488 racket rkt
489 rails
490 rake rake
491 rake rakefile
492 raml raml
493 razor cshtml:cshtml
494 razzle razzle.config.js
495 reactjs jsx
496 reacttemplate rt
497 reactts tsx
498 reason re
499 red red
500 registry reg
501 rehype .rehyperc:.rehypeignore:.rehyperc.js:.rehyperc.json:.rehyperc.yml:.rehyperc.yaml
502 remark .remarkrc:.remarkignore:.remarkrc.js:.remarkrc.json:.remarkrc.yml:.remarkrc.yaml
503 renovate .renovaterc:renovate.json:.renovaterc.json
504 rest rst
505 retext .retextrc:.retextignore:.retextrc.js:.retextrc.json:.retextrc.yml:.retextrc.yaml
506 riot tag
507 robotframework robot
508 robots robots.txt
509 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
510 rproj rproj
511 rspec .rspec
512 rubocop .rubocop.yml:.rubocop_todo.yml
513 ruby rb
514 rust rs
515 saltstack sls
516 san san
517 sass sass
518 sbt sbt
519 scala scala
520 script wsf
521 scss scssm:scss
522 scilab sce
523 sdlang sdl
524 sentry .sentryclirc
525 serverless serverless.yml
526 sequelize .sequelizerc:.sequelizerc.js:.sequelizerc.json
527 shaderlab shader
528 shell fish:sh
529 sketch sketch
530 slang slang
531 slice ice
532 slim slim
533 sln sln
534 sln2 sln
535 silverstripe ss
536 skipper eskip:eskip
537 smarty tpl
538 snapcraft snapcraft.yaml
539 snort snort
540 snyk .snyk
541 solidarity .solidarity:.solidarity.json
542 solidity sol
543 source
544 sqf sqf
545 sql sql
546 sqlite sqlite:sqlite3:db3
547 squirrel nut
548 sss sss
549 stan stan
550 stata dta:do
551 stencil stencil:html.stencil
552 style
553 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
554 stylable st.css
555 styled styled
556 stylus styl
557 storyboard storyboard
558 storybook story.js:story.jsx:story.ts:story.tsx:stories.js:stories.jsx:stories.ts:stories.tsx
559 subversion .svnignore
560 svelte svelte
561 svg svg
562 swagger swagger:swagger
563 swift package.pins swift
564 swig swig
565 symfony symfony.lock
566 systemd link
567 systemverilog sv
568 t4tt tt
569 tailwind tailwind.js:tailwind.coffee:tailwind.ts:tailwind.config.js:tailwind.config.coffee:tailwind.config.ts
570 tt tt2:tt3
571 tcl tcl:exp
572 tera tera
573 terraform tfstate:tf
574 test tst
575 testjs test.js:test.jsx:test.mjs:spec.js:spec.jsx:spec.mjs
576 testts test.ts:test.tsx:spec.ts:spec.tsx:e2e-test.ts:e2e-test.tsx:e2e-spec.ts:e2e-spec.tsx
577 tex texi:tikz:sty:tex:bib:dtx
578 text csv:txt
579 textile textile
580 tfs .tfignore
581 todo todo
582 toml toml
583 tox .ini
584 travis .travis.yml
585 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
586 tslint tslint.json:tslint.yaml:tslint.yml
587 ttcn ttcn3
588 twig twig
589 typescript ts
590 typescript_official ts
591 typescriptdef d.ts
592 typescriptdef_official d.ts
593 typo3 typoscript
594 unibeautify .unibeautifyrc:unibeautify.config.js:.unibeautifyrc.js:.unibeautifyrc.json:.unibeautifyrc.yaml:.unibeautifyrc.yml
595 vagrant vagrantfile
596 vala vala
597 vapi vapi
598 vash vash
599 vb vb
600 vba cls
601 vbhtml vbhtml
602 vbproj vbproj
603 vcxproj vcxproj
604 velocity vm
605 verilog v
606 vhdl vhdl
607 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
608 view
609 vim .vimrc:.gvimrc vim
610 vlang v
611 volt volt
612 vscode .vscodeignore:launch.json:tasks.json:vscodeignore.json
613 vscode2 .vscodeignore:launch.json:tasks.json:vscodeignore.json
614 vscode3 .vscodeignore:launch.json:tasks.json:vscodeignore.json
615 vscode-insiders .vscodeignore:launch.json:tasks.json:vscodeignore.json
616 vsix vsix
617 vsixmanifest vsixmanifest
618 vue vue
619 vueconfig .vuerc:vue.config.js
620 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
621 watchmanconfig .watchmanconfig
622 wasm wasm:wasm:wasm
623 webp webp
624 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
625 wercker wercker.yml
626 wolfram wl
627 word doc:docx:docm:dot:dotx:dotm:wll
628 word2 doc:docx:docm:dot:dotx:dotm:wll
629 wpml wpml-config.xml
630 wurst wurst:wurst
631 wxml wxml
632 wxss wxss
633 xcode xcodeproj
634 xfl xfl
635 xib xib
636 xliff xliff:xlf
637 xml pex:tmlanguage:xml
638 xquery xquery
639 xsl xsl
640 yaml yaml:YAML-tmLanguage
641 yamllint .yamllint
642 yandex .yaspellerrc:.yaspeller.json
643 yang yang
644 yarn yarn.lock:.yarnrc:.yarnclean:.yarn-integrity:.yarn-metadata.json:.yarnignore
645 yeoman .yo-rc.json
646 zeit now.json:.nowignore
647 zip zip:rar:7z:tar:gz:bzip2:xz:bz2
648 zip2 zip:rar:7z:tar:gz:bzip2:xz:bz2

View 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
1 folder
2 root_folder
3 android android
4 api api:.api
5 app app:.app
6 arangodb arangodb:arango
7 asset assets:.assets
8 aurelia aurelia_project
9 audio audio:.audio:audios:.audios:sound:.sound:sounds:.sounds
10 aws aws:.aws
11 azure azure:.azure
12 azurepipelines azure-pipelines:.azure-pipelines
13 binary bin:.bin
14 blueprint blueprint:.blueprint:blueprints:.blueprints
15 bower bower_components
16 buildkite .buildkite
17 cake cake:.cake
18 certificate certificates:.certificates:certs:certs.
19 chef chef:.chef
20 circleci .circleci
21 controller controllers:.controllers:handlers:.handlers
22 component components:.components:widgets
23 composer composer:.composer
24 cli cli:cmd:command:commands:commandline:console
25 client client
26 cmake .cmake:cmake
27 config config:.config:configs:.configs:configuration:.configuration:configurations:.configurations:setting:.setting:settings:.settings:ini:.ini:initializers:.initializers
28 coverage coverage
29 css css:_css
30 cypress cypress
31 db db:database:sql:data:repo:repository:repositories
32 debian debian
33 dist dist:dists:out:outs:export:exports:build:builds:release:releases:target:targets
34 docker docker:.docker
35 docs docs:doc
36 e2e e2e
37 elasticbeanstalk .elasticbeanstalk:.ebextensions
38 electron electron
39 favicon favicon:favicons
40 flow flow:flow-typed
41 fonts fonts:font:fnt
42 gcp gcp:.gcp
43 git .git:submodules:.submodules
44 github .github
45 gitlab .gitlab
46 gradle gradle:.gradle
47 graphql graphql
48 grunt grunt
49 gulp gulp:gulpfile.js:gulpfile.coffee:gulpfile.ts:gulpfile.babel.js:gulpfile.babel.coffee:gulpfile.babel.ts
50 haxelib .haxelib:haxe_libraries
51 helper helpers:.helpers
52 idea .idea
53 images images:image:img:icons:icon:ico:screenshot:screenshots:svg
54 include include:includes:incl:inc:.include:.includes:.incl:.inc:_include:_includes:_incl:_inc
55 interfaces interfaces
56 ios ios
57 js js
58 json json
59 json_official json
60 kubernetes kubernetes:k8s:kube:kuber:.kubernetes:.k8s:.kube:.kuber
61 less less:_less
62 library lib:.lib:library
63 linux linux
64 locale lang:language:languages:locale:locales:_locale:_locales:internationalization:globalization:localization:i18n:g11n:l10n
65 log log:logs
66 macos macos:darwin
67 mariadb mariadb:maria
68 maven .mvn
69 memcached memcached:.memcached
70 middleware middleware
71 mjml mjml:.mjml
72 minikube minikube:minik8s:minikuber
73 mock mocks:.mocks:__mocks__
74 model models:.models:entities:.entities
75 module modules
76 mongodb mongodb:mongo
77 mysql mysqldb:mysql
78 nginx nginx:conf.d
79 node node_modules
80 notification notification:notifications:event:events
81 nuget .nuget
82 package package:packages:.package:.packages:pkg
83 paket .paket
84 php php
85 platformio .pio:.pioenvs
86 plugin plugin:.plugin:plugins:.plugins:extension:.extension:extensions:.extensions
87 private private:.private
88 public public:.public
89 python .venv:.virtualenv
90 redis redis
91 ravendb ravendb
92 route route:routes:_route:_routes:routers
93 redux redux
94 meteor .meteor
95 sass sass:scss:_sass:_scss
96 script script:scripts
97 server server
98 services services
99 src src:source:sources
100 sso sso
101 story stories:__stories__
102 style style:styles
103 test tests:.tests:test:.test:__tests__:__test__:spec:.spec:specs:.specs
104 temp temp:.temp:tmp:.tmp
105 template template:.template:templates:.templates
106 theme theme:themes
107 travis .travis
108 tools tools:.tools:util:utils
109 typescript typescript:ts
110 typings typings:@types
111 typings2 typings:@types
112 vagrant vagrant:.vagrant
113 video video:.video:videos:.videos
114 view html:view:views:layout:layouts:page:pages:_view:_views:_layout:_layouts:_page:_pages
115 vs .vs
116 vs2 .vs
117 vscode .vscode:vscode
118 vscode2 .vscode:vscode
119 vscode3 .vscode:vscode
120 vscode_test .vscode-test
121 vscode_test2 .vscode-test
122 vscode_test3 .vscode-test
123 webpack webpack
124 windows windows:win32
125 www www:wwwroot

36
src/components/Clippy.tsx Normal file
View 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>
)
}

View 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>
)
}

View 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
}
}

View file

@ -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>
)
}

View file

@ -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>
)
}

View file

@ -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

View file

@ -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'}>

View file

@ -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>
&nbsp;/&nbsp;
<a className={'repo-name pjax-link'} href={repoUrl}>
<a className={'repo-name'} href={repoUrl}>
{metaData.repoName}
</a>
&nbsp;/&nbsp;

View file

@ -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} />
)}
</>
)
})

View file

@ -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),

View file

@ -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

View file

@ -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(

View file

@ -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>
)
}

View file

@ -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

View file

@ -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">
&nbsp;(?)
</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()}
/>
&nbsp;{option.label}&nbsp;
{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>
)
}

View file

@ -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)
}
}

View 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)}
/>
&nbsp;{field.label}&nbsp;
{field.wikiLink ? (
<a href={field.wikiLink} target={'_blank'}>
(?)
</a>
) : (
field.description && (
<span className={'description'} title={field.description}>
(?)
</span>
)
)}
</label>
)
}

View file

@ -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,
}),
)
}
}

View file

@ -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', {

View 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>
)
}

View 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>
)
}

View 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>
)
}

View 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
}
}

View file

@ -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;

View file

@ -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)

View file

@ -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)
}
}
}

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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

View file

@ -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

View file

@ -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
View 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
}

View file

@ -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"
]
}
]
}
}

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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 => {

View file

@ -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])
})
})
}

View file

@ -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])
}

View file

@ -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,
}

View 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}`)
}

View file

@ -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) {}
}

View file

@ -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,
}

View file

@ -202,7 +202,7 @@ type Options = {
compress?: boolean
}
export default class VisibleNodesGenerator {
export class VisibleNodesGenerator {
l1: L1
l2: L2
l3: L3

View file

@ -1,5 +1,4 @@
{
"files": ["src/content.tsx"],
"compilerOptions": {
"target": "es2016",
"outDir": "dist",

View file

@ -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
View file

@ -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"