feat: task system
All checks were successful
Build and Push App Image / build-and-push (push) Successful in 1m55s
All checks were successful
Build and Push App Image / build-and-push (push) Successful in 1m55s
This commit is contained in:
@@ -13,6 +13,16 @@
|
||||
<i class="mdi mdi-folder-open-outline me-1" aria-hidden="true"></i>
|
||||
Files
|
||||
</button>
|
||||
<button
|
||||
v-if="spaceId"
|
||||
class="btn btn-sm"
|
||||
:class="showTaskPicker ? 'btn-secondary' : 'btn-outline-secondary'"
|
||||
:title="showTaskPicker ? 'Hide task picker' : 'Browse & insert task mentions'"
|
||||
@click="toggleTaskPicker"
|
||||
>
|
||||
<i class="mdi mdi-checkbox-marked-circle-outline me-1" aria-hidden="true"></i>
|
||||
Tasks
|
||||
</button>
|
||||
<span class="save-status ms-auto" :class="saveState">{{ saveStatusLabel }}</span>
|
||||
</div>
|
||||
|
||||
@@ -25,19 +35,52 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div :class="showFileExplorer ? 'col-12 col-md-5' : 'col-12 col-md-6'">
|
||||
<div :class="editorColumnClass">
|
||||
<textarea ref="contentTextareaRef" v-model="editingNote.content" class="form-control editor-textarea" placeholder="Write your note in markdown..." @input="autoSave"></textarea>
|
||||
<div v-if="showTaskMention" class="task-mention-panel">
|
||||
<div class="small text-muted mb-1">Link task for "{{ taskMentionQuery }}"</div>
|
||||
<button v-for="task in taskMentionResults" :key="task.id" class="task-mention-option" @click="selectMentionTask(task)">
|
||||
<span>{{ task.title }}</span>
|
||||
<small>Depth {{ task.depth + 1 }}</small>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="showFileExplorer ? 'col-12 col-md-4 mt-3 mt-md-0' : 'col-12 col-md-6 mt-3 mt-md-0'">
|
||||
<div class="preview-pane border rounded p-3">
|
||||
<div :class="previewColumnClass">
|
||||
<div class="preview-pane border rounded p-3" @click="onPreviewClick">
|
||||
<div class="markdown-body" v-html="renderedMarkdown"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showFileExplorer" class="col-12 col-md-3 mt-3 mt-md-0">
|
||||
<div v-if="showFileExplorer" :class="fileExplorerColumnClass">
|
||||
<FileExplorer v-model="fileExplorerPrefix" :space-id="spaceId" @insert="insertAtCursor" />
|
||||
</div>
|
||||
|
||||
<div v-if="showTaskPicker" :class="taskPickerColumnClass">
|
||||
<div class="task-picker border rounded">
|
||||
<div class="task-picker-header px-2 py-1 border-bottom d-flex align-items-center gap-2">
|
||||
<i class="mdi mdi-checkbox-marked-circle-outline text-muted" aria-hidden="true"></i>
|
||||
<span class="small fw-semibold">Space Tasks</span>
|
||||
<button class="btn btn-link btn-sm p-0 text-muted ms-auto" title="Refresh" @click="refreshTaskPicker">
|
||||
<i class="mdi mdi-refresh" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="task-picker-search p-2 border-bottom">
|
||||
<input v-model="taskPickerQuery" type="text" class="form-control form-control-sm" placeholder="Search tasks by title..." />
|
||||
</div>
|
||||
<div v-if="taskPickerLoading" class="task-picker-empty text-muted small">
|
||||
<i class="mdi mdi-loading mdi-spin me-1" aria-hidden="true"></i>
|
||||
Loading tasks...
|
||||
</div>
|
||||
<div v-else-if="!taskPickerItems.length" class="task-picker-empty text-muted small">No tasks found.</div>
|
||||
<div v-else class="task-picker-list">
|
||||
<button v-for="task in taskPickerItems" :key="task.id" class="task-picker-item" @click="insertTaskMention(task)">
|
||||
<span class="task-picker-title">{{ task.title }}</span>
|
||||
<small>{{ task.picker_status_name }}</small>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
@@ -98,6 +141,7 @@
|
||||
import { ref, computed, watch, onBeforeUnmount, onMounted, nextTick } from "vue";
|
||||
import DOMPurify from "dompurify";
|
||||
import { useSettingsStore } from "../stores/settingsStore";
|
||||
import { useSpaceStore } from "../stores/spaceStore";
|
||||
import { renderMarkdown } from "../utils/markdown.js";
|
||||
import FileExplorer from "./FileExplorer.vue";
|
||||
|
||||
@@ -120,8 +164,9 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["save", "delete", "cancel"]);
|
||||
const emit = defineEmits(["save", "delete", "cancel", "open-linked-task"]);
|
||||
const settingsStore = useSettingsStore();
|
||||
const spaceStore = useSpaceStore();
|
||||
const publicSharingEnabled = ref(true);
|
||||
const fileExplorerEnabled = computed(() => settingsStore.fileExplorerEnabled);
|
||||
|
||||
@@ -135,12 +180,110 @@ const notePassword = ref("");
|
||||
const saveTimeout = ref(null);
|
||||
const saveState = ref("saved");
|
||||
const saveStateTimeout = ref(null);
|
||||
const taskMentionQuery = ref("");
|
||||
const taskMentionResults = ref([]);
|
||||
const showTaskMention = ref(false);
|
||||
const linkedTasks = ref([]);
|
||||
const showTaskPicker = ref(false);
|
||||
const taskPickerQuery = ref("");
|
||||
const taskPickerLoading = ref(false);
|
||||
|
||||
const hasAuxPanels = computed(() => showFileExplorer.value || showTaskPicker.value);
|
||||
const hasTwoAuxPanels = computed(() => showFileExplorer.value && showTaskPicker.value);
|
||||
|
||||
const editorColumnClass = computed(() => {
|
||||
if (hasTwoAuxPanels.value) {
|
||||
return "col-12 col-xl-4";
|
||||
}
|
||||
return hasAuxPanels.value ? "col-12 col-md-5" : "col-12 col-md-6";
|
||||
});
|
||||
|
||||
const previewColumnClass = computed(() => {
|
||||
if (hasTwoAuxPanels.value) {
|
||||
return "col-12 col-xl-4 mt-3 mt-xl-0";
|
||||
}
|
||||
return hasAuxPanels.value ? "col-12 col-md-4 mt-3 mt-md-0" : "col-12 col-md-6 mt-3 mt-md-0";
|
||||
});
|
||||
|
||||
const fileExplorerColumnClass = computed(() => {
|
||||
return hasTwoAuxPanels.value ? "col-12 col-md-6 col-xl-2 mt-3 mt-xl-0" : "col-12 col-md-3 mt-3 mt-md-0";
|
||||
});
|
||||
|
||||
const taskPickerColumnClass = computed(() => {
|
||||
return hasTwoAuxPanels.value ? "col-12 col-md-6 col-xl-2 mt-3 mt-xl-0" : "col-12 col-md-3 mt-3 mt-md-0";
|
||||
});
|
||||
|
||||
const taskStatusNameById = computed(() => {
|
||||
const map = new Map();
|
||||
for (const status of spaceStore.taskStatuses || []) {
|
||||
if (status?.id) {
|
||||
map.set(status.id, status.name || "");
|
||||
}
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
const taskPickerItems = computed(() => {
|
||||
const query = taskPickerQuery.value.trim().toLowerCase();
|
||||
const allTasks = [...(spaceStore.tasks || [])]
|
||||
.map((task) => ({
|
||||
...task,
|
||||
picker_status_name: task.status_name || task.status?.name || taskStatusNameById.value.get(task.status_id) || "Unknown",
|
||||
}))
|
||||
.sort((a, b) => (a.title || "").localeCompare(b.title || ""));
|
||||
if (!query) {
|
||||
return allTasks;
|
||||
}
|
||||
return allTasks.filter((task) => (task.title || "").toLowerCase().includes(query));
|
||||
});
|
||||
|
||||
const renderedMarkdown = computed(() => {
|
||||
const html = renderMarkdown(editingNote.value.content || "");
|
||||
const html = renderMarkdown(enrichTaskMentions(editingNote.value.content || ""));
|
||||
return DOMPurify.sanitize(html);
|
||||
});
|
||||
|
||||
const normalizeTaskTitle = (value) => (value || "").trim().toLowerCase();
|
||||
|
||||
const taskByTitle = computed(() => {
|
||||
const map = new Map();
|
||||
for (const task of linkedTasks.value || []) {
|
||||
const key = normalizeTaskTitle(task.title);
|
||||
if (!key || map.has(key)) {
|
||||
continue;
|
||||
}
|
||||
map.set(key, task);
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
const enrichTaskMentions = (content) => {
|
||||
if (!content) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return content.replace(/@task\(([^)]+)\)/gi, (full, rawTitle) => {
|
||||
const title = (rawTitle || "").trim();
|
||||
if (!title) {
|
||||
return full;
|
||||
}
|
||||
|
||||
const linkedTask = taskByTitle.value.get(normalizeTaskTitle(title));
|
||||
if (!linkedTask?.id) {
|
||||
return full;
|
||||
}
|
||||
|
||||
const statusName = (linkedTask.status_name || "Unknown").trim();
|
||||
const safeTitle = title.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
||||
const safeStatusName = statusName.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
||||
|
||||
const statusColor = (linkedTask.status_color || "").trim();
|
||||
const safeStatusColor = statusColor.replace(/"/g, "").replace(/</g, "").replace(/>/g, "");
|
||||
const styleAttr = safeStatusColor ? ` style="--task-status-color:${safeStatusColor}"` : "";
|
||||
|
||||
return `<a href="#task:${linkedTask.id}" class="task-inline-link"${styleAttr}><i class="mdi mdi-checkbox-marked-circle-outline" aria-hidden="true"></i><span class="task-inline-title">${safeTitle}</span><span class="task-inline-status">${safeStatusName}</span></a>`;
|
||||
});
|
||||
};
|
||||
|
||||
const saveStatusLabel = computed(() => {
|
||||
switch (saveState.value) {
|
||||
case "dirty":
|
||||
@@ -155,12 +298,21 @@ const saveStatusLabel = computed(() => {
|
||||
|
||||
watch(
|
||||
() => props.note,
|
||||
(newNote) => {
|
||||
async (newNote) => {
|
||||
editingNote.value = { ...newNote };
|
||||
tagsInput.value = newNote.tags?.join(", ") || "";
|
||||
passwordAction.value = "keep";
|
||||
notePassword.value = "";
|
||||
saveState.value = "saved";
|
||||
if (props.spaceId && newNote?.id) {
|
||||
try {
|
||||
linkedTasks.value = await spaceStore.fetchTasksForNote(props.spaceId, newNote.id);
|
||||
} catch {
|
||||
linkedTasks.value = [];
|
||||
}
|
||||
} else {
|
||||
linkedTasks.value = [];
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -211,12 +363,15 @@ const saveNote = () => {
|
||||
notePassword.value = "";
|
||||
}
|
||||
markSavedSoon();
|
||||
// Auto-link any @task(Title) mentions present in the saved content
|
||||
syncTaskMentionLinks(note.content || "");
|
||||
};
|
||||
|
||||
const autoSave = () => {
|
||||
saveState.value = "dirty";
|
||||
clearTimeout(saveTimeout.value);
|
||||
saveTimeout.value = setTimeout(saveNote, 3000);
|
||||
detectTaskMention();
|
||||
};
|
||||
|
||||
const confirmDelete = () => {
|
||||
@@ -249,6 +404,152 @@ const insertAtCursor = (snippet) => {
|
||||
});
|
||||
};
|
||||
|
||||
const detectTaskMention = async () => {
|
||||
const content = editingNote.value.content || "";
|
||||
const match = content.match(/@task\s+([^\n]{1,40})$/i);
|
||||
if (!match || !props.spaceId) {
|
||||
showTaskMention.value = false;
|
||||
taskMentionResults.value = [];
|
||||
taskMentionQuery.value = "";
|
||||
return;
|
||||
}
|
||||
const query = match[1].trim();
|
||||
taskMentionQuery.value = query;
|
||||
if (!query) {
|
||||
showTaskMention.value = false;
|
||||
taskMentionResults.value = [];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
taskMentionResults.value = await spaceStore.searchTasks(props.spaceId, query);
|
||||
showTaskMention.value = taskMentionResults.value.length > 0;
|
||||
} catch {
|
||||
taskMentionResults.value = [];
|
||||
showTaskMention.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const replaceTaskMentionText = (title) => {
|
||||
editingNote.value.content = (editingNote.value.content || "").replace(/@task\s+([^\n]{1,40})$/i, `@task(${title})`);
|
||||
};
|
||||
|
||||
const selectMentionTask = async (task) => {
|
||||
replaceTaskMentionText(task.title);
|
||||
showTaskMention.value = false;
|
||||
taskMentionResults.value = [];
|
||||
if (!props.spaceId || !editingNote.value.id) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await spaceStore.linkTaskToNote(props.spaceId, task.id, editingNote.value.id);
|
||||
linkedTasks.value = await spaceStore.fetchTasksForNote(props.spaceId, editingNote.value.id);
|
||||
} catch {
|
||||
alert("Unable to link task to this note.");
|
||||
}
|
||||
autoSave();
|
||||
};
|
||||
|
||||
const insertTaskMention = (task) => {
|
||||
if (!task?.title) {
|
||||
return;
|
||||
}
|
||||
insertAtCursor(`@task(${task.title})`);
|
||||
};
|
||||
|
||||
const refreshTaskPicker = async () => {
|
||||
if (!props.spaceId) {
|
||||
return;
|
||||
}
|
||||
taskPickerLoading.value = true;
|
||||
try {
|
||||
await Promise.all([spaceStore.fetchTasks(props.spaceId), spaceStore.fetchTaskStatuses(props.spaceId)]);
|
||||
} finally {
|
||||
taskPickerLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const toggleTaskPicker = async () => {
|
||||
showTaskPicker.value = !showTaskPicker.value;
|
||||
if (showTaskPicker.value && !spaceStore.tasks.length) {
|
||||
await refreshTaskPicker();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse all @task(Title) mentions in content and ensure each is linked.
|
||||
* Called after every real save so new mentions are linked automatically.
|
||||
*/
|
||||
const syncTaskMentionLinks = async (content) => {
|
||||
if (!props.spaceId || !editingNote.value.id) {
|
||||
return;
|
||||
}
|
||||
const mentionTitles = new Set();
|
||||
const rx = /@task\(([^)]+)\)/gi;
|
||||
let m;
|
||||
while ((m = rx.exec(content)) !== null) {
|
||||
const title = (m[1] || "").trim();
|
||||
if (title) {
|
||||
mentionTitles.add(title.toLowerCase());
|
||||
}
|
||||
}
|
||||
if (!mentionTitles.size) {
|
||||
return;
|
||||
}
|
||||
let current;
|
||||
try {
|
||||
current = await spaceStore.fetchTasksForNote(props.spaceId, editingNote.value.id);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
const linkedTitles = new Set((current || []).map((t) => (t.title || "").toLowerCase()));
|
||||
const toLink = [...mentionTitles].filter((title) => !linkedTitles.has(title));
|
||||
if (!toLink.length) {
|
||||
linkedTasks.value = current;
|
||||
return;
|
||||
}
|
||||
await Promise.all(
|
||||
toLink.map(async (title) => {
|
||||
try {
|
||||
const results = await spaceStore.searchTasks(props.spaceId, title);
|
||||
const exact = results.find((t) => (t.title || "").toLowerCase() === title);
|
||||
if (exact) {
|
||||
await spaceStore.linkTaskToNote(props.spaceId, exact.id, editingNote.value.id);
|
||||
}
|
||||
} catch {
|
||||
// best-effort — skip silently
|
||||
}
|
||||
}),
|
||||
);
|
||||
try {
|
||||
linkedTasks.value = await spaceStore.fetchTasksForNote(props.spaceId, editingNote.value.id);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
const onPreviewClick = (event) => {
|
||||
const anchor = event.target?.closest?.("a");
|
||||
if (!anchor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const href = anchor.getAttribute("href") || "";
|
||||
if (!href.startsWith("#task:")) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
const taskId = href.slice("#task:".length);
|
||||
if (!taskId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const matchedTask = (linkedTasks.value || []).find((task) => task.id === taskId);
|
||||
if (matchedTask) {
|
||||
emit("open-linked-task", matchedTask);
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearTimeout(saveTimeout.value);
|
||||
clearTimeout(saveStateTimeout.value);
|
||||
@@ -257,6 +558,16 @@ onBeforeUnmount(() => {
|
||||
onMounted(async () => {
|
||||
await settingsStore.loadFeatureFlags();
|
||||
publicSharingEnabled.value = settingsStore.publicSharingEnabled;
|
||||
if (props.spaceId && editingNote.value?.id) {
|
||||
try {
|
||||
linkedTasks.value = await spaceStore.fetchTasksForNote(props.spaceId, editingNote.value.id);
|
||||
} catch {
|
||||
linkedTasks.value = [];
|
||||
}
|
||||
}
|
||||
if (props.spaceId && !spaceStore.tasks.length) {
|
||||
await refreshTaskPicker();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -347,6 +658,133 @@ onMounted(async () => {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.task-mention-panel {
|
||||
margin-top: 0.45rem;
|
||||
border: 1px solid #dbe4f0;
|
||||
border-radius: 10px;
|
||||
background: #fbfdff;
|
||||
padding: 0.5rem;
|
||||
max-height: 220px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.task-mention-option {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 0.35rem 0.45rem;
|
||||
border-radius: 6px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.task-mention-option:hover {
|
||||
background: #eef3ff;
|
||||
}
|
||||
|
||||
.task-picker {
|
||||
background: #fff;
|
||||
min-height: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.task-picker-header {
|
||||
background: #f8f9fa;
|
||||
min-height: 34px;
|
||||
}
|
||||
|
||||
.task-picker-search {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.task-picker-list {
|
||||
overflow-y: auto;
|
||||
max-height: 520px;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.task-picker-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
border-radius: 8px;
|
||||
padding: 0.35rem 0.45rem;
|
||||
text-align: left;
|
||||
gap: 0.4rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.task-picker-item:hover {
|
||||
background: #eef3ff;
|
||||
}
|
||||
|
||||
.task-picker-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-picker-empty {
|
||||
padding: 0.7rem;
|
||||
}
|
||||
|
||||
.task-picker-item small {
|
||||
font-size: 0.7rem;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.task-picker .btn-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.task-picker-item {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.markdown-body :deep(.task-inline-link) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.18rem 0.5rem;
|
||||
border-radius: 999px;
|
||||
border: 1px solid #c7d8ff;
|
||||
background: #eef4ff;
|
||||
color: #2c4ea3;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.markdown-body :deep(.task-inline-title) {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.markdown-body :deep(.task-inline-status) {
|
||||
line-height: 1;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
border-radius: 999px;
|
||||
padding: 0.16rem 0.42rem;
|
||||
border: 1px solid color-mix(in srgb, var(--task-status-color, #5c7bd9) 60%, #ffffff 40%);
|
||||
background: color-mix(in srgb, var(--task-status-color, #5c7bd9) 18%, #ffffff 82%);
|
||||
color: color-mix(in srgb, var(--task-status-color, #5c7bd9) 72%, #0f172a 28%);
|
||||
}
|
||||
|
||||
.markdown-body :deep(.task-inline-link:hover) {
|
||||
background: #dfeaff;
|
||||
border-color: #aac4ff;
|
||||
}
|
||||
|
||||
.markdown-body :deep(.task-inline-link i) {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Dark mode overrides */
|
||||
:root[data-bs-theme="dark"] .editor-toolbar {
|
||||
border-bottom-color: #3a3f4b;
|
||||
@@ -373,4 +811,63 @@ onMounted(async () => {
|
||||
:root[data-bs-theme="dark"] .danger-zone-copy {
|
||||
color: #fca5a5;
|
||||
}
|
||||
|
||||
:root[data-bs-theme="dark"] .task-mention-panel {
|
||||
border-color: #3a4558;
|
||||
background: #1f2733;
|
||||
}
|
||||
|
||||
:root[data-bs-theme="dark"] .task-mention-option:hover {
|
||||
background: #2b3646;
|
||||
}
|
||||
|
||||
:root[data-bs-theme="dark"] .task-picker {
|
||||
border-color: #3a3f4b !important;
|
||||
background: #21252e;
|
||||
}
|
||||
|
||||
:root[data-bs-theme="dark"] .task-picker-header {
|
||||
background: #21252e;
|
||||
border-bottom-color: #3a3f4b !important;
|
||||
}
|
||||
|
||||
:root[data-bs-theme="dark"] .task-picker-search {
|
||||
background: #21252e;
|
||||
border-bottom-color: #3a3f4b !important;
|
||||
}
|
||||
|
||||
:root[data-bs-theme="dark"] .task-picker-search .form-control {
|
||||
background: #1f2430;
|
||||
border-color: #3a3f4b;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
:root[data-bs-theme="dark"] .task-picker-item {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
:root[data-bs-theme="dark"] .task-picker-item:hover {
|
||||
background: #2d3748;
|
||||
}
|
||||
|
||||
:root[data-bs-theme="dark"] .task-picker-item small {
|
||||
color: #a8b4c7;
|
||||
}
|
||||
|
||||
:root[data-bs-theme="dark"] .markdown-body :deep(.task-inline-link) {
|
||||
border-color: #35508b;
|
||||
background: #1b2a4a;
|
||||
color: #9ec0ff;
|
||||
}
|
||||
|
||||
:root[data-bs-theme="dark"] .markdown-body :deep(.task-inline-link:hover) {
|
||||
background: #22345c;
|
||||
border-color: #4566ad;
|
||||
}
|
||||
|
||||
:root[data-bs-theme="dark"] .markdown-body :deep(.task-inline-status) {
|
||||
border-color: color-mix(in srgb, var(--task-status-color, #7aa2f7) 65%, #1e293b 35%);
|
||||
background: color-mix(in srgb, var(--task-status-color, #7aa2f7) 26%, #111827 74%);
|
||||
color: color-mix(in srgb, var(--task-status-color, #7aa2f7) 70%, #dbeafe 30%);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user