Compare commits

..

2 Commits

Author SHA1 Message Date
261eb8348e Merge pull request 'feat(notes): add support for exporting individual notes' (#6) from feat/note-export into main
Reviewed-on: https://git.tristonarmstrong.com/Klectr/KSlab/pulls/6
2024-10-09 17:10:00 +00:00
77624aae8c
feat(notes): add support for exporting individual notes
This adds an export icon to the notes cards so a user can export an individual note for sharing or
saaving for later
2024-10-09 12:55:58 -04:00
8 changed files with 92 additions and 22 deletions

View File

@ -0,0 +1,7 @@
export function Divider() {
return (
<div className="border-l-[1px] border-l-[#9c9c9c]"></div>
)
}

View File

@ -1,4 +1,3 @@
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"
@ -7,6 +6,9 @@ import { LayerEnum } from "../utils/enums"
import { useThemeDetector } from "../utils/useThemeDetector" import { useThemeDetector } from "../utils/useThemeDetector"
import { MarkDownEditor } from "./MarkDownEditor/MarkDownEditor" import { MarkDownEditor } from "./MarkDownEditor/MarkDownEditor"
import { ChangeEvent } from "tiny-markdown-editor" import { ChangeEvent } from "tiny-markdown-editor"
import { Divider } from "./Divider"
import { ExportIcon } from "./icons/ExportIcon"
import { createFileAndExport } from "../utils/createFileAndExport"
namespace NoteCard { namespace NoteCard {
export interface NoteCardProps { export interface NoteCardProps {
@ -107,6 +109,10 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
focusedItem.value = itemKey focusedItem.value = itemKey
} }
function _handleExportClick(e) {
createFileAndExport("Note", item.contents, "text/markdown")
}
const cardPositionStyle = { 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`,
@ -128,7 +134,19 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
<div className="overflow-hidden flex-1 flex flex-col gap-1"> <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 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> <div style={saveIndicatorStyle} className="rounded-full w-1 h-1 dark:bg-white bg-green-500"></div>
<div className="flex gap-2">
<div
onclick={_handleExportClick}
className="flex items-center">
<ExportIcon
className="cursor-pointer w-4 h-4 text-[#9c9c9c] hover:text-blue-500 transition-color duration-300"
/>
</div>
<Divider />
<button className="text-md dark:text-[#777] text-black" onclick={_handleClose}>x</button> <button className="text-md dark:text-[#777] text-black" onclick={_handleClose}>x</button>
</div>
</div> </div>
<hr className="border dark:border-[#1c1c1c] border-[#ddd]" /> <hr className="border dark:border-[#1c1c1c] border-[#ddd]" />

View File

@ -4,6 +4,7 @@ import { ImageCardButton } from "./ImageCardButton"
import { ExportButton } from "./ExportButton" import { ExportButton } from "./ExportButton"
import { TextButton } from "./TextButton" import { TextButton } from "./TextButton"
import { ImportButton } from "./ImportButton" import { ImportButton } from "./ImportButton"
import { Divider } from "../Divider"
export function CardSelector() { export function CardSelector() {
const containerRef = useRef<HTMLDivElement>(null) const containerRef = useRef<HTMLDivElement>(null)
@ -27,16 +28,6 @@ export function CardSelector() {
) )
} }
function Divider() {
return (
<div style={{
margin: '2px 2px',
border: "1px solid #9c9c9c",
borderRight: 'none',
}}></div>
)
}

View File

@ -1,4 +1,5 @@
import { ImagesSignal, NotesSigal } from "../../signals" import { ImagesSignal, NotesSigal } from "../../signals"
import { createFileAndExport } from "../../utils/createFileAndExport"
import { Tooltip } from "./Tooltip" import { Tooltip } from "./Tooltip"
import { defaultClassName } from "./utils" import { defaultClassName } from "./utils"
@ -12,17 +13,9 @@ export function ExportButton() {
...notes.value, ...notes.value,
...images.value ...images.value
} }
const date = new Date().toDateString().split(' ').join('_') const name = `Kslab_export`
const name = `Kslab_export_${date}.json`
const jsonData = JSON.stringify(mergeState) const jsonData = JSON.stringify(mergeState)
const file = new File([jsonData], name, { createFileAndExport(name, jsonData, "text/json")
type: 'text/json'
})
const url = URL.createObjectURL(file)
const a = document.createElement('a')
a.download = name
a.href = url
a.click()
} }
return ( return (

View File

@ -0,0 +1,26 @@
namespace ExportIcon {
export interface Props {
className?: string
}
}
export function ExportIcon({ className }: ExportIcon.Props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
className={className ?? ""}
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="17 8 12 3 7 8" />
<line x1="12" x2="12" y1="3" y2="15" />
</svg>
)
}

View File

@ -0,0 +1,23 @@
import { createFileCompliantDateString } from "./createFileCompliantDateString"
import { createFileFromData } from "./createFileFromData"
export type acceptableFileTypes = "text/json" | "text/markdown"
export function createFileAndExport(
name: string,
data: string,
type: acceptableFileTypes
) {
const date = createFileCompliantDateString()
const fileName = `${name}_${date}.${_getFileType(type)}`
const file = createFileFromData(data, fileName, "text/json")
const url = URL.createObjectURL(file)
const a = document.createElement("a")
a.download = fileName
a.href = url
a.click()
}
function _getFileType(type: acceptableFileTypes): "json" | "md" {
if (type === "text/markdown") return "md"
return "json"
}

View File

@ -0,0 +1,3 @@
export function createFileCompliantDateString() {
return new Date().toDateString().split(" ").join("_")
}

View File

@ -0,0 +1,9 @@
export function createFileFromData(
data: string,
name: string,
type: BlobPropertyBag["type"]
) {
return new File(Array.from(data), name, {
type: type,
})
}