main.js
1 /* 2 THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 3 if you want to view the source, please visit the github repository of this plugin 4 */ 5 6 var __create = Object.create; 7 var __defProp = Object.defineProperty; 8 var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 9 var __getOwnPropNames = Object.getOwnPropertyNames; 10 var __getProtoOf = Object.getPrototypeOf; 11 var __hasOwnProp = Object.prototype.hasOwnProperty; 12 var __markAsModule = (target) => __defProp(target, "__esModule", { value: true }); 13 var __commonJS = (cb, mod) => function __require() { 14 return mod || (0, cb[Object.keys(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; 15 }; 16 var __export = (target, all) => { 17 __markAsModule(target); 18 for (var name in all) 19 __defProp(target, name, { get: all[name], enumerable: true }); 20 }; 21 var __reExport = (target, module2, desc) => { 22 if (module2 && typeof module2 === "object" || typeof module2 === "function") { 23 for (let key of __getOwnPropNames(module2)) 24 if (!__hasOwnProp.call(target, key) && key !== "default") 25 __defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable }); 26 } 27 return target; 28 }; 29 var __toModule = (module2) => { 30 return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2); 31 }; 32 var __async = (__this, __arguments, generator) => { 33 return new Promise((resolve, reject) => { 34 var fulfilled = (value) => { 35 try { 36 step(generator.next(value)); 37 } catch (e) { 38 reject(e); 39 } 40 }; 41 var rejected = (value) => { 42 try { 43 step(generator.throw(value)); 44 } catch (e) { 45 reject(e); 46 } 47 }; 48 var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); 49 step((generator = generator.apply(__this, __arguments)).next()); 50 }); 51 }; 52 53 // node_modules/parse-ms/index.js 54 var require_parse_ms = __commonJS({ 55 "node_modules/parse-ms/index.js"(exports, module2) { 56 "use strict"; 57 module2.exports = (milliseconds) => { 58 if (typeof milliseconds !== "number") { 59 throw new TypeError("Expected a number"); 60 } 61 const roundTowardsZero = milliseconds > 0 ? Math.floor : Math.ceil; 62 return { 63 days: roundTowardsZero(milliseconds / 864e5), 64 hours: roundTowardsZero(milliseconds / 36e5) % 24, 65 minutes: roundTowardsZero(milliseconds / 6e4) % 60, 66 seconds: roundTowardsZero(milliseconds / 1e3) % 60, 67 milliseconds: roundTowardsZero(milliseconds) % 1e3, 68 microseconds: roundTowardsZero(milliseconds * 1e3) % 1e3, 69 nanoseconds: roundTowardsZero(milliseconds * 1e6) % 1e3 70 }; 71 }; 72 } 73 }); 74 75 // node_modules/pretty-ms/index.js 76 var require_pretty_ms = __commonJS({ 77 "node_modules/pretty-ms/index.js"(exports, module2) { 78 "use strict"; 79 var parseMilliseconds = require_parse_ms(); 80 var pluralize = (word, count) => count === 1 ? word : `${word}s`; 81 var SECOND_ROUNDING_EPSILON = 1e-7; 82 module2.exports = (milliseconds, options = {}) => { 83 if (!Number.isFinite(milliseconds)) { 84 throw new TypeError("Expected a finite number"); 85 } 86 if (options.colonNotation) { 87 options.compact = false; 88 options.formatSubMilliseconds = false; 89 options.separateMilliseconds = false; 90 options.verbose = false; 91 } 92 if (options.compact) { 93 options.secondsDecimalDigits = 0; 94 options.millisecondsDecimalDigits = 0; 95 } 96 const result = []; 97 const floorDecimals = (value, decimalDigits) => { 98 const flooredInterimValue = Math.floor(value * 10 ** decimalDigits + SECOND_ROUNDING_EPSILON); 99 const flooredValue = Math.round(flooredInterimValue) / 10 ** decimalDigits; 100 return flooredValue.toFixed(decimalDigits); 101 }; 102 const add = (value, long, short, valueString) => { 103 if ((result.length === 0 || !options.colonNotation) && value === 0 && !(options.colonNotation && short === "m")) { 104 return; 105 } 106 valueString = (valueString || value || "0").toString(); 107 let prefix; 108 let suffix; 109 if (options.colonNotation) { 110 prefix = result.length > 0 ? ":" : ""; 111 suffix = ""; 112 const wholeDigits = valueString.includes(".") ? valueString.split(".")[0].length : valueString.length; 113 const minLength = result.length > 0 ? 2 : 1; 114 valueString = "0".repeat(Math.max(0, minLength - wholeDigits)) + valueString; 115 } else { 116 prefix = ""; 117 suffix = options.verbose ? " " + pluralize(long, value) : short; 118 } 119 result.push(prefix + valueString + suffix); 120 }; 121 const parsed = parseMilliseconds(milliseconds); 122 add(Math.trunc(parsed.days / 365), "year", "y"); 123 add(parsed.days % 365, "day", "d"); 124 add(parsed.hours, "hour", "h"); 125 add(parsed.minutes, "minute", "m"); 126 if (options.separateMilliseconds || options.formatSubMilliseconds || !options.colonNotation && milliseconds < 1e3) { 127 add(parsed.seconds, "second", "s"); 128 if (options.formatSubMilliseconds) { 129 add(parsed.milliseconds, "millisecond", "ms"); 130 add(parsed.microseconds, "microsecond", "\xB5s"); 131 add(parsed.nanoseconds, "nanosecond", "ns"); 132 } else { 133 const millisecondsAndBelow = parsed.milliseconds + parsed.microseconds / 1e3 + parsed.nanoseconds / 1e6; 134 const millisecondsDecimalDigits = typeof options.millisecondsDecimalDigits === "number" ? options.millisecondsDecimalDigits : 0; 135 const roundedMiliseconds = millisecondsAndBelow >= 1 ? Math.round(millisecondsAndBelow) : Math.ceil(millisecondsAndBelow); 136 const millisecondsString = millisecondsDecimalDigits ? millisecondsAndBelow.toFixed(millisecondsDecimalDigits) : roundedMiliseconds; 137 add(Number.parseFloat(millisecondsString, 10), "millisecond", "ms", millisecondsString); 138 } 139 } else { 140 const seconds = milliseconds / 1e3 % 60; 141 const secondsDecimalDigits = typeof options.secondsDecimalDigits === "number" ? options.secondsDecimalDigits : 1; 142 const secondsFixed = floorDecimals(seconds, secondsDecimalDigits); 143 const secondsString = options.keepDecimalsOnWholeSeconds ? secondsFixed : secondsFixed.replace(/\.0+$/, ""); 144 add(Number.parseFloat(secondsString, 10), "second", "s", secondsString); 145 } 146 if (result.length === 0) { 147 return "0" + (options.verbose ? " milliseconds" : "ms"); 148 } 149 if (options.compact) { 150 return result[0]; 151 } 152 if (typeof options.unitCount === "number") { 153 const separator = options.colonNotation ? "" : " "; 154 return result.slice(0, Math.max(options.unitCount, 1)).join(separator); 155 } 156 return options.colonNotation ? result.join("") : result.join(" "); 157 }; 158 } 159 }); 160 161 // node_modules/reading-time/lib/reading-time.js 162 var require_reading_time = __commonJS({ 163 "node_modules/reading-time/lib/reading-time.js"(exports, module2) { 164 "use strict"; 165 function codeIsInRanges(number, arrayOfRanges) { 166 return arrayOfRanges.some(([lowerBound, upperBound]) => lowerBound <= number && number <= upperBound); 167 } 168 function isCJK(c) { 169 if (typeof c !== "string") { 170 return false; 171 } 172 const charCode = c.charCodeAt(0); 173 return codeIsInRanges(charCode, [ 174 [12352, 12447], 175 [19968, 40959], 176 [44032, 55203], 177 [131072, 191456] 178 ]); 179 } 180 function isAnsiWordBound(c) { 181 return " \n\r ".includes(c); 182 } 183 function isPunctuation(c) { 184 if (typeof c !== "string") { 185 return false; 186 } 187 const charCode = c.charCodeAt(0); 188 return codeIsInRanges(charCode, [ 189 [33, 47], 190 [58, 64], 191 [91, 96], 192 [123, 126], 193 [12288, 12351], 194 [65280, 65519] 195 ]); 196 } 197 function readingTime2(text, options = {}) { 198 let words = 0, start = 0, end = text.length - 1; 199 const wordsPerMinute = options.wordsPerMinute || 200; 200 const isWordBound = options.wordBound || isAnsiWordBound; 201 while (isWordBound(text[start])) 202 start++; 203 while (isWordBound(text[end])) 204 end--; 205 const normalizedText = `${text} 206 `; 207 for (let i = start; i <= end; i++) { 208 if (isCJK(normalizedText[i]) || !isWordBound(normalizedText[i]) && (isWordBound(normalizedText[i + 1]) || isCJK(normalizedText[i + 1]))) { 209 words++; 210 } 211 if (isCJK(normalizedText[i])) { 212 while (i <= end && (isPunctuation(normalizedText[i + 1]) || isWordBound(normalizedText[i + 1]))) { 213 i++; 214 } 215 } 216 } 217 const minutes = words / wordsPerMinute; 218 const time = Math.round(minutes * 60 * 1e3); 219 const displayed = Math.ceil(minutes.toFixed(2)); 220 return { 221 text: displayed + " min read", 222 minutes, 223 time, 224 words 225 }; 226 } 227 module2.exports = readingTime2; 228 } 229 }); 230 231 // src/main.ts 232 __export(exports, { 233 default: () => ReadingTime 234 }); 235 var import_obsidian2 = __toModule(require("obsidian")); 236 237 // src/settings.ts 238 var import_obsidian = __toModule(require("obsidian")); 239 var RT_DEFAULT_SETTINGS = { 240 readingSpeed: 200, 241 format: "default", 242 appendText: "read" 243 }; 244 var ReadingTimeSettingsTab = class extends import_obsidian.PluginSettingTab { 245 constructor(app, plugin) { 246 super(app, plugin); 247 this.plugin = plugin; 248 } 249 display() { 250 const { containerEl } = this; 251 containerEl.empty(); 252 new import_obsidian.Setting(containerEl).setName("Reading speed").setDesc("Words per minute used for reading speed (default: 200).").addText((text) => { 253 text.setPlaceholder("Example: 200").setValue(this.plugin.settings.readingSpeed.toString()).onChange((value) => __async(this, null, function* () { 254 this.plugin.settings.readingSpeed = parseInt(value.trim()); 255 yield this.plugin.saveSettings().then(this.plugin.calculateReadingTime); 256 })); 257 }); 258 new import_obsidian.Setting(this.containerEl).setName("Format").setDesc("Choose the output format").addDropdown((dropdown) => dropdown.addOption("default", "Default (10 min)").addOption("compact", "Compact (10m)").addOption("simple", "Simple (10m 4s)").addOption("verbose", "Verbose (10 minutes 4 seconds)").addOption("digital", "Colon Notation (10:04)").setValue(this.plugin.settings.format).onChange((value) => __async(this, null, function* () { 259 this.plugin.settings.format = value; 260 yield this.plugin.saveSettings().then(this.plugin.calculateReadingTime); 261 }))); 262 new import_obsidian.Setting(this.containerEl).setName("Append Text").setDesc("Append 'read' to formatted string.").addText((text) => text.setValue(this.plugin.settings.appendText).onChange((value) => __async(this, null, function* () { 263 this.plugin.settings.appendText = value.trim(); 264 yield this.plugin.saveSettings().then(this.plugin.calculateReadingTime); 265 }))); 266 } 267 }; 268 269 // src/helpers.ts 270 var import_pretty_ms = __toModule(require_pretty_ms()); 271 var ReadTime = require_reading_time(); 272 function readingTimeText(text, plugin) { 273 const result = ReadTime(text, { 274 wordsPerMinute: plugin.settings.readingSpeed 275 }); 276 let options = { 277 secondsDecimalDigits: 0 278 }; 279 switch (plugin.settings.format) { 280 case "simple": 281 break; 282 case "compact": 283 if (result.time > 36e5) { 284 options.unitCount = 2; 285 } else { 286 options.compact = true; 287 } 288 break; 289 case "verbose": 290 options.verbose = true; 291 break; 292 case "digital": 293 options.colonNotation = true; 294 break; 295 case "default": 296 return plugin.settings.appendText ? result.text : result.text.replace(" read", ""); 297 } 298 let output = (0, import_pretty_ms.default)(result.time, options); 299 return plugin.settings.appendText ? `${output} ${plugin.settings.appendText}` : output; 300 } 301 302 // src/main.ts 303 var ReadingTime = class extends import_obsidian2.Plugin { 304 constructor() { 305 super(...arguments); 306 this.calculateReadingTime = () => { 307 const mdView = this.app.workspace.getActiveViewOfType(import_obsidian2.MarkdownView); 308 if (mdView && mdView.getViewData()) { 309 const result = readingTimeText(mdView.getViewData(), this); 310 this.statusBar.setText(`${result}`); 311 } else { 312 this.statusBar.setText("0 min read"); 313 } 314 }; 315 } 316 onload() { 317 return __async(this, null, function* () { 318 yield this.loadSettings(); 319 this.statusBar = this.addStatusBarItem(); 320 this.statusBar.setText(""); 321 this.addSettingTab(new ReadingTimeSettingsTab(this.app, this)); 322 this.addCommand({ 323 id: "reading-time-editor-command", 324 name: "Selected Text", 325 editorCallback: (editor, view) => { 326 new ReadingTimeModal(this.app, editor, this).open(); 327 } 328 }); 329 this.registerEvent(this.app.workspace.on("file-open", this.calculateReadingTime)); 330 this.registerEvent(this.app.workspace.on("editor-change", (0, import_obsidian2.debounce)(this.calculateReadingTime, 1e3))); 331 }); 332 } 333 loadSettings() { 334 return __async(this, null, function* () { 335 this.settings = Object.assign({}, RT_DEFAULT_SETTINGS, yield this.loadData()); 336 }); 337 } 338 saveSettings() { 339 return __async(this, null, function* () { 340 yield this.saveData(this.settings); 341 }); 342 } 343 }; 344 var ReadingTimeModal = class extends import_obsidian2.Modal { 345 constructor(app, editor, plugin) { 346 super(app); 347 this.editor = editor; 348 this.plugin = plugin; 349 } 350 onOpen() { 351 const { contentEl, titleEl } = this; 352 titleEl.setText("Reading Time of Selected Text"); 353 const stats = readingTime(this.editor.getSelection(), this.plugin); 354 contentEl.setText(`${stats} (at ${this.plugin.settings.readingSpeed} wpm)`); 355 } 356 onClose() { 357 const { contentEl } = this; 358 contentEl.empty(); 359 } 360 }; 361 /*! 362 * reading-time 363 * Copyright (c) Nicolas Gryman <ngryman@gmail.com> 364 * MIT Licensed 365 */