-
Notifications
You must be signed in to change notification settings - Fork 0
/
article-time-calculator.js
183 lines (159 loc) · 7.39 KB
/
article-time-calculator.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
(function(){
'use strict';
/**
* @desc Sets minutesToRead and formattedTime on object that calls this function.
* Do not call this directly, you should invoke it with .call or .apply from an object that has a formatTimeFn.
*
* @param {number} textLength - the length of the text to calculate the reading time of.
*/
function setTimeProperties(textLength){
this.minutesToRead = textLength / this.wordsPerMinute;
this.formattedTime = this.formatTimeFn(this.minutesToRead);
}
/**
* @desc returns a word that is pluralized according to the english language. Intended use is for the words
* "minute" and "second".
*
* @param {string} word - the word being pluralized
* @param {number} amount - the number of words to compare against.
* @returns {string}
*/
function pluralizeFn(word, amount){
return (amount > 1 || amount === 0) ? word + 's' : word;
}
/**
* @desc a function that returns the time formatted thusly: (x) minutes (y) seconds.
* @param {number|string} minutes - the number of minutes.
* @param {string} nameForMinutes - the word used to represent minutes.
* @param {number|string} seconds - the number of seconds.
* @param {string} nameForSeconds - the word used to represent seconds.
* @returns {string}
*/
function formatResult(minutes, nameForMinutes, seconds, nameForSeconds ){
return minutes + ' ' + nameForMinutes + ' ' + seconds + ' ' + nameForSeconds;
}
/**
* @desc A class that calculates the amount of time it takes to a number of words. defaults to 200 words per second.
*
* @property {Object} [configObj] - a configuration object that defaults to formatting the time thusly: "(x) minute/s (y) second/s".
* @proprety {number} [configObj.wordsPerMinute] - configure the reading speed for calculations. defaults to 200.
* @proprety {string} [configObj.nameForMinute] - the word used to represent minutes. defaults to 'minute'.
* @proprety {string} [configObj.nameForSecond] - the word used to represent seconds. defaults to 'second'.
* @proprety {function} [configObj.pluralizationInterceptor] - a function that returns words implementing the rules to use to pluralize the "nameForMinute" and "nameForSecond" parameters. Must accept parameters in the form of (singularVersionOfWord, totalNumberOfWords) and MUST return a string.
* @proprety {function} [configObj.formatResultInterceptor]- a function that returns the way you want the results formatted. Must accept parameters in the form of (numberOfMinutes, nameRepresentingMinutes, numberOfSeconds, nameRepresentingSeconds) and should return a string (or else whats the point)
*
* @constructor
*/
class WordReader{
constructor(configObj){
let _configObj = (typeof configObj === 'object' && configObj !== null && !Array.isArray(configObj)) ? configObj : {};
this.wordsPerMinute = _configObj.wordsPerMinute || 200;
this.nameForMinute = _configObj.nameForMinute || 'minute';
this.nameForSecond = _configObj.nameForSecond || 'second';
this.pluralizationInterceptor = _configObj.pluralizationInterceptor || pluralizeFn;
this.formatResultInterceptor = _configObj.formatResultInterceptor || formatResult;
this._debug = (typeof _configObj.debug === 'boolean') ? true : false;
}
formatTimeFn(timeInMinutes){
let minutes = Math.floor(timeInMinutes)
, secondsAsPercentOfMinute = Math.round((timeInMinutes % minutes) * 100) / 100
, seconds = (isNaN(secondsAsPercentOfMinute)) ? 0 : Math.round(secondsAsPercentOfMinute * 60)
, minuteWord = this.pluralizationInterceptor(this.nameForMinute, minutes)
, secondWord = this.pluralizationInterceptor(this.nameForSecond, seconds);
if(timeInMinutes < 1){
minutes = 0;
seconds = Math.round(timeInMinutes * 60);
}
return this.formatResultInterceptor(minutes, minuteWord, seconds, secondWord);
}
guessBasedOnWordCount(num){
setTimeProperties.call(this, num);
return this.formattedTime;
}
}
function handleErrors(elem, selector){
if(!elem){
if(this._debug === true){
console.warn('there was no element with the selector: ' + selector);
console.trace();
}
return null;
}
}
/**
* @desc a class with methods that calculates the amount of time it will take to read a given element in a DOM. Will not be accessible if the context is not a Window.
* @extends WordReader
* @constructor
*/
class DOMArticleReader extends WordReader{
constructor(configObj){
super(configObj);
}
guessTimeToReadSelector(selector){
let elem = document.querySelector(selector);
let text;
if( handleErrors.call(this, elem, selector) === null ){ return; }
text = elem.textContent.split(' ');
return this.guessBasedOnWordCount(text.length);
}
guessTimeToReadElement(elem){
let text;
handleErrors.call(this, elem);
if( handleErrors.call(this, elem, selector) === null ){ return; }
text = elem.textContent.split(' ');
return this.guessBasedOnWordCount(text.length);
}
}
/**
* NOTE: Everything after this (the whole exporting thing) is taken from lodash (https://github.com/lodash/lodash)
* and has been slightly modified to fit this Object (didn't see the point in adding a 15k line dependency for 50 lines).
*/
var objectTypes = {
'function': true,
'object': true
};
var freeExports = (objectTypes[typeof exports] && exports && !exports.nodeType) ? exports : undefined;
var checkGlobal = (value)=>{
return (value && value.Object === Object) ? value : null;
}
/** Detect free variable `module`. */
var freeModule = (objectTypes[typeof module] && module && !module.nodeType) ? module : undefined;
/** Detect the popular CommonJS extension `module.exports`. */
var moduleExports = (freeModule && freeModule.exports === freeExports) ? freeExports : undefined;
/** Detect free variable `global` from Node.js. */
var freeGlobal = checkGlobal(freeExports && freeModule && typeof global == 'object' && global);
/** Detect free variable `self`. */
var freeSelf = checkGlobal(objectTypes[typeof self] && self);
/** Detect free variable `window`. */
var freeWindow = checkGlobal(objectTypes[typeof window] && window);
/** Detect `this` as the global object. */
var thisGlobal = checkGlobal(objectTypes[typeof this] && this);
/**
* Used as a reference to the global object.
*
* The `this` value is used if it's the global object to avoid Greasemonkey's
* restricted `window` object, otherwise the `window` object is used.
*/
var root = freeGlobal ||
((freeWindow !== (thisGlobal && thisGlobal.window)) && freeWindow) ||
freeSelf || thisGlobal || Function('return this')();
// Some AMD build optimizers like r.js check for condition patterns like the following:
if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
define(function() {
return WordReader;
});
}
// Check for `exports` after `define` in case a build optimizer adds an `exports` object.
else if (freeExports && freeModule) {
// Export for Node.js.
if (moduleExports) {
freeModule.exports = WordReader;
}
// Export for CommonJS support.
freeExports.ReadingGuesstimator = WordReader;
}
else {
// Export to the global object.
root.ReadingGuesstimator = freeWindow ? DOMArticleReader : WordReader;
}
})();