Something went wrong. Try again.
A simple to-do app focused on tasks that can be completed within a specific time span. taskline.tobinio.dev
Something went wrong. Try again.
3.3 kB · 153 lines
Vue
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154<script setup lang="ts"> import { marked } from "marked";
const { t } = useI18n(); const note = defineModel<string>("note", { required: true });
const isEditing = ref(note.value === ""); const render = useTemplateRef("render");
const { handleKeyDown, handlePaste } = useMarkdownEditor(note);
marked.use({ renderer: { checkbox({ checked }) { return `<input ${checked ? 'checked="" ' : ""} type="checkbox"> `; }, }, });
async function renderMarkedown() { if (!render.value) { return; }
render.value.innerHTML = await marked.parse(note.value ?? "");
let count = 0; for (const checkbox of render.value.querySelectorAll( "li input[type=checkbox]", )) { let index = count; checkbox.addEventListener("change", () => { toggleMarkdownCheckbox(index); }); count++; } }
function toggleMarkdownCheckbox(targetIndex: number) { if (!note.value) return;
const checkboxRegex = /^(\s*[-*+|\d+.]\s+\[)([ xX])(\])/gm;
let currentIndex = 0;
note.value = note.value.replace( checkboxRegex, (match, prefix, checkState, suffix) => { if (currentIndex === targetIndex) { currentIndex++;
const newState = checkState.trim() ? " " : "x"; return `${prefix}${newState}${suffix}`; } currentIndex++; return match; }, ); }
watch(isEditing, async () => { if (!isEditing.value && render.value) { renderMarkedown(); } });
onMounted(async () => { if (render.value) { renderMarkedown(); } });</script>
<template> <button class="top 0 hover:bg-secondary-hover bg-secondary absolute right-0 h-7 w-7 cursor-pointer rounded" type="button" @click.prevent="() => (isEditing = !isEditing)" > <div class="flex h-full items-center justify-center"> <Icon v-if="isEditing" name="material-symbols:visibility" /> <Icon v-else name="material-symbols:edit" /> </div> </button> <div class="h-full w-full"> <textarea v-if="isEditing" v-model="note" @keydown="handleKeyDown" @paste="handlePaste" data-testid="note-input" class="border-secondary focus:border-secondary-hover h-full w-full resize-none rounded border pl-1 focus:outline-none" :placeholder="t('note')" /> <div :hidden="isEditing" class="h-full w-full overflow-auto"> <div ref="render" class="markdown" /> </div> </div></template>
<style> @reference "~/assets/css/main.css";
.markdown { h1 { @apply mt-6 mb-4 text-3xl leading-tight font-bold; } h2 { @apply mt-6 mb-3 text-2xl leading-snug font-semibold; } h3 { @apply mt-5 mb-2 text-xl leading-snug font-semibold; } h4 { @apply mt-4 mb-2 text-lg font-medium; } p { @apply mb-4 text-base leading-relaxed; } a { @apply text-primary hover:underline; } ul { @apply mb-4 list-inside list-disc space-y-1;
ul { @apply pl-6; } } ol { @apply mb-4 list-inside list-decimal space-y-1;
ol { @apply pl-6; } } blockquote { @apply text-background border-secondary my-4 border-l-4 pl-4 italic; } code { @apply inline font-mono text-sm bg-secondary px-1 rounded; } pre { @apply bg-secondary overflow-x-auto rounded p-2 text-sm; } img { @apply my-4 rounded-lg; } hr { @apply border-secondary my-6 border-t; } }</style>