generated from Klectr/KTemplate
feat/md #5
@ -5,6 +5,11 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
<title>Kslab</title>
|
<title>Kslab</title>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
type="text/css"
|
||||||
|
href="https://unpkg.com/tiny-markdown-editor/dist/tiny-mde.min.css"
|
||||||
|
/>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -13,7 +13,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^1",
|
"@tauri-apps/api": "^1",
|
||||||
"caniuse-lite": "^1.0.30001667",
|
"caniuse-lite": "^1.0.30001667",
|
||||||
"kaioken": "^0.32.1"
|
"kaioken": "^0.32.1",
|
||||||
|
"tiny-markdown-editor": "^0.1.26"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^1",
|
"@tauri-apps/cli": "^1",
|
||||||
|
30
src/components/MarkDownEditor/MarkDownEditor.tsx
Normal file
30
src/components/MarkDownEditor/MarkDownEditor.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Editor, Listener } from 'tiny-markdown-editor'
|
||||||
|
import './md.css'
|
||||||
|
import { useEffect, useRef } from "kaioken"
|
||||||
|
|
||||||
|
namespace MarkDownEditor {
|
||||||
|
export interface Props {
|
||||||
|
initial: string
|
||||||
|
onChange: Listener<'change'>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MarkDownEditor({ initial, onChange }: MarkDownEditor.Props) {
|
||||||
|
const elRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!elRef.current) return
|
||||||
|
const editor = new Editor({
|
||||||
|
element: elRef.current,
|
||||||
|
content: initial
|
||||||
|
})
|
||||||
|
editor.addEventListener('change', onChange)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={'h-full overflow-hidden'}
|
||||||
|
ref={elRef}
|
||||||
|
></div>
|
||||||
|
)
|
||||||
|
}
|
47
src/components/MarkDownEditor/md.css
Normal file
47
src/components/MarkDownEditor/md.css
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
.TinyMDE {
|
||||||
|
@apply h-full bg-transparent overflow-y-scroll dark:text-gray-300 text-gray-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TMMark_TMH1,
|
||||||
|
.TMMark_TMH2,
|
||||||
|
.TMMark_TMH3,
|
||||||
|
.TMMark_TMH4,
|
||||||
|
.TMMark_TMH5,
|
||||||
|
.TMMark_TMH6,
|
||||||
|
.TMMark_TMOL,
|
||||||
|
.TMMark_TMUL {
|
||||||
|
@apply text-blue-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TMH1,
|
||||||
|
.TMH2,
|
||||||
|
.TMH3,
|
||||||
|
.TMH4,
|
||||||
|
.TMH5,
|
||||||
|
.TMH6 {
|
||||||
|
@apply uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TMLink {
|
||||||
|
@apply decoration-blue-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TMImage {
|
||||||
|
@apply decoration-green-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TMCode {
|
||||||
|
@apply text-blue-800 px-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TMBlockquote {
|
||||||
|
@apply border-blue-500 bg-blue-100 dark:bg-opacity-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TMFencedCodeBacktick {
|
||||||
|
@apply dark:bg-white dark:bg-opacity-10 px-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TMInfoString {
|
||||||
|
@apply text-blue-500;
|
||||||
|
}
|
@ -21,6 +21,7 @@ export function MiniMap() {
|
|||||||
const width = canvasDimentsion.value.width / _MAP_SCALE_FACTOR
|
const width = canvasDimentsion.value.width / _MAP_SCALE_FACTOR
|
||||||
const height = canvasDimentsion.value.height / _MAP_SCALE_FACTOR
|
const height = canvasDimentsion.value.height / _MAP_SCALE_FACTOR
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function _handleScroll(_e: Event) {
|
function _handleScroll(_e: Event) {
|
||||||
scrollX.value = window.scrollX
|
scrollX.value = window.scrollX
|
||||||
@ -31,8 +32,6 @@ export function MiniMap() {
|
|||||||
return () => window.removeEventListener('scroll', _handleScroll)
|
return () => window.removeEventListener('scroll', _handleScroll)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="dark:bg-[#ffffff11] bg-[#0001] fixed rounded"
|
className="dark:bg-[#ffffff11] bg-[#0001] fixed rounded"
|
||||||
@ -48,22 +47,31 @@ export function MiniMap() {
|
|||||||
const el = useRef(null)
|
const el = useRef(null)
|
||||||
|
|
||||||
function _handleItemClick(_e: MouseEvent) {
|
function _handleItemClick(_e: MouseEvent) {
|
||||||
|
const newLeft = image.position.x - ((viewportWidth / 2) - (image.dimensions.w / 2))
|
||||||
|
const newTop = image.position.y - ((viewportHeight / 2) - (image.dimensions.h / 2))
|
||||||
|
|
||||||
window.scrollTo({
|
window.scrollTo({
|
||||||
left: image.position.x - ((viewportWidth / 2) - (image.dimensions.w / 2)),
|
left: newLeft,
|
||||||
top: image.position.y - ((viewportHeight / 2) - (image.dimensions.h / 2)),
|
top: newTop,
|
||||||
behavior: 'smooth'
|
behavior: 'smooth'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newWidth = image.dimensions.w / _MAP_SCALE_FACTOR
|
||||||
|
const newHeight = image.dimensions.h / _MAP_SCALE_FACTOR
|
||||||
|
const newTop = (image.position.y / _MAP_SCALE_FACTOR)
|
||||||
|
const newLeft = (image.position.x / _MAP_SCALE_FACTOR)
|
||||||
|
const newZIndex = LayerEnum.MINIMAP + 1
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={el} className={"absolute dark:bg-green-500 bg-green-300 dark:hover:bg-blue-500 hover:bg-blue-300 cursor-pointer border dark:border-[#222] border-green-500 rounded"}
|
<div ref={el} className={"absolute dark:bg-green-500 bg-green-300 dark:hover:bg-blue-500 hover:bg-blue-300 cursor-pointer border dark:border-[#222] border-green-500 rounded"}
|
||||||
onclick={_handleItemClick}
|
onclick={_handleItemClick}
|
||||||
style={{
|
style={{
|
||||||
width: `${image.dimensions.w / _MAP_SCALE_FACTOR}px`,
|
width: `${newWidth}px`,
|
||||||
height: `${image.dimensions.h / _MAP_SCALE_FACTOR}px`,
|
height: `${newHeight}px`,
|
||||||
top: `${(image.position.y / _MAP_SCALE_FACTOR)}px`,
|
top: `${newTop}px`,
|
||||||
left: `${(image.position.x / _MAP_SCALE_FACTOR)}px`,
|
left: `${newLeft}px`,
|
||||||
zIndex: `${LayerEnum.MINIMAP + 1}`
|
zIndex: `${newZIndex}`
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
)
|
)
|
||||||
@ -74,29 +82,37 @@ export function MiniMap() {
|
|||||||
const note = notes.notes.value[noteKey]
|
const note = notes.notes.value[noteKey]
|
||||||
|
|
||||||
function _handleItemClick(_e: MouseEvent) {
|
function _handleItemClick(_e: MouseEvent) {
|
||||||
|
const newLeft = note.position.x - ((viewportWidth / 2) - (note.dimensions.w / 2))
|
||||||
|
const newTop = note.position.y - ((viewportHeight / 2) - (note.dimensions.h / 2))
|
||||||
window.scrollTo({
|
window.scrollTo({
|
||||||
left: note.position.x - ((viewportWidth / 2) - (note.dimensions.w / 2)),
|
left: newLeft,
|
||||||
top: note.position.y - ((viewportHeight / 2) - (note.dimensions.h / 2)),
|
top: newTop,
|
||||||
behavior: 'smooth'
|
behavior: 'smooth'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newWidth = note.dimensions.w / _MAP_SCALE_FACTOR
|
||||||
|
const newHeight = note.dimensions.h / _MAP_SCALE_FACTOR
|
||||||
|
const newTop = (note.position.y / _MAP_SCALE_FACTOR)
|
||||||
|
const newLeft = (note.position.x / _MAP_SCALE_FACTOR)
|
||||||
|
const newZIndex = LayerEnum.MINIMAP + 1
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"absolute dark:bg-gray-500 bg-gray-300 hover:bg-blue-500 cursor-pointer border dark:border-[#222] border-gray-500 rounded"}
|
<div className={"absolute dark:bg-gray-500 bg-gray-300 hover:bg-blue-500 cursor-pointer border dark:border-[#222] border-gray-400 rounded"}
|
||||||
onclick={_handleItemClick}
|
onclick={_handleItemClick}
|
||||||
style={{
|
style={{
|
||||||
width: `${note.dimensions.w / _MAP_SCALE_FACTOR}px`,
|
width: `${newWidth}px`,
|
||||||
height: `${note.dimensions.h / _MAP_SCALE_FACTOR}px`,
|
height: `${newHeight}px`,
|
||||||
top: `${(note.position.y / _MAP_SCALE_FACTOR)}px`,
|
top: `${newTop}px`,
|
||||||
left: `${(note.position.x / _MAP_SCALE_FACTOR)}px`,
|
left: `${newLeft}px`,
|
||||||
zIndex: `${LayerEnum.MINIMAP + 1}`
|
zIndex: `${newZIndex}`
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={'absolute bg-blue-200 bg-opacity-10 border dark:border-blue-800 border-blue-500 bg-blue-500 rounded'}
|
className={'pointer-events-none absolute bg-blue-200 bg-opacity-10 border dark:border-blue-800 border-blue-500 bg-blue-500 rounded'}
|
||||||
style={{
|
style={{
|
||||||
width: `${viewportWidth / _MAP_SCALE_FACTOR}px`,
|
width: `${viewportWidth / _MAP_SCALE_FACTOR}px`,
|
||||||
height: `${viewportHeight / _MAP_SCALE_FACTOR}px`,
|
height: `${viewportHeight / _MAP_SCALE_FACTOR}px`,
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
|
|
||||||
import { signal, useRef } from "kaioken"
|
import { signal, useRef } from "kaioken"
|
||||||
import { NotesSigal, focusedItem } from "../signals"
|
import { NotesSigal, focusedItem } from "../signals"
|
||||||
import { useDebounce } from "../utils/useDebounce"
|
import { useDebounce } from "../utils/useDebounce"
|
||||||
import notes, { NoteCardType } from "../signals/notes"
|
import notes, { NoteCardType } from "../signals/notes"
|
||||||
import { LayerEnum } from "../utils/enums"
|
import { LayerEnum } from "../utils/enums"
|
||||||
import { useThemeDetector } from "../utils/useThemeDetector"
|
import { useThemeDetector } from "../utils/useThemeDetector"
|
||||||
|
import { MarkDownEditor } from "./MarkDownEditor/MarkDownEditor"
|
||||||
|
import { ChangeEvent } from "tiny-markdown-editor"
|
||||||
|
|
||||||
namespace NoteCard {
|
namespace NoteCard {
|
||||||
export interface NoteCardProps {
|
export interface NoteCardProps {
|
||||||
@ -27,6 +30,7 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
|
|||||||
function updateLocalStorage(time?: number) {
|
function updateLocalStorage(time?: number) {
|
||||||
debounce(() => {
|
debounce(() => {
|
||||||
localStorage.setItem("notes", JSON.stringify(notes.notes.value))
|
localStorage.setItem("notes", JSON.stringify(notes.notes.value))
|
||||||
|
saved.value = true
|
||||||
}, time)
|
}, time)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,45 +90,51 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
|
|||||||
window.removeEventListener('mouseup', _handleResizeMouseUp)
|
window.removeEventListener('mouseup', _handleResizeMouseUp)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
function _handleMdChange(e: ChangeEvent) {
|
||||||
<div
|
NotesSigal.default.updateNoteProperty(itemKey, 'contents', e.content)
|
||||||
onmousedown={() => focusedItem.value = itemKey}
|
NotesSigal.default.notes.notify()
|
||||||
className="text-[#333] dark:bg-[#111] dark:border-[#1c1c1c] bg-[#eee] select-none transition flex flex-col justify-stretch shadow-md rounded border border-[#ddd] absolute"
|
updateLocalStorage()
|
||||||
style={{
|
saved.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function _handleClose(_e: Event) {
|
||||||
|
NotesSigal.default.removeNote(item.id)
|
||||||
|
NotesSigal.default.notes.notify()
|
||||||
|
updateLocalStorage()
|
||||||
|
}
|
||||||
|
|
||||||
|
function _handleFocusCard() {
|
||||||
|
focusedItem.value = itemKey
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardPositionStyle = {
|
||||||
zIndex: `${focusedItem.value == itemKey ? LayerEnum.CARD_ELEVATED : LayerEnum.CARD}`,
|
zIndex: `${focusedItem.value == itemKey ? LayerEnum.CARD_ELEVATED : LayerEnum.CARD}`,
|
||||||
width: `${item.dimensions.w}px`,
|
width: `${item.dimensions.w}px`,
|
||||||
height: `${item.dimensions.h}px`,
|
height: `${item.dimensions.h}px`,
|
||||||
top: `${item.position.y}px`,
|
top: `${item.position.y}px`,
|
||||||
left: `${item.position.x}px`,
|
left: `${item.position.x}px`,
|
||||||
}}
|
}
|
||||||
>
|
|
||||||
<div className="flex-1 flex flex-col gap-1">
|
const saveIndicatorStyle = {
|
||||||
<div className="px-2 flex justify-between items-center cursor-move" onmousedown={_handleMouseDown}>
|
|
||||||
<div style={{
|
|
||||||
opacity: saved.value ? '0' : '100'
|
opacity: saved.value ? '0' : '100'
|
||||||
}} className={`rounded-full w-1 h-1 dark:bg-white bg-green-500`}></div>
|
}
|
||||||
<button className="text-md dark:text-[#777] text-black" onclick={(_e: Event) => {
|
|
||||||
NotesSigal.default.removeNote(item.id)
|
return (
|
||||||
NotesSigal.default.notes.notify()
|
<div
|
||||||
updateLocalStorage()
|
onmousedown={_handleFocusCard}
|
||||||
}}>x</button>
|
style={cardPositionStyle}
|
||||||
|
className="overflow-hidden text-[#333] dark:bg-[#1a1a1a] dark:border-[#1c1c1c] bg-[#eee] select-none transition flex flex-col justify-stretch shadow-md rounded border border-[#ddd] absolute"
|
||||||
|
>
|
||||||
|
<div className="overflow-hidden flex-1 flex flex-col gap-1">
|
||||||
|
<div className="px-2 flex justify-between items-center cursor-move" onmousedown={_handleMouseDown}>
|
||||||
|
<div style={saveIndicatorStyle} className="rounded-full w-1 h-1 dark:bg-white bg-green-500"></div>
|
||||||
|
<button className="text-md dark:text-[#777] text-black" onclick={_handleClose}>x</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr className="border dark:border-[#1c1c1c] border-[#ddd]" />
|
<hr className="border dark:border-[#1c1c1c] border-[#ddd]" />
|
||||||
<textarea
|
|
||||||
placeholder={"Todo: put some note here"}
|
|
||||||
className="flex resize-none px-2 w-full h-full bg-transparent resize-none focus:outline-none dark:text-gray-300"
|
|
||||||
value={item.contents}
|
|
||||||
onkeypress={() => { saved.value = false }}
|
|
||||||
onchange={(e) => {
|
|
||||||
NotesSigal.default.updateNoteProperty(itemKey, 'contents', e.target.value)
|
|
||||||
NotesSigal.default.notes.notify()
|
|
||||||
updateLocalStorage()
|
|
||||||
saved.value = true
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
<MarkDownEditor initial={item.contents} onChange={_handleMdChange} />
|
||||||
<ExpandIcon cb={_handleResizeMouseDown} />
|
<ExpandIcon cb={_handleResizeMouseDown} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div >
|
</div >
|
||||||
|
|
||||||
@ -156,3 +166,4 @@ function ExpandIcon({ cb }: {
|
|||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,14 +13,15 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
*::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
html {
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
overscroll-behavior-x: none;
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||||
font-synthesis: none;
|
font-synthesis: none;
|
||||||
|
Loading…
Reference in New Issue
Block a user