feat: codemirror theme

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2023-04-11 22:35:07 +08:00
parent 2df738dd24
commit 8d63bf8a87
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
13 changed files with 638 additions and 553 deletions

View File

@ -29,6 +29,7 @@
"@codemirror/language": "6.6.0",
"@codemirror/state": "6.2.0",
"@codemirror/view": "6.9.4",
"@codemirror/theme-one-dark": "6.1.1",
"@crossbell/connect-kit": "0.0.63",
"@crossbell/contract": "0.0.63",
"@crossbell/indexer": "0.0.63",
@ -36,6 +37,7 @@
"@crossbell/ui": "0.0.63",
"@crossbell/util-hooks": "0.0.63",
"@dqbd/tiktoken": "^1.0.4",
"@ddietr/codemirror-themes": "1.4.0",
"@emoji-mart/data": "1.1.2",
"@emoji-mart/react": "1.1.1",
"@floating-ui/react": "^0.23.0",
@ -116,7 +118,8 @@
"unist-util-visit": "4.1.2",
"use-onclickoutside": "0.4.1",
"uuid": "9.0.0",
"wagmi": "0.12.10"
"wagmi": "0.12.10",
"zustand": "4.3.7"
},
"devDependencies": {
"@next/bundle-analyzer": "13.3.0",

File diff suppressed because it is too large Load Diff

View File

@ -25,13 +25,13 @@ ${normalCSS}
}
@media (prefers-color-scheme: dark) {
html:not(.dark):not(.light) {
html:not(.light) {
${darkCSS}
}
}
@media (prefers-color-scheme: light) {
html:not(.dark):not(.light) {
html:not(.dark) {
${normalCSS}
}
}

View File

@ -74,10 +74,10 @@ const transformColorObjectOfDarkmode = () => {
black: "var(--tw-colors-i-black)",
white: "var(--tw-colors-i-white)",
})
cssVar["--tw-colors-i-black"] = "0, 0, 0"
cssVar["--tw-colors-i-white"] = "255, 255, 255"
cssVarDark["--tw-colors-i-black"] = "255, 255, 255"
cssVarDark["--tw-colors-i-white"] = "0, 0, 0"
cssVar["--tw-colors-i-black"] = "#000"
cssVar["--tw-colors-i-white"] = "#fff"
cssVarDark["--tw-colors-i-black"] = "#fff"
cssVarDark["--tw-colors-i-white"] = "#000"
return [nextColorObject, cssVar, cssVarDark]
}

View File

@ -1,20 +1,18 @@
import { useAccountState } from "@crossbell/connect-kit"
import { useRouter } from "next/router"
import React, { useEffect } from "react"
import { BlockchainInfo } from "~/components/common/BlockchainInfo"
import { useUserRole } from "~/hooks/useUserRole"
import { IS_PROD } from "~/lib/constants"
import { OUR_DOMAIN, SITE_URL } from "~/lib/env"
import { toGateway } from "~/lib/ipfs-parser"
import { getUserContentsUrl } from "~/lib/user-contents"
import { cn } from "~/lib/utils"
import { useCheckLike, useCheckMint, useGetPage } from "~/queries/page"
import { useGetSite, useGetSubscription } from "~/queries/site"
import { SEOHead } from "../common/SEOHead"
import { SiteFooter } from "./SiteFooter"
import { SiteHeader } from "./SiteHeader"
import { useRouter } from "next/router"
import { BlockchainInfo } from "~/components/common/BlockchainInfo"
import { useGetSite } from "~/queries/site"
import { useGetPage } from "~/queries/page"
import { OUR_DOMAIN, SITE_URL } from "~/lib/env"
import { IS_PROD } from "~/lib/constants"
import { toGateway } from "~/lib/ipfs-parser"
import { useUserRole } from "~/hooks/useUserRole"
import { useGetSubscription } from "~/queries/site"
import { cn } from "~/lib/utils"
import { useCheckLike, useCheckMint } from "~/queries/page"
import { useAccountState } from "@crossbell/connect-kit"
export type SiteLayoutProps = {
children: React.ReactNode

View File

@ -6,6 +6,11 @@ import type { EditorState } from "@codemirror/state"
import { useState, useEffect, useMemo } from "react"
import { useTranslation } from "next-i18next"
import { useMobileLayout } from "~/hooks/useMobileLayout"
import {
codemirrorReconfigureExtension,
useCodeMirrorAutoToggleTheme,
} from "~/hooks/useCodemirrorTheme"
import { useMediaStore } from "~/hooks/useDarkMode"
export const Editor: React.FC<{
value: string
@ -27,6 +32,9 @@ export const Editor: React.FC<{
const { t } = useTranslation("dashboard")
const [extensions, setExtensions] = useState<any>([])
const isMobileLayout = useMobileLayout()
const [editor, setCmEditor] = useState<EditorView | null>(null)
const isDark = useMediaStore((state) => state.isDark)
useCodeMirrorAutoToggleTheme(editor, isDark)
useEffect(() => {
setExtensions([
markdown(),
@ -46,6 +54,7 @@ export const Editor: React.FC<{
},
"&.cm-editor": {
height: "100%",
backgroundColor: "transparent",
},
}),
EditorView.domEventHandlers({
@ -82,6 +91,7 @@ export const Editor: React.FC<{
},
}),
EditorView.lineWrapping,
...codemirrorReconfigureExtension,
])
}, [onScroll, handleDropFile])
@ -93,7 +103,10 @@ export const Editor: React.FC<{
}`}
value={value}
extensions={extensions}
onCreateEditor={onCreateEditor}
onCreateEditor={(view, state) => {
setCmEditor(view)
onCreateEditor?.(view, state)
}}
basicSetup={{
lineNumbers: false,
foldGutter: false,

View File

@ -241,6 +241,8 @@
--tw-colors-i-rose-800: 159, 18, 57;
--tw-colors-i-rose-900: 136, 19, 55;
--tw-colors-i-rose-950: 76, 5, 25;
--tw-colors-i-black: #000;
--tw-colors-i-white: #fff;
}
html.dark {
@ -486,6 +488,8 @@ html.dark {
--tw-colors-i-rose-800: 254, 205, 211;
--tw-colors-i-rose-900: 255, 228, 230;
--tw-colors-i-rose-950: 255, 241, 242;
--tw-colors-i-black: #fff;
--tw-colors-i-white: #000;
}
html.light {
@ -731,10 +735,12 @@ html.light {
--tw-colors-i-rose-800: 159, 18, 57;
--tw-colors-i-rose-900: 136, 19, 55;
--tw-colors-i-rose-950: 76, 5, 25;
--tw-colors-i-black: #000;
--tw-colors-i-white: #fff;
}
@media (prefers-color-scheme: dark) {
html:not(.dark):not(.light) {
html:not(.light) {
--tw-colors-i-slate-50: 2, 6, 23;
--tw-colors-i-slate-100: 15, 23, 42;
--tw-colors-i-slate-200: 30, 41, 59;
@ -977,11 +983,13 @@ html.light {
--tw-colors-i-rose-800: 254, 205, 211;
--tw-colors-i-rose-900: 255, 228, 230;
--tw-colors-i-rose-950: 255, 241, 242;
--tw-colors-i-black: #fff;
--tw-colors-i-white: #000;
}
}
@media (prefers-color-scheme: light) {
html:not(.dark):not(.light) {
html:not(.dark) {
--tw-colors-i-slate-50: 248, 250, 252;
--tw-colors-i-slate-100: 241, 245, 249;
--tw-colors-i-slate-200: 226, 232, 240;
@ -1224,5 +1232,7 @@ html.light {
--tw-colors-i-rose-800: 159, 18, 57;
--tw-colors-i-rose-900: 136, 19, 55;
--tw-colors-i-rose-950: 76, 5, 25;
--tw-colors-i-black: #000;
--tw-colors-i-white: #fff;
}
}

View File

@ -17,7 +17,7 @@
}
body {
@apply break-words;
@apply break-words bg-white;
}
.container {

View File

@ -0,0 +1,33 @@
import type { Extension } from "@codemirror/state"
import { Compartment } from "@codemirror/state"
import { EditorView } from "@codemirror/view"
import { useEffect } from "react"
import { oneDark } from "@codemirror/theme-one-dark"
import { githubLight } from "@ddietr/codemirror-themes/theme/github-light"
const extensionMap = {
theme: new Compartment(),
}
export const codemirrorReconfigureExtension: Extension[] = [
extensionMap.theme.of([]),
]
export const useCodeMirrorAutoToggleTheme = (
view: EditorView | null,
isDark: boolean,
) => {
useEffect(() => {
if (!view) return
if (isDark) {
view.dispatch({
effects: [extensionMap.theme.reconfigure(oneDark)],
})
} else {
view.dispatch({
effects: [extensionMap.theme.reconfigure(githubLight)],
})
}
}, [view, isDark])
}

157
src/hooks/useDarkMode.ts Normal file
View File

@ -0,0 +1,157 @@
import { useEffect, useState } from "react"
import { create } from "zustand"
export const useMediaStore = create<{
isDark: boolean
setDark: (isDark: boolean) => void
}>((set) => {
return {
isDark: false,
setDark: (isDark: boolean) => set({ isDark }),
}
})
const isServerSide = () => typeof window === "undefined"
interface DarkModeConfig {
classNameDark?: string // A className to set "dark mode". Default = "dark-mode".
classNameLight?: string // A className to set "light mode". Default = "light-mode".
element?: HTMLElement | undefined | null // The element to apply the className. Default = `document.body`
storageKey?: string // Specify the `localStorage` key. Default = "darkMode". Sewt to `null` to disable persistent storage.
}
const useDarkMode = (
initialState: boolean | undefined,
options: DarkModeConfig,
) => {
const {
classNameDark = "dark",
classNameLight = "light",
storageKey,
element,
} = options
const [darkMode, setDarkMode] = useState(initialState)
useEffect(() => {
const presentedDarkMode = storageKey
? isServerSide()
? null
: localStorage.getItem(storageKey)
: null
if (presentedDarkMode !== null) {
if (presentedDarkMode === "true") {
setDarkMode(true)
} else if (presentedDarkMode === "false") {
setDarkMode(false)
}
} else if (typeof initialState === "undefined") {
setDarkMode(window.matchMedia("(prefers-color-scheme: dark)").matches)
}
}, [storageKey])
useEffect(() => {
const handler = (e: MediaQueryListEvent) => {
const storageValue = localStorage.getItem(storageKey || "darkMode")
if (storageValue === null) {
setDarkMode(e.matches)
}
}
const focusHandler = () => {
const storageValue = localStorage.getItem(storageKey || "darkMode")
if (storageValue === null) {
setDarkMode(window.matchMedia("(prefers-color-scheme: dark)").matches)
}
}
window.addEventListener("focus", focusHandler)
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", handler)
return () => {
window.removeEventListener("focus", focusHandler)
window
.matchMedia("(prefers-color-scheme: dark)")
.removeEventListener("change", handler)
}
}, [storageKey])
useEffect(() => {
if (isServerSide()) {
return
}
const $el = element || document.documentElement
if (darkMode) {
$el.classList.remove(classNameLight)
$el.classList.add(classNameDark)
} else {
$el.classList.remove(classNameDark)
$el.classList.add(classNameLight)
}
}, [classNameDark, classNameLight, darkMode, element])
if (isServerSide()) {
return {
toggle: () => {},
value: false,
}
}
return {
value: darkMode,
toggle: () => {
setDarkMode((d) => {
if (storageKey && !isServerSide()) {
localStorage.setItem(storageKey, String(!d))
}
return !d
})
},
}
}
const noop = () => {}
const mockElement = {
classList: {
add: noop,
remove: noop,
},
}
const darkModeKey = "darkMode"
export const useMediaToggle = () => {
const { toggle, value } = useDarkMode(undefined, {
classNameDark: "dark",
classNameLight: "light",
storageKey: darkModeKey,
element: (globalThis.document && document.documentElement) || mockElement,
})
useEffect(() => {
useMediaStore.getState().setDark(value ? true : false)
}, [value])
useEffect(() => {
const handler = () => {
if (window.matchMedia("(prefers-color-scheme: dark)").matches === value) {
localStorage.removeItem(darkModeKey)
}
}
window.addEventListener("beforeunload", handler)
return () => {
window.removeEventListener("beforeunload", handler)
}
}, [value])
return {
toggle,
value,
}
}

View File

@ -20,6 +20,7 @@ import { toGateway } from "~/lib/ipfs-parser"
import { createIDBPersister } from "~/lib/persister.client"
import { urlComposer } from "~/lib/url-composer"
import { AppPropsWithLayout } from "types/next"
import { useMediaToggle } from "~/hooks/useDarkMode"
Network.setIpfsGateway(IPFS_GATEWAY)

View File

@ -635,7 +635,7 @@ export default function SubdomainEditor() {
}
}}
onChange={(e) => updateValue("title", e.target.value)}
className="h-12 ml-1 inline-flex items-center border-none text-3xl font-bold w-full focus:outline-none"
className="h-12 ml-1 inline-flex items-center border-none text-3xl font-bold w-full focus:outline-none bg-white"
placeholder={t("Title goes here...") || ""}
/>
</div>

View File

@ -285,4 +285,9 @@ export default {
900: "rgba(var(--tw-colors-i-rose-900), <alpha-value>)",
950: "rgba(var(--tw-colors-i-rose-950), <alpha-value>)",
},
inherit: "inherit",
current: "currentColor",
transparent: "transparent",
black: "var(--tw-colors-i-black)",
white: "var(--tw-colors-i-white)",
}