diff --git a/app/hooks/use-debounce.ts b/app/hooks/use-debounce.ts
new file mode 100644
index 0000000..56b0b97
--- /dev/null
+++ b/app/hooks/use-debounce.ts
@@ -0,0 +1,20 @@
+import { debounce } from 'lodash-es';
+import { useEffect, useMemo, useRef } from 'react';
+
+export const useDebounce = (callback: () => void) => {
+ const ref = useRef<() => void>();
+
+ useEffect(() => {
+ ref.current = callback;
+ }, [callback]);
+
+ const debouncedCallback = useMemo(() => {
+ const func = () => {
+ ref.current?.();
+ };
+
+ return debounce(func, 500);
+ }, []);
+
+ return debouncedCallback;
+};
diff --git a/components/editor/editor-pane.tsx b/components/editor/editor-pane.tsx
index 4ddf60b..50ab543 100644
--- a/components/editor/editor-pane.tsx
+++ b/components/editor/editor-pane.tsx
@@ -1,6 +1,10 @@
+import { useState } from "react"
import dynamic from "next/dynamic"
-import { SchemaState } from "@/store/main"
+import { SchemaState, useMainStore } from "@/store/main"
+import { parse, serialize } from "@/lib/json"
+
+import { ImportDialog } from "./import-dialog"
import { EditorMenu } from "./menu"
export interface EditorPane {
@@ -24,15 +28,25 @@ export const EditorPane = ({
setValueString,
...props
}: EditorPane) => {
+ const [openImportDialog, setOpenImportDialog] = useState(false)
+ const editorMode = useMainStore(
+ (state) => state.editors[editorKey].mode ?? state.userSettings.mode
+ )
return (
<>
-
{heading}
+ {heading}
setOpenImportDialog(true)}
+ onFormat={() => {
+ setValueString(
+ serialize(editorMode, parse(editorMode, value ?? ""))
+ )
+ }}
/>
+
>
)
}
diff --git a/components/editor/import-dialog.tsx b/components/editor/import-dialog.tsx
new file mode 100644
index 0000000..81e9a00
--- /dev/null
+++ b/components/editor/import-dialog.tsx
@@ -0,0 +1,146 @@
+import { useState } from 'react'
+import { Button } from "../ui/button"
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogPortal,
+} from "../ui/dialog"
+import { Input } from "../ui/input"
+import { Label } from "../ui/label"
+import { Separator } from "../ui/separator"
+import { JSONModes } from '@/types/editor'
+import json5 from 'json5'
+import { load as parseYaml } from "js-yaml"
+import { Check } from 'lucide-react'
+import { serialize } from '@/lib/json'
+import { SchemaState, useMainStore } from '@/store/main'
+import { useDebounce } from '@/app/hooks/use-debounce'
+import { isValidUrl } from '@/lib/utils'
+
+export interface ImportDialogProps {
+ open: boolean
+ onOpenChange?: (open: boolean) => void
+ heading: string
+ editorKey: keyof SchemaState["editors"]
+ setValueString: (val: string) => void
+}
+export const ImportDialog = ({ open, onOpenChange, heading, editorKey, setValueString }: ImportDialogProps) => {
+ const [imported, setImported] = useState(undefined)
+ const [importUrl, setImportUrl] = useState('');
+
+ const editorMode = useMainStore(
+ (state) =>
+ state.editors[editorKey].mode ??
+ state.userSettings.mode
+ )
+
+ const debouncedImportUrlRequest = useDebounce(() => {
+ console.log('fetch import url', importUrl);
+ if (isValidUrl(importUrl)) {
+ fetch(importUrl)
+ .then((res) => res.text())
+ .then((text) => {
+ if (importUrl.includes(JSONModes.JSON5)) {
+ setImported(json5.parse(text))
+ } else if (importUrl.includes("json")) {
+ setImported(JSON.parse(text))
+ }
+
+ if (importUrl.includes("yaml")) {
+ setImported(parseYaml(text))
+ }
+ })
+ .catch((err) => {
+ console.error(err)
+ })
+ }
+ });
+
+ return (
+
+ )
+}
diff --git a/components/editor/json-editor.tsx b/components/editor/json-editor.tsx
index 2019882..d714a51 100644
--- a/components/editor/json-editor.tsx
+++ b/components/editor/json-editor.tsx
@@ -21,7 +21,8 @@ import { JSONModes } from "@/types/editor"
import { serialize } from "@/lib/json"
// import { debounce } from "@/lib/utils"
-import { jsonDark, jsonDarkTheme } from "./theme"
+import { jsonDark, jsonDarkTheme, jsonLight, jsonLightTheme, lightHighlightStyle } from "./theme"
+import { useTheme } from 'next-themes'
/**
* none of these are required for json4 or 5
@@ -30,10 +31,8 @@ import { jsonDark, jsonDarkTheme } from "./theme"
const commonExtensions = [
history(),
autocompletion(),
- jsonDark,
EditorView.lineWrapping,
EditorState.tabSize.of(2),
- syntaxHighlighting(oneDarkHighlightStyle),
]
const languageExtensions = {
@@ -59,7 +58,10 @@ export const JSONEditor = ({
state.editors[editorKey as keyof SchemaState["editors"]].mode ??
state.userSettings.mode
)
+ const {theme} = useTheme();
const languageExtension = languageExtensions[editorMode](schema)
+ const themeExtensions = theme === 'light' ? jsonLight : jsonDark;
+
const editorRef = useRef(null)
useEffect(() => {
@@ -73,9 +75,9 @@ export const JSONEditor = ({
return (
void
menuPrefix?: React.ReactNode
menuSuffix?: React.ReactNode
+ onOpenImportDialog?: () => void
+ onFormat?: () => void
}
export const EditorMenu = ({
@@ -46,9 +50,9 @@ export const EditorMenu = ({
menuPrefix,
menuSuffix,
value,
+ onOpenImportDialog,
+ onFormat,
}: EditorMenu) => {
- const [imported, setImported] = useState(undefined)
-
const setEditorSetting = useMainStore((state) => state.setEditorSetting)
const editorMode = useMainStore(
@@ -67,126 +71,42 @@ export const EditorMenu = ({
-
- {menuPrefix && menuPrefix}
-
- setEditorSetting(editorKey, "mode", val)}
- >
-
- JSON4
-
-
+
+ {menuPrefix && menuPrefix}
+
+ setEditorSetting(editorKey, "mode", val)}
>
- JSON5
-
-
-
-
-
- e.preventDefault()}>
-
-
-
- Export
-
-
-
- value && setValueString(value)}>
- Format
-
-
- {menuSuffix && menuSuffix}
-
+
+ JSON4
+
+
+ JSON5
+
+
+
+
+
+ Import
+ Export
+
+
+
+ onFormat?.()} className='cursor-pointer'>
+ Format
+
+
+ {menuSuffix && menuSuffix}
+
+
)
}
diff --git a/components/editor/theme.ts b/components/editor/theme.ts
index 40e90d6..4eae801 100644
--- a/components/editor/theme.ts
+++ b/components/editor/theme.ts
@@ -1,8 +1,8 @@
-import {EditorView} from "@codemirror/view"
-import {Extension} from "@codemirror/state"
-import {HighlightStyle, syntaxHighlighting} from "@codemirror/language"
-import {tags as t} from "@lezer/highlight"
-import { createTheme } from '@uiw/codemirror-themes';
+import { HighlightStyle, syntaxHighlighting } from "@codemirror/language"
+import { Extension } from "@codemirror/state"
+import { EditorView } from "@codemirror/view"
+import { tags as t } from "@lezer/highlight"
+import { createTheme } from "@uiw/codemirror-themes"
// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors
@@ -16,13 +16,13 @@ const chalky = "#e5c07b",
sage = "#98c379",
whiskey = "#d19a66",
violet = "#c678dd",
- darkBackground = "rgb(15 23 42 / 1)",
- highlightBackground = "rgb(30 41 59 / 1)", // 71 85 105
+ darkBackground = "rgb(15 23 42 / 1)",
+ highlightBackground = "rgb(30 41 59 / 1)", // 71 85 105
background = "rgb(15 23 42 / 1)",
tooltipBackground = "rgb(30 41 59 / 1)",
selection = "#3E4451",
cursor = "#528bff",
- borderRadius = '0px';
+ borderRadius = "0px"
// --tw-bg-opacity: 1;
// background-color: rgb(30 41 59 / var(--tw-bg-opacity));
@@ -44,169 +44,356 @@ export const color = {
background,
tooltipBackground,
selection,
- cursor
+ cursor,
}
/// The editor theme styles for One Dark.
-export const oneDarkTheme = EditorView.theme({
+export const oneDarkTheme = EditorView.theme(
+ {
+ "&": {
+ color: ivory,
+ backgroundColor: background,
+ borderRadius: borderRadius,
+ },
+
+ ".cm-content": {
+ caretColor: cursor,
+ },
+
+ ".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor },
+ "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
+ { backgroundColor: selection },
+
+ ".cm-scroller": { borderRadius: borderRadius },
+ ".cm-panels": { backgroundColor: darkBackground, color: ivory },
+ ".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
+ ".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
+
+ ".cm-searchMatch": {
+ backgroundColor: "#72a1ff59",
+ outline: "1px solid #457dff",
+ },
+ ".cm-searchMatch.cm-searchMatch-selected": {
+ backgroundColor: "#6199ff2f",
+ },
+
+ ".cm-activeLine": { backgroundColor: "#6699ff0b" },
+ ".cm-selectionMatch": { backgroundColor: "#aafe661a" },
+
+ "&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": {
+ backgroundColor: "#bad0f847",
+ },
+
+ ".cm-gutters": {
+ backgroundColor: background,
+ color: stone,
+ border: "none",
+ },
+
+ ".cm-activeLineGutter": {
+ backgroundColor: highlightBackground,
+ },
+
+ ".cm-foldPlaceholder": {
+ backgroundColor: "transparent",
+ border: "none",
+ color: "#ddd",
+ },
+
+ ".cm-tooltip": {
+ border: "none",
+ backgroundColor: tooltipBackground,
+ },
+ ".cm-tooltip .cm-tooltip-arrow:before": {
+ borderTopColor: "transparent",
+ borderBottomColor: "transparent",
+ },
+ ".cm-tooltip .cm-tooltip-arrow:after": {
+ borderTopColor: tooltipBackground,
+ borderBottomColor: tooltipBackground,
+ },
+ ".cm-tooltip-autocomplete": {
+ "& > ul > li[aria-selected]": {
+ backgroundColor: highlightBackground,
+ color: ivory,
+ },
+ },
+ },
+ { dark: true }
+)
+
+/// The highlighting style for code in the One Dark theme.
+export const oneDarkHighlightStyle = HighlightStyle.define([
+ { tag: t.keyword, color: violet },
+ {
+ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
+ color: coral,
+ },
+ { tag: [t.function(t.variableName), t.labelName], color: malibu },
+ { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey },
+ { tag: [t.definition(t.name), t.separator], color: ivory },
+ {
+ tag: [
+ t.typeName,
+ t.className,
+ t.number,
+ t.changed,
+ t.annotation,
+ t.modifier,
+ t.self,
+ t.namespace,
+ ],
+ color: chalky,
+ },
+ {
+ tag: [
+ t.operator,
+ t.operatorKeyword,
+ t.url,
+ t.escape,
+ t.regexp,
+ t.link,
+ t.special(t.string),
+ ],
+ color: cyan,
+ },
+ { tag: [t.meta, t.comment], color: stone },
+ { tag: t.strong, fontWeight: "bold" },
+ { tag: t.emphasis, fontStyle: "italic" },
+ { tag: t.strikethrough, textDecoration: "line-through" },
+ { tag: t.link, color: stone, textDecoration: "underline" },
+ { tag: t.heading, fontWeight: "bold", color: coral },
+ { tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey },
+ { tag: [t.processingInstruction, t.string, t.inserted], color: sage },
+ { tag: t.invalid, color: invalid },
+])
+
+/// Extension to enable the One Dark theme (both the editor theme and
+/// the highlight style).
+export const jsonDark: Extension = [
+ oneDarkTheme,
+ syntaxHighlighting(oneDarkHighlightStyle),
+]
+
+export const jsonDarkTheme = createTheme({
+ theme: "dark",
+ settings: {
+ background,
+ backgroundImage: "",
+ foreground: ivory,
+ caret: cursor,
+ selection: selection,
+ selectionMatch: selection,
+ lineHighlight: "#6699ff0b",
+ gutterBackground: background,
+ gutterForeground: stone,
+ },
+ styles: [
+ { tag: t.keyword, color: violet },
+ {
+ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
+ color: coral,
+ },
+ { tag: [t.function(t.variableName), t.labelName], color: malibu },
+ { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey },
+ { tag: [t.definition(t.name), t.separator], color: ivory },
+ {
+ tag: [
+ t.typeName,
+ t.className,
+ t.number,
+ t.changed,
+ t.annotation,
+ t.modifier,
+ t.self,
+ t.namespace,
+ ],
+ color: chalky,
+ },
+ {
+ tag: [
+ t.operator,
+ t.operatorKeyword,
+ t.url,
+ t.escape,
+ t.regexp,
+ t.link,
+ t.special(t.string),
+ ],
+ color: cyan,
+ },
+ { tag: [t.meta, t.comment], color: stone },
+ { tag: t.strong, fontWeight: "bold" },
+ { tag: t.emphasis, fontStyle: "italic" },
+ { tag: t.strikethrough, textDecoration: "line-through" },
+ { tag: t.link, color: stone, textDecoration: "underline" },
+ { tag: t.heading, fontWeight: "bold", color: coral },
+ { tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey },
+ { tag: [t.processingInstruction, t.string, t.inserted], color: sage },
+ { tag: t.invalid, color: invalid },
+ ],
+})
+
+const lightColors = {
+ chalky: "#e5c07b",
+ coral: "#e06c75",
+ cyan: "#56b6c2",
+ invalid: "#ffffff",
+ ivory: "#abb2bf",
+ stone: "#7d8799", // Brightened compared to original to increase contrast
+ malibu: "#61afef",
+ sage: "#98c379",
+ whiskey: "#d19a66",
+ violet: "#c678dd",
+ // darkBackground: "rgb(15 23 42 / 1)",
+ highlightBackground: "rgb(30 41 59 / 1)", // 71 85 105
+ // background: "rgb(15 23 42 / 1)",
+ tooltipBackground: "#f0f0f0",
+ // selection: "#3E4451",
+ cursor: "#528bff",
+ borderRadius: "0px",
+ background: "#fff",
+ foreground: "#24292e",
+ selection: "#BBDFFF",
+ selectionMatch: "#BBDFFF",
+ gutterBackground: "#fff",
+ gutterForeground: "#6e7781",
+}
+/// The editor theme styles for One Dark.
+export const lightTheme = EditorView.theme({
"&": {
- color: ivory,
- backgroundColor: background,
- borderRadius: borderRadius,
+ color: lightColors.foreground,
+ backgroundColor: lightColors.background,
+ borderRadius: lightColors.borderRadius,
},
".cm-content": {
- caretColor: cursor
+ caretColor: lightColors.cursor,
},
- ".cm-cursor, .cm-dropCursor": {borderLeftColor: cursor},
- "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {backgroundColor: selection},
+ ".cm-cursor, .cm-dropCursor": { borderLeftColor: lightColors.cursor },
+ "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
+ { backgroundColor: lightColors.selection },
- '.cm-scroller': {borderRadius: borderRadius},
- ".cm-panels": {backgroundColor: darkBackground, color: ivory},
- ".cm-panels.cm-panels-top": {borderBottom: "2px solid black"},
- ".cm-panels.cm-panels-bottom": {borderTop: "2px solid black"},
+ ".cm-scroller": { borderRadius: lightColors.borderRadius },
+ ".cm-panels": {
+ backgroundColor: lightColors.background,
+ color: lightColors.foreground,
+ },
+ ".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
+ ".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
".cm-searchMatch": {
backgroundColor: "#72a1ff59",
- outline: "1px solid #457dff"
+ outline: "1px solid #457dff",
},
".cm-searchMatch.cm-searchMatch-selected": {
- backgroundColor: "#6199ff2f"
+ backgroundColor: "#6199ff2f",
},
- ".cm-activeLine": {backgroundColor: "#6699ff0b"},
- ".cm-selectionMatch": {backgroundColor: "#aafe661a"},
+ ".cm-activeLine": { backgroundColor: "#6699ff0b" },
+ ".cm-selectionMatch": { backgroundColor: lightColors.selectionMatch },
"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": {
- backgroundColor: "#bad0f847"
+ backgroundColor: "#bad0f847",
},
".cm-gutters": {
- backgroundColor: background,
- color: stone,
- border: "none"
+ backgroundColor: lightColors.gutterBackground,
+ color: lightColors.gutterForeground,
+ border: "none",
},
".cm-activeLineGutter": {
- backgroundColor: highlightBackground
+ backgroundColor: '#f0f0f0',
},
".cm-foldPlaceholder": {
backgroundColor: "transparent",
border: "none",
- color: "#ddd"
+ color: "#ddd",
},
".cm-tooltip": {
border: "none",
- backgroundColor: tooltipBackground
+ backgroundColor: lightColors.tooltipBackground,
},
".cm-tooltip .cm-tooltip-arrow:before": {
borderTopColor: "transparent",
- borderBottomColor: "transparent"
+ borderBottomColor: "transparent",
},
".cm-tooltip .cm-tooltip-arrow:after": {
- borderTopColor: tooltipBackground,
- borderBottomColor: tooltipBackground
+ borderTopColor: lightColors.tooltipBackground,
+ borderBottomColor: lightColors.tooltipBackground,
},
".cm-tooltip-autocomplete": {
"& > ul > li[aria-selected]": {
- backgroundColor: highlightBackground,
- color: ivory
- }
- }
-}, {dark: true})
+ backgroundColor: lightColors.highlightBackground,
+ color: lightColors.foreground,
+ },
+ },
+})
/// The highlighting style for code in the One Dark theme.
-export const oneDarkHighlightStyle = HighlightStyle.define([
- {tag: t.keyword,
- color: violet},
- {tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
- color: coral},
- {tag: [t.function(t.variableName), t.labelName],
- color: malibu},
- {tag: [t.color, t.constant(t.name), t.standard(t.name)],
- color: whiskey},
- {tag: [t.definition(t.name), t.separator],
- color: ivory},
- {tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
- color: chalky},
- {tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)],
- color: cyan},
- {tag: [t.meta, t.comment],
- color: stone},
- {tag: t.strong,
- fontWeight: "bold"},
- {tag: t.emphasis,
- fontStyle: "italic"},
- {tag: t.strikethrough,
- textDecoration: "line-through"},
- {tag: t.link,
- color: stone,
- textDecoration: "underline"},
- {tag: t.heading,
- fontWeight: "bold",
- color: coral},
- {tag: [t.atom, t.bool, t.special(t.variableName)],
- color: whiskey },
- {tag: [t.processingInstruction, t.string, t.inserted],
- color: sage},
- {tag: t.invalid,
- color: invalid},
+export const lightHighlightStyle = HighlightStyle.define([
+ { tag: [t.standard(t.tagName), t.tagName], color: "#116329" },
+ { tag: [t.comment, t.bracket], color: "#6a737d" },
+ { tag: [t.className, t.propertyName], color: "#6f42c1" },
+ {
+ tag: [t.variableName, t.attributeName, t.number, t.operator],
+ color: "#005cc5",
+ },
+ {
+ tag: [t.keyword, t.typeName, t.typeOperator, t.typeName],
+ color: "#d73a49",
+ },
+ { tag: [t.string, t.meta, t.regexp], color: "#032f62" },
+ { tag: [t.name, t.quote], color: "#22863a" },
+ { tag: [t.heading, t.strong], color: "#24292e", fontWeight: "bold" },
+ { tag: [t.emphasis], color: "#24292e", fontStyle: "italic" },
+ { tag: [t.deleted], color: "#b31d28", backgroundColor: "ffeef0" },
+ { tag: [t.atom, t.bool, t.special(t.variableName)], color: "#e36209" },
+ { tag: [t.url, t.escape, t.regexp, t.link], color: "#032f62" },
+ { tag: t.link, textDecoration: "underline" },
+ { tag: t.strikethrough, textDecoration: "line-through" },
+ { tag: t.invalid, color: "#cb2431" },
])
+export const jsonLight: Extension = [
+ lightTheme,
+ syntaxHighlighting(lightHighlightStyle),
+]
-/// Extension to enable the One Dark theme (both the editor theme and
-/// the highlight style).
-export const jsonDark: Extension = [oneDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)]
-
-export const jsonDarkTheme = createTheme({
- theme: 'dark',
+export const jsonLightTheme = createTheme({
+ theme: "light",
settings: {
- background,
- backgroundImage: '',
- foreground: ivory,
- caret: cursor,
- selection: selection,
- selectionMatch: selection,
- lineHighlight: '#6699ff0b',
- gutterBackground: background,
- gutterForeground: stone,
+ background: "#fff",
+ foreground: "#24292e",
+ selection: "#BBDFFF",
+ selectionMatch: "#BBDFFF",
+ gutterBackground: "#fff",
+ gutterForeground: "#6e7781",
},
styles: [
- {tag: t.keyword,
- color: violet},
- {tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
- color: coral},
- {tag: [t.function(t.variableName), t.labelName],
- color: malibu},
- {tag: [t.color, t.constant(t.name), t.standard(t.name)],
- color: whiskey},
- {tag: [t.definition(t.name), t.separator],
- color: ivory},
- {tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
- color: chalky},
- {tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)],
- color: cyan},
- {tag: [t.meta, t.comment],
- color: stone},
- {tag: t.strong,
- fontWeight: "bold"},
- {tag: t.emphasis,
- fontStyle: "italic"},
- {tag: t.strikethrough,
- textDecoration: "line-through"},
- {tag: t.link,
- color: stone,
- textDecoration: "underline"},
- {tag: t.heading,
- fontWeight: "bold",
- color: coral},
- {tag: [t.atom, t.bool, t.special(t.variableName)],
- color: whiskey },
- {tag: [t.processingInstruction, t.string, t.inserted],
- color: sage},
- {tag: t.invalid,
- color: invalid},
+ { tag: [t.standard(t.tagName), t.tagName], color: "#116329" },
+ { tag: [t.comment, t.bracket], color: "#6a737d" },
+ { tag: [t.className, t.propertyName], color: "#6f42c1" },
+ {
+ tag: [t.variableName, t.attributeName, t.number, t.operator],
+ color: "#005cc5",
+ },
+ {
+ tag: [t.keyword, t.typeName, t.typeOperator, t.typeName],
+ color: "#d73a49",
+ },
+ { tag: [t.string, t.meta, t.regexp], color: "#032f62" },
+ { tag: [t.name, t.quote], color: "#22863a" },
+ { tag: [t.heading, t.strong], color: "#24292e", fontWeight: "bold" },
+ { tag: [t.emphasis], color: "#24292e", fontStyle: "italic" },
+ { tag: [t.deleted], color: "#b31d28", backgroundColor: "ffeef0" },
+ { tag: [t.atom, t.bool, t.special(t.variableName)], color: "#e36209" },
+ { tag: [t.url, t.escape, t.regexp, t.link], color: "#032f62" },
+ { tag: t.link, textDecoration: "underline" },
+ { tag: t.strikethrough, textDecoration: "line-through" },
+ { tag: t.invalid, color: "#cb2431" },
],
-});
+})
diff --git a/lib/utils.ts b/lib/utils.ts
index ad4fdcf..c98c9c8 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -27,3 +27,12 @@ export function debounce(
return [debouncedFunc, teardown];
}
+
+export const isValidUrl = (url: string) => {
+ try {
+ new URL(url);
+ return true;
+ } catch (e) {
+ return false;
+ }
+}