mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
Merge branch 'develop'
This commit is contained in:
commit
38e250dbb3
16 changed files with 154 additions and 58 deletions
|
|
@ -19,9 +19,9 @@ Yet another extension for GitHub, available on both Chrome and Firefox. Inspired
|
|||
|
||||
[](https://chrome.google.com/webstore/detail/gitako-github-file-tree/giljefjcheohhamkjphiebfjnlphnokk)
|
||||
|
||||
[Or install for Firefox](https://addons.mozilla.org/en-US/firefox/addon/gitako-github-file-tree/)
|
||||
[for Edge](https://microsoftedge.microsoft.com/addons/detail/alpoloddcggjhakjemghahlkofjekbca)
|
||||
|
||||
> Gitako for Firefox is still in beta
|
||||
[for Firefox(beta)](https://addons.mozilla.org/en-US/firefox/addon/gitako-github-file-tree/)
|
||||
|
||||
### Issues
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "gitako",
|
||||
"version": "0.8.2",
|
||||
"version": "0.8.7",
|
||||
"description": "The missing part of GitHub.",
|
||||
"repository": "https://github.com/EnixCoda/Gitako",
|
||||
"author": "EnixCoda",
|
||||
|
|
@ -56,8 +56,8 @@
|
|||
"json-loader": "^0.5.7",
|
||||
"less": "^3.9.0",
|
||||
"less-loader": "^4.0.5",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"raw-loader": "^4.0.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"typescript": "^3.7.2",
|
||||
"uglifyjs-webpack-plugin": "^2.1.2",
|
||||
"url-loader": "^1.1.2",
|
||||
|
|
|
|||
|
|
@ -5,13 +5,45 @@ import { IN_PRODUCTION_MODE, VERSION } from 'env'
|
|||
const PUBLIC_KEY = 'd22ec5c9cc874539a51c78388c12e3b0'
|
||||
const PROJECT_ID = '1406497'
|
||||
|
||||
const MAX_REPORT_COUNT = 10 // protect for error leaking
|
||||
let countReportedError = 0
|
||||
|
||||
const errorSet = new Set<string>([
|
||||
'inThisSearch is null', // weird bug in Firefox
|
||||
'The quota has been exceeded.', // caused by other addons in Firefox
|
||||
'NetworkError when attempting to fetch resource.', // network fail in Firefox
|
||||
'Failed to fetch', // network fail in Chrome
|
||||
])
|
||||
|
||||
const sentryOptions: Sentry.BrowserOptions = {
|
||||
dsn: `https://${PUBLIC_KEY}@sentry.io/${PROJECT_ID}`,
|
||||
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
|
||||
integrations: ints => ints.filter(({ name }) => name !== 'TryCatch'),
|
||||
defaultIntegrations: IN_PRODUCTION_MODE ? undefined : false,
|
||||
integrations: integrations => integrations.filter(({ name }) => name !== 'TryCatch'),
|
||||
beforeSend(event) {
|
||||
const message = event.exception?.values?.[0].value || event.message
|
||||
if (message) {
|
||||
if (errorSet.has(message)) return null
|
||||
errorSet.add(message) // prevent reporting duplicated error
|
||||
}
|
||||
if (countReportedError < MAX_REPORT_COUNT) {
|
||||
++countReportedError
|
||||
return event
|
||||
}
|
||||
return null
|
||||
},
|
||||
beforeBreadcrumb(breadcrumb, hint) {
|
||||
if (breadcrumb.category === 'ui.click') {
|
||||
const ariaLabel = hint?.event?.target?.ariaLabel
|
||||
if (ariaLabel) {
|
||||
breadcrumb.message = ariaLabel
|
||||
}
|
||||
}
|
||||
return breadcrumb
|
||||
},
|
||||
}
|
||||
Sentry.init(sentryOptions)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { connect } from 'driver/connect'
|
|||
import { FileExplorerCore } from 'driver/core'
|
||||
import { ConnectorState, Props } from 'driver/core/FileExplorer'
|
||||
import * as React from 'react'
|
||||
import { useEvent } from 'react-use'
|
||||
import useEvent from 'react-use/esm/useEvent'
|
||||
import { FixedSizeList as List, ListChildComponentProps, ListProps } from 'react-window'
|
||||
import { cx } from 'utils/cx'
|
||||
import { useOnLocationChange, usePrevious } from 'utils/hooks'
|
||||
|
|
|
|||
|
|
@ -125,16 +125,19 @@ type Props = {
|
|||
}
|
||||
|
||||
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)
|
||||
let children: React.ReactNode = null
|
||||
if (!placeholder) {
|
||||
const { name, IconComponent } = getSVGIconComponent(type)
|
||||
const mergedClassName = cx('octicon', name)
|
||||
children = React.createElement(Octicon, {
|
||||
icon: IconComponent,
|
||||
className: mergedClassName,
|
||||
verticalAlign: 'text-bottom',
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div className={cx('octicon-wrapper', className)} {...otherProps}>
|
||||
{React.createElement(Octicon, {
|
||||
icon: IconComponent,
|
||||
className: mergedClassName,
|
||||
verticalAlign: 'text-bottom',
|
||||
})}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,14 +77,19 @@ const NodeItemIcon = React.memo(function NodeItemIcon({
|
|||
val: { icons },
|
||||
} = useConfigs()
|
||||
|
||||
if (icons === 'native') return <Icon type={getIconType(node)} />
|
||||
const src = React.useMemo(
|
||||
() => (node.type === 'tree' ? getFolderIconSrc(node, open) : getFileIconSrc(node)),
|
||||
[open],
|
||||
)
|
||||
|
||||
if (icons === 'native') return <Icon type={getIconType(node)} />
|
||||
return (
|
||||
<>
|
||||
<Icon placeholder={node.type !== 'tree'} type={getIconType(node)} />
|
||||
<Icon
|
||||
className={'node-item-type-icon'}
|
||||
placeholder={node.type !== 'tree'}
|
||||
type={getIconType(node)}
|
||||
/>
|
||||
{node.type === 'commit' ? (
|
||||
<Icon type={getIconType(node)} />
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
import * as React from 'react'
|
||||
import * as DOMHelper from 'utils/DOMHelper'
|
||||
|
||||
type Props<P> = {
|
||||
to: string
|
||||
children: React.ReactElement<P>
|
||||
}
|
||||
|
||||
export function PJAXLink<P>({ to, children }: Props<P>) {
|
||||
return React.cloneElement(children, {
|
||||
...children.props,
|
||||
onClick: () => DOMHelper.loadWithPJAX(to),
|
||||
})
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ import { SideBarCore } from 'driver/core'
|
|||
import { ConnectorState, Props } from 'driver/core/SideBar'
|
||||
import { oauth } from 'env'
|
||||
import * as React from 'react'
|
||||
import { useEvent } from 'react-use'
|
||||
import useEvent from 'react-use/esm/useEvent'
|
||||
import { cx } from 'utils/cx'
|
||||
import * as DOMHelper from 'utils/DOMHelper'
|
||||
import { JSONRequest, parseURLSearch } from 'utils/general'
|
||||
|
|
@ -85,7 +85,7 @@ const RawGitako: React.FC<Props & ConnectorState> = function RawGitako(props) {
|
|||
// init again when setting new accessToken
|
||||
useDidUpdate(() => {
|
||||
props.init()
|
||||
}, [accessToken])
|
||||
}, [accessToken || '']) // fallback for preventing duplicated requests
|
||||
|
||||
const {
|
||||
errorDueToAuth,
|
||||
|
|
|
|||
|
|
@ -358,12 +358,29 @@
|
|||
transition: all 0.5s ease;
|
||||
white-space: nowrap;
|
||||
|
||||
&-icon {
|
||||
.icon-size() {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.octicon-wrapper:first-child {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
&-type-icon {
|
||||
margin-right: 4px;
|
||||
.icon-size();
|
||||
|
||||
& + .octicon-wrapper,
|
||||
& + .node-item-icon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
&-icon {
|
||||
.icon-size();
|
||||
object-fit: contain;
|
||||
vertical-align: text-bottom;
|
||||
padding-left: 4px;
|
||||
box-sizing: content-box;
|
||||
|
||||
&.dim {
|
||||
|
|
@ -379,12 +396,9 @@
|
|||
.node-item-label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.node-item-name {
|
||||
padding-left: 6px;
|
||||
}
|
||||
}
|
||||
.node-item:hover .node-item-name {
|
||||
|
||||
&:hover .node-item-name {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,14 @@ import './content.less'
|
|||
|
||||
addMiddleware(withErrorLog)
|
||||
|
||||
const SideBarElement = document.createElement('div')
|
||||
document.body.appendChild(SideBarElement)
|
||||
function init() {
|
||||
const SideBarElement = document.createElement('div')
|
||||
document.body.appendChild(SideBarElement)
|
||||
ReactDOM.render(<Gitako />, SideBarElement)
|
||||
}
|
||||
|
||||
ReactDOM.render(<Gitako />, SideBarElement)
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init)
|
||||
} else {
|
||||
init()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Gitako - Github file tree",
|
||||
"version": "0.8.2",
|
||||
"version": "0.8.7",
|
||||
"description": "The missing part of GitHub.",
|
||||
"author": "EnixCoda",
|
||||
"icons": {
|
||||
|
|
@ -21,6 +21,9 @@
|
|||
"matches": [
|
||||
"https://github.com/*"
|
||||
],
|
||||
"css": [
|
||||
"content.css"
|
||||
],
|
||||
"js": [
|
||||
"firefox-shim.js",
|
||||
"browser-polyfill.js",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export function parse(): MetaData & { path: string[] } {
|
|||
repoName,
|
||||
type,
|
||||
...path // should be [...branchName.split('/'), ...filePath.split('/')]
|
||||
] = pathname.split('/')
|
||||
] = unescape(decodeURIComponent(pathname)).split('/')
|
||||
return {
|
||||
userName,
|
||||
repoName,
|
||||
|
|
@ -80,7 +80,10 @@ export function getCurrentPath(branchName = '') {
|
|||
splitBranchName.shift()
|
||||
path.shift()
|
||||
} else {
|
||||
raiseError(new Error(`branch name and path prefix not match`))
|
||||
raiseError(new Error(`branch name and path prefix not match`), {
|
||||
branchName,
|
||||
path: parse().path,
|
||||
})
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react'
|
||||
import { useLocation } from 'react-use'
|
||||
import useLocation from 'react-use/esm/useLocation'
|
||||
import { createStyleSheet, setStyleSheetMedia } from './general'
|
||||
|
||||
export function useWindowSize(
|
||||
|
|
|
|||
|
|
@ -65,10 +65,14 @@ export function getFileIconSrc(node: TreeNode) {
|
|||
return getIconSrc('file', iconName)
|
||||
}
|
||||
|
||||
// memorize for
|
||||
// 1. swap time with space
|
||||
// 2. prevent app crash on when extension context invalidates
|
||||
const extensionURL = browser.runtime.getURL('').replace(/\/$/, '')
|
||||
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}`)
|
||||
return extensionURL + `/icons/vscode/${filename}`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ const CopyWebpackPlugin = require('copy-webpack-plugin')
|
|||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
|
||||
const path = require('path')
|
||||
const Dotenv = require('dotenv-webpack')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
|
||||
|
||||
const srcPath = path.resolve(__dirname, 'src')
|
||||
|
|
@ -33,6 +34,7 @@ const plugins = [
|
|||
]),
|
||||
new ForkTsCheckerWebpackPlugin(),
|
||||
new Dotenv(),
|
||||
new MiniCssExtractPlugin(),
|
||||
]
|
||||
|
||||
const analyse = process.env.ANALYSE !== undefined
|
||||
|
|
@ -75,7 +77,7 @@ module.exports = {
|
|||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
loader: ['style-loader', 'css-loader', 'less-loader'],
|
||||
loader: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
|
||||
include: [srcPath],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
57
yarn.lock
57
yarn.lock
|
|
@ -4685,7 +4685,7 @@ is-path-inside@^1.0.0:
|
|||
dependencies:
|
||||
path-is-inside "^1.0.1"
|
||||
|
||||
is-plain-obj@^1.1.0:
|
||||
is-plain-obj@^1.0.0, is-plain-obj@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
|
||||
integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4=
|
||||
|
|
@ -5373,6 +5373,16 @@ mimic-response@^1.0.0, mimic-response@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
|
||||
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
|
||||
|
||||
mini-css-extract-plugin@^0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e"
|
||||
integrity sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==
|
||||
dependencies:
|
||||
loader-utils "^1.1.0"
|
||||
normalize-url "1.9.1"
|
||||
schema-utils "^1.0.0"
|
||||
webpack-sources "^1.1.0"
|
||||
|
||||
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
|
||||
|
|
@ -5707,6 +5717,16 @@ normalize-path@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||
|
||||
normalize-url@1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c"
|
||||
integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=
|
||||
dependencies:
|
||||
object-assign "^4.0.1"
|
||||
prepend-http "^1.0.0"
|
||||
query-string "^4.1.0"
|
||||
sort-keys "^1.0.0"
|
||||
|
||||
normalize-url@^4.1.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129"
|
||||
|
|
@ -6248,6 +6268,11 @@ prelude-ls@~1.1.2:
|
|||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
|
||||
|
||||
prepend-http@^1.0.0:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
|
||||
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
|
||||
|
||||
prepend-http@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
||||
|
|
@ -6405,6 +6430,14 @@ qs@~6.5.1, qs@~6.5.2:
|
|||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
|
||||
|
||||
query-string@^4.1.0:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
|
||||
integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s=
|
||||
dependencies:
|
||||
object-assign "^4.1.0"
|
||||
strict-uri-encode "^1.0.0"
|
||||
|
||||
querystring-es3@^0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
|
||||
|
|
@ -7185,6 +7218,13 @@ sonic-boom@^0.7.5:
|
|||
dependencies:
|
||||
flatstr "^1.0.12"
|
||||
|
||||
sort-keys@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
|
||||
integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0=
|
||||
dependencies:
|
||||
is-plain-obj "^1.0.0"
|
||||
|
||||
source-list-map@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
|
||||
|
|
@ -7395,6 +7435,11 @@ stream-to-promise@2.2.0:
|
|||
end-of-stream "~1.1.0"
|
||||
stream-to-array "~2.3.0"
|
||||
|
||||
strict-uri-encode@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
|
||||
integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
|
||||
|
||||
string-width@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
|
||||
|
|
@ -7517,14 +7562,6 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
|
|||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
|
||||
|
||||
style-loader@^0.23.1:
|
||||
version "0.23.1"
|
||||
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.1.tgz#cb9154606f3e771ab6c4ab637026a1049174d925"
|
||||
integrity sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==
|
||||
dependencies:
|
||||
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"
|
||||
|
|
@ -8202,7 +8239,7 @@ webpack-log@^2.0.0:
|
|||
ansi-colors "^3.0.0"
|
||||
uuid "^3.3.2"
|
||||
|
||||
webpack-sources@^1.4.0, webpack-sources@^1.4.1:
|
||||
webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
|
||||
integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==
|
||||
|
|
|
|||
Loading…
Reference in a new issue