2064 lines
80 KiB
JavaScript
2064 lines
80 KiB
JavaScript
/*
|
|
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
|
if you want to view the source, please visit the github repository of this plugin
|
|
*/
|
|
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
|
|
// main.ts
|
|
var main_exports = {};
|
|
__export(main_exports, {
|
|
default: () => NovelWordCountPlugin
|
|
});
|
|
module.exports = __toCommonJS(main_exports);
|
|
|
|
// logic/debug.ts
|
|
var DebugHelper = class {
|
|
constructor() {
|
|
this.debugMode = false;
|
|
this.idCounter = 0;
|
|
}
|
|
setDebugMode(debug) {
|
|
this.debugMode = debug;
|
|
}
|
|
debug(...args) {
|
|
if (!this.debugMode) {
|
|
return;
|
|
}
|
|
console.log("novel-word-count:", ...args);
|
|
}
|
|
error(message) {
|
|
if (!this.debugMode) {
|
|
return;
|
|
}
|
|
console.error(message);
|
|
}
|
|
debugStart(name) {
|
|
if (!this.debugMode) {
|
|
return () => {
|
|
};
|
|
}
|
|
var qualifiedName = `novel-word-count|${name} (${++this.idCounter})`;
|
|
console.time(qualifiedName);
|
|
return () => console.timeEnd(qualifiedName);
|
|
}
|
|
};
|
|
|
|
// logic/event.ts
|
|
var import_obsidian = require("obsidian");
|
|
|
|
// logic/cancellation.ts
|
|
var CANCEL = Symbol("Cancel");
|
|
var CancellationToken = class {
|
|
constructor() {
|
|
this._isCancelled = false;
|
|
}
|
|
get isCancelled() {
|
|
return this._isCancelled;
|
|
}
|
|
[CANCEL]() {
|
|
this._isCancelled = true;
|
|
}
|
|
};
|
|
var CancellationTokenSource = class {
|
|
constructor() {
|
|
this.token = new CancellationToken();
|
|
}
|
|
cancel() {
|
|
this.token[CANCEL]();
|
|
}
|
|
};
|
|
|
|
// logic/event.ts
|
|
var EventHelper = class {
|
|
constructor(plugin, app, debugHelper, fileHelper) {
|
|
this.plugin = plugin;
|
|
this.app = app;
|
|
this.debugHelper = debugHelper;
|
|
this.fileHelper = fileHelper;
|
|
this.cancellationSources = [];
|
|
}
|
|
async handleEvents() {
|
|
const debouncedFileModified = (0, import_obsidian.debounce)(async (file) => {
|
|
const countToken = this.registerNewCountToken();
|
|
await this.fileHelper.updateFileCounts(
|
|
file,
|
|
this.plugin.savedData.cachedCounts,
|
|
countToken.token
|
|
);
|
|
this.cancelToken(countToken);
|
|
await this.plugin.updateDisplayedCounts(file);
|
|
this.plugin.saveSettingsDebounced();
|
|
}, 500);
|
|
this.plugin.registerEvent(
|
|
this.app.metadataCache.on("changed", async (file) => {
|
|
this.debugHelper.debug(
|
|
"[changed] metadataCache hook fired, scheduling file for analysis",
|
|
file.path
|
|
);
|
|
debouncedFileModified(file);
|
|
})
|
|
);
|
|
this.app.workspace.onLayoutReady(() => {
|
|
this.plugin.registerEvent(
|
|
this.app.vault.on("create", async (file) => {
|
|
this.debugHelper.debug(
|
|
"[create] vault hook fired, analyzing file",
|
|
file.path
|
|
);
|
|
const countToken = this.registerNewCountToken();
|
|
await this.fileHelper.updateFileCounts(
|
|
file,
|
|
this.plugin.savedData.cachedCounts,
|
|
countToken.token
|
|
);
|
|
this.cancelToken(countToken);
|
|
await this.plugin.updateDisplayedCounts(file);
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
this.plugin.registerEvent(
|
|
this.app.vault.on("modify", async (file) => {
|
|
this.debugHelper.debug(
|
|
"[modify] vault hook fired, scheduling file for analysis",
|
|
file.path
|
|
);
|
|
debouncedFileModified(file);
|
|
})
|
|
);
|
|
});
|
|
this.plugin.registerEvent(
|
|
this.app.vault.on("delete", async (file) => {
|
|
this.debugHelper.debug(
|
|
"[delete] vault hook fired, forgetting file",
|
|
file.path
|
|
);
|
|
this.fileHelper.removeFileCounts(
|
|
file.path,
|
|
this.plugin.savedData.cachedCounts
|
|
);
|
|
await this.plugin.updateDisplayedCounts(file);
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
this.plugin.registerEvent(
|
|
this.app.vault.on("rename", async (file, oldPath) => {
|
|
if (file instanceof import_obsidian.TFolder) {
|
|
return;
|
|
}
|
|
this.debugHelper.debug(
|
|
"[rename] vault hook fired, recounting file",
|
|
file.path
|
|
);
|
|
this.fileHelper.removeFileCounts(
|
|
oldPath,
|
|
this.plugin.savedData.cachedCounts
|
|
);
|
|
const countToken = this.registerNewCountToken();
|
|
await this.fileHelper.updateFileCounts(
|
|
file,
|
|
this.plugin.savedData.cachedCounts,
|
|
countToken.token
|
|
);
|
|
this.cancelToken(countToken);
|
|
await this.plugin.updateDisplayedCounts(file);
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
const reshowCountsIfNeeded = async (hookName) => {
|
|
this.debugHelper.debug(`[${hookName}] hook fired`);
|
|
const fileExplorerLeaf = await this.plugin.getFileExplorerLeaf();
|
|
if (this.isContainerTouched(fileExplorerLeaf)) {
|
|
this.debugHelper.debug(
|
|
"container already touched, skipping display update"
|
|
);
|
|
return;
|
|
}
|
|
this.debugHelper.debug("container is clean, updating display");
|
|
await this.plugin.updateDisplayedCounts();
|
|
};
|
|
this.plugin.registerEvent(
|
|
this.app.workspace.on(
|
|
"layout-change",
|
|
(0, import_obsidian.debounce)(reshowCountsIfNeeded.bind(this, "layout-change"), 1e3)
|
|
)
|
|
);
|
|
}
|
|
isContainerTouched(leaf) {
|
|
const container = leaf.view.containerEl;
|
|
return container.className.includes("novel-word-count--");
|
|
}
|
|
async reinitializeAllCounts() {
|
|
this.cancelAllCountTokens();
|
|
const countToken = this.registerNewCountToken();
|
|
this.debugHelper.debug("refreshAllCounts");
|
|
this.plugin.savedData.cachedCounts = await this.fileHelper.initializeAllFileCounts(
|
|
countToken.token
|
|
);
|
|
this.cancelToken(countToken);
|
|
await this.plugin.saveSettings();
|
|
}
|
|
/*
|
|
CANCELLATION HANDLING
|
|
*/
|
|
registerNewCountToken() {
|
|
const cancellationSource = new CancellationTokenSource();
|
|
this.cancellationSources.push(cancellationSource);
|
|
return cancellationSource;
|
|
}
|
|
cancelToken(source) {
|
|
source.cancel();
|
|
if (this.cancellationSources.includes(source)) {
|
|
this.cancellationSources.splice(
|
|
this.cancellationSources.indexOf(source),
|
|
1
|
|
);
|
|
}
|
|
}
|
|
cancelAllCountTokens() {
|
|
for (const source of this.cancellationSources) {
|
|
source.cancel();
|
|
}
|
|
this.cancellationSources = [];
|
|
}
|
|
};
|
|
|
|
// logic/file.ts
|
|
var import_obsidian2 = require("obsidian");
|
|
|
|
// logic/settings.ts
|
|
var $CountType = /* @__PURE__ */ (($CountType2) => {
|
|
$CountType2["None"] = "none";
|
|
$CountType2["Word"] = "word";
|
|
$CountType2["Page"] = "page";
|
|
$CountType2["PageDecimal"] = "pagedecimal";
|
|
$CountType2["Linebreak"] = "linebreak";
|
|
$CountType2["ReadTime"] = "readtime";
|
|
$CountType2["PercentGoal"] = "percentgoal";
|
|
$CountType2["Note"] = "note";
|
|
$CountType2["Character"] = "character";
|
|
$CountType2["Link"] = "link";
|
|
$CountType2["Embed"] = "embed";
|
|
$CountType2["Alias"] = "alias";
|
|
$CountType2["Created"] = "created";
|
|
$CountType2["Modified"] = "modified";
|
|
$CountType2["FileSize"] = "filesize";
|
|
$CountType2["FrontmatterKey"] = "frontmatterKey";
|
|
$CountType2["TrackSession"] = "tracksession";
|
|
return $CountType2;
|
|
})($CountType || {});
|
|
var COUNT_TYPES = Object.values($CountType);
|
|
var SESSION_COUNT_TYPES = [
|
|
"word" /* Word */,
|
|
"page" /* Page */,
|
|
"pagedecimal" /* PageDecimal */,
|
|
"linebreak" /* Linebreak */,
|
|
"note" /* Note */,
|
|
"character" /* Character */
|
|
];
|
|
var COUNT_TYPE_DISPLAY_STRINGS = {
|
|
["none" /* None */]: "None",
|
|
["word" /* Word */]: "Word Count",
|
|
["page" /* Page */]: "Page Count",
|
|
["pagedecimal" /* PageDecimal */]: "Page Count (decimal)",
|
|
["linebreak" /* Linebreak */]: "Line Break Count",
|
|
["readtime" /* ReadTime */]: "Reading Time",
|
|
["percentgoal" /* PercentGoal */]: "% of Word Goal",
|
|
["note" /* Note */]: "Note Count",
|
|
["character" /* Character */]: "Character Count",
|
|
["link" /* Link */]: "Link Count",
|
|
["embed" /* Embed */]: "Embed Count",
|
|
["alias" /* Alias */]: "First Alias",
|
|
["created" /* Created */]: "Created Date",
|
|
["modified" /* Modified */]: "Last Updated Date",
|
|
["filesize" /* FileSize */]: "File Size",
|
|
["frontmatterKey" /* FrontmatterKey */]: "Frontmatter Key",
|
|
["tracksession" /* TrackSession */]: "Track Session"
|
|
};
|
|
var COUNT_TYPE_DESCRIPTIONS = {
|
|
["none" /* None */]: "Hidden.",
|
|
["word" /* Word */]: "Total words.",
|
|
["page" /* Page */]: "Total pages, rounded up.",
|
|
["pagedecimal" /* PageDecimal */]: "Total pages, precise to 2 digits after the decimal.",
|
|
["linebreak" /* Linebreak */]: "Newlines (\xB6), including empty lines.",
|
|
["readtime" /* ReadTime */]: "Estimated time to read the note.",
|
|
["percentgoal" /* PercentGoal */]: "Set a word goal by adding the 'word-goal' property to a note.",
|
|
["note" /* Note */]: "Total notes.",
|
|
["character" /* Character */]: "Total characters (letters, symbols, numbers, and spaces).",
|
|
["link" /* Link */]: "Total links to other notes.",
|
|
["embed" /* Embed */]: "Total embedded images, files, and notes.",
|
|
["alias" /* Alias */]: "The first alias property of each note.",
|
|
["created" /* Created */]: "Creation date. (On folders: earliest creation date of any note.)",
|
|
["modified" /* Modified */]: "Date of last edit. (On folders: latest edit date of any note.)",
|
|
["filesize" /* FileSize */]: "Total size on hard drive.",
|
|
["frontmatterKey" /* FrontmatterKey */]: "Key in the frontmatter block.",
|
|
["tracksession" /* TrackSession */]: "Track progress since last Obsidian startup, plugin init, settings change, or recount"
|
|
};
|
|
var UNFORMATTABLE_COUNT_TYPES = [
|
|
"none" /* None */,
|
|
"alias" /* Alias */,
|
|
"filesize" /* FileSize */,
|
|
"readtime" /* ReadTime */
|
|
];
|
|
var COUNT_TYPE_DEFAULT_SHORT_SUFFIXES = {
|
|
["word" /* Word */]: "w",
|
|
["page" /* Page */]: "p",
|
|
["pagedecimal" /* PageDecimal */]: "p",
|
|
["linebreak" /* Linebreak */]: "\xB6",
|
|
["percentgoal" /* PercentGoal */]: "%",
|
|
["note" /* Note */]: "n",
|
|
["character" /* Character */]: "ch",
|
|
["link" /* Link */]: "x",
|
|
["embed" /* Embed */]: "em",
|
|
["created" /* Created */]: "/c",
|
|
["modified" /* Modified */]: "/u",
|
|
["frontmatterKey" /* FrontmatterKey */]: "",
|
|
["tracksession" /* TrackSession */]: "/s"
|
|
};
|
|
function getDescription(countType) {
|
|
return `[${COUNT_TYPE_DISPLAY_STRINGS[countType]}] ${COUNT_TYPE_DESCRIPTIONS[countType]}`;
|
|
}
|
|
var ALIGNMENT_TYPES = [
|
|
"inline" /* Inline */,
|
|
"right" /* Right */,
|
|
"below" /* Below */
|
|
];
|
|
var DEFAULT_SETTINGS = {
|
|
// FORMATTING
|
|
useAdvancedFormatting: false,
|
|
// NOTES
|
|
countType: "word" /* Word */,
|
|
countConfig: {
|
|
customSuffix: "w",
|
|
$sessionCountType: "word" /* Word */
|
|
},
|
|
countType2: "none" /* None */,
|
|
countConfig2: {
|
|
$sessionCountType: "word" /* Word */
|
|
},
|
|
countType3: "none" /* None */,
|
|
countConfig3: {
|
|
$sessionCountType: "word" /* Word */
|
|
},
|
|
pipeSeparator: "|",
|
|
abbreviateDescriptions: false,
|
|
alignment: "inline" /* Inline */,
|
|
// FOLDERS
|
|
showSameCountsOnFolders: true,
|
|
folderCountType: "word" /* Word */,
|
|
folderCountConfig: {
|
|
customSuffix: "w",
|
|
$sessionCountType: "word" /* Word */
|
|
},
|
|
folderCountType2: "none" /* None */,
|
|
folderCountConfig2: {
|
|
$sessionCountType: "word" /* Word */
|
|
},
|
|
folderCountType3: "none" /* None */,
|
|
folderCountConfig3: {
|
|
$sessionCountType: "word" /* Word */
|
|
},
|
|
folderPipeSeparator: "|",
|
|
folderAbbreviateDescriptions: false,
|
|
folderAlignment: "inline" /* Inline */,
|
|
// ROOT
|
|
showSameCountsOnRoot: true,
|
|
rootCountType: "word" /* Word */,
|
|
rootCountConfig: {
|
|
customSuffix: "w",
|
|
$sessionCountType: "word" /* Word */
|
|
},
|
|
rootCountType2: "none" /* None */,
|
|
rootCountConfig2: {
|
|
$sessionCountType: "word" /* Word */
|
|
},
|
|
rootCountType3: "none" /* None */,
|
|
rootCountConfig3: {
|
|
$sessionCountType: "word" /* Word */
|
|
},
|
|
rootPipeSeparator: "|",
|
|
rootAbbreviateDescriptions: false,
|
|
// ADVANCED
|
|
showAdvanced: false,
|
|
labelOpacity: 0.75,
|
|
wordsPerMinute: 265,
|
|
charsPerMinute: 500,
|
|
wordsPerPage: 300,
|
|
charsPerPage: 1500,
|
|
charsPerPageIncludesWhitespace: false,
|
|
characterCountType: "AllCharacters" /* StringLength */,
|
|
pageCountType: "ByWords" /* ByWords */,
|
|
includeDirectories: "",
|
|
excludeComments: false,
|
|
excludeCodeBlocks: false,
|
|
excludeNonVisibleLinkPortions: false,
|
|
excludeFootnotes: false,
|
|
momentDateFormat: "",
|
|
debugMode: false
|
|
};
|
|
|
|
// logic/parser.ts
|
|
var cjkRegex = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/gu;
|
|
var allSymbolsRegex = /[\p{S}\p{P}]/gu;
|
|
function countMarkdown(content, config) {
|
|
content = removeNonCountedContent(content, config);
|
|
let wordSequences = content.replace(cjkRegex, " ").replace(allSymbolsRegex, "").trim().split(/\s+/);
|
|
if (wordSequences.length === 1 && wordSequences[0] === "") {
|
|
wordSequences = [];
|
|
}
|
|
let lineSequences = content.split("\n");
|
|
if (lineSequences.length === 1 && lineSequences[0] === "") {
|
|
lineSequences = [];
|
|
}
|
|
const result = {
|
|
charCount: content.length,
|
|
nonWhitespaceCharCount: countNonWhitespaceCharacters(content),
|
|
spaceDelimitedWordCount: wordSequences.length,
|
|
cjkWordCount: (content.match(cjkRegex) || []).length,
|
|
newlineCount: lineSequences.length
|
|
};
|
|
return result;
|
|
}
|
|
var whitespaceRegex = /\s/g;
|
|
function countNonWhitespaceCharacters(content) {
|
|
return content.replace(whitespaceRegex, "").length;
|
|
}
|
|
function removeNonCountedContent(content, config) {
|
|
if (config.excludeCodeBlocks) {
|
|
content = content.replace(/(```.+?```)/gims, "");
|
|
}
|
|
if (config.excludeComments) {
|
|
content = content.replace(/(%%.+?%%|<!--.+?-->)/gims, "");
|
|
}
|
|
if (config.excludeNonVisibleLinkPortions) {
|
|
content = content.replace(/\[([^\]]*?)\]\([^\)]*?\)/gim, "$1");
|
|
content = content.replace(/\[\[(.*?)\]\]/gim, (_, $1) => {
|
|
return !$1 ? "" : $1.includes("|") ? $1.slice($1.indexOf("|") + 1) : $1;
|
|
});
|
|
}
|
|
if (config.excludeFootnotes) {
|
|
content = content.replace(/\[\^.+?\]: .*/gim, "");
|
|
content = content.replace(/\[\^.+?\]/gim, "");
|
|
}
|
|
return content;
|
|
}
|
|
|
|
// logic/canvas.ts
|
|
var CanvasHelper = class {
|
|
constructor(debug) {
|
|
this.debug = debug;
|
|
}
|
|
getCanvasText(file, content) {
|
|
try {
|
|
const canvas = JSON.parse(content);
|
|
const texts = canvas.nodes.map((node) => node.text).filter((text) => !!text);
|
|
return texts.join("\n");
|
|
} catch (ex) {
|
|
this.debug.error(`Unable to parse canvas file [${file.name}]: ${ex}`);
|
|
return "";
|
|
}
|
|
}
|
|
};
|
|
|
|
// logic/file.ts
|
|
var FileHelper = class {
|
|
constructor(app, plugin) {
|
|
this.app = app;
|
|
this.plugin = plugin;
|
|
this.debugHelper = new DebugHelper();
|
|
this.canvasHelper = new CanvasHelper(this.debugHelper);
|
|
this.pathIncludeMatchers = [];
|
|
this.pathExcludeMatchers = [];
|
|
this.FileTypeAllowlist = /* @__PURE__ */ new Set([
|
|
"",
|
|
// Markdown extensions
|
|
"markdown",
|
|
"md",
|
|
"mdml",
|
|
"mdown",
|
|
"mdtext",
|
|
"mdtxt",
|
|
"mdwn",
|
|
"mkd",
|
|
"mkdn",
|
|
// Obsidian canvas
|
|
"canvas",
|
|
// Text files
|
|
"txt",
|
|
"text",
|
|
"rtf",
|
|
// MD with embedded code
|
|
"qmd",
|
|
"rmd",
|
|
// MD for screenwriters
|
|
"fountain",
|
|
// LaTeX files
|
|
"tex"
|
|
]);
|
|
}
|
|
get settings() {
|
|
return this.plugin.settings;
|
|
}
|
|
get vault() {
|
|
return this.app.vault;
|
|
}
|
|
async initializeAllFileCounts(cancellationToken) {
|
|
const debugEnd = this.debugHelper.debugStart("getAllFileCounts");
|
|
const files = this.vault.getFiles();
|
|
if (typeof this.plugin.settings.includeDirectories === "string" && this.plugin.settings.includeDirectories.trim() !== "*" && this.plugin.settings.includeDirectories.trim() !== "") {
|
|
const allMatchers = this.plugin.settings.includeDirectories.trim().split(",").map((matcher) => matcher.trim());
|
|
const includeMatchers = allMatchers.filter(
|
|
(matcher) => !matcher.startsWith("!")
|
|
);
|
|
const excludeMatchers = allMatchers.filter((matcher) => matcher.startsWith("!")).map((matcher) => matcher.slice(1));
|
|
const matchedFiles = files.filter(
|
|
(file) => (includeMatchers.length === 0 ? true : includeMatchers.some((matcher) => file.path.includes(matcher))) && !excludeMatchers.some((matcher) => file.path.includes(matcher))
|
|
);
|
|
if (matchedFiles.length > 0) {
|
|
this.pathIncludeMatchers = includeMatchers;
|
|
this.pathExcludeMatchers = excludeMatchers;
|
|
} else {
|
|
this.pathIncludeMatchers = [];
|
|
this.pathExcludeMatchers = [];
|
|
this.debugHelper.debug(
|
|
"No files matched by includeDirectories setting. Defaulting to all files."
|
|
);
|
|
}
|
|
}
|
|
const counts = {};
|
|
for (const file of files) {
|
|
if (cancellationToken.isCancelled) {
|
|
break;
|
|
}
|
|
this.setCounts(counts, file, true);
|
|
}
|
|
debugEnd();
|
|
return counts;
|
|
}
|
|
getCachedDataForPath(counts, path) {
|
|
if (counts.hasOwnProperty(path)) {
|
|
return counts[path];
|
|
}
|
|
const childPaths = this.getChildPaths(counts, path);
|
|
const directoryDefault = {
|
|
isCountable: false,
|
|
targetNodeType: this.isRoot(path) ? "root" /* Root */ : "directory" /* Directory */,
|
|
noteCount: 0,
|
|
wordCount: 0,
|
|
wordCountTowardGoal: 0,
|
|
wordGoal: 0,
|
|
pageCount: 0,
|
|
characterCount: 0,
|
|
nonWhitespaceCharacterCount: 0,
|
|
newlineCount: 0,
|
|
readingTimeInMinutes: 0,
|
|
linkCount: 0,
|
|
embedCount: 0,
|
|
aliases: null,
|
|
createdDate: 0,
|
|
modifiedDate: 0,
|
|
sizeInBytes: 0,
|
|
sessionStart: {
|
|
noteCount: 0,
|
|
pageCount: 0,
|
|
wordCount: 0,
|
|
characterCount: 0,
|
|
nonWhitespaceCharacterCount: 0,
|
|
newlineCount: 0
|
|
}
|
|
};
|
|
return childPaths.reduce((total, childPath) => {
|
|
const childCount = this.getCachedDataForPath(counts, childPath);
|
|
return {
|
|
isCountable: total.isCountable || childCount.isCountable,
|
|
targetNodeType: total.targetNodeType,
|
|
noteCount: total.noteCount + childCount.noteCount,
|
|
linkCount: total.linkCount + childCount.linkCount,
|
|
embedCount: total.embedCount + childCount.embedCount,
|
|
aliases: [],
|
|
wordCount: total.wordCount + childCount.wordCount,
|
|
wordCountTowardGoal: total.wordCountTowardGoal + childCount.wordCountTowardGoal,
|
|
wordGoal: total.wordGoal + childCount.wordGoal,
|
|
pageCount: total.pageCount + childCount.pageCount,
|
|
characterCount: total.characterCount + childCount.characterCount,
|
|
nonWhitespaceCharacterCount: total.nonWhitespaceCharacterCount + childCount.nonWhitespaceCharacterCount,
|
|
newlineCount: total.newlineCount + childCount.newlineCount,
|
|
readingTimeInMinutes: total.readingTimeInMinutes + childCount.readingTimeInMinutes,
|
|
createdDate: total.createdDate === 0 ? childCount.createdDate : Math.min(total.createdDate, childCount.createdDate),
|
|
modifiedDate: Math.max(total.modifiedDate, childCount.modifiedDate),
|
|
sizeInBytes: total.sizeInBytes + childCount.sizeInBytes,
|
|
sessionStart: {
|
|
noteCount: total.sessionStart.noteCount + childCount.sessionStart.noteCount,
|
|
pageCount: total.sessionStart.pageCount + childCount.sessionStart.pageCount,
|
|
wordCount: total.sessionStart.wordCount + childCount.sessionStart.wordCount,
|
|
characterCount: total.sessionStart.characterCount + childCount.sessionStart.characterCount,
|
|
nonWhitespaceCharacterCount: total.sessionStart.nonWhitespaceCharacterCount + childCount.sessionStart.nonWhitespaceCharacterCount,
|
|
newlineCount: total.sessionStart.newlineCount + childCount.sessionStart.newlineCount
|
|
}
|
|
};
|
|
}, directoryDefault);
|
|
}
|
|
setDebugMode(debug) {
|
|
this.debugHelper.setDebugMode(debug);
|
|
}
|
|
removeFileCounts(path, counts) {
|
|
this.removeCounts(counts, path);
|
|
for (const childPath of this.getChildPaths(counts, path)) {
|
|
this.removeCounts(counts, childPath);
|
|
}
|
|
}
|
|
async updateFileCounts(abstractFile, counts, cancellationToken) {
|
|
if (abstractFile instanceof import_obsidian2.TFolder) {
|
|
for (const child of abstractFile.children) {
|
|
await this.updateFileCounts(child, counts, cancellationToken);
|
|
}
|
|
return;
|
|
}
|
|
if (abstractFile instanceof import_obsidian2.TFile) {
|
|
await this.setCounts(counts, abstractFile, false);
|
|
}
|
|
}
|
|
countEmbeds(metadata) {
|
|
var _a, _b;
|
|
return (_b = (_a = metadata == null ? void 0 : metadata.embeds) == null ? void 0 : _a.length) != null ? _b : 0;
|
|
}
|
|
countLinks(metadata) {
|
|
var _a, _b;
|
|
return (_b = (_a = metadata == null ? void 0 : metadata.links) == null ? void 0 : _a.length) != null ? _b : 0;
|
|
}
|
|
getChildPaths(counts, path) {
|
|
const childPaths = Object.keys(counts).filter(
|
|
(countPath) => path === "/" || countPath.startsWith(path + "/")
|
|
);
|
|
return childPaths;
|
|
}
|
|
isRoot(path) {
|
|
return !path || path === "/";
|
|
}
|
|
removeCounts(counts, path) {
|
|
delete counts[path];
|
|
}
|
|
async setCounts(counts, file, startNewSession) {
|
|
var _a, _b;
|
|
const metadata = this.app.metadataCache.getFileCache(
|
|
file
|
|
);
|
|
const shouldCountFile = this.shouldCountFile(file, metadata);
|
|
const existingSession = (_a = counts[file.path]) == null ? void 0 : _a.sessionStart;
|
|
counts[file.path] = {
|
|
isCountable: shouldCountFile,
|
|
targetNodeType: "file" /* File */,
|
|
noteCount: 0,
|
|
wordCount: 0,
|
|
wordCountTowardGoal: 0,
|
|
wordGoal: 0,
|
|
pageCount: 0,
|
|
characterCount: 0,
|
|
nonWhitespaceCharacterCount: 0,
|
|
newlineCount: 0,
|
|
readingTimeInMinutes: 0,
|
|
linkCount: 0,
|
|
embedCount: 0,
|
|
aliases: [],
|
|
createdDate: file.stat.ctime,
|
|
modifiedDate: file.stat.mtime,
|
|
sizeInBytes: file.stat.size,
|
|
sessionStart: startNewSession || !existingSession ? {
|
|
noteCount: 0,
|
|
pageCount: 0,
|
|
wordCount: 0,
|
|
characterCount: 0,
|
|
nonWhitespaceCharacterCount: 0,
|
|
newlineCount: 0
|
|
} : existingSession
|
|
};
|
|
if (!shouldCountFile) {
|
|
return;
|
|
}
|
|
let content = await this.vault.cachedRead(file);
|
|
if (file.extension.toLowerCase() === "canvas") {
|
|
content = this.canvasHelper.getCanvasText(file, content);
|
|
} else {
|
|
content = this.trimFrontmatter(content, metadata);
|
|
}
|
|
const countResult = countMarkdown(content, {
|
|
excludeCodeBlocks: this.settings.excludeCodeBlocks,
|
|
excludeComments: this.settings.excludeComments,
|
|
excludeNonVisibleLinkPortions: this.settings.excludeNonVisibleLinkPortions,
|
|
excludeFootnotes: this.settings.excludeFootnotes
|
|
});
|
|
const combinedWordCount = countResult.cjkWordCount + countResult.spaceDelimitedWordCount;
|
|
const wordGoal = this.getWordGoal(metadata);
|
|
const cjkReadingTime = countResult.cjkWordCount / (this.settings.charsPerMinute || 500);
|
|
const spaceDelimitedReadingTime = countResult.spaceDelimitedWordCount / (this.settings.wordsPerMinute || 265);
|
|
const readingTimeInMinutes = cjkReadingTime + spaceDelimitedReadingTime;
|
|
let pageCount = 0;
|
|
if (this.settings.pageCountType === "ByWords" /* ByWords */) {
|
|
const wordsPerPage = Number(this.settings.wordsPerPage);
|
|
const wordsPerPageValid = !isNaN(wordsPerPage) && wordsPerPage > 0;
|
|
pageCount = combinedWordCount / (wordsPerPageValid ? wordsPerPage : 300);
|
|
} else if (this.settings.pageCountType === "ByChars" /* ByChars */ && !this.settings.charsPerPageIncludesWhitespace) {
|
|
const charsPerPage = Number(this.settings.charsPerPage);
|
|
const charsPerPageValid = !isNaN(charsPerPage) && charsPerPage > 0;
|
|
pageCount = countResult.nonWhitespaceCharCount / (charsPerPageValid ? charsPerPage : 1500);
|
|
} else if (this.settings.pageCountType === "ByChars" /* ByChars */ && this.settings.charsPerPageIncludesWhitespace) {
|
|
const charsPerPage = Number(this.settings.charsPerPage);
|
|
const charsPerPageValid = !isNaN(charsPerPage) && charsPerPage > 0;
|
|
pageCount = countResult.charCount / (charsPerPageValid ? charsPerPage : 1500);
|
|
}
|
|
Object.assign(counts[file.path], {
|
|
noteCount: 1,
|
|
wordCount: combinedWordCount,
|
|
wordCountTowardGoal: wordGoal !== null ? combinedWordCount : 0,
|
|
wordGoal,
|
|
pageCount,
|
|
characterCount: countResult.charCount,
|
|
nonWhitespaceCharacterCount: countResult.nonWhitespaceCharCount,
|
|
newlineCount: countResult.newlineCount,
|
|
readingTimeInMinutes,
|
|
linkCount: this.countLinks(metadata),
|
|
embedCount: this.countEmbeds(metadata),
|
|
aliases: (0, import_obsidian2.parseFrontMatterAliases)(metadata == null ? void 0 : metadata.frontmatter),
|
|
frontmatter: metadata == null ? void 0 : metadata.frontmatter,
|
|
sessionStart: {
|
|
...(_b = counts[file.path]) == null ? void 0 : _b.sessionStart,
|
|
...startNewSession ? {
|
|
noteCount: 1,
|
|
pageCount,
|
|
wordCount: combinedWordCount,
|
|
characterCount: countResult.charCount,
|
|
nonWhitespaceCharacterCount: countResult.nonWhitespaceCharCount,
|
|
newlineCount: countResult.newlineCount
|
|
} : {}
|
|
}
|
|
});
|
|
}
|
|
getWordGoal(metadata) {
|
|
const goal = metadata && metadata.frontmatter && metadata.frontmatter["word-goal"];
|
|
if (!goal || isNaN(Number(goal))) {
|
|
return null;
|
|
}
|
|
return Number(goal);
|
|
}
|
|
trimFrontmatter(content, metadata) {
|
|
let meaningfulContent = content;
|
|
const hasFrontmatter = !!metadata && !!metadata.frontmatter;
|
|
if (hasFrontmatter) {
|
|
const frontmatterPos = metadata.frontmatterPosition || metadata.frontmatter.position;
|
|
meaningfulContent = frontmatterPos && frontmatterPos.start && frontmatterPos.end ? meaningfulContent.slice(0, frontmatterPos.start.offset) + meaningfulContent.slice(frontmatterPos.end.offset) : meaningfulContent;
|
|
}
|
|
return meaningfulContent;
|
|
}
|
|
shouldCountFile(file, metadata) {
|
|
if (this.pathIncludeMatchers.length > 0 && !this.pathIncludeMatchers.some((matcher) => file.path.includes(matcher))) {
|
|
return false;
|
|
}
|
|
if (this.pathExcludeMatchers.length > 0 && this.pathExcludeMatchers.some((matcher) => file.path.includes(matcher))) {
|
|
return false;
|
|
}
|
|
if (!this.FileTypeAllowlist.has(file.extension.toLowerCase())) {
|
|
return false;
|
|
}
|
|
if (!metadata) {
|
|
return true;
|
|
}
|
|
if (metadata.frontmatter && metadata.frontmatter.hasOwnProperty("wordcount") && (metadata.frontmatter.wordcount === null || metadata.frontmatter.wordcount === false || metadata.frontmatter.wordcount === "false")) {
|
|
return false;
|
|
}
|
|
const tags = (0, import_obsidian2.getAllTags)(metadata).map((tag) => tag.toLowerCase());
|
|
if (tags.length && (tags.includes("#excalidraw") || tags.filter((tag) => tag.startsWith("#exclude")).map((tag) => tag.replace(/[-_]/g, "")).includes("#excludefromwordcount"))) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// logic/locale_format.ts
|
|
var locales = [...navigator.languages, "en-US"];
|
|
var NumberFormatDefault = new Intl.NumberFormat(locales);
|
|
var NumberFormatDecimal = new Intl.NumberFormat(locales, {
|
|
minimumFractionDigits: 1,
|
|
maximumFractionDigits: 2
|
|
});
|
|
var NumberFormatFileSize = new Intl.NumberFormat(locales, {
|
|
minimumFractionDigits: 0,
|
|
maximumFractionDigits: 2
|
|
});
|
|
|
|
// logic/filesize.ts
|
|
var formatThresholds = [{
|
|
suffix: "B",
|
|
suffixLong: " B",
|
|
divisor: 1
|
|
}, {
|
|
suffix: "kB",
|
|
suffixLong: " kB",
|
|
divisor: 1e3
|
|
}, {
|
|
suffix: "MB",
|
|
suffixLong: " MB",
|
|
divisor: 1e6
|
|
}, {
|
|
suffix: "GB",
|
|
suffixLong: " GB",
|
|
divisor: 1e9
|
|
}, {
|
|
suffix: "TB",
|
|
suffixLong: " TB",
|
|
divisor: 1e12
|
|
}];
|
|
var FileSizeHelper = class {
|
|
formatFileSize(bytes, shouldAbbreviate) {
|
|
const largestThreshold = formatThresholds.last();
|
|
for (const formatThreshold of formatThresholds) {
|
|
if (bytes < formatThreshold.divisor * 1e3 || formatThreshold === largestThreshold) {
|
|
const units = bytes / formatThreshold.divisor;
|
|
const suffix = shouldAbbreviate ? formatThreshold.suffix : formatThreshold.suffixLong;
|
|
return `${NumberFormatFileSize.format(units)}${suffix}`;
|
|
}
|
|
}
|
|
return `?B`;
|
|
}
|
|
};
|
|
|
|
// logic/readtime.ts
|
|
var ReadTimeHelper = class {
|
|
formatReadTime(minutes, shouldAbbreviate) {
|
|
const final = shouldAbbreviate ? "" : " read";
|
|
if (minutes * 60 < 1) {
|
|
return `0m${final}`;
|
|
}
|
|
if (minutes < 1) {
|
|
const seconds = Math.round(minutes * 60);
|
|
return `${seconds}s${final}`;
|
|
}
|
|
if (minutes < 60) {
|
|
return `${Math.round(minutes)}m${final}`;
|
|
}
|
|
const hours = NumberFormatDefault.format(Math.floor(minutes / 60));
|
|
const remainder = Math.floor(minutes) % 60;
|
|
return remainder === 0 ? `${hours}h${final}` : `${hours}h${remainder}m${final}`;
|
|
}
|
|
};
|
|
|
|
// logic/node_label.ts
|
|
var import_obsidian3 = require("obsidian");
|
|
var NodeLabelHelper = class {
|
|
constructor(plugin) {
|
|
this.plugin = plugin;
|
|
this.fileSizeHelper = new FileSizeHelper();
|
|
this.readTimeHelper = new ReadTimeHelper();
|
|
this.unconditionalCountTypes = [
|
|
"created" /* Created */,
|
|
"filesize" /* FileSize */,
|
|
"modified" /* Modified */
|
|
];
|
|
}
|
|
get settings() {
|
|
return this.plugin.settings;
|
|
}
|
|
getNodeLabel(counts) {
|
|
let countTypes;
|
|
let abbreviateDescriptions;
|
|
let separator;
|
|
const noteCountTypes = [
|
|
this.getCountTypeWithSuffix(
|
|
this.settings.countType,
|
|
this.settings.countConfig
|
|
),
|
|
this.getCountTypeWithSuffix(
|
|
this.settings.countType2,
|
|
this.settings.countConfig2
|
|
),
|
|
this.getCountTypeWithSuffix(
|
|
this.settings.countType3,
|
|
this.settings.countConfig3
|
|
)
|
|
];
|
|
const noteAbbreviateDescriptions = this.settings.abbreviateDescriptions;
|
|
const noteSeparator = this.settings.useAdvancedFormatting ? this.settings.pipeSeparator : "|";
|
|
switch (counts.targetNodeType) {
|
|
case "root" /* Root */:
|
|
if (this.settings.showSameCountsOnRoot) {
|
|
countTypes = noteCountTypes;
|
|
abbreviateDescriptions = noteAbbreviateDescriptions;
|
|
separator = noteSeparator;
|
|
break;
|
|
}
|
|
countTypes = [
|
|
this.getCountTypeWithSuffix(
|
|
this.settings.rootCountType,
|
|
this.settings.rootCountConfig
|
|
),
|
|
this.getCountTypeWithSuffix(
|
|
this.settings.rootCountType2,
|
|
this.settings.rootCountConfig2
|
|
),
|
|
this.getCountTypeWithSuffix(
|
|
this.settings.rootCountType3,
|
|
this.settings.rootCountConfig3
|
|
)
|
|
];
|
|
abbreviateDescriptions = this.settings.rootAbbreviateDescriptions;
|
|
separator = this.settings.useAdvancedFormatting ? this.settings.rootPipeSeparator : noteSeparator;
|
|
break;
|
|
case "directory" /* Directory */:
|
|
if (this.settings.showSameCountsOnFolders) {
|
|
countTypes = noteCountTypes;
|
|
abbreviateDescriptions = noteAbbreviateDescriptions;
|
|
separator = noteSeparator;
|
|
break;
|
|
}
|
|
countTypes = [
|
|
this.getCountTypeWithSuffix(
|
|
this.settings.folderCountType,
|
|
this.settings.folderCountConfig
|
|
),
|
|
this.getCountTypeWithSuffix(
|
|
this.settings.folderCountType2,
|
|
this.settings.folderCountConfig2
|
|
),
|
|
this.getCountTypeWithSuffix(
|
|
this.settings.folderCountType3,
|
|
this.settings.folderCountConfig3
|
|
)
|
|
];
|
|
abbreviateDescriptions = this.settings.folderAbbreviateDescriptions;
|
|
separator = this.settings.useAdvancedFormatting ? this.settings.folderPipeSeparator : noteSeparator;
|
|
break;
|
|
default:
|
|
countTypes = noteCountTypes;
|
|
abbreviateDescriptions = noteAbbreviateDescriptions;
|
|
separator = noteSeparator;
|
|
break;
|
|
}
|
|
return countTypes.filter((ct) => ct.$countType !== "none" /* None */).map(
|
|
(ct) => this.getDataTypeLabel(
|
|
counts,
|
|
ct,
|
|
abbreviateDescriptions
|
|
)
|
|
).filter((display) => display !== null).join(` ${separator} `);
|
|
}
|
|
getCountTypeWithSuffix($countType, countConfig) {
|
|
return {
|
|
$countType,
|
|
customSuffix: this.settings.useAdvancedFormatting ? countConfig.customSuffix : null,
|
|
frontmatterKey: countConfig.frontmatterKey,
|
|
$sessionCountType: countConfig.$sessionCountType
|
|
};
|
|
}
|
|
getBasicCountString(config) {
|
|
var _a;
|
|
const defaultSuffix = config.abbreviateDescriptions ? config.abbreviatedNoun : ` ${config.noun}${config.count == "1" ? "" : "s"}`;
|
|
const suffix = (_a = config.customSuffix) != null ? _a : defaultSuffix;
|
|
return `${config.count}${suffix}`;
|
|
}
|
|
getDataTypeLabel(counts, config, abbreviateDescriptions) {
|
|
var _a, _b;
|
|
if (!counts || typeof counts.wordCount !== "number") {
|
|
return null;
|
|
}
|
|
if (!counts.isCountable && !this.unconditionalCountTypes.includes(config.$countType)) {
|
|
return null;
|
|
}
|
|
switch (config.$countType) {
|
|
case "none" /* None */:
|
|
return null;
|
|
case "word" /* Word */:
|
|
return this.getBasicCountString({
|
|
count: NumberFormatDefault.format(Math.ceil(counts.wordCount)),
|
|
noun: "word",
|
|
abbreviatedNoun: COUNT_TYPE_DEFAULT_SHORT_SUFFIXES["word" /* Word */],
|
|
abbreviateDescriptions,
|
|
customSuffix: config.customSuffix
|
|
});
|
|
case "page" /* Page */:
|
|
return this.getBasicCountString({
|
|
count: NumberFormatDefault.format(Math.ceil(counts.pageCount)),
|
|
noun: "page",
|
|
abbreviatedNoun: COUNT_TYPE_DEFAULT_SHORT_SUFFIXES["page" /* Page */],
|
|
abbreviateDescriptions,
|
|
customSuffix: config.customSuffix
|
|
});
|
|
case "pagedecimal" /* PageDecimal */:
|
|
return this.getBasicCountString({
|
|
count: NumberFormatDecimal.format(counts.pageCount),
|
|
noun: "page",
|
|
abbreviatedNoun: COUNT_TYPE_DEFAULT_SHORT_SUFFIXES["pagedecimal" /* PageDecimal */],
|
|
abbreviateDescriptions,
|
|
customSuffix: config.customSuffix
|
|
});
|
|
case "linebreak" /* Linebreak */:
|
|
return this.getBasicCountString({
|
|
count: NumberFormatDefault.format(counts.newlineCount),
|
|
noun: "line",
|
|
abbreviatedNoun: COUNT_TYPE_DEFAULT_SHORT_SUFFIXES["linebreak" /* Linebreak */],
|
|
abbreviateDescriptions,
|
|
customSuffix: config.customSuffix
|
|
});
|
|
case "percentgoal" /* PercentGoal */: {
|
|
if (counts.wordGoal <= 0) {
|
|
return null;
|
|
}
|
|
const fraction = counts.wordCountTowardGoal / counts.wordGoal;
|
|
const percent = NumberFormatDefault.format(Math.round(fraction * 100));
|
|
const defaultSuffix = abbreviateDescriptions ? "%" : `% of ${NumberFormatDefault.format(counts.wordGoal)}`;
|
|
const suffix = (_a = config.customSuffix) != null ? _a : defaultSuffix;
|
|
return `${percent}${suffix}`;
|
|
}
|
|
case "note" /* Note */:
|
|
return this.getBasicCountString({
|
|
count: NumberFormatDefault.format(counts.noteCount),
|
|
noun: "note",
|
|
abbreviatedNoun: COUNT_TYPE_DEFAULT_SHORT_SUFFIXES["note" /* Note */],
|
|
abbreviateDescriptions,
|
|
customSuffix: config.customSuffix
|
|
});
|
|
case "character" /* Character */: {
|
|
const characterCount = this.settings.characterCountType === "ExcludeWhitespace" /* ExcludeWhitespace */ ? counts.nonWhitespaceCharacterCount : counts.characterCount;
|
|
return this.getBasicCountString({
|
|
count: NumberFormatDefault.format(characterCount),
|
|
noun: "character",
|
|
abbreviatedNoun: COUNT_TYPE_DEFAULT_SHORT_SUFFIXES["character" /* Character */],
|
|
abbreviateDescriptions,
|
|
customSuffix: config.customSuffix
|
|
});
|
|
}
|
|
case "readtime" /* ReadTime */:
|
|
return this.readTimeHelper.formatReadTime(
|
|
counts.readingTimeInMinutes,
|
|
abbreviateDescriptions
|
|
);
|
|
case "link" /* Link */:
|
|
if (counts.linkCount === 0) {
|
|
return null;
|
|
}
|
|
return this.getBasicCountString({
|
|
count: NumberFormatDefault.format(counts.linkCount),
|
|
noun: "link",
|
|
abbreviatedNoun: COUNT_TYPE_DEFAULT_SHORT_SUFFIXES["link" /* Link */],
|
|
abbreviateDescriptions,
|
|
customSuffix: config.customSuffix
|
|
});
|
|
case "embed" /* Embed */:
|
|
if (counts.embedCount === 0) {
|
|
return null;
|
|
}
|
|
return this.getBasicCountString({
|
|
count: NumberFormatDefault.format(counts.embedCount),
|
|
noun: "embed",
|
|
abbreviatedNoun: COUNT_TYPE_DEFAULT_SHORT_SUFFIXES["embed" /* Embed */],
|
|
abbreviateDescriptions,
|
|
customSuffix: config.customSuffix
|
|
});
|
|
case "alias" /* Alias */:
|
|
if (!counts.aliases || !Array.isArray(counts.aliases) || !counts.aliases.length) {
|
|
return null;
|
|
}
|
|
return abbreviateDescriptions ? `${counts.aliases[0]}` : `alias: ${counts.aliases[0]}${counts.aliases.length > 1 ? ` +${counts.aliases.length - 1}` : ""}`;
|
|
case "created" /* Created */: {
|
|
if (counts.createdDate === 0) {
|
|
return null;
|
|
}
|
|
const cDate = (0, import_obsidian3.moment)(counts.createdDate).format(this.settings.momentDateFormat || "YYYY/MM/DD");
|
|
if (config.customSuffix !== null) {
|
|
return `${cDate}${config.customSuffix}`;
|
|
}
|
|
return abbreviateDescriptions ? `${cDate}/c` : `Created ${cDate}`;
|
|
}
|
|
case "modified" /* Modified */: {
|
|
if (counts.modifiedDate === 0) {
|
|
return null;
|
|
}
|
|
const uDate = (0, import_obsidian3.moment)(counts.modifiedDate).format(this.settings.momentDateFormat || "YYYY/MM/DD");
|
|
if (config.customSuffix !== null) {
|
|
return `${uDate}${config.customSuffix}`;
|
|
}
|
|
return abbreviateDescriptions ? `${uDate}/u` : `Updated ${uDate}`;
|
|
}
|
|
case "filesize" /* FileSize */:
|
|
return this.fileSizeHelper.formatFileSize(
|
|
counts.sizeInBytes,
|
|
abbreviateDescriptions
|
|
);
|
|
case "frontmatterKey" /* FrontmatterKey */: {
|
|
if (!config.frontmatterKey) {
|
|
return null;
|
|
}
|
|
const value = (_b = counts == null ? void 0 : counts.frontmatter) == null ? void 0 : _b[config.frontmatterKey];
|
|
if (value === void 0 || value === null) {
|
|
return null;
|
|
}
|
|
if (config.customSuffix !== null) {
|
|
return `${value}${config.customSuffix}`;
|
|
}
|
|
return value;
|
|
}
|
|
case "tracksession" /* TrackSession */: {
|
|
if (!config.$sessionCountType) {
|
|
return null;
|
|
}
|
|
const quantity = this.getSessionQuantity(counts, config.$sessionCountType);
|
|
if (config.customSuffix !== null) {
|
|
return `${quantity}${config.customSuffix}`;
|
|
}
|
|
return abbreviateDescriptions ? `${quantity}/s` : `Session: ${quantity}`;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
getSessionQuantity(counts, $countType) {
|
|
switch ($countType) {
|
|
case "word" /* Word */:
|
|
return NumberFormatDefault.format(Math.ceil(counts.wordCount - counts.sessionStart.wordCount));
|
|
case "page" /* Page */:
|
|
return NumberFormatDefault.format(Math.ceil(counts.pageCount - counts.sessionStart.pageCount));
|
|
case "pagedecimal" /* PageDecimal */:
|
|
return NumberFormatDecimal.format(counts.pageCount - counts.sessionStart.pageCount);
|
|
case "linebreak" /* Linebreak */:
|
|
return NumberFormatDefault.format(counts.newlineCount - counts.sessionStart.newlineCount);
|
|
case "note" /* Note */:
|
|
return NumberFormatDefault.format(counts.noteCount - counts.sessionStart.noteCount);
|
|
case "character" /* Character */: {
|
|
const characterCount = this.settings.characterCountType === "ExcludeWhitespace" /* ExcludeWhitespace */ ? counts.nonWhitespaceCharacterCount : counts.characterCount;
|
|
const startingCharacterCount = this.settings.characterCountType === "ExcludeWhitespace" /* ExcludeWhitespace */ ? counts.sessionStart.nonWhitespaceCharacterCount : counts.sessionStart.characterCount;
|
|
return NumberFormatDefault.format(characterCount - startingCharacterCount);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// logic/saved_data.ts
|
|
var SavedDataHelper = class {
|
|
constructor(plugin) {
|
|
this.plugin = plugin;
|
|
}
|
|
async getSavedData() {
|
|
const loaded = await this.plugin.loadData();
|
|
const denulled = Object.assign({}, loaded);
|
|
denulled.settings = Object.assign({}, DEFAULT_SETTINGS, denulled.settings);
|
|
const migrated = migrateSavedData(denulled);
|
|
return migrated;
|
|
}
|
|
};
|
|
function migrateSavedData(saved) {
|
|
const migrations = [
|
|
overwriteInvalidCountTypes,
|
|
migrateToCountConfigurationObject
|
|
];
|
|
for (const migrate of migrations) {
|
|
saved = migrate(saved);
|
|
}
|
|
return saved;
|
|
}
|
|
var overwriteInvalidCountTypes = (saved) => {
|
|
var _a;
|
|
if (!((_a = saved == null ? void 0 : saved.settings) == null ? void 0 : _a.countType)) {
|
|
return;
|
|
}
|
|
const fieldsToCheck = [
|
|
"countType",
|
|
"countType2",
|
|
"countType3",
|
|
"folderCountType",
|
|
"folderCountType2",
|
|
"folderCountType3",
|
|
"rootCountType",
|
|
"rootCountType2",
|
|
"rootCountType3"
|
|
];
|
|
for (const field of fieldsToCheck) {
|
|
if (!COUNT_TYPES.includes(saved.settings[field])) {
|
|
saved.settings[field] = field === "countType" ? "word" /* Word */ : "none" /* None */;
|
|
}
|
|
}
|
|
return saved;
|
|
};
|
|
var migrateToCountConfigurationObject = (saved) => {
|
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
const settings = saved.settings;
|
|
const oldSettings = saved.settings;
|
|
(_a = settings.countConfig) != null ? _a : settings.countConfig = {};
|
|
if (typeof oldSettings.countTypeSuffix === "string") {
|
|
settings.countConfig.customSuffix = oldSettings.countTypeSuffix;
|
|
delete oldSettings.countTypeSuffix;
|
|
}
|
|
if (typeof oldSettings.frontmatterKey === "string") {
|
|
settings.countConfig.frontmatterKey = oldSettings.frontmatterKey;
|
|
delete oldSettings.frontmatterKey;
|
|
}
|
|
(_b = settings.countConfig2) != null ? _b : settings.countConfig2 = {};
|
|
if (typeof oldSettings.countType2Suffix === "string") {
|
|
settings.countConfig2.customSuffix = oldSettings.countType2Suffix;
|
|
delete oldSettings.countType2Suffix;
|
|
}
|
|
if (typeof oldSettings.frontmatterKey2 === "string") {
|
|
settings.countConfig2.frontmatterKey = oldSettings.frontmatterKey2;
|
|
delete oldSettings.frontmatterKey2;
|
|
}
|
|
(_c = settings.countConfig3) != null ? _c : settings.countConfig3 = {};
|
|
if (typeof oldSettings.countType3Suffix === "string") {
|
|
settings.countConfig3.customSuffix = oldSettings.countType3Suffix;
|
|
delete oldSettings.countType3Suffix;
|
|
}
|
|
if (typeof oldSettings.frontmatterKey3 === "string") {
|
|
settings.countConfig3.frontmatterKey = oldSettings.frontmatterKey3;
|
|
delete oldSettings.frontmatterKey3;
|
|
}
|
|
(_d = settings.folderCountConfig) != null ? _d : settings.folderCountConfig = {};
|
|
if (typeof oldSettings.folderCountTypeSuffix === "string") {
|
|
settings.folderCountConfig.customSuffix = oldSettings.folderCountTypeSuffix;
|
|
delete oldSettings.folderCountTypeSuffix;
|
|
}
|
|
(_e = settings.folderCountConfig2) != null ? _e : settings.folderCountConfig2 = {};
|
|
if (typeof oldSettings.folderCountType2Suffix === "string") {
|
|
settings.folderCountConfig2.customSuffix = oldSettings.folderCountType2Suffix;
|
|
delete oldSettings.folderCountType2Suffix;
|
|
}
|
|
(_f = settings.folderCountConfig3) != null ? _f : settings.folderCountConfig3 = {};
|
|
if (typeof oldSettings.folderCountType3Suffix === "string") {
|
|
settings.folderCountConfig3.customSuffix = oldSettings.folderCountType3Suffix;
|
|
delete oldSettings.folderCountType3Suffix;
|
|
}
|
|
(_g = settings.rootCountConfig) != null ? _g : settings.rootCountConfig = {};
|
|
if (typeof oldSettings.rootCountTypeSuffix === "string") {
|
|
settings.rootCountConfig.customSuffix = oldSettings.rootCountTypeSuffix;
|
|
delete oldSettings.rootCountTypeSuffix;
|
|
}
|
|
(_h = settings.rootCountConfig2) != null ? _h : settings.rootCountConfig2 = {};
|
|
if (typeof oldSettings.rootCountType2Suffix === "string") {
|
|
settings.rootCountConfig2.customSuffix = oldSettings.rootCountType2Suffix;
|
|
delete oldSettings.rootCountType2Suffix;
|
|
}
|
|
(_i = settings.rootCountConfig3) != null ? _i : settings.rootCountConfig3 = {};
|
|
if (typeof oldSettings.rootCountType3Suffix === "string") {
|
|
settings.rootCountConfig3.customSuffix = oldSettings.rootCountType3Suffix;
|
|
delete oldSettings.rootCountType3Suffix;
|
|
}
|
|
return saved;
|
|
};
|
|
|
|
// logic/settings.tab.ts
|
|
var import_obsidian4 = require("obsidian");
|
|
var NovelWordCountSettingTab = class extends import_obsidian4.PluginSettingTab {
|
|
constructor(app, plugin) {
|
|
super(app, plugin);
|
|
this.plugin = plugin;
|
|
}
|
|
display() {
|
|
const { containerEl } = this;
|
|
containerEl.empty();
|
|
this.renderNoteSettings(containerEl);
|
|
this.renderFolderSettings(containerEl);
|
|
this.renderRootSettings(containerEl);
|
|
this.renderAdvancedSettings(containerEl);
|
|
this.renderReanalyzeButton(containerEl);
|
|
this.renderDonationButton(containerEl);
|
|
}
|
|
//
|
|
// NOTES
|
|
//
|
|
renderNoteSettings(containerEl) {
|
|
const mainHeader = containerEl.createEl("div", {
|
|
cls: [
|
|
"setting-item",
|
|
"setting-item-heading",
|
|
"novel-word-count-settings-header"
|
|
]
|
|
});
|
|
mainHeader.createEl("div", { text: "Notes" });
|
|
mainHeader.createEl("div", {
|
|
text: "You can display up to three data types side by side.",
|
|
cls: "setting-item-description"
|
|
});
|
|
new import_obsidian4.Setting(containerEl).setDesc("Use advanced formatting").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.useAdvancedFormatting).onChange(async (value) => {
|
|
this.plugin.settings.useAdvancedFormatting = value;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.updateDisplayedCounts();
|
|
this.display();
|
|
})
|
|
);
|
|
this.renderCountTypeSetting(containerEl, {
|
|
name: "1st data type to show",
|
|
oldCountType: this.plugin.settings.countType,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.countType = value;
|
|
this.plugin.settings.countConfig.customSuffix = COUNT_TYPE_DEFAULT_SHORT_SUFFIXES[this.plugin.settings.countType];
|
|
}
|
|
});
|
|
this.renderSessionCountTypeSetting(containerEl, {
|
|
parentCountType: this.plugin.settings.countType,
|
|
oldCountType: this.plugin.settings.countConfig.$sessionCountType,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.countConfig.$sessionCountType = value;
|
|
}
|
|
});
|
|
this.renderFrontmatterKeySetting(containerEl, {
|
|
countType: this.plugin.settings.countType,
|
|
oldKey: this.plugin.settings.countConfig.frontmatterKey,
|
|
setNewKey: (value) => this.plugin.settings.countConfig.frontmatterKey = value
|
|
});
|
|
this.renderCustomFormatSetting(containerEl, {
|
|
countType: this.plugin.settings.countType,
|
|
oldSuffix: this.plugin.settings.countConfig.customSuffix,
|
|
setNewSuffix: (value) => this.plugin.settings.countConfig.customSuffix = value
|
|
});
|
|
this.renderCountTypeSetting(containerEl, {
|
|
name: "2nd data type to show",
|
|
oldCountType: this.plugin.settings.countType2,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.countType2 = value;
|
|
this.plugin.settings.countConfig2.customSuffix = COUNT_TYPE_DEFAULT_SHORT_SUFFIXES[this.plugin.settings.countType2];
|
|
}
|
|
});
|
|
this.renderSessionCountTypeSetting(containerEl, {
|
|
parentCountType: this.plugin.settings.countType2,
|
|
oldCountType: this.plugin.settings.countConfig2.$sessionCountType,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.countConfig2.$sessionCountType = value;
|
|
}
|
|
});
|
|
this.renderFrontmatterKeySetting(containerEl, {
|
|
countType: this.plugin.settings.countType2,
|
|
oldKey: this.plugin.settings.countConfig2.frontmatterKey,
|
|
setNewKey: (value) => this.plugin.settings.countConfig2.frontmatterKey = value
|
|
});
|
|
this.renderCustomFormatSetting(containerEl, {
|
|
countType: this.plugin.settings.countType2,
|
|
oldSuffix: this.plugin.settings.countConfig2.customSuffix,
|
|
setNewSuffix: (value) => this.plugin.settings.countConfig2.customSuffix = value
|
|
});
|
|
this.renderCountTypeSetting(containerEl, {
|
|
name: "3rd data type to show",
|
|
oldCountType: this.plugin.settings.countType3,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.countType3 = value;
|
|
this.plugin.settings.countConfig3.customSuffix = COUNT_TYPE_DEFAULT_SHORT_SUFFIXES[this.plugin.settings.countType3];
|
|
}
|
|
});
|
|
this.renderSessionCountTypeSetting(containerEl, {
|
|
parentCountType: this.plugin.settings.countType3,
|
|
oldCountType: this.plugin.settings.countConfig3.$sessionCountType,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.countConfig3.$sessionCountType = value;
|
|
}
|
|
});
|
|
this.renderFrontmatterKeySetting(containerEl, {
|
|
countType: this.plugin.settings.countType3,
|
|
oldKey: this.plugin.settings.countConfig3.frontmatterKey,
|
|
setNewKey: (value) => this.plugin.settings.countConfig3.frontmatterKey = value
|
|
});
|
|
this.renderCustomFormatSetting(containerEl, {
|
|
countType: this.plugin.settings.countType3,
|
|
oldSuffix: this.plugin.settings.countConfig3.customSuffix,
|
|
setNewSuffix: (value) => this.plugin.settings.countConfig3.customSuffix = value
|
|
});
|
|
if (this.plugin.settings.useAdvancedFormatting) {
|
|
new import_obsidian4.Setting(containerEl).setName("Data type separator").addText(
|
|
(text) => text.setValue(this.plugin.settings.pipeSeparator).onChange(async (value) => {
|
|
this.plugin.settings.pipeSeparator = value;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.updateDisplayedCounts();
|
|
})
|
|
);
|
|
}
|
|
if (!this.plugin.settings.useAdvancedFormatting) {
|
|
new import_obsidian4.Setting(containerEl).setName("Abbreviate descriptions").setDesc("E.g. show '120w' instead of '120 words'").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.abbreviateDescriptions).onChange(async (value) => {
|
|
this.plugin.settings.abbreviateDescriptions = value;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.updateDisplayedCounts();
|
|
})
|
|
);
|
|
}
|
|
new import_obsidian4.Setting(containerEl).setName("Alignment").setDesc(
|
|
"Show data inline with file/folder names, right-aligned, or underneath"
|
|
).addDropdown((drop) => {
|
|
drop.addOption("inline" /* Inline */, "Inline").addOption("right" /* Right */, "Right-aligned").addOption("below" /* Below */, "Below").setValue(this.plugin.settings.alignment).onChange(async (value) => {
|
|
this.plugin.settings.alignment = value;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.updateDisplayedCounts();
|
|
});
|
|
});
|
|
}
|
|
renderFolderSettings(containerEl) {
|
|
this.renderSeparator(containerEl);
|
|
new import_obsidian4.Setting(containerEl).setHeading().setName("Folders: Same data as Notes").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.showSameCountsOnFolders).onChange(async (value) => {
|
|
this.plugin.settings.showSameCountsOnFolders = value;
|
|
await this.plugin.saveSettings();
|
|
this.display();
|
|
await this.plugin.updateDisplayedCounts();
|
|
})
|
|
);
|
|
if (!this.plugin.settings.showSameCountsOnFolders) {
|
|
this.renderCountTypeSetting(containerEl, {
|
|
name: "1st data type to show",
|
|
oldCountType: this.plugin.settings.folderCountType,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.folderCountType = value;
|
|
this.plugin.settings.folderCountConfig.customSuffix = COUNT_TYPE_DEFAULT_SHORT_SUFFIXES[this.plugin.settings.folderCountType];
|
|
}
|
|
});
|
|
this.renderSessionCountTypeSetting(containerEl, {
|
|
parentCountType: this.plugin.settings.folderCountType,
|
|
oldCountType: this.plugin.settings.folderCountConfig.$sessionCountType,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.folderCountConfig.$sessionCountType = value;
|
|
}
|
|
});
|
|
this.renderCustomFormatSetting(containerEl, {
|
|
countType: this.plugin.settings.folderCountType,
|
|
oldSuffix: this.plugin.settings.folderCountConfig.customSuffix,
|
|
setNewSuffix: (value) => this.plugin.settings.folderCountConfig.customSuffix = value
|
|
});
|
|
this.renderCountTypeSetting(containerEl, {
|
|
name: "2nd data type to show",
|
|
oldCountType: this.plugin.settings.folderCountType2,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.folderCountType2 = value;
|
|
this.plugin.settings.folderCountConfig2.customSuffix = COUNT_TYPE_DEFAULT_SHORT_SUFFIXES[this.plugin.settings.folderCountType2];
|
|
}
|
|
});
|
|
this.renderSessionCountTypeSetting(containerEl, {
|
|
parentCountType: this.plugin.settings.folderCountType2,
|
|
oldCountType: this.plugin.settings.folderCountConfig2.$sessionCountType,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.folderCountConfig2.$sessionCountType = value;
|
|
}
|
|
});
|
|
this.renderCustomFormatSetting(containerEl, {
|
|
countType: this.plugin.settings.folderCountType2,
|
|
oldSuffix: this.plugin.settings.folderCountConfig2.customSuffix,
|
|
setNewSuffix: (value) => this.plugin.settings.folderCountConfig2.customSuffix = value
|
|
});
|
|
this.renderCountTypeSetting(containerEl, {
|
|
name: "3rd data type to show",
|
|
oldCountType: this.plugin.settings.folderCountType3,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.folderCountType3 = value;
|
|
this.plugin.settings.folderCountConfig3.customSuffix = COUNT_TYPE_DEFAULT_SHORT_SUFFIXES[this.plugin.settings.folderCountType3];
|
|
}
|
|
});
|
|
this.renderSessionCountTypeSetting(containerEl, {
|
|
parentCountType: this.plugin.settings.folderCountType3,
|
|
oldCountType: this.plugin.settings.folderCountConfig3.$sessionCountType,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.folderCountConfig3.$sessionCountType = value;
|
|
}
|
|
});
|
|
this.renderCustomFormatSetting(containerEl, {
|
|
countType: this.plugin.settings.folderCountType3,
|
|
oldSuffix: this.plugin.settings.folderCountConfig3.customSuffix,
|
|
setNewSuffix: (value) => this.plugin.settings.folderCountConfig3.customSuffix = value
|
|
});
|
|
if (this.plugin.settings.useAdvancedFormatting) {
|
|
new import_obsidian4.Setting(containerEl).setName("Data type separator").addText(
|
|
(text) => text.setValue(this.plugin.settings.folderPipeSeparator).onChange(async (value) => {
|
|
this.plugin.settings.folderPipeSeparator = value;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.updateDisplayedCounts();
|
|
})
|
|
);
|
|
}
|
|
if (!this.plugin.settings.useAdvancedFormatting) {
|
|
new import_obsidian4.Setting(containerEl).setName("Abbreviate descriptions").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.folderAbbreviateDescriptions).onChange(async (value) => {
|
|
this.plugin.settings.folderAbbreviateDescriptions = value;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.updateDisplayedCounts();
|
|
})
|
|
);
|
|
}
|
|
new import_obsidian4.Setting(containerEl).setName("Alignment").addDropdown((drop) => {
|
|
drop.addOption("inline" /* Inline */, "Inline").addOption("right" /* Right */, "Right-aligned").addOption("below" /* Below */, "Below").setValue(this.plugin.settings.folderAlignment).onChange(async (value) => {
|
|
this.plugin.settings.folderAlignment = value;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.updateDisplayedCounts();
|
|
});
|
|
});
|
|
}
|
|
}
|
|
renderRootSettings(containerEl) {
|
|
this.renderSeparator(containerEl);
|
|
new import_obsidian4.Setting(containerEl).setHeading().setName("Root: Same data as Notes").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.showSameCountsOnRoot).onChange(async (value) => {
|
|
this.plugin.settings.showSameCountsOnRoot = value;
|
|
await this.plugin.saveSettings();
|
|
this.display();
|
|
await this.plugin.updateDisplayedCounts();
|
|
})
|
|
);
|
|
if (!this.plugin.settings.showSameCountsOnRoot) {
|
|
this.renderCountTypeSetting(containerEl, {
|
|
name: "1st data type to show",
|
|
oldCountType: this.plugin.settings.rootCountType,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.rootCountType = value;
|
|
this.plugin.settings.rootCountConfig.customSuffix = COUNT_TYPE_DEFAULT_SHORT_SUFFIXES[this.plugin.settings.rootCountType];
|
|
}
|
|
});
|
|
this.renderSessionCountTypeSetting(containerEl, {
|
|
parentCountType: this.plugin.settings.rootCountType,
|
|
oldCountType: this.plugin.settings.rootCountConfig.$sessionCountType,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.rootCountConfig.$sessionCountType = value;
|
|
}
|
|
});
|
|
this.renderCustomFormatSetting(containerEl, {
|
|
countType: this.plugin.settings.rootCountType,
|
|
oldSuffix: this.plugin.settings.rootCountConfig.customSuffix,
|
|
setNewSuffix: (value) => this.plugin.settings.rootCountConfig.customSuffix = value
|
|
});
|
|
this.renderCountTypeSetting(containerEl, {
|
|
name: "2nd data type to show",
|
|
oldCountType: this.plugin.settings.rootCountType2,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.rootCountType2 = value;
|
|
this.plugin.settings.rootCountConfig2.customSuffix = COUNT_TYPE_DEFAULT_SHORT_SUFFIXES[this.plugin.settings.rootCountType2];
|
|
}
|
|
});
|
|
this.renderSessionCountTypeSetting(containerEl, {
|
|
parentCountType: this.plugin.settings.rootCountType2,
|
|
oldCountType: this.plugin.settings.rootCountConfig2.$sessionCountType,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.rootCountConfig2.$sessionCountType = value;
|
|
}
|
|
});
|
|
this.renderCustomFormatSetting(containerEl, {
|
|
countType: this.plugin.settings.rootCountType2,
|
|
oldSuffix: this.plugin.settings.rootCountConfig2.customSuffix,
|
|
setNewSuffix: (value) => this.plugin.settings.rootCountConfig2.customSuffix = value
|
|
});
|
|
this.renderCountTypeSetting(containerEl, {
|
|
name: "3rd data type to show",
|
|
oldCountType: this.plugin.settings.rootCountType3,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.rootCountType3 = value;
|
|
this.plugin.settings.rootCountConfig3.customSuffix = COUNT_TYPE_DEFAULT_SHORT_SUFFIXES[this.plugin.settings.rootCountType3];
|
|
}
|
|
});
|
|
this.renderSessionCountTypeSetting(containerEl, {
|
|
parentCountType: this.plugin.settings.rootCountType3,
|
|
oldCountType: this.plugin.settings.rootCountConfig3.$sessionCountType,
|
|
setNewCountType: (value) => {
|
|
this.plugin.settings.rootCountConfig3.$sessionCountType = value;
|
|
}
|
|
});
|
|
this.renderCustomFormatSetting(containerEl, {
|
|
countType: this.plugin.settings.rootCountType3,
|
|
oldSuffix: this.plugin.settings.rootCountConfig3.customSuffix,
|
|
setNewSuffix: (value) => this.plugin.settings.rootCountConfig3.customSuffix = value
|
|
});
|
|
if (this.plugin.settings.useAdvancedFormatting) {
|
|
new import_obsidian4.Setting(containerEl).setName("Data type separator").addText(
|
|
(text) => text.setValue(this.plugin.settings.rootPipeSeparator).onChange(async (value) => {
|
|
this.plugin.settings.rootPipeSeparator = value;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.updateDisplayedCounts();
|
|
})
|
|
);
|
|
}
|
|
if (!this.plugin.settings.useAdvancedFormatting) {
|
|
new import_obsidian4.Setting(containerEl).setName("Abbreviate descriptions").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.rootAbbreviateDescriptions).onChange(async (value) => {
|
|
this.plugin.settings.rootAbbreviateDescriptions = value;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.updateDisplayedCounts();
|
|
})
|
|
);
|
|
}
|
|
}
|
|
}
|
|
renderAdvancedSettings(containerEl) {
|
|
this.renderSeparator(containerEl);
|
|
new import_obsidian4.Setting(containerEl).setHeading().setName("Show advanced options").setDesc("Language compatibility and fine-tuning").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.showAdvanced).onChange(async (value) => {
|
|
this.plugin.settings.showAdvanced = value;
|
|
await this.plugin.saveSettings();
|
|
this.display();
|
|
})
|
|
);
|
|
if (this.plugin.settings.showAdvanced) {
|
|
const opacityChanged = async (value) => {
|
|
this.plugin.settings.labelOpacity = Math.clamp(value, 0, 1);
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.updateDisplayedCounts();
|
|
};
|
|
new import_obsidian4.Setting(containerEl).setName("Label opacity").setDesc("Increase this value to make all count labels in the File Explorer more visible.").addSlider((slider) => {
|
|
slider.setLimits(0, 1, 0.05).setDynamicTooltip().setValue(this.plugin.settings.labelOpacity).onChange((0, import_obsidian4.debounce)(opacityChanged.bind(this), 500));
|
|
});
|
|
const includePathsChanged = async (txt, value) => {
|
|
this.plugin.settings.includeDirectories = value;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.initialize();
|
|
};
|
|
new import_obsidian4.Setting(containerEl).setName("Include file/folder names").setDesc(
|
|
"Only count paths matching the indicated term(s). Case-sensitive, comma-separated. Defaults to all files. Any term starting with ! will be excluded instead of included."
|
|
).addText((txt) => {
|
|
txt.setPlaceholder("").setValue(this.plugin.settings.includeDirectories).onChange((0, import_obsidian4.debounce)(includePathsChanged.bind(this, txt), 1e3));
|
|
});
|
|
new import_obsidian4.Setting(containerEl).setName("Exclude comments").setDesc(
|
|
"Exclude %%Obsidian%% and <!--HTML--> comments from counts. May affect performance on large vaults."
|
|
).addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.excludeComments).onChange(async (value) => {
|
|
this.plugin.settings.excludeComments = value;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.initialize();
|
|
})
|
|
);
|
|
new import_obsidian4.Setting(containerEl).setName("Exclude code blocks").setDesc(
|
|
"Exclude ```code blocks``` (e.g. DataView snippets) from all counts. May affect performance on large vaults."
|
|
).addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.excludeCodeBlocks).onChange(async (value) => {
|
|
this.plugin.settings.excludeCodeBlocks = value;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.initialize();
|
|
})
|
|
);
|
|
new import_obsidian4.Setting(containerEl).setName("Exclude non-visible portions of links").setDesc(
|
|
"For external links, exclude the URI from all counts. For internal links with aliases, only count the alias. May affect performance on large vaults."
|
|
).addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.excludeNonVisibleLinkPortions).onChange(async (value) => {
|
|
this.plugin.settings.excludeNonVisibleLinkPortions = value;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.initialize();
|
|
})
|
|
);
|
|
new import_obsidian4.Setting(containerEl).setName("Exclude footnotes").setDesc(
|
|
"Exclude footnotes[^1] from counts. May affect performance on large vaults."
|
|
).addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.excludeFootnotes).onChange(async (value) => {
|
|
this.plugin.settings.excludeFootnotes = value;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.initialize();
|
|
})
|
|
);
|
|
new import_obsidian4.Setting(containerEl).setName("Character count method").setDesc("For language compatibility").addDropdown((drop) => {
|
|
drop.addOption("AllCharacters" /* StringLength */, "All characters").addOption(
|
|
"ExcludeWhitespace" /* ExcludeWhitespace */,
|
|
"Exclude whitespace"
|
|
).setValue(this.plugin.settings.characterCountType).onChange(async (value) => {
|
|
this.plugin.settings.characterCountType = value;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.initialize();
|
|
});
|
|
});
|
|
new import_obsidian4.Setting(containerEl).setName("Page count method").setDesc("For language compatibility").addDropdown((drop) => {
|
|
drop.addOption("ByWords" /* ByWords */, "Words per page").addOption("ByChars" /* ByChars */, "Characters per page").setValue(this.plugin.settings.pageCountType).onChange(async (value) => {
|
|
this.plugin.settings.pageCountType = value;
|
|
await this.plugin.saveSettings();
|
|
this.display();
|
|
await this.plugin.updateDisplayedCounts();
|
|
});
|
|
});
|
|
const wordsPerMinuteChanged = async (txt, value) => {
|
|
const asNumber = Number(value);
|
|
const isValid = !isNaN(asNumber) && asNumber > 0;
|
|
txt.inputEl.style.borderColor = isValid ? null : "red";
|
|
this.plugin.settings.wordsPerMinute = isValid ? Number(value) : 265;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.initialize();
|
|
};
|
|
new import_obsidian4.Setting(containerEl).setName("Words per minute").setDesc(
|
|
"Used to calculate Reading Time. 265 is the average speed of an English-speaking adult."
|
|
).addText((txt) => {
|
|
txt.setPlaceholder("265").setValue(this.plugin.settings.wordsPerMinute.toString()).onChange((0, import_obsidian4.debounce)(wordsPerMinuteChanged.bind(this, txt), 1e3));
|
|
});
|
|
const charsPerMinuteChanged = async (txt, value) => {
|
|
const asNumber = Number(value);
|
|
const isValid = !isNaN(asNumber) && asNumber > 0;
|
|
txt.inputEl.style.borderColor = isValid ? null : "red";
|
|
this.plugin.settings.charsPerMinute = isValid ? Number(value) : 500;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.initialize();
|
|
};
|
|
new import_obsidian4.Setting(containerEl).setName("CJK characters per minute").setDesc(
|
|
"Used to calculate Reading Time. 500 is the average speed for CJK texts."
|
|
).addText((txt) => {
|
|
txt.setPlaceholder("500").setValue(this.plugin.settings.charsPerMinute.toString()).onChange((0, import_obsidian4.debounce)(charsPerMinuteChanged.bind(this, txt), 1e3));
|
|
});
|
|
if (this.plugin.settings.pageCountType === "ByWords" /* ByWords */) {
|
|
const wordsPerPageChanged = async (txt, value) => {
|
|
const asNumber = Number(value);
|
|
const isValid = !isNaN(asNumber) && asNumber > 0;
|
|
txt.inputEl.style.borderColor = isValid ? null : "red";
|
|
this.plugin.settings.wordsPerPage = isValid ? Number(value) : 300;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.initialize();
|
|
};
|
|
new import_obsidian4.Setting(containerEl).setName("Words per page").setDesc(
|
|
"Used for page count. 300 is standard in English language publishing."
|
|
).addText((txt) => {
|
|
txt.setPlaceholder("300").setValue(this.plugin.settings.wordsPerPage.toString()).onChange((0, import_obsidian4.debounce)(wordsPerPageChanged.bind(this, txt), 1e3));
|
|
});
|
|
}
|
|
if (this.plugin.settings.pageCountType === "ByChars" /* ByChars */) {
|
|
new import_obsidian4.Setting(containerEl).setName("Include whitespace characters in page count").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.charsPerPageIncludesWhitespace).onChange(async (value) => {
|
|
this.plugin.settings.charsPerPageIncludesWhitespace = value;
|
|
await this.plugin.saveSettings();
|
|
this.display();
|
|
await this.plugin.initialize();
|
|
})
|
|
);
|
|
const charsPerPageChanged = async (txt, value) => {
|
|
const asNumber = Number(value);
|
|
const isValid = !isNaN(asNumber) && asNumber > 0;
|
|
txt.inputEl.style.borderColor = isValid ? null : "red";
|
|
const defaultCharsPerPage = 1500;
|
|
this.plugin.settings.charsPerPage = isValid ? Number(value) : defaultCharsPerPage;
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.initialize();
|
|
};
|
|
new import_obsidian4.Setting(containerEl).setName("Characters per page").setDesc(
|
|
`Used for page count. ${this.plugin.settings.charsPerPageIncludesWhitespace ? "2400 is common in Danish." : "1500 is common in German (Normseite)."}`
|
|
).addText((txt) => {
|
|
txt.setPlaceholder("1500").setValue(this.plugin.settings.charsPerPage.toString()).onChange((0, import_obsidian4.debounce)(charsPerPageChanged.bind(this, txt), 1e3));
|
|
});
|
|
}
|
|
const dateFormatChanged = async (txt, value) => {
|
|
const isValid = typeof value === "string" && !!value.trim();
|
|
this.plugin.settings.momentDateFormat = isValid ? value : "";
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.initialize();
|
|
};
|
|
new import_obsidian4.Setting(containerEl).setName("Date format").setDesc("MomentJS date format to use for date strings").addText((txt) => {
|
|
txt.setPlaceholder("YYYY/MM/DD").setValue(this.plugin.settings.momentDateFormat).onChange((0, import_obsidian4.debounce)(dateFormatChanged.bind(this, txt), 1e3));
|
|
});
|
|
new import_obsidian4.Setting(containerEl).setName("Debug mode").setDesc("Log debugging information to the developer console").addToggle(
|
|
(toggle) => toggle.setValue(this.plugin.settings.debugMode).onChange(async (value) => {
|
|
this.plugin.settings.debugMode = value;
|
|
this.plugin.debugHelper.setDebugMode(value);
|
|
this.plugin.fileHelper.setDebugMode(value);
|
|
await this.plugin.saveSettings();
|
|
})
|
|
);
|
|
}
|
|
}
|
|
renderReanalyzeButton(containerEl) {
|
|
this.renderSeparator(containerEl);
|
|
new import_obsidian4.Setting(containerEl).setHeading().setName("Recount all documents").setDesc(
|
|
"If changes have occurred outside of Obsidian, you may need to trigger a manual recount"
|
|
).addButton(
|
|
(button) => button.setButtonText("Recount").setCta().onClick(async () => {
|
|
button.disabled = true;
|
|
await this.plugin.initialize();
|
|
button.setButtonText("Done");
|
|
button.removeCta();
|
|
setTimeout(() => {
|
|
button.setButtonText("Recount");
|
|
button.setCta();
|
|
button.disabled = false;
|
|
}, 1e3);
|
|
})
|
|
);
|
|
}
|
|
renderDonationButton(containerEl) {
|
|
this.renderSeparator(containerEl);
|
|
const label = containerEl.createEl("div", {
|
|
cls: [
|
|
"setting-item",
|
|
"setting-item-heading",
|
|
"novel-word-count-settings-header",
|
|
"novel-word-count-donation-line"
|
|
]
|
|
});
|
|
label.createEl("div", {
|
|
text: "Enjoying this plugin? Want more features?"
|
|
});
|
|
const button = label.createEl("div");
|
|
button.innerHTML = `<a href='https://ko-fi.com/J3J6OWA5C' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi2.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>`;
|
|
}
|
|
renderCountTypeSetting(containerEl, config) {
|
|
new import_obsidian4.Setting(containerEl).setName(config.name).setDesc(getDescription(config.oldCountType)).addDropdown((drop) => {
|
|
for (const countType of COUNT_TYPES) {
|
|
drop.addOption(countType, COUNT_TYPE_DISPLAY_STRINGS[countType]);
|
|
}
|
|
drop.setValue(config.oldCountType).onChange(async (value) => {
|
|
config.setNewCountType(value);
|
|
await this.plugin.saveSettings();
|
|
this.display();
|
|
await this.plugin.updateDisplayedCounts();
|
|
});
|
|
});
|
|
}
|
|
renderSessionCountTypeSetting(containerEl, config) {
|
|
if (config.parentCountType !== "tracksession" /* TrackSession */) {
|
|
return;
|
|
}
|
|
new import_obsidian4.Setting(containerEl).setDesc("[Track Session] Session count type").addDropdown((drop) => {
|
|
for (const countType of SESSION_COUNT_TYPES) {
|
|
drop.addOption(countType, COUNT_TYPE_DISPLAY_STRINGS[countType]);
|
|
}
|
|
drop.setValue(config.oldCountType).onChange(async (value) => {
|
|
config.setNewCountType(value);
|
|
await this.plugin.saveSettings();
|
|
this.display();
|
|
await this.plugin.updateDisplayedCounts();
|
|
});
|
|
});
|
|
}
|
|
renderCustomFormatSetting(containerEl, config) {
|
|
if (!this.plugin.settings.useAdvancedFormatting || config.countType === "none" /* None */) {
|
|
return;
|
|
}
|
|
if (UNFORMATTABLE_COUNT_TYPES.includes(config.countType)) {
|
|
new import_obsidian4.Setting(containerEl).setDesc(
|
|
`[${COUNT_TYPE_DISPLAY_STRINGS[config.countType]}] can't be formatted.`
|
|
);
|
|
} else {
|
|
new import_obsidian4.Setting(containerEl).setDesc(
|
|
`[${COUNT_TYPE_DISPLAY_STRINGS[config.countType]}] Custom suffix`
|
|
).addText(
|
|
(text) => text.setValue(config.oldSuffix).onChange(async (value) => {
|
|
config.setNewSuffix(value);
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.updateDisplayedCounts();
|
|
})
|
|
);
|
|
}
|
|
}
|
|
renderFrontmatterKeySetting(containerEl, config) {
|
|
if (config.countType !== "frontmatterKey" /* FrontmatterKey */) {
|
|
return;
|
|
}
|
|
new import_obsidian4.Setting(containerEl).setDesc(
|
|
`[${COUNT_TYPE_DISPLAY_STRINGS["frontmatterKey" /* FrontmatterKey */]}] Key name`
|
|
).addText(
|
|
(text) => text.setValue(config.oldKey).onChange(async (value) => {
|
|
config.setNewKey(value);
|
|
await this.plugin.saveSettings();
|
|
await this.plugin.updateDisplayedCounts();
|
|
})
|
|
);
|
|
}
|
|
renderSeparator(containerEl) {
|
|
containerEl.createEl("hr", {
|
|
cls: "novel-word-count-hr"
|
|
});
|
|
}
|
|
};
|
|
|
|
// main.ts
|
|
var import_obsidian5 = require("obsidian");
|
|
var NovelWordCountPlugin = class extends import_obsidian5.Plugin {
|
|
constructor(app, manifest) {
|
|
super(app, manifest);
|
|
this.debugHelper = new DebugHelper();
|
|
this.FIVE_MINUTES = 5 * 60 * 1e3;
|
|
this.saveSettingsDebounced = (0, import_obsidian5.debounce)(this.saveSettings, this.FIVE_MINUTES, false);
|
|
this.fileHelper = new FileHelper(this.app, this);
|
|
this.eventHelper = new EventHelper(
|
|
this,
|
|
app,
|
|
this.debugHelper,
|
|
this.fileHelper
|
|
);
|
|
this.nodeLabelHelper = new NodeLabelHelper(this);
|
|
this.savedDataHelper = new SavedDataHelper(this);
|
|
}
|
|
get settings() {
|
|
return this.savedData.settings;
|
|
}
|
|
// LIFECYCLE
|
|
async onload() {
|
|
this.savedData = await this.savedDataHelper.getSavedData();
|
|
this.fileHelper.setDebugMode(this.savedData.settings.debugMode);
|
|
this.debugHelper.setDebugMode(this.savedData.settings.debugMode);
|
|
this.debugHelper.debug(`Detected locales: [${navigator.languages}]`);
|
|
this.debugHelper.debug("onload lifecycle hook");
|
|
this.addSettingTab(new NovelWordCountSettingTab(this.app, this));
|
|
this.addCommand({
|
|
id: "recount-vault",
|
|
name: "Recount all notes / Reset session",
|
|
callback: async () => {
|
|
this.debugHelper.debug("[Recount] command triggered");
|
|
await this.initialize();
|
|
}
|
|
});
|
|
this.addCommand({
|
|
id: "cycle-count-type",
|
|
name: "Show next data type (1st position)",
|
|
callback: async () => {
|
|
this.debugHelper.debug("[Cycle next data type] command triggered");
|
|
this.settings.countType = COUNT_TYPES[(COUNT_TYPES.indexOf(this.settings.countType) + 1) % COUNT_TYPES.length];
|
|
await this.saveSettings();
|
|
this.updateDisplayedCounts();
|
|
}
|
|
});
|
|
this.addCommand({
|
|
id: "toggle-abbreviate",
|
|
name: "Toggle abbreviation on Notes",
|
|
callback: async () => {
|
|
this.debugHelper.debug(
|
|
"[Toggle abbrevation - Notes] command triggered"
|
|
);
|
|
this.settings.abbreviateDescriptions = !this.settings.abbreviateDescriptions;
|
|
await this.saveSettings();
|
|
this.updateDisplayedCounts();
|
|
}
|
|
});
|
|
for (const countType of COUNT_TYPES) {
|
|
this.addCommand({
|
|
id: `set-count-type-${countType}`,
|
|
name: `Show ${COUNT_TYPE_DISPLAY_STRINGS[countType]} (1st position)`,
|
|
callback: async () => {
|
|
this.debugHelper.debug(
|
|
`[Set count type to ${countType}] command triggered`
|
|
);
|
|
this.settings.countType = countType;
|
|
await this.saveSettings();
|
|
this.updateDisplayedCounts();
|
|
}
|
|
});
|
|
}
|
|
this.eventHelper.handleEvents();
|
|
this.initialize();
|
|
}
|
|
async onunload() {
|
|
await this.saveSettings();
|
|
}
|
|
// SETTINGS
|
|
async saveSettings() {
|
|
this.debugHelper.debug("Saving to data.json.");
|
|
await this.saveData(this.savedData);
|
|
}
|
|
// PUBLIC
|
|
/**
|
|
Called with (true) when the plugin initializes or the user clicks Reanalyze.
|
|
Called with (false) every second while waiting for the file explorer to load.
|
|
*/
|
|
async initialize(reinitializeAllCounts = true) {
|
|
this.debugHelper.debug("initialize");
|
|
this.app.workspace.onLayoutReady(async () => {
|
|
if (reinitializeAllCounts) {
|
|
await this.eventHelper.reinitializeAllCounts();
|
|
}
|
|
try {
|
|
await this.getFileExplorerLeaf();
|
|
await this.updateDisplayedCounts();
|
|
} catch (err) {
|
|
this.debugHelper.debug("Error while updating displayed counts");
|
|
this.debugHelper.error(err);
|
|
setTimeout(() => {
|
|
this.initialize(false);
|
|
}, 1e3);
|
|
}
|
|
});
|
|
}
|
|
async updateDisplayedCounts(file = null) {
|
|
var _a, _b;
|
|
const debugEnd = this.debugHelper.debugStart(
|
|
`updateDisplayedCounts [${file == null ? "ALL" : file.path}]`
|
|
);
|
|
if (!Object.keys(this.savedData.cachedCounts).length) {
|
|
this.debugHelper.debug("No cached data found; skipping update.");
|
|
return;
|
|
}
|
|
let fileExplorerLeaf;
|
|
try {
|
|
fileExplorerLeaf = await this.getFileExplorerLeaf();
|
|
} catch (err) {
|
|
this.debugHelper.debug("File explorer leaf not found; skipping update.");
|
|
return;
|
|
}
|
|
this.setContainerClass(fileExplorerLeaf);
|
|
const fileExplorerView = fileExplorerLeaf.view;
|
|
const fileItems = fileExplorerView.fileItems;
|
|
if ((_a = fileExplorerView == null ? void 0 : fileExplorerView.headerDom) == null ? void 0 : _a.navButtonsEl) {
|
|
const counts = this.fileHelper.getCachedDataForPath(
|
|
this.savedData.cachedCounts,
|
|
"/"
|
|
);
|
|
fileExplorerView.headerDom.navButtonsEl.setAttribute(
|
|
"data-novel-word-count-plugin",
|
|
this.nodeLabelHelper.getNodeLabel(counts)
|
|
);
|
|
document.documentElement.style.setProperty("--novel-word-count-opacity", `${this.settings.labelOpacity}`);
|
|
}
|
|
if (file) {
|
|
const relevantItems = Object.keys(fileItems).filter(
|
|
(path) => file.path.includes(path)
|
|
);
|
|
this.debugHelper.debug(
|
|
"Setting display counts for",
|
|
relevantItems.length,
|
|
"fileItems matching path",
|
|
file.path
|
|
);
|
|
} else {
|
|
this.debugHelper.debug(
|
|
`Setting display counts for ${Object.keys(fileItems).length} fileItems`
|
|
);
|
|
}
|
|
for (const path in fileItems) {
|
|
if (file && (!file.path.includes(path) || file.path === "/")) {
|
|
continue;
|
|
}
|
|
const counts = this.fileHelper.getCachedDataForPath(
|
|
this.savedData.cachedCounts,
|
|
path
|
|
);
|
|
const item = fileItems[path];
|
|
((_b = item.titleEl) != null ? _b : item.selfEl).setAttribute(
|
|
"data-novel-word-count-plugin",
|
|
this.nodeLabelHelper.getNodeLabel(counts)
|
|
);
|
|
}
|
|
debugEnd();
|
|
}
|
|
// FUNCTIONALITY
|
|
async getFileExplorerLeaf() {
|
|
return new Promise((resolve, reject) => {
|
|
let foundLeaf = null;
|
|
this.app.workspace.iterateAllLeaves((leaf) => {
|
|
if (foundLeaf) {
|
|
return;
|
|
}
|
|
const view = leaf.view;
|
|
if (!view || !view.fileItems) {
|
|
return;
|
|
}
|
|
foundLeaf = leaf;
|
|
resolve(foundLeaf);
|
|
});
|
|
if (!foundLeaf) {
|
|
reject(Error("Could not find file explorer leaf."));
|
|
}
|
|
});
|
|
}
|
|
setContainerClass(leaf) {
|
|
const container = leaf.view.containerEl;
|
|
container.toggleClass(`novel-word-count--active`, true);
|
|
const notePrefix = `novel-word-count--note-`;
|
|
const folderPrefix = `novel-word-count--folder-`;
|
|
const alignmentClasses = ALIGNMENT_TYPES.map((at) => notePrefix + at).concat(ALIGNMENT_TYPES.map((at) => folderPrefix + at));
|
|
for (const ac of alignmentClasses) {
|
|
container.toggleClass(ac, false);
|
|
}
|
|
container.toggleClass(notePrefix + this.settings.alignment, true);
|
|
const folderAlignment = this.settings.showSameCountsOnFolders ? this.settings.alignment : this.settings.folderAlignment;
|
|
container.toggleClass(folderPrefix + folderAlignment, true);
|
|
}
|
|
};
|
|
|
|
/* nosourcemap */ |