Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
994 changes: 597 additions & 397 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"esbuild": "0.17.3",
"esbuild": "^0.27.3",
"obsidian": "latest",
"obsidian-dataview": "^0.5.64",
"obsidian-dataview": "^0.5.68",
"tslib": "2.4.0",
"typescript": "4.7.4"
},
Expand Down
1 change: 1 addition & 0 deletions src/i18/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export class En implements Local {
form_count_field_count_field_type_page_prop = "Page Property";

form_count_field_count_field_type_task_prop = "Task Property";
form_count_field_count_field_type_query_instances = "Query instances (count occurrences per page)";
form_title_font_size_label = "Title font Size";
form_number_input_min_warning = "allow min value is {value}";
form_number_input_max_warning = "allow max value is {value}";
Expand Down
1 change: 1 addition & 0 deletions src/i18/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export interface Local {
form_count_field_count_field_type_page_prop: string;

form_count_field_count_field_type_task_prop: string;
form_count_field_count_field_type_query_instances: string;
form_title_font_size_label: string;
form_number_input_min_warning: string;
form_number_input_max_warning: string;
Expand Down
1 change: 1 addition & 0 deletions src/i18/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export class Zh implements Local {
form_count_field_count_field_type_page_prop = "文档属性";

form_count_field_count_field_type_task_prop = "任务属性";
form_count_field_count_field_type_query_instances = "查询出现次数(每页统计)"; // Likely incorrect translation (I only know english)
form_title_font_size_label = "标题字体大小";
form_number_input_min_warning = "允许的最小值为 {value}";
form_number_input_max_warning = "允许的最大值为 {value}";
Expand Down
12 changes: 6 additions & 6 deletions src/processor/codeBlockProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ export class CodeBlockProcessor {
}

async renderFromYaml(graphConfig: YamlGraphConfig, el: HTMLElement, app: App) {
const renderCallback = () => {
const renderCallback = async () => {
try {
// validate
YamlGraphConfig.validate(graphConfig);
const data = this.dataSourceQuery.query(
const data = await this.dataSourceQuery.query(
graphConfig.dataSource,
app
);
Expand Down Expand Up @@ -72,12 +72,12 @@ export class CodeBlockProcessor {
});
}
if (dv.index.initialized) {
renderCallback();
await renderCallback();
} else {
// @ts-ignore
app.metadataCache.on("dataview:index-ready", () => {
renderCallback();
})
app.metadataCache.on("dataview:index-ready", async () => {
await renderCallback();
});
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/processor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export class YamlGraphConfig {
mainContainerStyle?: Partial<CSSStyleDeclaration>;
cellStyle?: Partial<CSSStyleDeclaration>;
cellStyleRules?: CellStyleRule[];
countQueryInstances?: boolean; /** When true (and using default dataSource), count query occurrences per page instead of page count */

// deprecated
days?: number;
Expand Down Expand Up @@ -76,6 +77,7 @@ export class YamlGraphConfig {
this.dateFieldFormat = undefined;
this.dateField = undefined;
this.days = undefined;
this.countQueryInstances = undefined;
}

static toContributionGraphConfig(
Expand Down
5 changes: 4 additions & 1 deletion src/processor/yamlConfigReconciler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ export class YamlConfigReconciler {
format: yamlConfig.dateFieldFormat,
},
countField: {
type: "DEFAULT",
type: yamlConfig.countQueryInstances
? "QUERY_INSTANCES"
: "DEFAULT",
},
};
}
Expand All @@ -39,6 +41,7 @@ export class YamlConfigReconciler {
yamlConfig.query = undefined;
yamlConfig.dateField = undefined;
yamlConfig.dateFieldFormat = undefined;
yamlConfig.countQueryInstances = undefined;
return yamlConfig;
}
}
45 changes: 38 additions & 7 deletions src/query/baseDataviewSourceQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,18 @@ import { dataviewDataFilterChain } from "./filter/dataviewDataFilter";
export abstract class BaseDataviewDataSourceQuery {
abstract accept(source: DataSource): boolean;

query(source: DataSource, app: App): Contribution[] {
async query(source: DataSource, app: App): Promise<Contribution[]> {
this.reconcileSourceValueIfNotExists(source);
const dv = this.checkAndGetApi(app);
const data = this.doQuery(dv, source);
const queryData = this.mapToQueryData(data, source);
let queryData = this.mapToQueryData(data, source);
if (typeof (this as any).enrichQueryData === "function") {
queryData = await (this as any).enrichQueryData(
queryData,
source,
app
);
}
const unsatisfiedData = queryData.filter((item) => !item.date);
if (unsatisfiedData.length > 0) {
console.warn(
Expand Down Expand Up @@ -50,20 +57,23 @@ export abstract class BaseDataviewDataSourceQuery {
label = item.raw.text;
}

const value =
const itemValue =
this.getAndConvertValueByCustomizeProperty(
item,
source.countField?.type,
source.countField?.value
);

if (source.countField?.type == "PAGE_PROPERTY") {
label += ` [${source.countField?.value}:${value}]`;
label += ` [${source.countField?.value}:${itemValue}]`;
}
if (source.countField?.type == "QUERY_INSTANCES") {
label += ` [instances: ${itemValue}]`;
}

return {
label: label,
value: value,
value: itemValue,
link: {
// @ts-ignore
href: item.raw.file.path,
Expand Down Expand Up @@ -237,6 +247,19 @@ export abstract class BaseDataviewDataSourceQuery {
return groupData.length;
}

if (propertyType === "QUERY_INSTANCES") {
return groupData
.map((item) =>
this.getAndConvertValueByCustomizeProperty(
item,
"QUERY_INSTANCES",
"_instanceCount"
)
)
.array()
.reduce((a, b) => a + b, 0);
}

if (propertyName) {
return groupData
.map((item) => {
Expand All @@ -257,7 +280,10 @@ export abstract class BaseDataviewDataSourceQuery {
propertyType?: CountFieldType,
propertyName?: string
): number {
if (propertyName) {
const effectiveName =
propertyName ||
(propertyType === "QUERY_INSTANCES" ? "_instanceCount" : undefined);
if (effectiveName) {
let propertySource: PropertySource;
switch (propertyType) {
case "PAGE_PROPERTY":
Expand All @@ -266,6 +292,9 @@ export abstract class BaseDataviewDataSourceQuery {
case "TASK_PROPERTY":
propertySource = "TASK";
break;
case "QUERY_INSTANCES":
propertySource = "PAGE";
break;
default:
propertySource = "UNKNOWN";
break;
Expand All @@ -274,7 +303,7 @@ export abstract class BaseDataviewDataSourceQuery {
const r = this.getValueByCustomizeProperty(
item.raw,
propertySource,
propertyName
effectiveName
);
if (r == undefined || r == null) {
return 0;
Expand Down Expand Up @@ -317,6 +346,8 @@ export abstract class BaseDataviewDataSourceQuery {
return "PAGE";
case "TASK_PROPERTY":
return "TASK";
case "QUERY_INSTANCES":
return "PAGE";
default:
return "UNKNOWN";
}
Expand Down
63 changes: 62 additions & 1 deletion src/query/dataviewPageDataSourceQuery.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { App } from "obsidian";
import { DataArray, DataviewApi, Literal } from "obsidian-dataview";
import { BaseDataviewDataSourceQuery } from "./baseDataviewSourceQuery";
import { DataSource, PropertySource } from "./types";
import { Data, DataSource, PropertySource } from "./types";

export class DataviewPageDataSourceQuery extends BaseDataviewDataSourceQuery {
accept(source: DataSource): boolean {
Expand All @@ -14,11 +15,71 @@ export class DataviewPageDataSourceQuery extends BaseDataviewDataSourceQuery {
return dv.pages(source.value);
}

async enrichQueryData(
queryData: DataArray<Data<Record<string, Literal>>>,
source: DataSource,
app: App
): Promise<DataArray<Data<Record<string, Literal>>>> {
if (
source.type !== "PAGE" ||
source.countField?.type !== "QUERY_INSTANCES"
) {
return queryData;
}
const pattern = this.getSearchPatternFromQuery(source.value);
const arr = queryData.array();
for (const data of arr) {
const raw = data.raw as Record<string, Literal & { _instanceCount?: number }>;
// @ts-ignore - file exists on page rows
const path = data.raw.file?.path;
if (!path) {
raw._instanceCount = 0;
continue;
}
try {
const content = await app.vault.adapter.read(path);
raw._instanceCount = this.countOccurrences(content, pattern);
} catch {
raw._instanceCount = 0;
}
}
return queryData;
}

private getSearchPatternFromQuery(value: string): string {
const trimmed = value.trim();
if (
trimmed.length >= 2 &&
trimmed.startsWith('"') &&
trimmed.endsWith('"')
) {
return trimmed.slice(1, -1);
}
return trimmed;
}

private countOccurrences(content: string, pattern: string): number {
if (!pattern) return 0;
let count = 0;
let pos = 0;
while (true) {
const i = content.indexOf(pattern, pos);
if (i === -1) break;
count++;
pos = i + 1;
}
return count;
}

getValueByCustomizeProperty(
data: Record<string, Literal>,
propertyType: PropertySource,
propertyName: string
): any {
if (propertyType === "PAGE" && propertyName === "_instanceCount") {
const count = (data as Record<string, unknown>)._instanceCount;
return typeof count === "number" ? count : 0;
}
if (propertyType === "PAGE") {
return data[propertyName];
}
Expand Down
7 changes: 6 additions & 1 deletion src/query/filter/dataviewDataFilter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ export class CountFieldDataViewDataFilter implements DataViewDataFilter {
if (!source.countField) {
return true;
}
if (source.countField.type == 'DEFAULT') {
if (source.countField.type == "DEFAULT") {
return true;
}
if (source.countField.type == "QUERY_INSTANCES") {
return true;
}
const propertyType = getPropertySourceByCountFieldType(
Expand Down Expand Up @@ -88,6 +91,8 @@ function getPropertySourceByCountFieldType(
return "PAGE";
case "TASK_PROPERTY":
return "TASK";
case "QUERY_INSTANCES":
return "PAGE";
default:
return "UNKNOWN";
}
Expand Down
2 changes: 1 addition & 1 deletion src/query/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const FILE_MTIME_FIELD = "file.mtime";

export const FILE_NAME = "file.name";

export type CountFieldType = "DEFAULT" | "PAGE_PROPERTY" | "TASK_PROPERTY";
export type CountFieldType = "DEFAULT" | "PAGE_PROPERTY" | "TASK_PROPERTY" | "QUERY_INSTANCES";

export type PropertySource = "UNKNOWN" | "PAGE" | "TASK";

Expand Down
7 changes: 7 additions & 0 deletions src/view/form/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ export const countFieldTypes = (
},
];

if (source === "PAGE") {
options.push({
label: Locals.get().form_count_field_count_field_type_query_instances,
value: "QUERY_INSTANCES",
});
}

if (source === "ALL_TASK" || source === "TASK_IN_SPECIFIC_PAGE") {
options.push({
label: Locals.get().form_count_field_count_field_type_task_prop,
Expand Down
834 changes: 833 additions & 1 deletion styles.css

Large diffs are not rendered by default.