Skip to content
Merged
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
32 changes: 17 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ You may dynamically query view count data using the [Dataview plugin](https://ob

### Example 1 - Query view count using Dataview

If you have **Sync view count to frontmatter** enabled you may query the view count property from the frontmatter of each markdown note.
If you have **Sync view count to frontmatter** enabled you may query the view count property from the frontmatter of each note.

````markdown
```dataview
Expand All @@ -84,7 +84,7 @@ TABLE view-count AS "View Count" SORT view-count DESC LIMIT 10
Let's analyze this codeblock:

1. Render a table using the [Dataview Query Language](https://blacksmithgu.github.io/obsidian-dataview/queries/structure/)
2. Query the `view-count` property in each markdown note
2. Query the `view-count` property in each note
3. Display that property in a column called "View Count"
4. Sort the results in descending order (highest to lowest)
5. Limit the results to 10 notes
Expand Down Expand Up @@ -171,36 +171,38 @@ Here are the options:

## Settings

### View count type
### Count method

View count can defined 2 ways: the number of times a file has been opened or the number of unique days a file has been opened.
The method used to calculate view counts. This can be the total number of times a file has been opened or the unique days a file has been opened.

This option can be toggled whenever without affecting the functionality of the plugin. Both the values will be stored in the `.obsidian/view-count.json` file.
For example, consider a user that opened a file 3 times in 1 day. Using `Total times opened` the view count would be 3. However, using `Unique days opened` the view count would be 1.

A unique day is considered an opening of a file after 12 am your local time.
A unique day is considered an opening of a file after 12 am local time.

### Excluded paths

The folder paths that should be excluded from view count tracking. Please separate individual paths by commas. e.g. `folder1,folder2/inner`
The folder paths that should be excluded from view count tracking. Please separate individual paths by commas e.g. `folder1,folder2`

### Sync view count to frontmatter
### Sync view count

For each markdown note, save the current view count to a property in its frontmatter.
The view count for all files is stored in a JSON file located in the Obsidian configuration folder e.g. `.obsidian/view-count.json`. When `Sync view count` is enabled, the plugin will add a view count property to all notes and continuously update them to match the values stored in the JSON file.

This setting makes view count available on mobile. In the future, this toggling this setting will not be needed for mobile.
Enabling this setting makes view count visible on mobile devices.

The view count information for all files is stored in `.obsidian/view-count.json`. This setting is optional, as it duplicates data that already exists into the frontmatter of your markdown notes.
### Skip new notes

### View count property name
When enabled, new notes will not have a view count property added upon creation. However, if `Sync View Count` is enabled, a property will be added when the note is opened.

The name of the property that the view will be stored in.
### Property name

The name of the property that the view count will be stored in.

> [!WARNING]
> Please rename the existing view count property before updating this value. This will prevent the creation of a duplicate property. You can rename the existing property using the **rename** option in the **All Properties** view in the right sidebar.
> Please rename the existing view count property before updating this value. You can rename the existing property using the **rename** option in the **All Properties** view in the right sidebar.

### Templater delay

The delay in milliseconds before inserting view count frontmatter. Increase this value if you're using the Templater plugin and your template is being overwritten.
The time to wait in milliseconds before adding a view count property to a new note. You should increase this value if you're using the [Templater plugin](https://github.com/SilentVoid13/Templater) and the template applied during new note creation is being overwritten.

## License

Expand Down
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "view-count",
"name": "View Count",
"version": "2.4.1",
"version": "2.5.0",
"minAppVersion": "1.4.0",
"description": "Track view count for each vault file.",
"author": "DecafDev",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "obsidian-view-count",
"version": "2.4.1",
"version": "2.5.0",
"description": "Track view count for each vault file",
"main": "main.js",
"scripts": {
Expand Down
10 changes: 5 additions & 5 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { TimePeriod, TView, ViewCountPluginSettings } from "./types";
export const VIEW_COUNT_ITEM_VIEW = "view-count";

export const DEFAULT_SETTINGS: ViewCountPluginSettings = {
viewCountType: "unique-days-opened",
saveViewCountToFrontmatter: false,
viewCountPropertyName: "view-count",
countMethod: "unique-days-opened",
skipNewNotes: false,
syncToFrontmatter: false,
propertyName: "view-count",
pluginVersion: "",
logLevel: LOG_LEVEL_OFF,
excludedPaths: [],
templaterDelay: 0,
currentView: TView.VIEWS,
timePeriod: TimePeriod.DAYS_3,
itemCount: 20,
}

};
178 changes: 116 additions & 62 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { Plugin, TFile } from 'obsidian';
import ViewCountSettingsTab from './obsidian/view-count-settings-tab';
import { TView, TimePeriod, ViewCountPluginSettings } from './types';
import ViewCountItemView from './obsidian/view-count-item-view';
import { DEFAULT_SETTINGS, VIEW_COUNT_ITEM_VIEW } from './constants';
import Logger from 'js-logger';
import { formatMessageForLogger, stringToLogLevel } from './logger';
import _ from 'lodash';
import { isVersionLessThan } from './utils';
import ViewCountCache from './storage/view-count-cache';
import { migrateFileStorage } from './migration/migrate-file-storage';
import { migratePropertyStorage } from './migration/migrate-property-storage';
import { ViewCountPluginSettings_1_2_2 } from './types/types-1.2.2';
import { ViewCountPluginSettings_1_2_1 } from './types/types-1.2.1';
import { ViewCountPluginSettings_2_3_1 } from './types/types-2.3.1';
import { DurationFilter_2_4_0, TView_2_4_0, ViewCountPluginSettings_2_4_0 } from './types/types-2.4.0';
import { Plugin, TFile } from "obsidian";
import ViewCountSettingsTab from "./obsidian/view-count-settings-tab";
import { TView, TimePeriod, ViewCountPluginSettings } from "./types";
import ViewCountItemView from "./obsidian/view-count-item-view";
import { DEFAULT_SETTINGS, VIEW_COUNT_ITEM_VIEW } from "./constants";
import Logger from "js-logger";
import { formatMessageForLogger, stringToLogLevel } from "./logger";
import _ from "lodash";
import { isVersionLessThan } from "./utils";
import ViewCountCache from "./storage/view-count-cache";
import { migrateFileStorage } from "./migration/migrate-file-storage";
import { migratePropertyStorage } from "./migration/migrate-property-storage";
import { ViewCountPluginSettings_1_2_2 } from "./types/types-1.2.2";
import { ViewCountPluginSettings_1_2_1 } from "./types/types-1.2.1";
import { ViewCountPluginSettings_2_3_1 } from "./types/types-2.3.1";
import {
DurationFilter_2_4_0,
TView_2_4_0,
ViewCountPluginSettings_2_4_0,
} from "./types/types-2.4.0";
import { ViewCountPluginSettings_2_4_1 } from "./types/types-2.4.1";

export default class ViewCountPlugin extends Plugin {
settings: ViewCountPluginSettings = DEFAULT_SETTINGS;
Expand All @@ -32,7 +37,7 @@ export default class ViewCountPlugin extends Plugin {

this.registerView(
VIEW_COUNT_ITEM_VIEW,
(leaf) => new ViewCountItemView(leaf, this),
(leaf) => new ViewCountItemView(leaf, this)
);

this.addSettingTab(new ViewCountSettingsTab(this.app, this));
Expand All @@ -48,7 +53,7 @@ export default class ViewCountPlugin extends Plugin {
name: "Open view count view",
callback: () => {
this.openViewCountView(true);
}
},
});

this.app.workspace.onLayoutReady(async () => {
Expand All @@ -73,22 +78,27 @@ export default class ViewCountPlugin extends Plugin {
if (settingsVersion !== null) {
if (isVersionLessThan(settingsVersion, "1.2.2")) {
console.log("Migrating settings from 1.2.1 to 1.2.2");
const typedData = (data as unknown) as ViewCountPluginSettings_1_2_1;
const typedData =
data as unknown as ViewCountPluginSettings_1_2_1;
const newData: ViewCountPluginSettings_1_2_2 = {
...typedData,
templaterDelay: 0
}
templaterDelay: 0,
};
data = newData as unknown as Record<string, unknown>;
}
if (isVersionLessThan(settingsVersion, "2.0.0")) {
console.log("Migrating settings from 1.2.2 to 2.0.0");
const typedData = (data as unknown) as ViewCountPluginSettings_1_2_2;
const typedData =
data as unknown as ViewCountPluginSettings_1_2_2;

const newData: ViewCountPluginSettings_2_3_1 = {
...typedData,
saveViewCountToFrontmatter: typedData.storageType === "property" ? true : false,
viewCountType: typedData.incrementOnceADay ? "unique-days-opened" : "total-times-opened",
}
saveViewCountToFrontmatter:
typedData.storageType === "property" ? true : false,
viewCountType: typedData.incrementOnceADay
? "unique-days-opened"
: "total-times-opened",
};
data = newData as unknown as Record<string, unknown>;

this.settings_1_2_2 = structuredClone(typedData);
Expand All @@ -97,29 +107,48 @@ export default class ViewCountPlugin extends Plugin {

if (isVersionLessThan(settingsVersion, "2.4.0")) {
console.log("Migrating settings from 2.3.1 to 2.4.0");
const typedData = (data as unknown) as ViewCountPluginSettings_2_3_1;
const typedData =
data as unknown as ViewCountPluginSettings_2_3_1;
const newData: ViewCountPluginSettings_2_4_0 = {
...typedData,
durationFilter: DurationFilter_2_4_0.DAYS_3,
currentView: TView_2_4_0.MOST_VIEWED,
listSize: 20
}
listSize: 20,
};
data = newData as unknown as Record<string, unknown>;
}

if (isVersionLessThan(settingsVersion, "2.4.1")) {
console.log("Migrating settings from 2.4.0 to 2.4.1");
const typedData = (data as unknown) as ViewCountPluginSettings_2_4_0;
const newData: ViewCountPluginSettings = {
const typedData =
data as unknown as ViewCountPluginSettings_2_4_0;
const newData: ViewCountPluginSettings_2_4_1 = {
...typedData,
timePeriod: TimePeriod.DAYS_3,
currentView: TView.VIEWS,
itemCount: 20
}
itemCount: 20,
};
delete (newData as any).durationFilter;
delete (newData as any).listSize;
data = newData as unknown as Record<string, unknown>;
}

if (isVersionLessThan(settingsVersion, "2.5.0")) {
console.log("Migrating settings from 2.4.1 to 2.5.0");
const typedData =
data as unknown as ViewCountPluginSettings_2_4_1;
const newData: ViewCountPluginSettings = {
...typedData,
skipNewNotes: false,
countMethod: typedData.viewCountType,
propertyName: typedData.viewCountPropertyName,
syncToFrontmatter: typedData.saveViewCountToFrontmatter,
};
delete (newData as any).viewCountType;
delete (newData as any).viewCountPropertyName;
delete (newData as any).saveViewCountToFrontmatter;
data = newData as unknown as Record<string, unknown>;
}
}
}

Expand All @@ -140,38 +169,60 @@ export default class ViewCountPlugin extends Plugin {
throw new Error("View count cache is null");
}

this.registerEvent(this.app.workspace.on("file-open", async (file) => {
if (file === null) return;
await this.debounceHandleFileOpen(file);
}));

this.registerEvent(this.app.workspace.on("active-leaf-change", async (leaf) => {
if (leaf === null) return;
const viewType = leaf.view.getViewType();
this.registerEvent(
this.app.workspace.on("file-open", async (file) => {
if (file === null) return;
await this.debounceHandleFileOpen(file);
})
);

if (viewType !== "markdown" && viewType !== "image" && viewType !== "pdf" && viewType != "dataloom" && viewType != "audio" && viewType != "video") {
Logger.debug({ fileName: "main.ts", functionName: "active-leaf-change", message: "view count not supported for view type" }, { viewType });
this.viewCountStatusBarItem?.setText("");
return;
} else {
const file = (leaf.view as any).file as TFile | null;
if (file != null) {
await this.debounceHandleFileOpen(file);
this.registerEvent(
this.app.workspace.on("active-leaf-change", async (leaf) => {
if (leaf === null) return;
const viewType = leaf.view.getViewType();

if (
viewType !== "markdown" &&
viewType !== "image" &&
viewType !== "pdf" &&
viewType != "dataloom" &&
viewType != "audio" &&
viewType != "video"
) {
Logger.debug(
{
fileName: "main.ts",
functionName: "active-leaf-change",
message: "view count not supported for view type",
},
{ viewType }
);
this.viewCountStatusBarItem?.setText("");
return;
} else {
const file = (leaf.view as any).file as TFile | null;
if (file != null) {
await this.debounceHandleFileOpen(file);
}
}
}
}));
})
);

this.registerEvent(this.app.vault.on("rename", async (file, oldPath) => {
if (file instanceof TFile) {
await cache.renameEntry(file.path, oldPath);
}
}));
this.registerEvent(
this.app.vault.on("rename", async (file, oldPath) => {
if (file instanceof TFile) {
await cache.renameEntry(file.path, oldPath);
}
})
);

this.registerEvent(this.app.vault.on("delete", async (file) => {
if (file instanceof TFile) {
await cache.deleteEntry(file);
}
}));
this.registerEvent(
this.app.vault.on("delete", async (file) => {
if (file instanceof TFile) {
await cache.deleteEntry(file);
}
})
);
}

private openViewCountView(active: boolean) {
Expand All @@ -184,9 +235,12 @@ export default class ViewCountPlugin extends Plugin {
}
}


private async handleFileOpen(file: TFile) {
Logger.trace({ fileName: "main.ts", functionName: "handleFileOpen", message: "called" });
Logger.trace({
fileName: "main.ts",
functionName: "handleFileOpen",
message: "called",
});
if (this.viewCountCache === null) {
throw new Error("View count cache is null");
}
Expand Down
Loading