mirror of
https://github.com/jupyter/notebook.git
synced 2024-12-15 04:00:34 +08:00
commit
5fa5466697
@ -72,6 +72,8 @@ async function main() {
|
|||||||
require('@jupyterlab-classic/application-extension'),
|
require('@jupyterlab-classic/application-extension'),
|
||||||
require('@jupyterlab-classic/docmanager-extension'),
|
require('@jupyterlab-classic/docmanager-extension'),
|
||||||
require('@jupyterlab-classic/notebook-extension'),
|
require('@jupyterlab-classic/notebook-extension'),
|
||||||
|
// to handle opening new tabs after creating a new terminal
|
||||||
|
require('@jupyterlab-classic/terminal-extension'),
|
||||||
|
|
||||||
// @jupyterlab plugins
|
// @jupyterlab plugins
|
||||||
require('@jupyterlab/apputils-extension').default.filter(({ id }) =>
|
require('@jupyterlab/apputils-extension').default.filter(({ id }) =>
|
||||||
@ -99,6 +101,8 @@ async function main() {
|
|||||||
),
|
),
|
||||||
require('@jupyterlab/rendermime-extension'),
|
require('@jupyterlab/rendermime-extension'),
|
||||||
require('@jupyterlab/shortcuts-extension'),
|
require('@jupyterlab/shortcuts-extension'),
|
||||||
|
// so new terminals can be create from the menu
|
||||||
|
require('@jupyterlab/terminal-extension'),
|
||||||
require('@jupyterlab/theme-light-extension'),
|
require('@jupyterlab/theme-light-extension'),
|
||||||
require('@jupyterlab/theme-dark-extension')
|
require('@jupyterlab/theme-dark-extension')
|
||||||
];
|
];
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
"@jupyterlab-classic/application-extension": "^0.1.0",
|
"@jupyterlab-classic/application-extension": "^0.1.0",
|
||||||
"@jupyterlab-classic/docmanager-extension": "^0.1.0",
|
"@jupyterlab-classic/docmanager-extension": "^0.1.0",
|
||||||
"@jupyterlab-classic/notebook-extension": "^0.1.0",
|
"@jupyterlab-classic/notebook-extension": "^0.1.0",
|
||||||
|
"@jupyterlab-classic/terminal-extension": "^0.1.0",
|
||||||
"@jupyterlab-classic/tree-extension": "^0.1.0",
|
"@jupyterlab-classic/tree-extension": "^0.1.0",
|
||||||
"@jupyterlab-classic/ui-components": "^0.1.0",
|
"@jupyterlab-classic/ui-components": "^0.1.0",
|
||||||
"@jupyterlab/apputils-extension": "^3.0.0-rc.12",
|
"@jupyterlab/apputils-extension": "^3.0.0-rc.12",
|
||||||
@ -27,6 +28,7 @@
|
|||||||
"@jupyterlab/rendermime-extension": "^3.0.0-rc.12",
|
"@jupyterlab/rendermime-extension": "^3.0.0-rc.12",
|
||||||
"@jupyterlab/running-extension": "^3.0.0-rc.12",
|
"@jupyterlab/running-extension": "^3.0.0-rc.12",
|
||||||
"@jupyterlab/shortcuts-extension": "^3.0.0-rc.12",
|
"@jupyterlab/shortcuts-extension": "^3.0.0-rc.12",
|
||||||
|
"@jupyterlab/terminal-extension": "^3.0.0-rc.12",
|
||||||
"@jupyterlab/tooltip-extension": "^3.0.0-rc.12",
|
"@jupyterlab/tooltip-extension": "^3.0.0-rc.12",
|
||||||
"@jupyterlab/theme-light-extension": "^3.0.0-rc.12",
|
"@jupyterlab/theme-light-extension": "^3.0.0-rc.12",
|
||||||
"@jupyterlab/theme-dark-extension": "^3.0.0-rc.12",
|
"@jupyterlab/theme-dark-extension": "^3.0.0-rc.12",
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
@import url('~@jupyterlab/fileeditor/style/index.css');
|
@import url('~@jupyterlab/fileeditor/style/index.css');
|
||||||
@import url('~@jupyterlab/tooltip/style/index.css');
|
@import url('~@jupyterlab/tooltip/style/index.css');
|
||||||
@import url('~@jupyterlab/running/style/index.css');
|
@import url('~@jupyterlab/running/style/index.css');
|
||||||
|
@import url('~@jupyterlab/terminal/style/index.css');
|
||||||
|
|
||||||
@import url('~@jupyterlab/codemirror-extension/style/index.css');
|
@import url('~@jupyterlab/codemirror-extension/style/index.css');
|
||||||
@import url('~@jupyterlab/docmanager-extension/style/index.css');
|
@import url('~@jupyterlab/docmanager-extension/style/index.css');
|
||||||
|
@ -77,6 +77,7 @@ class ClassicTreeHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, Ext
|
|||||||
@web.authenticated
|
@web.authenticated
|
||||||
def get(self, path=None):
|
def get(self, path=None):
|
||||||
page_config = self.get_page_config()
|
page_config = self.get_page_config()
|
||||||
|
page_config['terminalsAvailable'] = self.settings['terminals_available']
|
||||||
return self.write(
|
return self.write(
|
||||||
self.render_template(
|
self.render_template(
|
||||||
"tree.html",
|
"tree.html",
|
||||||
@ -87,6 +88,21 @@ class ClassicTreeHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, Ext
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ClassicTerminalHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler):
|
||||||
|
@web.authenticated
|
||||||
|
def get(self, path=None):
|
||||||
|
page_config = self.get_page_config()
|
||||||
|
page_config['terminalsAvailable'] = self.settings['terminals_available']
|
||||||
|
return self.write(
|
||||||
|
self.render_template(
|
||||||
|
"terminals.html",
|
||||||
|
base_url=self.base_url,
|
||||||
|
token=self.settings["token"],
|
||||||
|
page_config=page_config,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ClassicFileHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler):
|
class ClassicFileHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler):
|
||||||
@web.authenticated
|
@web.authenticated
|
||||||
def get(self, path=None):
|
def get(self, path=None):
|
||||||
@ -100,6 +116,7 @@ class ClassicFileHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, Ext
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ClassicNotebookHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler):
|
class ClassicNotebookHandler(ClassicPageConfigMixin, ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler):
|
||||||
@web.authenticated
|
@web.authenticated
|
||||||
def get(self, path=None):
|
def get(self, path=None):
|
||||||
@ -132,6 +149,7 @@ class ClassicApp(NBClassicConfigShimMixin, LabServerApp):
|
|||||||
self.handlers.append(("/classic/tree(.*)", ClassicTreeHandler))
|
self.handlers.append(("/classic/tree(.*)", ClassicTreeHandler))
|
||||||
self.handlers.append(("/classic/notebooks(.*)", ClassicNotebookHandler))
|
self.handlers.append(("/classic/notebooks(.*)", ClassicNotebookHandler))
|
||||||
self.handlers.append(("/classic/edit(.*)", ClassicFileHandler))
|
self.handlers.append(("/classic/edit(.*)", ClassicFileHandler))
|
||||||
|
self.handlers.append(("/classic/terminals/(.*)", ClassicTerminalHandler))
|
||||||
|
|
||||||
def initialize_templates(self):
|
def initialize_templates(self):
|
||||||
super().initialize_templates()
|
super().initialize_templates()
|
||||||
|
36
jupyterlab_classic/templates/terminals.html
Normal file
36
jupyterlab_classic/templates/terminals.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>{{page_config['appName'] | e}} - Terminal</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
{# Copy so we do not modify the page_config with updates. #}
|
||||||
|
{% set page_config_full = page_config.copy() %}
|
||||||
|
|
||||||
|
{# Set a dummy variable - we just want the side effect of the update. #}
|
||||||
|
{% set _ = page_config_full.update(baseUrl=base_url, wsUrl=ws_url) %}
|
||||||
|
|
||||||
|
{# Sentinel value to say that we are on the tree page #}
|
||||||
|
{% set _ = page_config_full.update(classicPage='terminals') %}
|
||||||
|
|
||||||
|
<script id="jupyter-config-data" type="application/json">
|
||||||
|
{{ page_config_full | tojson }}
|
||||||
|
</script>
|
||||||
|
<script src="{{page_config['fullStaticUrl'] | e}}/bundle.js" main="index"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
/* Remove token from URL. */
|
||||||
|
(function () {
|
||||||
|
var parsedUrl = new URL(window.location.href);
|
||||||
|
if (parsedUrl.searchParams.get('token')) {
|
||||||
|
parsedUrl.searchParams.delete('token');
|
||||||
|
window.history.replaceState({ }, '', parsedUrl.href);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -13,3 +13,7 @@
|
|||||||
.jp-Document {
|
.jp-Document {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.jp-MainAreaWidget {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
@ -102,11 +102,9 @@ export class ClassicShell extends Widget implements JupyterFrontEnd.IShell {
|
|||||||
if (area === 'menu') {
|
if (area === 'menu') {
|
||||||
return this._menuHandler.addWidget(widget, rank);
|
return this._menuHandler.addWidget(widget, rank);
|
||||||
}
|
}
|
||||||
if (area === 'main') {
|
if (area === 'main' || area === undefined) {
|
||||||
if (this._main.widgets.length > 0) {
|
if (this._main.widgets.length > 0) {
|
||||||
// do not add the widget if there is already one
|
// do not add the widget if there is already one
|
||||||
// TODO: should the widget be closed?
|
|
||||||
widget.close();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._main.addWidget(widget);
|
this._main.addWidget(widget);
|
||||||
|
54
packages/terminal-extension/package.json
Normal file
54
packages/terminal-extension/package.json
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"name": "@jupyterlab-classic/terminal-extension",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "JupyterLab Classic - Terminal Extension",
|
||||||
|
"homepage": "https://github.com/jtpio/jupyterlab-classic",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/jtpio/jupyterlab-classic/issues"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/jtpio/jupyterlab-classic.git"
|
||||||
|
},
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"author": "Project Jupyter",
|
||||||
|
"sideEffects": [
|
||||||
|
"style/**/*.css"
|
||||||
|
],
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"types": "lib/index.d.ts",
|
||||||
|
"style": "style/index.css",
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib/"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"lib/*.d.ts",
|
||||||
|
"lib/*.js.map",
|
||||||
|
"lib/*.js",
|
||||||
|
"schema/*.json",
|
||||||
|
"style/**/*.css"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -b",
|
||||||
|
"clean": "rimraf lib && rimraf tsconfig.tsbuildinfo",
|
||||||
|
"docs": "typedoc src",
|
||||||
|
"prepublishOnly": "npm run build",
|
||||||
|
"watch": "tsc -b --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@jupyterlab/application": "^3.0.0-rc.12",
|
||||||
|
"@jupyterlab/coreutils": "^5.0.0-rc.12",
|
||||||
|
"@jupyterlab/terminal": "^3.0.0-rc.12",
|
||||||
|
"@lumino/algorithm": "^1.3.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"rimraf": "~3.0.0",
|
||||||
|
"typescript": "~4.0.2"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"jupyterlab": {
|
||||||
|
"extension": true
|
||||||
|
}
|
||||||
|
}
|
77
packages/terminal-extension/src/index.ts
Normal file
77
packages/terminal-extension/src/index.ts
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright (c) Jupyter Development Team.
|
||||||
|
// Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
import {
|
||||||
|
IRouter,
|
||||||
|
JupyterFrontEnd,
|
||||||
|
JupyterFrontEndPlugin
|
||||||
|
} from '@jupyterlab/application';
|
||||||
|
|
||||||
|
import { PageConfig } from '@jupyterlab/coreutils';
|
||||||
|
|
||||||
|
import { ITerminalTracker } from '@jupyterlab/terminal';
|
||||||
|
|
||||||
|
import { find } from '@lumino/algorithm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A plugin to terminals in a new tab
|
||||||
|
*/
|
||||||
|
const opener: JupyterFrontEndPlugin<void> = {
|
||||||
|
id: '@jupyterlab-classic/terminal-extension:opener',
|
||||||
|
requires: [IRouter],
|
||||||
|
autoStart: true,
|
||||||
|
activate: (app: JupyterFrontEnd, router: IRouter) => {
|
||||||
|
const { commands } = app;
|
||||||
|
const terminalPattern = new RegExp('/terminals/(.*)');
|
||||||
|
|
||||||
|
const command = 'router:terminal';
|
||||||
|
commands.addCommand(command, {
|
||||||
|
execute: (args: any) => {
|
||||||
|
const parsed = args as IRouter.ILocation;
|
||||||
|
const matches = parsed.path.match(terminalPattern);
|
||||||
|
if (!matches) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [, name] = matches;
|
||||||
|
if (!name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.execute('terminal:open', { name });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.register({ command, pattern: terminalPattern });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open terminals in a new tab.
|
||||||
|
*/
|
||||||
|
const redirect: JupyterFrontEndPlugin<void> = {
|
||||||
|
id: '@jupyterlab-classic/terminal-extension:redirect',
|
||||||
|
requires: [ITerminalTracker],
|
||||||
|
autoStart: true,
|
||||||
|
activate: (app: JupyterFrontEnd, tracker: ITerminalTracker) => {
|
||||||
|
const baseUrl = PageConfig.getBaseUrl();
|
||||||
|
tracker.widgetAdded.connect((send, terminal) => {
|
||||||
|
const widget = find(app.shell.widgets('main'), w => w.id === terminal.id);
|
||||||
|
if (widget) {
|
||||||
|
// bail if the terminal is already added to the main area
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const name = terminal.content.session.name;
|
||||||
|
window.open(`${baseUrl}classic/terminals/${name}`, '_blank');
|
||||||
|
|
||||||
|
// dispose the widget since it is not used
|
||||||
|
terminal.dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export the plugins as default.
|
||||||
|
*/
|
||||||
|
const plugins: JupyterFrontEndPlugin<any>[] = [opener, redirect];
|
||||||
|
|
||||||
|
export default plugins;
|
0
packages/terminal-extension/style/base.css
Normal file
0
packages/terminal-extension/style/base.css
Normal file
1
packages/terminal-extension/style/index.css
Normal file
1
packages/terminal-extension/style/index.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
@import url('./base.css');
|
8
packages/terminal-extension/tsconfig.json
Normal file
8
packages/terminal-extension/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfigbase",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "lib",
|
||||||
|
"rootDir": "src"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"]
|
||||||
|
}
|
43
yarn.lock
43
yarn.lock
@ -2339,6 +2339,39 @@
|
|||||||
react "^17.0.1"
|
react "^17.0.1"
|
||||||
typestyle "^2.0.4"
|
typestyle "^2.0.4"
|
||||||
|
|
||||||
|
"@jupyterlab/terminal-extension@^3.0.0-rc.12":
|
||||||
|
version "3.0.0-rc.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jupyterlab/terminal-extension/-/terminal-extension-3.0.0-rc.13.tgz#e457ddafe23495488852cf0e26e6fbbebbefd677"
|
||||||
|
integrity sha512-nVmcZzRiD9te3lR8icnJ30HAjXtDXLJwqFS/GhpVV7CUCOxNjyQQirE5Nn4aNGMtp8/7g6syRbKYPpSyeNut7A==
|
||||||
|
dependencies:
|
||||||
|
"@jupyterlab/application" "^3.0.0-rc.13"
|
||||||
|
"@jupyterlab/apputils" "^3.0.0-rc.13"
|
||||||
|
"@jupyterlab/launcher" "^3.0.0-rc.13"
|
||||||
|
"@jupyterlab/mainmenu" "^3.0.0-rc.13"
|
||||||
|
"@jupyterlab/running" "^3.0.0-rc.13"
|
||||||
|
"@jupyterlab/services" "^6.0.0-rc.13"
|
||||||
|
"@jupyterlab/settingregistry" "^3.0.0-rc.13"
|
||||||
|
"@jupyterlab/terminal" "^3.0.0-rc.13"
|
||||||
|
"@jupyterlab/translation" "^3.0.0-rc.13"
|
||||||
|
"@jupyterlab/ui-components" "^3.0.0-rc.13"
|
||||||
|
"@lumino/algorithm" "^1.3.3"
|
||||||
|
"@lumino/widgets" "^1.16.1"
|
||||||
|
|
||||||
|
"@jupyterlab/terminal@^3.0.0-rc.12", "@jupyterlab/terminal@^3.0.0-rc.13":
|
||||||
|
version "3.0.0-rc.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jupyterlab/terminal/-/terminal-3.0.0-rc.13.tgz#3651a03b96691bae22a0562f6a6a22f2f718ff25"
|
||||||
|
integrity sha512-n1Wl9HAeGvkpuHJIJpd5JUsrTJtrbzDP7XAdWn+SuITdTPxz5cleT5lAxk5jRukN69r9S9nicLig+WHJgQL8AA==
|
||||||
|
dependencies:
|
||||||
|
"@jupyterlab/apputils" "^3.0.0-rc.13"
|
||||||
|
"@jupyterlab/services" "^6.0.0-rc.13"
|
||||||
|
"@jupyterlab/translation" "^3.0.0-rc.13"
|
||||||
|
"@lumino/coreutils" "^1.5.3"
|
||||||
|
"@lumino/domutils" "^1.2.3"
|
||||||
|
"@lumino/messaging" "^1.4.3"
|
||||||
|
"@lumino/widgets" "^1.16.1"
|
||||||
|
xterm "~4.8.1"
|
||||||
|
xterm-addon-fit "~0.4.0"
|
||||||
|
|
||||||
"@jupyterlab/testutils@^3.0.0-rc.12":
|
"@jupyterlab/testutils@^3.0.0-rc.12":
|
||||||
version "3.0.0-rc.12"
|
version "3.0.0-rc.12"
|
||||||
resolved "https://registry.yarnpkg.com/@jupyterlab/testutils/-/testutils-3.0.0-rc.12.tgz#accdebf76e700552328913ab2a7261031844776d"
|
resolved "https://registry.yarnpkg.com/@jupyterlab/testutils/-/testutils-3.0.0-rc.12.tgz#accdebf76e700552328913ab2a7261031844776d"
|
||||||
@ -12406,6 +12439,16 @@ xtend@^4.0.1, xtend@~4.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||||
|
|
||||||
|
xterm-addon-fit@~0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.4.0.tgz#06e0c5d0a6aaacfb009ef565efa1c81e93d90193"
|
||||||
|
integrity sha512-p4BESuV/g2L6pZzFHpeNLLnep9mp/DkF3qrPglMiucSFtD8iJxtMufEoEJbN8LZwB4i+8PFpFvVuFrGOSpW05w==
|
||||||
|
|
||||||
|
xterm@~4.8.1:
|
||||||
|
version "4.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.8.1.tgz#155a1729a43e1a89b406524e22c5634339e39ca1"
|
||||||
|
integrity sha512-ax91ny4tI5eklqIfH79OUSGE2PUX2rGbwONmB6DfqpyhSZO8/cf++sqiaMWEVCMjACyMfnISW7C3gGMoNvNolQ==
|
||||||
|
|
||||||
y18n@^4.0.0:
|
y18n@^4.0.0:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
|
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
|
||||||
|
Loading…
Reference in New Issue
Block a user