/* tasks查询工具顶部浮动,hover 出现 */
.block-language-tasks>div{
position: relative;
}
.plugin-tasks-toolbar {
position: absolute;
top: 0;
right: 0px;
/* transform: translateX(-50%); */
/* z-index: 999; */
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
}
.block-language-tasks:hover .plugin-tasks-toolbar,
.plugin-tasks-toolbar:hover {
opacity: 1;
pointer-events: auto;
}
datacore版本 无需启动dataview
```datacorejsx
// Helpers
function getFormattedWeekString(date) {
const year = date.format("GGGG");
const week = date.format("WW");
return `${year}-W${week}`;
}
// Ensures moment locale
window.moment.updateLocale("en", {
week: { dow: 1 },
});
// 使用 Datacore 内置的 dc.Markdown(底层为 Obsidian MarkdownRenderer),无需启用 Dataview
function TasksRenderer({ query, sourcePath }) {
const content = "```tasks\n" + query + "\n```";
return (
<div style={{ width: "100%" }}>
<dc.Markdown content={content} sourcePath={sourcePath} inline={false} />
</div>
);
}
// Main View Component exported to Datacore
return function WeeklyTaskReport() {
const fontSizeVar = 0.7;
const sourcePath = dc.useCurrentPath();
const today = dc.useMemo(() => window.moment(), []);
// States from global or default
const [selectedDate, setSelectedDate] = dc.useState(() => {
return window.tasksQueryData?.selectedDate
? window.moment(window.tasksQueryData.selectedDate)
: today.clone();
});
const [currentWeekOffset, setCurrentWeekOffset] = dc.useState(() => window.tasksQueryData?.currentWeekOffset || 0);
const [showTree, setShowTree] = dc.useState(() => window.tasksQueryData?.showTree ?? true);
const [showWeekTasks, setShowWeekTasks] = dc.useState(() => window.tasksQueryData?.showWeekTasks ?? false);
const [showInbox, setShowInbox] = dc.useState(() => window.tasksQueryData?.showInbox ?? false);
const [searchQuery, setSearchQuery] = dc.useState(() => window.tasksQueryData?.searchQuery || "");
const [searchExpanded, setSearchExpanded] = dc.useState(false);
const [searchInputVal, setSearchInputVal] = dc.useState(searchQuery);
// Persist global state when vital view states change
dc.useEffect(() => {
window.tasksQueryData = {
showTree,
showWeekTasks,
showInbox,
currentWeekOffset,
selectedDate: selectedDate.format("YYYY-MM-DD"),
searchQuery
};
}, [showTree, showWeekTasks, showInbox, currentWeekOffset, selectedDate, searchQuery]);
// 与周一~周日条一致:周一=0 … 周日=6(与 handleWeekInputChange 中 dayOffset 相同)
const mondayIndex = (m) => (m.day() === 0 ? 6 : m.day() - 1);
// Handlers:切换周时同步 selectedDate,否则「周报」query 仍用旧周的 weekStr
const handlePrevWeek = () => {
setCurrentWeekOffset((prev) => {
const next = prev - 1;
const idx = mondayIndex(selectedDate);
setSelectedDate(today.clone().startOf("week").add(next, "weeks").add(idx, "days"));
return next;
});
};
const handleNextWeek = () => {
setCurrentWeekOffset((prev) => {
const next = prev + 1;
const idx = mondayIndex(selectedDate);
setSelectedDate(today.clone().startOf("week").add(next, "weeks").add(idx, "days"));
return next;
});
};
const handleToday = () => {
setCurrentWeekOffset(0);
setSelectedDate(today.clone());
};
const syncWeekOffsetToDate = (m) => {
const w = m.clone().startOf("week").diff(today.clone().startOf("week"), "weeks");
setCurrentWeekOffset(w);
};
const handlePrevDay = () => {
const newDate = selectedDate.clone().subtract(1, "day");
setSelectedDate(newDate);
syncWeekOffsetToDate(newDate);
};
const handleNextDay = () => {
const newDate = selectedDate.clone().add(1, "day");
setSelectedDate(newDate);
syncWeekOffsetToDate(newDate);
};
const handleWeekInputChange = (e) => {
const val = e.target.value;
if (!val) return;
const [year, week] = val.split('-W').map(str => parseInt(str));
const firstWeek = today.clone().year(year).startOf('year').week(1);
const targetWeekStart = firstWeek.add(week - 1, 'weeks');
const newOffset = targetWeekStart.week() - today.week();
setCurrentWeekOffset(newOffset);
const dayOffset = selectedDate.day() === 0 ? 6 : selectedDate.day() - 1;
const newSelectedDate = today.clone().startOf("week").add(newOffset, "weeks").add(dayOffset, "days");
setSelectedDate(newSelectedDate);
};
const handleCreateTask = async () => {
const tasksApi = app.plugins.plugins['obsidian-tasks-plugin'].apiV1;
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
const sectionToRemove = document.querySelector('section.tasks-modal-dependencies-section');
if (sectionToRemove) {
sectionToRemove.remove();
console.log('已删除 section.tasks-modal-dependencies-section');
observer.disconnect();
}
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
let taskLine = await tasksApi.createTaskLineModal();
if (!taskLine) return;
observer.disconnect();
const dailySettings = app.plugins.plugins["periodic-notes"].settings["daily"];
const dailyFormat = dailySettings['format'];
const path = `${dailySettings.folder}/${window.moment().format(dailyFormat)}.md`;
let file = app.vault.getFileByPath(path);
if (!file) {
const templatePath = dailySettings['template'];
let data = "";
if (templatePath) {
const templateFile = app.vault.getFileByPath(templatePath + ".md");
if (templateFile) {
data = await app.vault.read(templateFile);
}
}
file = await app.vault.create(path, data);
}
await app.vault.append(file, taskLine + '\n');
};
const toggleTree = () => setShowTree(prev => !prev);
const toggleWeekTasks = () => {
setShowWeekTasks(prev => !prev);
setShowInbox(false);
};
const toggleInbox = () => {
setShowInbox(prev => !prev);
setShowWeekTasks(false);
};
const executeSearch = () => setSearchQuery(searchInputVal);
const clearSearch = () => {
setSearchInputVal("");
setSearchQuery("");
};
// Query Generation
const dateStr = selectedDate.format("YYYY-MM-DD");
const weekStr = selectedDate.format("YYYY-[W]WW");
const searchFilter = searchQuery.trim() ? `
filter by function \\
const searchQueryText = '${searchQuery}'.toLowerCase();\\
const searchKeywords = searchQueryText.split('\\\\s+').filter(k => k.length > 0);\\
const taskDesc = task.description.toLowerCase();\\
const taskFilename = task.file.filenameWithoutExtension.toLowerCase();\\
const taskTags = task.tags.map(t => t.toLowerCase()).join(' ');\\
const taskContent = taskDesc + ' ' + taskFilename + ' ' + taskTags;\\
return searchKeywords.every(keyword => taskContent.includes(keyword));
` : "";
const showTreeOption = showTree ? "show tree" : "";
const querStr = `OR {(happens in or before ${dateStr}) AND (not done) AND (happens on ${weekStr})} OR {(created in or before ${dateStr}) AND (not done) AND (happens on ${weekStr})}`;
const queryDayOfWeek = `
{(done on ${dateStr}) OR (happens on ${dateStr}) OR ( CANCELLED on ${dateStr})}\\
${querStr}\\
OR {filter by function \\
const filename = task.file.filenameWithoutExtension;\\
const date1 = window.moment(filename).format('YYYY-MM-DD');\\
return date1 === '${dateStr}';}
${searchFilter}
${showTreeOption}
group by filename
group by status.name reverse
short mode
sort by priority
is not recurring
`;
const queryWeek = `
group by function task.description.includes("http") ? "🌐阅读记录" : "📅任务记录"
{(done on ${weekStr}) OR (happens on ${weekStr})}
${searchFilter}
${showTreeOption}
is not recurring
group by done reverse
short mode
limit 100
`;
const queryInboxStr = `
not done
group by function task.due.category.groupText
${searchFilter}
${showTreeOption}
# 不包含的路径
path does not include "700【模板】Template"
# 不包含看板文件的任务
filter by function !task.file.hasProperty('kanban-plugin')
short mode
hide tags
limit groups 10
`;
const finalQuery = showWeekTasks ? queryWeek : (showInbox ? queryInboxStr : queryDayOfWeek);
// Common Button Style
const getBtnStyle = (isActive) => ({
border: "none",
margin: "0",
fontSize: `${fontSizeVar}rem`,
cursor: "pointer",
padding: "5px 10px",
color: isActive ? "var(--text-on-accent)" : "var(--text-on-accent)",
backgroundColor: isActive ? "var(--interactive-accent)" : "var(--interactive-accent)",
});
const getToggleBtnStyle = (isActive) => ({
...getBtnStyle(true),
color: isActive ? "var(--text-on-accent)" : "var(--text-normal)",
backgroundColor: isActive ? "var(--interactive-accent)" : "transparent",
});
return (
<div style={{ width: "100%", display: "flex", flexDirection: "column", alignItems: "center" }}>
<div style={{ textAlign: "center", marginBottom: "10px", width: "100%" }}>
{/* Control Buttons Container */}
<div className="task-control-buttons-container" style={{ width: "100%", display: "flex", alignItems: "center", justifyContent: "center", gap: "5px" }}>
<button style={getToggleBtnStyle(showInbox)} onClick={toggleInbox}>Inbox</button>
<button style={getToggleBtnStyle(showWeekTasks)} onClick={toggleWeekTasks}>周报</button>
<button style={getBtnStyle(true)} onClick={handlePrevWeek}>←</button>
<input
type="week"
value={getFormattedWeekString(today.clone().startOf('week').add(currentWeekOffset, 'weeks'))}
onChange={handleWeekInputChange}
style={{
fontSize: `${fontSizeVar + 0.2}rem`,
color: "var(--text-normal)",
backgroundColor: "var(--background-primary)",
border: "1px solid var(--background-modifier-border)",
borderRadius: "4px",
padding: "0.2rem",
outline: "none"
}}
/>
<button style={getBtnStyle(true)} onClick={handleNextWeek}>→</button>
<button style={getToggleBtnStyle(showTree)} onClick={toggleTree}>↳</button>
<button type="button" title="上一天" style={{ ...getBtnStyle(true), padding: "5px 6px" }} onClick={handlePrevDay}>‹</button>
<button style={getBtnStyle(true)} onClick={handleToday} onDoubleClick={() => app.commands.executeCommandById("daily-notes")}>今日</button>
<button type="button" title="下一天" style={{ ...getBtnStyle(true), padding: "5px 6px" }} onClick={handleNextDay}>›</button>
<button style={getBtnStyle(true)} onClick={handleCreateTask}>✚</button>
<button
style={{...getBtnStyle(true), padding: "5px 8px", borderRadius: "4px"}}
onClick={() => setSearchExpanded(!searchExpanded)}
>
🔍
</button>
</div>
{/* Search Row Container */}
<div style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "100%",
height: searchExpanded ? "auto" : "0",
overflow: "hidden",
marginTop: searchExpanded ? "5px" : "0"
}}>
<div style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
gap: "3px",
width: searchExpanded ? "100%" : "0",
opacity: searchExpanded ? 1 : 0,
transition: "all 0.2s"
}}>
<input
type="text"
placeholder="搜索..."
value={searchInputVal}
onChange={(e) => setSearchInputVal(e.target.value)}
onKeyPress={(e) => e.key === "Enter" && executeSearch()}
style={{
fontSize: `${fontSizeVar}rem`,
color: "var(--text-normal)",
backgroundColor: "var(--background-primary)",
border: "1px solid var(--background-modifier-border)",
borderRadius: "4px",
padding: "3px 8px",
outline: "none",
flex: "1"
}}
/>
<button
style={{...getBtnStyle(true), borderRadius: "4px", padding: "5px 6px", display: searchExpanded ? "block" : "none"}}
onClick={clearSearch}
>清空</button>
<button
style={{...getBtnStyle(true), borderRadius: "4px", padding: "5px 6px", display: searchExpanded ? "block" : "none"}}
onClick={executeSearch}
>搜索</button>
</div>
</div>
</div>
{/* Week Selection Buttons Container */}
<div className="week-select-buttons-container" style={{ display: "flex", justifyContent: "center", width: "100%" }}>
{["周一", "周二", "周三", "周四", "周五", "周六", "周天"].map((dayName, index) => {
const date = today.clone().startOf("week").add(currentWeekOffset, "weeks").add(index, "days");
const isSelected = selectedDate.format("YYYY-MM-DD") === date.format("YYYY-MM-DD");
return (
<button
key={index}
className="week-day-button"
onClick={() => setSelectedDate(date)}
style={{
border: "none",
borderRadius: "0px",
cursor: "pointer",
fontSize: `${fontSizeVar}rem`,
flex: "1 1 auto",
color: isSelected ? "var(--text-on-accent)" : "var(--text-normal)",
backgroundColor: isSelected ? "var(--interactive-accent)" : "transparent",
}}
>
{`${dayName}(${date.format("DD")})`}
</button>
);
})}
</div>
{/* Tasks Plugin Codeblock Render Area */}
<TasksRenderer query={finalQuery} sourcePath={sourcePath} />
</div>
);
}
```
1 个赞
