Convert to typescript
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -21,3 +21,5 @@
|
|||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
|
.idea
|
||||||
32
package.json
32
package.json
@@ -3,26 +3,40 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@monaco-editor/react": "^4.3.1",
|
"@monaco-editor/react": "4.4.6",
|
||||||
"@testing-library/jest-dom": "^5.11.4",
|
|
||||||
"@testing-library/react": "^13.3.0",
|
|
||||||
"@testing-library/user-event": "^14.2.0",
|
|
||||||
"content-type-parser": "^1.0.2",
|
|
||||||
"copy-to-clipboard": "^3.3.1",
|
"copy-to-clipboard": "^3.3.1",
|
||||||
"history": "^5.0.0",
|
"history": "^5.0.0",
|
||||||
"local-storage": "^2.0.0",
|
"local-storage": "^2.0.0",
|
||||||
"pako": "^2.0.3",
|
"pako": "^2.0.3",
|
||||||
"react": "^18.1.0",
|
"react": "^18.2.0",
|
||||||
"react-device-detect": "^2.1.2",
|
"react-device-detect": "^2.1.2",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"styled-components": "^5.2.1"
|
"styled-components": "^5.2.1",
|
||||||
|
"typescript": "^4.8.4",
|
||||||
|
"whatwg-mimetype": "^3.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@testing-library/jest-dom": "^5.11.4",
|
||||||
|
"@testing-library/react": "^13.3.0",
|
||||||
|
"@testing-library/user-event": "^14.2.0",
|
||||||
|
"@types/jest": "^29.2.2",
|
||||||
|
"@types/node": "^18.11.9",
|
||||||
|
"@types/pako": "^2.0.0",
|
||||||
|
"@types/react": "^18.0.25",
|
||||||
|
"@types/react-dom": "^18.0.8",
|
||||||
|
"@types/styled-components": "^5.1.26",
|
||||||
|
"@types/whatwg-mimetype": "^3.0.0",
|
||||||
|
"monaco-editor": "^0.34.1",
|
||||||
|
"prettier": "^2.7.1",
|
||||||
|
"prettier-plugin-organize-imports": "^3.1.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject",
|
||||||
|
"format": "prettier --write '**/*.ts' '**/*.tsx' '**/*.css'"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|||||||
@@ -1,62 +1,27 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import Editor from './components/Editor';
|
import Editor from './components/Editor';
|
||||||
import parseContentType from 'content-type-parser';
|
import { loadFromBytebin } from './util/storage';
|
||||||
import { languageIds } from './util/highlighting';
|
|
||||||
import { bytebinUrl } from './util/constants';
|
|
||||||
|
|
||||||
function getPasteIdFromUrl() {
|
|
||||||
const path = window.location.pathname;
|
|
||||||
if (path && /^\/[a-zA-Z0-9]+$/.test(path)) {
|
|
||||||
return path.substring(1);
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadFromBytebin(id) {
|
|
||||||
try {
|
|
||||||
const resp = await fetch(bytebinUrl + id);
|
|
||||||
if (resp.ok) {
|
|
||||||
const content = await resp.text();
|
|
||||||
const type = parseLanguageFromContentType(
|
|
||||||
resp.headers.get('content-type')
|
|
||||||
);
|
|
||||||
|
|
||||||
document.title = 'pastes | ' + id;
|
|
||||||
return { ok: true, content, type };
|
|
||||||
} else {
|
|
||||||
return { ok: false };
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return { ok: false };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseLanguageFromContentType(contentType) {
|
|
||||||
const { type, subtype: subType } = parseContentType(contentType);
|
|
||||||
if (type === 'application' && subType === 'json') {
|
|
||||||
return 'json';
|
|
||||||
}
|
|
||||||
if (type === 'text' && languageIds.includes(subType.toLowerCase())) {
|
|
||||||
return subType.toLowerCase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const INITIAL = Symbol();
|
const INITIAL = Symbol();
|
||||||
const LOADING = Symbol();
|
const LOADING = Symbol();
|
||||||
const LOADED = Symbol();
|
const LOADED = Symbol();
|
||||||
|
|
||||||
export default function App() {
|
type LoadingState = typeof INITIAL | typeof LOADING | typeof LOADED;
|
||||||
const [pasteId] = useState(getPasteIdFromUrl);
|
|
||||||
const [state, setState] = useState(INITIAL);
|
|
||||||
const [forcedContent, setForcedContent] = useState('');
|
|
||||||
const [actualContent, setActualContent] = useState('');
|
|
||||||
const [contentType, setContentType] = useState();
|
|
||||||
|
|
||||||
const setContent = useCallback((content) => {
|
export default function App() {
|
||||||
|
const [pasteId] = useState<string | undefined>(getPasteIdFromUrl);
|
||||||
|
const [state, setState] = useState<LoadingState>(INITIAL);
|
||||||
|
const [forcedContent, setForcedContent] = useState<string>('');
|
||||||
|
const [actualContent, setActualContent] = useState<string>('');
|
||||||
|
const [contentType, setContentType] = useState<string>();
|
||||||
|
|
||||||
|
const setContent = useCallback(
|
||||||
|
(content: string) => {
|
||||||
setActualContent(content);
|
setActualContent(content);
|
||||||
setForcedContent(content);
|
setForcedContent(content);
|
||||||
}, [setActualContent, setForcedContent]);
|
},
|
||||||
|
[setActualContent, setForcedContent]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pasteId && state === INITIAL) {
|
if (pasteId && state === INITIAL) {
|
||||||
@@ -88,7 +53,7 @@ export default function App() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function get404Message(pasteId) {
|
function get404Message(pasteId: string) {
|
||||||
return `
|
return `
|
||||||
██╗ ██╗ ██████╗ ██╗ ██╗
|
██╗ ██╗ ██████╗ ██╗ ██╗
|
||||||
██║ ██║██╔═████╗██║ ██║
|
██║ ██║██╔═████╗██║ ██║
|
||||||
@@ -101,3 +66,12 @@ function get404Message(pasteId) {
|
|||||||
maybe the paste expired?
|
maybe the paste expired?
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPasteIdFromUrl() {
|
||||||
|
const path = window.location.pathname;
|
||||||
|
if (path && /^\/[a-zA-Z0-9]+$/.test(path)) {
|
||||||
|
return path.substring(1);
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/components/Button.tsx
Normal file
23
src/components/Button.tsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Button = styled.div`
|
||||||
|
cursor: pointer;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 0.25em;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
:hover {
|
||||||
|
background: ${props => props.theme.highlight};
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Button;
|
||||||
@@ -1,11 +1,21 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { ThemeProvider, createGlobalStyle } from 'styled-components';
|
|
||||||
import { isMobile } from 'react-device-detect';
|
import { isMobile } from 'react-device-detect';
|
||||||
import ls from 'local-storage';
|
import { ThemeProvider } from 'styled-components';
|
||||||
|
|
||||||
|
import usePreference from '../hooks/usePreference';
|
||||||
|
import themes, { Themes } from '../style/themes';
|
||||||
import EditorControls from './EditorControls';
|
import EditorControls from './EditorControls';
|
||||||
|
import EditorGlobalStyle from './EditorGlobalStyle';
|
||||||
import EditorTextArea from './EditorTextArea';
|
import EditorTextArea from './EditorTextArea';
|
||||||
import themes from '../style/themes';
|
|
||||||
|
export interface EditorProps {
|
||||||
|
forcedContent: string;
|
||||||
|
setForcedContent: (value: string) => void;
|
||||||
|
actualContent: string;
|
||||||
|
setActualContent: (value: string) => void;
|
||||||
|
contentType?: string;
|
||||||
|
pasteId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default function Editor({
|
export default function Editor({
|
||||||
forcedContent,
|
forcedContent,
|
||||||
@@ -14,16 +24,16 @@ export default function Editor({
|
|||||||
setActualContent,
|
setActualContent,
|
||||||
contentType,
|
contentType,
|
||||||
pasteId,
|
pasteId,
|
||||||
}) {
|
}: EditorProps) {
|
||||||
const [language, setLanguage] = useState('plain');
|
const [language, setLanguage] = useState<string>('plain');
|
||||||
const [readOnly, setReadOnly] = useState(isMobile && pasteId);
|
const [readOnly, setReadOnly] = useState<boolean>(isMobile && !!pasteId);
|
||||||
|
|
||||||
const [theme, setTheme] = usePreference(
|
const [theme, setTheme] = usePreference<keyof Themes>(
|
||||||
'theme',
|
'theme',
|
||||||
'dark',
|
'dark',
|
||||||
pref => !!themes[pref]
|
pref => !!themes[pref]
|
||||||
);
|
);
|
||||||
const [fontSize, setFontSize, fontSizeCheck] = usePreference(
|
const [fontSize, setFontSize, fontSizeCheck] = usePreference<number>(
|
||||||
'fontsize',
|
'fontsize',
|
||||||
16,
|
16,
|
||||||
pref => pref >= 10 && pref <= 22
|
pref => pref >= 10 && pref <= 22
|
||||||
@@ -35,7 +45,7 @@ export default function Editor({
|
|||||||
}
|
}
|
||||||
}, [contentType]);
|
}, [contentType]);
|
||||||
|
|
||||||
function zoom(delta) {
|
function zoom(delta: number) {
|
||||||
const newFontSize = fontSize + delta;
|
const newFontSize = fontSize + delta;
|
||||||
if (fontSizeCheck(newFontSize)) {
|
if (fontSizeCheck(newFontSize)) {
|
||||||
setFontSize(newFontSize);
|
setFontSize(newFontSize);
|
||||||
@@ -65,39 +75,8 @@ export default function Editor({
|
|||||||
language={language}
|
language={language}
|
||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
setReadOnly={setReadOnly}
|
|
||||||
/>
|
/>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const EditorGlobalStyle = createGlobalStyle`
|
|
||||||
html, body {
|
|
||||||
color-scheme: ${props => props.theme.lightOrDark};
|
|
||||||
scrollbar-color: ${props => props.theme.lightOrDark};
|
|
||||||
background-color: ${props => props.theme.editor.background};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// hook used to load "preference" settings from local storage, or fall back to a default value.
|
|
||||||
function usePreference(id, defaultValue, valid) {
|
|
||||||
const [value, setValue] = useState(() => {
|
|
||||||
const pref = ls.get(id);
|
|
||||||
if (pref && valid(pref)) {
|
|
||||||
return pref;
|
|
||||||
} else {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (value === defaultValue) {
|
|
||||||
ls.remove(id);
|
|
||||||
} else {
|
|
||||||
ls.set(id, value);
|
|
||||||
}
|
|
||||||
}, [value, id, defaultValue]);
|
|
||||||
|
|
||||||
return [value, setValue, valid];
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,25 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { gzip } from 'pako';
|
|
||||||
import history from 'history/browser';
|
|
||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
|
import history from 'history/browser';
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { MenuButton, Button } from './Menu';
|
import themes, { Themes } from '../style/themes';
|
||||||
import { languages } from '../util/highlighting';
|
import { languages } from '../util/highlighting';
|
||||||
import themes from '../style/themes';
|
import { saveToBytebin } from '../util/storage';
|
||||||
import { postUrl } from '../util/constants';
|
import Button from './Button';
|
||||||
|
import MenuButton from './MenuButton';
|
||||||
|
|
||||||
|
export interface EditorControlsProps {
|
||||||
|
actualContent: string;
|
||||||
|
setForcedContent: (value: string) => void;
|
||||||
|
language: string;
|
||||||
|
setLanguage: (value: string) => void;
|
||||||
|
readOnly: boolean;
|
||||||
|
setReadOnly: (value: boolean) => void;
|
||||||
|
theme: keyof Themes;
|
||||||
|
setTheme: (value: keyof Themes) => void;
|
||||||
|
zoom: (delta: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export default function EditorControls({
|
export default function EditorControls({
|
||||||
actualContent,
|
actualContent,
|
||||||
@@ -19,9 +31,9 @@ export default function EditorControls({
|
|||||||
theme,
|
theme,
|
||||||
setTheme,
|
setTheme,
|
||||||
zoom,
|
zoom,
|
||||||
}) {
|
}: EditorControlsProps) {
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState<boolean>(false);
|
||||||
const [recentlySaved, setRecentlySaved] = useState(false);
|
const [recentlySaved, setRecentlySaved] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRecentlySaved(false);
|
setRecentlySaved(false);
|
||||||
@@ -46,7 +58,7 @@ export default function EditorControls({
|
|||||||
}, [actualContent, language, recentlySaved]);
|
}, [actualContent, language, recentlySaved]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listener = e => {
|
const listener = (e: KeyboardEvent) => {
|
||||||
if (e.ctrlKey || e.metaKey) {
|
if (e.ctrlKey || e.metaKey) {
|
||||||
if (e.key === 's' || e.key === 'S') {
|
if (e.key === 's' || e.key === 'S') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -100,7 +112,7 @@ export default function EditorControls({
|
|||||||
label="theme"
|
label="theme"
|
||||||
value={theme}
|
value={theme}
|
||||||
setValue={setTheme}
|
setValue={setTheme}
|
||||||
ids={Object.keys(themes)}
|
ids={Object.keys(themes) as (keyof Themes)[]}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className="optional"
|
className="optional"
|
||||||
@@ -139,36 +151,3 @@ const Section = styled.div`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function langaugeToContentType(language) {
|
|
||||||
if (language === 'json') {
|
|
||||||
return 'application/json';
|
|
||||||
} else {
|
|
||||||
return 'text/' + language;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveToBytebin(code, language) {
|
|
||||||
try {
|
|
||||||
const compressed = gzip(code);
|
|
||||||
const contentType = langaugeToContentType(language);
|
|
||||||
|
|
||||||
const resp = await fetch(postUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': contentType,
|
|
||||||
'Content-Encoding': 'gzip',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
body: compressed,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (resp.ok) {
|
|
||||||
const json = await resp.json();
|
|
||||||
return json.key;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
12
src/components/EditorGlobalStyle.tsx
Normal file
12
src/components/EditorGlobalStyle.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { createGlobalStyle, ThemeProps } from 'styled-components';
|
||||||
|
import { Theme } from '../style/themes';
|
||||||
|
|
||||||
|
const EditorGlobalStyle = createGlobalStyle<ThemeProps<Theme>>`
|
||||||
|
html, body {
|
||||||
|
color-scheme: ${props => props.theme.lightOrDark};
|
||||||
|
scrollbar-color: ${props => props.theme.lightOrDark};
|
||||||
|
background-color: ${props => props.theme.editor.background};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default EditorGlobalStyle;
|
||||||
@@ -1,8 +1,25 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import Editor, {
|
||||||
|
BeforeMount,
|
||||||
|
Monaco,
|
||||||
|
OnChange,
|
||||||
|
OnMount,
|
||||||
|
} from '@monaco-editor/react';
|
||||||
import history from 'history/browser';
|
import history from 'history/browser';
|
||||||
import Editor from '@monaco-editor/react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import themes, { makeMonacoTheme } from '../style/themes';
|
import themes, { makeMonacoTheme, Theme } from '../style/themes';
|
||||||
|
|
||||||
|
import type { editor } from 'monaco-editor';
|
||||||
|
|
||||||
|
export interface EditorTextAreaProps {
|
||||||
|
forcedContent: string;
|
||||||
|
actualContent: string;
|
||||||
|
setActualContent: (value: string) => void;
|
||||||
|
theme: Theme;
|
||||||
|
language: string;
|
||||||
|
fontSize: number;
|
||||||
|
readOnly: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export default function EditorTextArea({
|
export default function EditorTextArea({
|
||||||
forcedContent,
|
forcedContent,
|
||||||
@@ -12,11 +29,12 @@ export default function EditorTextArea({
|
|||||||
language,
|
language,
|
||||||
fontSize,
|
fontSize,
|
||||||
readOnly,
|
readOnly,
|
||||||
}) {
|
}: EditorTextAreaProps) {
|
||||||
const [editor, setEditor] = useState();
|
const [editor, setEditor] = useState<editor.IStandaloneCodeEditor>();
|
||||||
const [monaco, setMonaco] = useState();
|
const [monaco, setMonaco] = useState<Monaco>();
|
||||||
const [selected, toggleSelected] = useSelectedLine();
|
const [selected, toggleSelected] = useSelectedLine();
|
||||||
const editorAreaRef = useRef();
|
const editorAreaRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useLineNumberMagic(
|
useLineNumberMagic(
|
||||||
editorAreaRef,
|
editorAreaRef,
|
||||||
selected,
|
selected,
|
||||||
@@ -25,9 +43,10 @@ export default function EditorTextArea({
|
|||||||
editor,
|
editor,
|
||||||
monaco
|
monaco
|
||||||
);
|
);
|
||||||
|
|
||||||
usePlaceholderText(actualContent, editor, monaco);
|
usePlaceholderText(actualContent, editor, monaco);
|
||||||
|
|
||||||
function beforeMount(monaco) {
|
const beforeMount: BeforeMount = monaco => {
|
||||||
for (const [id, theme] of Object.entries(themes)) {
|
for (const [id, theme] of Object.entries(themes)) {
|
||||||
monaco.editor.defineTheme(id, makeMonacoTheme(theme));
|
monaco.editor.defineTheme(id, makeMonacoTheme(theme));
|
||||||
}
|
}
|
||||||
@@ -36,32 +55,35 @@ export default function EditorTextArea({
|
|||||||
noSemanticValidation: true,
|
noSemanticValidation: true,
|
||||||
noSyntaxValidation: true,
|
noSyntaxValidation: true,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
function onMount(editor, monaco) {
|
const onMount: OnMount = (editor, monaco) => {
|
||||||
editor.addAction({
|
editor.addAction({
|
||||||
id: 'search',
|
id: 'search',
|
||||||
label: 'Search with Google',
|
label: 'Search with Google',
|
||||||
contextMenuGroupId: '9_cutcopypaste',
|
contextMenuGroupId: '9_cutcopypaste',
|
||||||
contextMenuOrder: 5,
|
contextMenuOrder: 5,
|
||||||
run: (editor) => {
|
run: editor => {
|
||||||
const selection = editor.getSelection()
|
const selection = editor.getSelection();
|
||||||
if (!selection.isEmpty()) {
|
if (selection && !selection.isEmpty()) {
|
||||||
const query = editor.getModel().getValueInRange(selection);
|
const model = editor.getModel();
|
||||||
|
if (model) {
|
||||||
|
const query = model.getValueInRange(selection);
|
||||||
window.open('https://www.google.com/search?q=' + query, '_blank');
|
window.open('https://www.google.com/search?q=' + query, '_blank');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
});
|
||||||
|
|
||||||
setEditor(editor);
|
setEditor(editor);
|
||||||
setMonaco(monaco);
|
setMonaco(monaco);
|
||||||
|
|
||||||
editor.focus();
|
editor.focus();
|
||||||
}
|
};
|
||||||
|
|
||||||
const onChange = useCallback(
|
const onChange: OnChange = useCallback(
|
||||||
value => {
|
value => {
|
||||||
setActualContent(value);
|
setActualContent(value as string);
|
||||||
},
|
},
|
||||||
[setActualContent]
|
[setActualContent]
|
||||||
);
|
);
|
||||||
@@ -72,8 +94,13 @@ export default function EditorTextArea({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.getModel().detectIndentation(true, 2);
|
const model = editor.getModel();
|
||||||
}, [editor, forcedContent])
|
if (!model) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
model.detectIndentation(true, 2);
|
||||||
|
}, [editor, forcedContent]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditorArea ref={editorAreaRef}>
|
<EditorArea ref={editorAreaRef}>
|
||||||
@@ -84,9 +111,9 @@ export default function EditorTextArea({
|
|||||||
fontFamily: 'JetBrains Mono',
|
fontFamily: 'JetBrains Mono',
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
fontLigatures: true,
|
fontLigatures: true,
|
||||||
wordWrap: true,
|
wordWrap: 'on',
|
||||||
renderLineHighlight: 'none',
|
renderLineHighlight: 'none',
|
||||||
renderValidationDecorations: false,
|
renderValidationDecorations: 'off',
|
||||||
readOnly,
|
readOnly,
|
||||||
domReadOnly: readOnly,
|
domReadOnly: readOnly,
|
||||||
}}
|
}}
|
||||||
@@ -124,13 +151,17 @@ const EditorArea = styled.main`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function usePlaceholderText(actualContent, editor, monaco) {
|
function usePlaceholderText(
|
||||||
|
actualContent: string,
|
||||||
|
editor: editor.IStandaloneCodeEditor | undefined,
|
||||||
|
monaco: Monaco | undefined
|
||||||
|
) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editor || !monaco || actualContent) {
|
if (!editor || !monaco || actualContent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const decoration = {
|
const decoration = {
|
||||||
range: new monaco.Range(1, 1, 1, 1),
|
range: new monaco.Range(1, 0, 1, 1),
|
||||||
options: {
|
options: {
|
||||||
after: {
|
after: {
|
||||||
content: '\u200B',
|
content: '\u200B',
|
||||||
@@ -146,13 +177,63 @@ function usePlaceholderText(actualContent, editor, monaco) {
|
|||||||
}, [editor, monaco, actualContent]);
|
}, [editor, monaco, actualContent]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SelectedLine = [number, number];
|
||||||
|
type ToggleSelectedFunction = (lineNo: number, e: MouseEvent) => void;
|
||||||
|
|
||||||
|
function useSelectedLine(): [SelectedLine, ToggleSelectedFunction] {
|
||||||
|
// extract highlighted lines from window hash
|
||||||
|
const [selected, setSelected] = useState<[number, number]>(() => {
|
||||||
|
const hash = window.location.hash;
|
||||||
|
if (/^#L\d+(-\d+)?$/.test(hash)) {
|
||||||
|
const [start, end] = hash.substring(2).split('-').map(Number);
|
||||||
|
return [start, isNaN(end) ? start : end];
|
||||||
|
} else {
|
||||||
|
return [-1, -1];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// update window hash when a new line is highlighted
|
||||||
|
useEffect(() => {
|
||||||
|
let hash = '';
|
||||||
|
|
||||||
|
if (selected[0] !== -1) {
|
||||||
|
if (selected[1] !== selected[0]) {
|
||||||
|
const start = Math.min(...selected);
|
||||||
|
const end = Math.max(...selected);
|
||||||
|
hash = `#L${start}-${end}`;
|
||||||
|
} else {
|
||||||
|
hash = `#L${selected[0]}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
history.replace({ hash });
|
||||||
|
}, [selected]);
|
||||||
|
|
||||||
|
// toggle the highlighting for a given line
|
||||||
|
const toggleSelected = useCallback(
|
||||||
|
(lineNo: number, e: MouseEvent) => {
|
||||||
|
const shift = e.shiftKey;
|
||||||
|
if (selected[0] === lineNo && selected[1] === lineNo) {
|
||||||
|
setSelected([-1, -1]);
|
||||||
|
} else if (selected[0] === -1 || !shift) {
|
||||||
|
setSelected([lineNo, lineNo]);
|
||||||
|
} else {
|
||||||
|
setSelected([selected[0], lineNo]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[selected, setSelected]
|
||||||
|
);
|
||||||
|
|
||||||
|
return [selected, toggleSelected];
|
||||||
|
}
|
||||||
|
|
||||||
function useLineNumberMagic(
|
function useLineNumberMagic(
|
||||||
editorAreaRef,
|
editorAreaRef: React.RefObject<HTMLDivElement>,
|
||||||
selected,
|
selected: SelectedLine,
|
||||||
toggleSelected,
|
toggleSelected: ToggleSelectedFunction,
|
||||||
forcedContent,
|
forcedContent: string,
|
||||||
editor,
|
editor: editor.IStandaloneCodeEditor | undefined,
|
||||||
monaco
|
monaco: Monaco | undefined
|
||||||
) {
|
) {
|
||||||
// add an event listener for clicking on line numbers
|
// add an event listener for clicking on line numbers
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -161,9 +242,13 @@ function useLineNumberMagic(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handler = click => {
|
const handler = (click: MouseEvent) => {
|
||||||
const target = click?.target;
|
const target = click?.target as HTMLElement;
|
||||||
if (target && target.classList.contains('line-numbers')) {
|
if (
|
||||||
|
target &&
|
||||||
|
target.classList.contains('line-numbers') &&
|
||||||
|
target.textContent
|
||||||
|
) {
|
||||||
toggleSelected(parseInt(target.textContent), click);
|
toggleSelected(parseInt(target.textContent), click);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -204,47 +289,3 @@ function useLineNumberMagic(
|
|||||||
};
|
};
|
||||||
}, [editor, monaco, selected, forcedContent]);
|
}, [editor, monaco, selected, forcedContent]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function useSelectedLine() {
|
|
||||||
// extract highlighted lines from window hash
|
|
||||||
const [selected, setSelected] = useState(() => {
|
|
||||||
const hash = window.location.hash;
|
|
||||||
if (/^#L\d+(-\d+)?$/.test(hash)) {
|
|
||||||
const [start, end] = hash.substring(2).split('-').map(Number);
|
|
||||||
return [start, isNaN(end) ? start : end];
|
|
||||||
} else {
|
|
||||||
return [-1, -1];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// update window hash when a new line is highlighted
|
|
||||||
useEffect(() => {
|
|
||||||
let hash = '';
|
|
||||||
|
|
||||||
if (selected[0] !== -1) {
|
|
||||||
if (selected[1] !== selected[0]) {
|
|
||||||
const start = Math.min(...selected);
|
|
||||||
const end = Math.max(...selected);
|
|
||||||
hash = `#L${start}-${end}`;
|
|
||||||
} else {
|
|
||||||
hash = `#L${selected[0]}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
history.replace({ hash });
|
|
||||||
}, [selected]);
|
|
||||||
|
|
||||||
// toggle the highlighting for a given line
|
|
||||||
function toggleSelected(lineNo, e) {
|
|
||||||
const shift = e.shiftKey;
|
|
||||||
if (selected[0] === lineNo && selected[1] === lineNo) {
|
|
||||||
setSelected([-1, -1]);
|
|
||||||
} else if (selected[0] === -1 || !shift) {
|
|
||||||
setSelected([lineNo, lineNo]);
|
|
||||||
} else {
|
|
||||||
setSelected([selected[0], lineNo]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [selected, toggleSelected];
|
|
||||||
}
|
|
||||||
@@ -1,25 +1,74 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import Button from './Button';
|
||||||
|
|
||||||
export const Button = styled.div`
|
interface MenuButtonProps<T extends string> {
|
||||||
cursor: pointer;
|
label: string;
|
||||||
height: 100%;
|
ids: T[] | Record<string, T[]>;
|
||||||
display: flex;
|
value: T;
|
||||||
align-items: center;
|
setValue: (value: T) => void;
|
||||||
padding: 0 0.25em;
|
}
|
||||||
color: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
:hover {
|
export default function MenuButton<T extends string>({
|
||||||
background: ${props => props.theme.highlight};
|
label,
|
||||||
|
ids,
|
||||||
|
value,
|
||||||
|
setValue,
|
||||||
|
}: MenuButtonProps<T>) {
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
// close the menu when a click is made elsewhere
|
||||||
|
useEffect(() => {
|
||||||
|
const listener = (e: MouseEvent) => setOpen(false);
|
||||||
|
window.addEventListener('click', listener);
|
||||||
|
return () => window.removeEventListener('click', listener);
|
||||||
|
}, [setOpen]);
|
||||||
|
|
||||||
|
function toggleOpen(e: React.MouseEvent) {
|
||||||
|
e.stopPropagation();
|
||||||
|
setOpen(!open);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
function select(e: React.MouseEvent<HTMLLIElement>, id: T) {
|
||||||
span {
|
e.stopPropagation();
|
||||||
display: none;
|
setOpen(false);
|
||||||
|
setValue(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function make(ids: T[]) {
|
||||||
|
return ids.map(id => (
|
||||||
|
<li
|
||||||
|
key={id}
|
||||||
|
className={id === value ? 'selected' : undefined}
|
||||||
|
onClick={e => select(e, id)}
|
||||||
|
>
|
||||||
|
{id}
|
||||||
|
</li>
|
||||||
|
));
|
||||||
}
|
}
|
||||||
`;
|
|
||||||
|
let items;
|
||||||
|
if (Array.isArray(ids)) {
|
||||||
|
items = make(ids);
|
||||||
|
} else {
|
||||||
|
items = Object.entries(ids).map(([title, values]) =>
|
||||||
|
[
|
||||||
|
<li className="title" key={title} onClick={e => e.stopPropagation()}>
|
||||||
|
[{title}]
|
||||||
|
</li>,
|
||||||
|
]
|
||||||
|
.concat(make(values))
|
||||||
|
.flat()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button onClick={toggleOpen}>
|
||||||
|
[<span>{label}: </span>
|
||||||
|
{value}]{open && <Menu>{items}</Menu>}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const Menu = styled.ul`
|
const Menu = styled.ul`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -60,58 +109,3 @@ const Menu = styled.ul`
|
|||||||
background-color: ${props => props.theme.secondary};
|
background-color: ${props => props.theme.secondary};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const MenuButton = ({ label, ids, value, setValue }) => {
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const listener = e => setOpen(false);
|
|
||||||
window.addEventListener('click', listener);
|
|
||||||
return () => window.removeEventListener('click', listener);
|
|
||||||
}, [setOpen]);
|
|
||||||
|
|
||||||
function toggleOpen(e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
setOpen(!open);
|
|
||||||
}
|
|
||||||
|
|
||||||
function select(e, id) {
|
|
||||||
e.stopPropagation();
|
|
||||||
setOpen(false);
|
|
||||||
setValue(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function make(ids) {
|
|
||||||
return ids.map(id => (
|
|
||||||
<li
|
|
||||||
key={id}
|
|
||||||
className={id === value ? 'selected' : undefined}
|
|
||||||
onClick={e => select(e, id)}
|
|
||||||
>
|
|
||||||
{id}
|
|
||||||
</li>
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let items;
|
|
||||||
if (Array.isArray(ids)) {
|
|
||||||
items = make(ids);
|
|
||||||
} else {
|
|
||||||
items = Object.entries(ids).map(([title, values]) =>
|
|
||||||
[
|
|
||||||
<li className="title" key={title} onClick={e => e.stopPropagation()}>
|
|
||||||
[{title}]
|
|
||||||
</li>,
|
|
||||||
]
|
|
||||||
.concat(make(values))
|
|
||||||
.flat()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button onClick={toggleOpen}>
|
|
||||||
[<span>{label}: </span>
|
|
||||||
{value}]{open && <Menu>{items}</Menu>}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
28
src/hooks/usePreference.ts
Normal file
28
src/hooks/usePreference.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { get as lsGet, remove as lsRemove, set as lsSet } from 'local-storage';
|
||||||
|
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
// hook used to load "preference" settings from local storage, or fall back to a default value.
|
||||||
|
export default function usePreference<T>(
|
||||||
|
id: string,
|
||||||
|
defaultValue: T,
|
||||||
|
valid: (value: T) => boolean
|
||||||
|
): [T, Dispatch<SetStateAction<T>>, (value: T) => boolean] {
|
||||||
|
const [value, setValue] = useState<T>(() => {
|
||||||
|
const pref = lsGet(id) as T;
|
||||||
|
if (pref && valid(pref)) {
|
||||||
|
return pref;
|
||||||
|
} else {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (value === defaultValue) {
|
||||||
|
lsRemove(id);
|
||||||
|
} else {
|
||||||
|
lsSet(id, value);
|
||||||
|
}
|
||||||
|
}, [value, id, defaultValue]);
|
||||||
|
|
||||||
|
return [value, setValue, valid];
|
||||||
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import './style/base.css';
|
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import './style/base.css';
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
const root = ReactDOM.createRoot(
|
||||||
|
document.getElementById('root') as HTMLElement
|
||||||
|
);
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<App />
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1,4 +1,43 @@
|
|||||||
const themes = {
|
import type { editor } from 'monaco-editor';
|
||||||
|
|
||||||
|
export interface Theme {
|
||||||
|
id: string;
|
||||||
|
primary: string;
|
||||||
|
secondary: string;
|
||||||
|
highlight: string;
|
||||||
|
lightOrDark: string;
|
||||||
|
|
||||||
|
editor: {
|
||||||
|
background: string;
|
||||||
|
lineNumber: string;
|
||||||
|
lineNumberHl: string;
|
||||||
|
lineNumberHlBackground: string;
|
||||||
|
primary: string;
|
||||||
|
selection: string;
|
||||||
|
comment: string;
|
||||||
|
commentTag: string;
|
||||||
|
punctuation: string;
|
||||||
|
annotation: string;
|
||||||
|
namespace: string;
|
||||||
|
property: string;
|
||||||
|
constant: string;
|
||||||
|
number: string;
|
||||||
|
selector: string;
|
||||||
|
operator: string;
|
||||||
|
keyword: string;
|
||||||
|
function: string;
|
||||||
|
className: string;
|
||||||
|
variable: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Themes {
|
||||||
|
light: Theme;
|
||||||
|
blue: Theme;
|
||||||
|
dark: Theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
const themes: Themes = {
|
||||||
light: {
|
light: {
|
||||||
id: 'light',
|
id: 'light',
|
||||||
primary: '#aaddff',
|
primary: '#aaddff',
|
||||||
@@ -93,7 +132,7 @@ const themes = {
|
|||||||
|
|
||||||
export default themes;
|
export default themes;
|
||||||
|
|
||||||
export function makeMonacoTheme(theme) {
|
export function makeMonacoTheme(theme: Theme): editor.IStandaloneThemeData {
|
||||||
return {
|
return {
|
||||||
base: theme.lightOrDark === 'light' ? 'vs' : 'vs-dark',
|
base: theme.lightOrDark === 'light' ? 'vs' : 'vs-dark',
|
||||||
inherit: true,
|
inherit: true,
|
||||||
83
src/util/storage.ts
Normal file
83
src/util/storage.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { gzip } from 'pako';
|
||||||
|
import MIMEType from 'whatwg-mimetype';
|
||||||
|
import { bytebinUrl, postUrl } from './constants';
|
||||||
|
import { languageIds } from './highlighting';
|
||||||
|
|
||||||
|
interface LoadResultSuccess {
|
||||||
|
ok: true;
|
||||||
|
content: string;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoadResultFail {
|
||||||
|
ok: false;
|
||||||
|
content?: never;
|
||||||
|
type?: never;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LoadResult = LoadResultSuccess | LoadResultFail;
|
||||||
|
|
||||||
|
export async function loadFromBytebin(id: string): Promise<LoadResult> {
|
||||||
|
try {
|
||||||
|
const resp = await fetch(bytebinUrl + id);
|
||||||
|
if (resp.ok) {
|
||||||
|
const content = await resp.text();
|
||||||
|
const type = contentTypeToLanguage(
|
||||||
|
resp.headers.get('content-type') as string
|
||||||
|
);
|
||||||
|
|
||||||
|
document.title = 'pastes | ' + id;
|
||||||
|
return { ok: true, content, type };
|
||||||
|
} else {
|
||||||
|
return { ok: false };
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveToBytebin(
|
||||||
|
code: string,
|
||||||
|
language: string
|
||||||
|
): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const compressed = gzip(code);
|
||||||
|
const contentType = languageToContentType(language);
|
||||||
|
|
||||||
|
const resp = await fetch(postUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': contentType,
|
||||||
|
'Content-Encoding': 'gzip',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
body: compressed,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resp.ok) {
|
||||||
|
const json = await resp.json();
|
||||||
|
return json.key;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function contentTypeToLanguage(contentType: string) {
|
||||||
|
const { type, subtype: subType } = new MIMEType(contentType);
|
||||||
|
if (type === 'application' && subType === 'json') {
|
||||||
|
return 'json';
|
||||||
|
}
|
||||||
|
if (type === 'text' && languageIds.includes(subType.toLowerCase())) {
|
||||||
|
return subType.toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function languageToContentType(language: string) {
|
||||||
|
if (language === 'json') {
|
||||||
|
return 'application/json';
|
||||||
|
} else {
|
||||||
|
return 'text/' + language;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/*.ts",
|
||||||
|
"src/*.tsx"
|
||||||
|
]
|
||||||
|
}
|
||||||
100
yarn.lock
100
yarn.lock
@@ -1565,10 +1565,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
state-local "^1.0.6"
|
state-local "^1.0.6"
|
||||||
|
|
||||||
"@monaco-editor/react@^4.3.1":
|
"@monaco-editor/react@4.4.6":
|
||||||
version "4.4.5"
|
version "4.4.6"
|
||||||
resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.4.5.tgz#beabe491efeb2457441a00d1c7651c653697f65b"
|
resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.4.6.tgz#8ae500b0edf85276d860ed702e7056c316548218"
|
||||||
integrity sha512-IImtzU7sRc66OOaQVCG+5PFHkSWnnhrUWGBuH6zNmH2h0YgmAhcjHZQc/6MY9JWEbUtVF1WPBMJ9u1XuFbRrVA==
|
integrity sha512-Gr3uz3LYf33wlFE3eRnta4RxP5FSNxiIV9ENn2D2/rN8KgGAD8ecvcITRtsbbyuOuNkwbuHYxfeaz2Vr+CtyFA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@monaco-editor/loader" "^1.3.2"
|
"@monaco-editor/loader" "^1.3.2"
|
||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
@@ -1961,6 +1961,14 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/hoist-non-react-statics@*":
|
||||||
|
version "3.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
|
||||||
|
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
hoist-non-react-statics "^3.3.0"
|
||||||
|
|
||||||
"@types/html-minifier-terser@^6.0.0":
|
"@types/html-minifier-terser@^6.0.0":
|
||||||
version "6.1.0"
|
version "6.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
|
resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35"
|
||||||
@@ -2000,6 +2008,14 @@
|
|||||||
expect "^29.0.0"
|
expect "^29.0.0"
|
||||||
pretty-format "^29.0.0"
|
pretty-format "^29.0.0"
|
||||||
|
|
||||||
|
"@types/jest@^29.2.2":
|
||||||
|
version "29.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.2.2.tgz#874e7dc6702fa6a3fe6107792aa98636dcc480b4"
|
||||||
|
integrity sha512-og1wAmdxKoS71K2ZwSVqWPX6OVn3ihZ6ZT2qvZvZQm90lJVDyXIjYcu4Khx2CNIeaFv12rOU/YObOsI3VOkzog==
|
||||||
|
dependencies:
|
||||||
|
expect "^29.0.0"
|
||||||
|
pretty-format "^29.0.0"
|
||||||
|
|
||||||
"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
|
"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
|
||||||
version "7.0.11"
|
version "7.0.11"
|
||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
||||||
@@ -2020,6 +2036,16 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.14.tgz#0fe081752a3333392d00586d815485a17c2cf3c9"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.14.tgz#0fe081752a3333392d00586d815485a17c2cf3c9"
|
||||||
integrity sha512-6bbDaETVi8oyIARulOE9qF1/Qdi/23z6emrUh0fNJRUmjznqrixD4MpGDdgOFk5Xb0m2H6Xu42JGdvAxaJR/wA==
|
integrity sha512-6bbDaETVi8oyIARulOE9qF1/Qdi/23z6emrUh0fNJRUmjznqrixD4MpGDdgOFk5Xb0m2H6Xu42JGdvAxaJR/wA==
|
||||||
|
|
||||||
|
"@types/node@^18.11.9":
|
||||||
|
version "18.11.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4"
|
||||||
|
integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==
|
||||||
|
|
||||||
|
"@types/pako@^2.0.0":
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/pako/-/pako-2.0.0.tgz#12ab4c19107528452e73ac99132c875ccd43bdfb"
|
||||||
|
integrity sha512-10+iaz93qR5WYxTo+PMifD5TSxiOtdRaxBf7INGGXMQgTCu8Z/7GYWYFUOS3q/G0nE5boj1r4FEB+WSy7s5gbA==
|
||||||
|
|
||||||
"@types/parse-json@^4.0.0":
|
"@types/parse-json@^4.0.0":
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||||
@@ -2057,6 +2083,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
|
"@types/react-dom@^18.0.8":
|
||||||
|
version "18.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.8.tgz#d2606d855186cd42cc1b11e63a71c39525441685"
|
||||||
|
integrity sha512-C3GYO0HLaOkk9dDAz3Dl4sbe4AKUGTCfFIZsz3n/82dPNN8Du533HzKatDxeUYWu24wJgMP1xICqkWk1YOLOIw==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react@*":
|
"@types/react@*":
|
||||||
version "18.0.18"
|
version "18.0.18"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.18.tgz#9f16f33d57bc5d9dca848d12c3572110ff9429ac"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.18.tgz#9f16f33d57bc5d9dca848d12c3572110ff9429ac"
|
||||||
@@ -2066,6 +2099,15 @@
|
|||||||
"@types/scheduler" "*"
|
"@types/scheduler" "*"
|
||||||
csstype "^3.0.2"
|
csstype "^3.0.2"
|
||||||
|
|
||||||
|
"@types/react@^18.0.25":
|
||||||
|
version "18.0.25"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.25.tgz#8b1dcd7e56fe7315535a4af25435e0bb55c8ae44"
|
||||||
|
integrity sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g==
|
||||||
|
dependencies:
|
||||||
|
"@types/prop-types" "*"
|
||||||
|
"@types/scheduler" "*"
|
||||||
|
csstype "^3.0.2"
|
||||||
|
|
||||||
"@types/resolve@1.17.1":
|
"@types/resolve@1.17.1":
|
||||||
version "1.17.1"
|
version "1.17.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
|
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
|
||||||
@@ -2110,6 +2152,15 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
|
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
|
||||||
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
|
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
|
||||||
|
|
||||||
|
"@types/styled-components@^5.1.26":
|
||||||
|
version "5.1.26"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.26.tgz#5627e6812ee96d755028a98dae61d28e57c233af"
|
||||||
|
integrity sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==
|
||||||
|
dependencies:
|
||||||
|
"@types/hoist-non-react-statics" "*"
|
||||||
|
"@types/react" "*"
|
||||||
|
csstype "^3.0.2"
|
||||||
|
|
||||||
"@types/testing-library__jest-dom@^5.9.1":
|
"@types/testing-library__jest-dom@^5.9.1":
|
||||||
version "5.14.5"
|
version "5.14.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz#d113709c90b3c75fdb127ec338dad7d5f86c974f"
|
resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz#d113709c90b3c75fdb127ec338dad7d5f86c974f"
|
||||||
@@ -2122,6 +2173,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
|
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
|
||||||
integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==
|
integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==
|
||||||
|
|
||||||
|
"@types/whatwg-mimetype@^3.0.0":
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#4ff45f787a085e7d22b7e01f3724f28a97d84c67"
|
||||||
|
integrity sha512-xHFOhd41VpUR6Y0k8ZinlyFv5cyhC/r2zghJgWWN8oNxqNo45Nf0qCBInJsFeifLeoHcIF4voEfap4A2GYHWkw==
|
||||||
|
|
||||||
"@types/ws@^8.5.1":
|
"@types/ws@^8.5.1":
|
||||||
version "8.5.3"
|
version "8.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d"
|
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d"
|
||||||
@@ -3234,11 +3290,6 @@ content-disposition@0.5.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "5.2.1"
|
safe-buffer "5.2.1"
|
||||||
|
|
||||||
content-type-parser@^1.0.2:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7"
|
|
||||||
integrity sha512-lM4l4CnMEwOLHAHr/P6MEZwZFPJFtAAKgL6pogbXmVZggIqXhdB6RbBtPOTsw2FcXwYhehRGERJmRrjOiIB8pQ==
|
|
||||||
|
|
||||||
content-type@~1.0.4:
|
content-type@~1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
||||||
@@ -4764,7 +4815,7 @@ history@^5.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.7.6"
|
"@babel/runtime" "^7.7.6"
|
||||||
|
|
||||||
hoist-non-react-statics@^3.0.0:
|
hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0:
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||||
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
|
||||||
@@ -6213,6 +6264,11 @@ mkdirp@~0.5.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
minimist "^1.2.6"
|
minimist "^1.2.6"
|
||||||
|
|
||||||
|
monaco-editor@^0.34.1:
|
||||||
|
version "0.34.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.34.1.tgz#1b75c4ad6bc4c1f9da656d740d98e0b850a22f87"
|
||||||
|
integrity sha512-FKc80TyiMaruhJKKPz5SpJPIjL+dflGvz4CpuThaPMc94AyN7SeC9HQ8hrvaxX7EyHdJcUY5i4D0gNyJj1vSZQ==
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
@@ -7196,6 +7252,16 @@ prelude-ls@~1.1.2:
|
|||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||||
integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==
|
integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==
|
||||||
|
|
||||||
|
prettier-plugin-organize-imports@^3.1.1:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.1.1.tgz#e581cb6fa528cf72f7d95b807ce38c3e51c3c27c"
|
||||||
|
integrity sha512-6bHIQzybqA644h0WGUW3gpWEVbMBvzui5wCMRBi7qA++d5ov2xjjfDk8pxJJ/ardfZrGAwizKMq/fQMFdJ+0Zw==
|
||||||
|
|
||||||
|
prettier@^2.7.1:
|
||||||
|
version "2.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64"
|
||||||
|
integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==
|
||||||
|
|
||||||
pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
|
pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
|
||||||
version "5.6.0"
|
version "5.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
|
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
|
||||||
@@ -7389,7 +7455,7 @@ react-device-detect@^2.1.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ua-parser-js "^1.0.2"
|
ua-parser-js "^1.0.2"
|
||||||
|
|
||||||
react-dom@^18.1.0:
|
react-dom@^18.2.0:
|
||||||
version "18.2.0"
|
version "18.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
||||||
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
|
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
|
||||||
@@ -7477,7 +7543,7 @@ react-scripts@5.0.1:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "^2.3.2"
|
fsevents "^2.3.2"
|
||||||
|
|
||||||
react@^18.1.0:
|
react@^18.2.0:
|
||||||
version "18.2.0"
|
version "18.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
|
||||||
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
|
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
|
||||||
@@ -8547,6 +8613,11 @@ typedarray-to-buffer@^3.1.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-typedarray "^1.0.0"
|
is-typedarray "^1.0.0"
|
||||||
|
|
||||||
|
typescript@^4.8.4:
|
||||||
|
version "4.8.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
|
||||||
|
integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==
|
||||||
|
|
||||||
ua-parser-js@^1.0.2:
|
ua-parser-js@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.2.tgz#e2976c34dbfb30b15d2c300b2a53eac87c57a775"
|
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.2.tgz#e2976c34dbfb30b15d2c300b2a53eac87c57a775"
|
||||||
@@ -8871,6 +8942,11 @@ whatwg-mimetype@^2.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
|
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
|
||||||
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
|
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
|
||||||
|
|
||||||
|
whatwg-mimetype@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7"
|
||||||
|
integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==
|
||||||
|
|
||||||
whatwg-url@^7.0.0:
|
whatwg-url@^7.0.0:
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
|
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
|
||||||
|
|||||||
Reference in New Issue
Block a user