Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
DIYgod 2023-04-18 00:54:20 +01:00
commit 86fa187a45
No known key found for this signature in database
12 changed files with 1028 additions and 801 deletions

View File

@ -29,6 +29,7 @@
"@codemirror/commands": "6.2.2",
"@codemirror/lang-markdown": "6.1.1",
"@codemirror/language": "6.6.0",
"@codemirror/language-data": "6.2.1",
"@codemirror/state": "6.2.0",
"@codemirror/theme-one-dark": "6.1.1",
"@codemirror/view": "6.9.4",
@ -46,6 +47,7 @@
"@headlessui/react": "1.7.14",
"@heroicons/react": "2.0.17",
"@iconify-json/mingcute": "^1.1.5",
"@lezer/highlight": "*",
"@mantine/core": "^6.0.7",
"@monaco-editor/react": "4.5.0",
"@popperjs/core": "^2.11.7",
@ -54,7 +56,6 @@
"@tanstack/react-query-devtools": "4.29.3",
"@tanstack/react-query-persist-client": "4.29.3",
"@uiw/codemirror-extensions-events": "4.19.16",
"@uiw/react-codemirror": "4.19.16",
"@urql/core": "4.0.4",
"aplayer": "^1.10.1",
"aplayer-react": "^1.0.5",

File diff suppressed because it is too large Load Diff

View File

@ -23,7 +23,7 @@ import { Logo } from "~/components/common/Logo"
import { getStorage } from "~/lib/storage"
import { useGetPagesBySite } from "~/queries/page"
import { DashboardTopbar } from "./DashboardTopbar"
import { useMobileLayout } from "~/hooks/useMobileLayout"
import { useIsMobileLayout } from "~/hooks/useMobileLayout"
export function DashboardLayout({
children,
@ -47,7 +47,7 @@ export function DashboardLayout({
const [hasPermission, setHasPermission] = React.useState(false)
const { t } = useTranslation("dashboard")
const isMobileLayout = useMobileLayout()
const isMobileLayout = useIsMobileLayout()
useEffect(() => {
if (ssrReady) {

View File

@ -1,6 +1,6 @@
import { cn } from "~/lib/utils"
import { useTranslation } from "next-i18next"
import { useMobileLayout } from "~/hooks/useMobileLayout"
import { useIsMobileLayout } from "~/hooks/useMobileLayout"
export const DashboardMain: React.FC<{
children: React.ReactNode
@ -9,7 +9,7 @@ export const DashboardMain: React.FC<{
className?: string
}> = ({ children, fullWidth, title, className }) => {
const { t } = useTranslation("dashboard")
const isMobileLayout = useMobileLayout()
const isMobileLayout = useIsMobileLayout()
return (
<div className="flex-1">

View File

@ -0,0 +1,323 @@
import type { EditorState } from "@codemirror/state"
import { Annotation } from "@codemirror/state"
import { HighlightStyle } from "@codemirror/language"
import { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view"
import { tags } from "@lezer/highlight"
import type { Tag } from "@lezer/highlight"
import {
Suspense,
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState,
} from "react"
import { useTranslation } from "react-i18next"
import {
useCodeMirrorAutoToggleTheme,
useCodeMirrorStyle,
} from "~/hooks/useCodemirrorTheme"
import { useIsDark } from "~/hooks/useDarkMode"
import { useGetState } from "~/hooks/useGetState"
import { useIsUnmounted } from "~/hooks/useLifecycle"
import { useIsMobileLayout } from "~/hooks/useMobileLayout"
const LoadingHolder = () => {
const { t } = useTranslation("common")
return (
<div className="flex-1 h-12 flex items-center justify-center">
{t("Loading")}...
</div>
)
}
interface XLogCodeMirrorEditorProps {
value: string
onChange?: (value: string, viewUpdate: ViewUpdate) => void
handleDropFile?: (file: File) => void
onScroll?: (scrollTop: number) => void
onUpdate?: (update: ViewUpdate) => void
onCreateEditor?: (view: EditorView, state: EditorState) => void
onMouseEnter?: () => void
}
export const CodeMirrorEditor = forwardRef<
EditorView | null,
XLogCodeMirrorEditorProps
>((props, ref) => {
return (
<Suspense fallback={<LoadingHolder />}>
<LazyCodeMirrorEditor {...props} ref={ref} />
</Suspense>
)
})
CodeMirrorEditor.displayName = "CodeMirrorEditor"
const External = Annotation.define<boolean>()
const LazyCodeMirrorEditor = forwardRef<
EditorView | null,
XLogCodeMirrorEditorProps
>((props, ref) => {
const [loading, setLoading] = useState(true)
const editorElementRef = useRef<HTMLDivElement>(null)
const isUnmounted = useIsUnmounted()
const { t } = useTranslation("dashboard")
const isMobileLayout = useIsMobileLayout()
const [cmEditor, setCmEditor] = useState<EditorView | null>(null)
const isDark = useIsDark()
useCodeMirrorStyle(cmEditor)
useCodeMirrorAutoToggleTheme(cmEditor, isDark)
useImperativeHandle(ref, () => cmEditor!)
const { value, handleDropFile, onScroll, onMouseEnter } = props
const getHandleDropFile = useGetState(handleDropFile)
const getOnScroll = useGetState(onScroll)
const getValue = useGetState(value)
const getProps = useGetState(props)
useEffect(() => {
if (!cmEditor) return
if (value === undefined) {
return
}
const currentValue = cmEditor ? cmEditor.state.doc.toString() : ""
if (cmEditor && value !== currentValue) {
cmEditor.dispatch({
changes: { from: 0, to: currentValue.length, insert: value || "" },
annotations: [External.of(true)],
})
}
}, [value, cmEditor])
useEffect(() => {
Promise.all([
import("@codemirror/state"),
import("@codemirror/view"),
import("@codemirror/lang-markdown"),
import("@uiw/codemirror-extensions-events"),
import("~/hooks/useCodemirrorTheme"),
import("@codemirror/language"),
import("@codemirror/commands"),
import("@codemirror/language-data"),
]).then((modules) => {
if (isUnmounted()) return
if (!editorElementRef.current) return
const [
// @codemirror/state
{ EditorState },
// @codemirror/view
{
EditorView,
dropCursor,
drawSelection,
crosshairCursor,
keymap,
placeholder,
},
// @codemirror/lang-markdown
{ markdown, markdownKeymap, markdownLanguage },
// @uiw/codemirror-extensions-events
{ scroll },
{ codemirrorReconfigureExtension },
// @codemirror/language
{ syntaxHighlighting, indentOnInput },
// @codemirror/commands
{ defaultKeymap, history, historyKeymap, indentWithTab },
// @codemirror/language-data
{ languages },
] = modules
const editorState = EditorState.create({
doc: getValue(),
extensions: [
placeholder(t("Start writing...") || ""),
EditorView.updateListener.of((vu) => {
const props = getProps()
const { onUpdate, onChange } = props
onUpdate?.(vu)
if (
vu.docChanged &&
typeof onChange === "function" &&
// Fix echoing of the remote changes:
// If transaction is market as remote we don't have to call `onChange` handler again
!vu.transactions.some((tr) => tr.annotation(External))
) {
const doc = vu.state.doc
const value = doc.toString()
onChange(value, vu)
}
}),
indentOnInput(),
drawSelection(),
dropCursor(),
crosshairCursor(),
history(),
EditorState.allowMultipleSelections.of(true),
[EditorView.theme({}), syntaxHighlighting(codeMirrorMarkdownSyntax)],
...codemirrorReconfigureExtension,
markdown({
base: markdownLanguage,
codeLanguages: languages,
}),
keymap.of([
...defaultKeymap,
...historyKeymap,
...markdownKeymap,
indentWithTab,
] as KeyBinding[]),
EditorView.domEventHandlers({
drop(e) {
const items = Array.from(e.dataTransfer?.items || [])
{
;(async () => {
for (let i = 0; i < items.length; i++) {
const file = items[i]?.getAsFile()
if (file) {
getHandleDropFile()?.(file)
}
}
})()
}
},
paste(e) {
const files = e.clipboardData?.files
if (files) {
const items = Array.from(files)
{
;(async () => {
for (let i = 0; i < items.length; i++) {
getHandleDropFile()?.(items[i])
}
})()
}
}
},
}),
scroll({
scroll: (evn) => {
getOnScroll()?.((evn.target as any)?.scrollTop)
},
}),
EditorView.lineWrapping,
],
})
const view = new EditorView({
state: editorState,
parent: editorElementRef.current,
})
getProps().onCreateEditor?.(view, editorState)
setCmEditor(view)
setLoading(false)
})
}, [])
useEffect(() => () => cmEditor?.destroy(), [cmEditor])
return (
<>
<div
ref={editorElementRef}
onMouseEnter={onMouseEnter}
className={
loading
? ""
: `h-full ${isMobileLayout ? "w-full" : "border-r w-1/2 px-5"}`
}
/>
{loading && <LoadingHolder />}
</>
)
})
LazyCodeMirrorEditor.displayName = "LazyCodeMirrorEditor"
const monoSpaceTags: Tag[] = [
tags.bracket,
tags.angleBracket,
tags.squareBracket,
tags.paren,
tags.brace,
tags.float,
tags.monospace,
tags.keyword,
tags.character,
tags.propertyName,
tags.macroName,
tags.function(tags.variableName),
tags.labelName,
tags.definition(tags.name),
tags.typeName,
tags.annotation,
tags.modifier,
tags.self,
tags.namespace,
tags.comment,
tags.bool,
/*@__PURE__*/ tags.special(tags.variableName),
tags.className,
tags.number,
tags.changed,
tags.operator,
tags.operatorKeyword,
tags.escape,
tags.regexp,
/*@__PURE__*/ tags.special(tags.string),
tags.name,
tags.deleted,
tags.character,
]
const codeMirrorMarkdownSyntax = HighlightStyle.define([
{
tag: tags.heading1,
fontSize: "1.4em",
fontWeight: "bold",
},
{
tag: tags.heading2,
fontSize: "1.3em",
fontWeight: "bold",
},
{
tag: tags.heading3,
fontSize: "1.2em",
fontWeight: "bold",
},
{
tag: tags.heading4,
fontSize: "1.1em",
fontWeight: "bold",
},
{
tag: tags.heading5,
fontSize: "1.1em",
fontWeight: "bold",
},
{
tag: tags.heading6,
fontSize: "1.1em",
fontWeight: "bold",
},
{ tag: tags.strong, fontWeight: "bold" },
{ tag: tags.emphasis, fontStyle: "italic" },
{ tag: tags.deleted, textDecoration: "line-through" },
...monoSpaceTags.map((tag) => ({
tag,
fontFamily: `"OperatorMonoSSmLig Nerd Font","Cascadia Code PL","FantasqueSansMono Nerd Font","operator mono","Fira code Retina","Fira code","Consolas", Monaco, "Hannotate SC", monospace, -apple-system`,
})),
])

View File

@ -1,134 +0,0 @@
import CodeMirror from "@uiw/react-codemirror"
import { EditorView, ViewUpdate } from "@codemirror/view"
import { markdown } from "@codemirror/lang-markdown"
import { scroll } from "@uiw/codemirror-extensions-events"
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 { useIsDark } from "~/hooks/useDarkMode"
export const Editor: React.FC<{
value: string
onChange?: (value: string, viewUpdate: ViewUpdate) => void
handleDropFile?: (file: File) => void
onScroll?: (scrollTop: number) => void
onUpdate?: (update: ViewUpdate) => void
onCreateEditor?: (view: EditorView, state: EditorState) => void
onMouseEnter?: () => void
}> = ({
value,
onChange,
handleDropFile,
onScroll,
onUpdate,
onCreateEditor,
onMouseEnter,
}) => {
const { t } = useTranslation("dashboard")
const [extensions, setExtensions] = useState<any>([])
const isMobileLayout = useMobileLayout()
const [editor, setCmEditor] = useState<EditorView | null>(null)
const isDark = useIsDark()
useCodeMirrorAutoToggleTheme(editor, isDark)
useEffect(() => {
setExtensions([
markdown(),
EditorView.theme({
".cm-scroller": {
fontFamily: "var(--font-sans)",
fontSize: "1rem",
overflow: "auto",
height: "100%",
padding: isMobileLayout ? "0 1.25rem" : "unset",
},
".cm-content": {
paddingBottom: "600px",
},
"&.cm-editor.cm-focused": {
outline: "none",
},
"&.cm-editor": {
height: "100%",
backgroundColor: "transparent",
},
}),
EditorView.domEventHandlers({
drop(e) {
const items = Array.from(e.dataTransfer?.items || [])
{
;(async () => {
for (let i = 0; i < items.length; i++) {
const file = items[i]?.getAsFile()
if (file) {
handleDropFile?.(file)
}
}
})()
}
},
paste(e) {
const files = e.clipboardData?.files
if (files) {
const items = Array.from(files)
{
;(async () => {
for (let i = 0; i < items.length; i++) {
handleDropFile?.(items[i])
}
})()
}
}
},
}),
scroll({
scroll: (evn) => {
onScroll?.((evn.target as any)?.scrollTop)
},
}),
EditorView.lineWrapping,
...codemirrorReconfigureExtension,
])
}, [onScroll, handleDropFile])
return useMemo(
() => (
<CodeMirror
className={`h-full ${
isMobileLayout ? "w-full" : "border-r w-1/2 px-5"
}`}
value={value}
extensions={extensions}
onCreateEditor={(view, state) => {
setCmEditor(view)
onCreateEditor?.(view, state)
}}
basicSetup={{
lineNumbers: false,
foldGutter: false,
highlightActiveLine: false,
history: true,
defaultKeymap: true,
historyKeymap: true,
dropCursor: true,
drawSelection: true,
indentOnInput: true,
crosshairCursor: true,
allowMultipleSelections: true,
syntaxHighlighting: true,
}}
indentWithTab={true}
onChange={onChange}
placeholder={t("Start writing...") || ""}
onUpdate={onUpdate}
onMouseEnter={onMouseEnter}
/>
),
[value, onChange, extensions, onUpdate, onCreateEditor, onMouseEnter],
)
}

View File

@ -1,7 +1,7 @@
import React, { useEffect } from "react"
import { default as NextImage, ImageProps } from "next/image"
import { toGateway, toIPFS } from "~/lib/ipfs-parser"
import { useMobileLayout } from "~/hooks/useMobileLayout"
import { useIsMobileLayout } from "~/hooks/useMobileLayout"
import { useGetState } from "~/hooks/useGetState"
type TImageProps = {
@ -32,7 +32,7 @@ export const Image: React.FC<TImageProps> = ({
const noOptimization = className?.includes("no-optimization")
const imageRefInternal = React.useRef<HTMLImageElement>(null)
const isMobileLayout = useMobileLayout()
const isMobileLayout = useIsMobileLayout()
const getSrc = useGetState(src)
useEffect(() => {

View File

@ -1,17 +1,21 @@
import type { Extension } from "@codemirror/state"
import { Compartment } from "@codemirror/state"
import { EditorView } from "@codemirror/view"
import { useEffect } from "react"
import { useEffect, useRef } from "react"
import { oneDark } from "@codemirror/theme-one-dark"
import { githubLight } from "@ddietr/codemirror-themes/theme/github-light"
import { useIsUnmounted } from "./useLifecycle"
import { useIsMobileLayout } from "./useMobileLayout"
const extensionMap = {
theme: new Compartment(),
style: new Compartment(),
}
export const codemirrorReconfigureExtension: Extension[] = [
extensionMap.theme.of([]),
extensionMap.style.of([]),
]
export const useCodeMirrorAutoToggleTheme = (
@ -31,3 +35,49 @@ export const useCodeMirrorAutoToggleTheme = (
}
}, [view, isDark])
}
export const useCodeMirrorStyle = (view: EditorView | null) => {
const isMobileLayout = useIsMobileLayout()
const isUnmounted = useIsUnmounted()
const once = useRef(false)
const getStyle = () => {
return {
".cm-scroller": {
fontFamily: "var(--font-sans)",
fontSize: "1rem",
overflow: "auto",
height: "100%",
padding: isMobileLayout ? "0 1.25rem" : "unset",
},
".cm-content": {
paddingBottom: "600px",
},
"&.cm-editor.cm-focused": {
outline: "none",
},
"&.cm-editor": {
height: "100%",
backgroundColor: "transparent",
},
".cm-s-markdown .cm-string": {
fontFamily: '"Fira Code", monospace',
},
}
}
useEffect(() => {
if (!view) return
view.dispatch({
effects: [extensionMap.style.reconfigure(EditorView.theme(getStyle()))],
})
}, [view, isMobileLayout])
if (isUnmounted()) return
if (!once.current) {
if (!view) return
view.dispatch({
effects: [extensionMap.style.reconfigure(EditorView.theme(getStyle()))],
})
once.current = true
}
}

View File

@ -1,6 +1,6 @@
import { useEffect, useRef } from "react"
export const useGetState = (state: any) => {
export const useGetState = <T>(state: T): (() => T) => {
const ref = useRef(state)
useEffect(() => void (ref.current = state), [state])

View File

@ -1,24 +1,31 @@
import { useCallback, useEffect, useState } from "react"
import { useCallback, useEffect } from "react"
import { create } from "zustand"
const mobileWidth = 1024
const useLayoutStore = create<{
isMobile: boolean | undefined
}>(() => ({
isMobile: undefined,
}))
export function useMobileLayout() {
const [isMobile, setIsMobile] = useState<boolean>()
const onResize = useCallback(() => {
setIsMobile(window.innerWidth < mobileWidth)
const calc = useCallback(() => {
useLayoutStore.setState({
isMobile: window.innerWidth < mobileWidth,
})
}, [])
useEffect(() => {
window.addEventListener("resize", onResize)
window.addEventListener("resize", calc)
return () => {
window.removeEventListener("resize", onResize)
window.removeEventListener("resize", calc)
}
}, [onResize])
}, [calc])
useEffect(() => {
setIsMobile(window.innerWidth < mobileWidth)
calc()
}, [])
return isMobile
}
export const useIsMobileLayout = () => useLayoutStore((state) => state.isMobile)

View File

@ -21,6 +21,7 @@ import { createIDBPersister } from "~/lib/persister.client"
import { urlComposer } from "~/lib/url-composer"
import { AppPropsWithLayout } from "types/next"
import { useDarkMode } from "~/hooks/useDarkMode"
import { useMobileLayout } from "~/hooks/useMobileLayout"
Network.setIpfsGateway(IPFS_GATEWAY)
@ -40,6 +41,8 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
const getLayout = Component.getLayout ?? ((page) => page)
useDarkMode()
useMobileLayout()
return (
<WagmiConfig client={wagmiClient}>
<PersistQueryClientProvider

View File

@ -30,7 +30,7 @@ import { useQueryClient } from "@tanstack/react-query"
import { PageContent } from "~/components/common/PageContent"
import type { Root } from "hast"
import type { EditorView } from "@codemirror/view"
import { Editor } from "~/components/ui/Editor"
import { renderPageContent } from "~/markdown"
import { Button } from "~/components/ui/Button"
import { Modal } from "~/components/ui/Modal"
@ -42,9 +42,10 @@ import { getServerSideProps as getLayoutServerSideProps } from "~/components/das
import { GetServerSideProps } from "next"
import { serverSidePropsHandler } from "~/lib/server-side-props"
import { getDefaultSlug } from "~/lib/default-slug"
import { useMobileLayout } from "~/hooks/useMobileLayout"
import { useIsMobileLayout } from "~/hooks/useMobileLayout"
import { OptionsButton } from "~/components/dashboard/OptionsButton"
import NodeID3 from "node-id3"
import { CodeMirrorEditor } from "~/components/ui/CodeMirror"
export const getServerSideProps: GetServerSideProps = serverSidePropsHandler(
async (ctx) => {
@ -167,7 +168,7 @@ export default function SubdomainEditor() {
const createOrUpdatePage = useCreateOrUpdatePage()
const isMobileLayout = useMobileLayout()
const isMobileLayout = useIsMobileLayout()
const [isRendering, setIsRendering] = useState(false)
const savePage = async (published: boolean) => {
@ -682,7 +683,7 @@ export default function SubdomainEditor() {
<div className="mt-5 flex-1 flex overflow-hidden">
{isMobileLayout ? (
!isRendering ? (
<Editor
<CodeMirrorEditor
value={values.content}
onChange={onChange}
handleDropFile={handleDropFile}
@ -708,7 +709,7 @@ export default function SubdomainEditor() {
)
) : (
<>
<Editor
<CodeMirrorEditor
value={initialContent}
onChange={onChange}
handleDropFile={handleDropFile}