Action

Stats (with Read Times)

Posted by @gilsdesk, Last update almost 4 years ago

Calculate and present preview of common readability statistics for the current draft.
Slight modification to original Stats Action to include Read Aloud and Silent Read times for the current draft.

Steps

  • script

    // Make sure draft is not blank first...
    
    if (draft.content.length == 0) {
       alert("Empty draft. Text required to calculate statistics.");
    	context.cancel();
    }
  • script

    // Readability Stats with Read Times
    
    // This is the middle step. It comes after a blank-draft check and before a CSS table formatting step
    
    // Show alert displaying common text statistics for current draft
    // Includes syllable counts, Flesh-Kincaid, SMOG index scoring, etc.
    
    // adapted from...
    // TextStatistics.js
    // Christopher Giffard (2012)
    // 1:1 API Fork of TextStatistics.php by Dave Child (Thanks mate!)
    // https://github.com/DaveChild/Text-Statistics
    
    // Read Time functions added by Gil Rognstad
    
    // BEGIN LIBRARY	
    function cleanText(text) {
    	// all these tags should be preceeded by a full stop. 
    	var fullStopTags = ['li', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'dd'];
    	
    	fullStopTags.forEach(function(tag) {
    		text = text.replace("</" + tag + ">",".");
    	})
    	
    	text = text
    		.replace(/<[^>]+>/g, "")				// Strip tags
    		.replace(/[,:;()\-]/, " ")				// Replace commans, hyphens etc (count them as spaces)
    		.replace(/[\.!?]/, ".")					// Unify terminators
    		.replace(/^\s+/,"")						// Strip leading whitespace
    		.replace(/[ ]*(\n|\r\n|\r)[ ]*/," ")	// Replace new lines with spaces
    		.replace(/([\.])[\. ]+/,".")			// Check for duplicated terminators
    		.replace(/[ ]*([\.])/,". ")				// Pad sentence terminators
    		.replace(/\s+/," ")						// Remove multiple spaces
    		.replace(/\s+$/,"");					// Strip trailing whitespace
    		
    	text += "."; // Add final terminator, just in case it's missing.
    	
    	return text;
    }
    
    var TextStatistics = function TextStatistics(text) {
    	this.text = text ? cleanText(text) : this.text;
    };
    
    TextStatistics.prototype.fleschKincaidReadingEase = function(text) {
    	text = text ? cleanText(text) : this.text;
    	return Math.round((206.835 - (1.015 * this.averageWordsPerSentence(text)) - (84.6 * this.averageSyllablesPerWord(text)))*10)/10;
    };
    
    TextStatistics.prototype.fleschKincaidGradeLevel = function(text) {
    	text = text ? cleanText(text) : this.text;
    	return Math.round(((0.39 * this.averageWordsPerSentence(text)) + (11.8 * this.averageSyllablesPerWord(text)) - 15.59)*10)/10;
    };
    
    TextStatistics.prototype.gunningFogScore = function(text) {
    	text = text ? cleanText(text) : this.text;
    	return Math.round(((this.averageWordsPerSentence(text) + this.percentageWordsWithThreeSyllables(text, false)) * 0.4)*10)/10;
    };
    
    TextStatistics.prototype.colemanLiauIndex = function(text) {
    	text = text ? cleanText(text) : this.text;
    	return Math.round(((5.89 * (this.letterCount(text) / this.wordCount(text))) - (0.3 * (this.sentenceCount(text) / this.wordCount(text))) - 15.8 ) *10)/10;
    };
    
    TextStatistics.prototype.smogIndex = function(text) {
    	text = text ? cleanText(text) : this.text;
    	return Math.round(1.043 * Math.sqrt((this.wordsWithThreeSyllables(text) * (30 / this.sentenceCount(text))) + 3.1291)*10)/10;
    };
    
    TextStatistics.prototype.automatedReadabilityIndex = function(text) {
    	text = text ? cleanText(text) : this.text;
    	return Math.round(((4.71 * (this.letterCount(text) / this.wordCount(text))) + (0.5 * (this.wordCount(text) / this.sentenceCount(text))) - 21.43)*10)/10;
    };
    
    TextStatistics.prototype.textLength = function(text) {
    	text = text ? cleanText(text) : this.text;
    	return text.length;
    };
    
    TextStatistics.prototype.letterCount = function(text) {
    	text = text ? cleanText(text) : this.text;
    	text = text.replace(/[^a-z]+/ig,"");
    	return text.length;
    };
    
    TextStatistics.prototype.sentenceCount = function(text) {
    	text = text ? cleanText(text) : this.text;
    	
    	// Will be tripped up by "Mr." or "U.K.". Not a major concern at this point.
    	return text.replace(/[^\.!?]/g, '').length || 1;
    };
    
    TextStatistics.prototype.wordCount = function(text) {
    	text = text ? cleanText(text) : this.text;
    	return text.split(/[^a-z0-9]+/i).length || 1;
    };
    
    TextStatistics.prototype.averageWordsPerSentence = function(text) {
    	text = text ? cleanText(text) : this.text;
    	return this.wordCount(text) / this.sentenceCount(text);
    };
    
    TextStatistics.prototype.readAloudTimeSlow = function(text) {
    	text = text ? cleanText(text) : this.text;
    	return Math.round(this.wordCount(text) / 100);
    };
    
    TextStatistics.prototype.readAloudTimeRelaxed = function(text) {
    	text = text ? cleanText(text) : this.text;
    	return Math.round(this.wordCount(text) / 130);
    };
    
    TextStatistics.prototype.readAloudTimeFast = function(text) {
    	text = text ? cleanText(text) : this.text;
    	return Math.round(this.wordCount(text) / 160);
    };
    
    TextStatistics.prototype.silentReadTime = function(text) {
    	text = text ? cleanText(text) : this.text;
    	return Math.round(this.wordCount(text) / 200);
    };
    
    TextStatistics.prototype.averageSyllablesPerWord = function(text) {
    	text = text ? cleanText(text) : this.text;
    	var syllableCount = 0, wordCount = this.wordCount(text), self = this;
    	
    	text.split(/\s+/).forEach(function(word) {
    		syllableCount += self.syllableCount(word);
    	});
    	
    	// Prevent NaN...
    	return (syllableCount||1) / (wordCount||1);
    };
    
    TextStatistics.prototype.wordsWithThreeSyllables = function(text, countProperNouns) {
    	text = text ? cleanText(text) : this.text;
    	var longWordCount = 0, self = this;
    	
    	countProperNouns = countProperNouns === false ? false : true;
    	
    	text.split(/\s+/).forEach(function(word) {
    		
    		// We don't count proper nouns or capitalised words if the countProperNouns attribute is set.
    		// Defaults to true.
    		if (!word.match(/^[A-Z]/) || countProperNouns) {
    			if (self.syllableCount(word) > 2) longWordCount ++;
    		}
    	});
    	
    	return longWordCount;
    };
    
    TextStatistics.prototype.percentageWordsWithThreeSyllables = function(text, countProperNouns) {
    	text = text ? cleanText(text) : this.text;
    	
    	return (this.wordsWithThreeSyllables(text,countProperNouns) / this.wordCount(text)) * 100;
    };
    
    TextStatistics.prototype.syllableCount = function(word) {
    	var syllableCount = 0,
    		prefixSuffixCount = 0,
    		wordPartCount = 0;
    	
    	// Prepare word - make lower case and remove non-word characters
    	word = word.toLowerCase().replace(/[^a-z]/g,"");
    
    	// Specific common exceptions that don't follow the rule set below are handled individually
    	// Array of problem words (with word as key, syllable count as value)
    	var problemWords = {
    		"simile":		3,
    		"forever":		3,
    		"shoreline":	2
    	};
    	
    	// Return if we've hit one of those...
    	if (problemWords.hasOwnProperty(word)) return problemWords[word];
    	
    	// These syllables would be counted as two but should be one
    	var subSyllables = [
    		/cial/,
    		/tia/,
    		/cius/,
    		/cious/,
    		/giu/,
    		/ion/,
    		/iou/,
    		/sia$/,
    		/[^aeiuoyt]{2,}ed$/,
    		/.ely$/,
    		/[cg]h?e[rsd]?$/,
    		/rved?$/,
    		/[aeiouy][dt]es?$/,
    		/[aeiouy][^aeiouydt]e[rsd]?$/,
    		/^[dr]e[aeiou][^aeiou]+$/, // Sorts out deal, deign etc
    		/[aeiouy]rse$/ // Purse, hearse
    	];
    
    	// These syllables would be counted as one but should be two
    	var addSyllables = [
    		/ia/,
    		/riet/,
    		/dien/,
    		/iu/,
    		/io/,
    		/ii/,
    		/[aeiouym]bl$/,
    		/[aeiou]{3}/,
    		/^mc/,
    		/ism$/,
    		/([^aeiouy])\1l$/,
    		/[^l]lien/,
    		/^coa[dglx]./,
    		/[^gq]ua[^auieo]/,
    		/dnt$/,
    		/uity$/,
    		/ie(r|st)$/
    	];
    
    	// Single syllable prefixes and suffixes
    	var prefixSuffix = [
    		/^un/,
    		/^fore/,
    		/ly$/,
    		/less$/,
    		/ful$/,
    		/ers?$/,
    		/ings?$/
    	];
    
    	// Remove prefixes and suffixes and count how many were taken
    	prefixSuffix.forEach(function(regex) {
    		if (word.match(regex)) {
    			word = word.replace(regex,"");
    			prefixSuffixCount ++;
    		}
    	});
    	
    	wordPartCount = word
    		.split(/[^aeiouy]+/ig)
    		.filter(function(wordPart) {
    			return !!wordPart.replace(/\s+/ig,"").length
    		})
    		.length;
    	
    	// Get preliminary syllable count...
    	syllableCount = wordPartCount + prefixSuffixCount;
    	
    	// Some syllables do not follow normal rules - check for them
    	subSyllables.forEach(function(syllable) {
    		if (word.match(syllable)) syllableCount --;
    	});
    	
    	addSyllables.forEach(function(syllable) {
    		if (word.match(syllable)) syllableCount ++;
    	});
    	
    	return syllableCount || 1;
    };
    
    function textStatistics(text) {
    	return new TextStatistics(text);
    }
    // END LIBRARY
    	
    
    var result = "";
    var s = draft.content;
    var stats = textStatistics(s);
    
    var t = ["<table>"];
    function addRow(key, val, link) {
    	t.push("<tr><td class='lbl'>");
    	t.push(key);
    	t.push("</td><td class='val'>");
    	t.push(val);
    	t.push("</td>");
    	if (link) {
    	  if (link == "mins") {
    	   t.push("<td>");
    	   t.push("mins");
    	   t.push("</td>");
    	   }
    	  else {
    		t.push("<td>");
    		t.push("<a href='" + link + "'>info</a>");
    		t.push("</td>");
    	}}
    	else {
    		t.push("<td></td>");
    	}
    	t.push("</tr>")
    }
    
    addRow("Words", stats.wordCount());
    addRow("Sentences", stats.sentenceCount());
    addRow("Words per sentence", Math.round(stats.averageWordsPerSentence(), 2));
    addRow("Flesch-Kincaid Ease", stats.fleschKincaidReadingEase(), "http://www.readabilityformulas.com/flesch-reading-ease-readability-formula.php");
    addRow("Flesch-Kincaid Grade Level", stats.fleschKincaidGradeLevel(), "http://www.readabilityformulas.com/flesch-grade-level-readability-formula.php");
    addRow("Gunning Fog", stats.gunningFogScore(), "https://en.wikipedia.org/wiki/Gunning_fog_index");
    addRow("SMOG Index", stats.smogIndex(), "http://www.readabilityformulas.com/smog-readability-formula.php");
    addRow("Automated Readability", stats.automatedReadabilityIndex(), "http://www.readabilityformulas.com/automated-readability-index.php");
    addRow("Coleman Liau", stats.colemanLiauIndex(), "http://www.readabilityformulas.com/coleman-liau-readability-formula.php");
    addRow("Read Aloud Time (Slow)", stats.readAloudTimeSlow(), "mins");
    addRow("Read Aloud Time (Relaxed)", stats.readAloudTimeRelaxed(), "mins");
    addRow("Read Aloud Time (Fast)", stats.readAloudTimeFast(), "mins");
    addRow("Silent Read Time", stats.silentReadTime(), "mins");
    
    t.push("</table>");
    
    draft.setTemplateTag("htmltable", t.join(""));
    
  • script

    // create theme sensitive css for preview
    
    if (app.themeMode == 'dark') {
    	var css = "html, body { background: #222; color: #ddd; }";
    	draft.setTemplateTag("bodystyle", css);
    }
    else {
    	var css = "html, body { background: #fff; color: #444; }";
    	draft.setTemplateTag("bodystyle", css);
    }
  • htmlpreview

    <!DOCTYPE html>
    <html dir="auto">
        <head>
            <title>Preview</title>
    		<meta name="viewport" content="width=device-width, initial-scale=1">
            <style>
            @import url(http://fonts.googleapis.com/css?family=Vollkorn:400,400italic,700,700italic&subset=latin);
    
            html, body {
                    padding:0em;
                    margin:auto;
                    max-width:55em;
                   text-align: center;
            	}
            body {
            	font: 1.3em "Vollkorn", Palatino, Times;
            	color: #333;
            	line-height: 1.4;
            	text-align: center;
            	}
            header, nav, article, footer {
            	width: 700px;
            	margin:0 auto;
            	}
            article {
            	margin-top: 4em;
            	margin-bottom: 4em;
            	min-height: 400px;
            	}
            footer {
            	margin-bottom:50px;
            	}
            video {
            	margin: 2em 0;
            	border:1px solid #ddd;
            	}
    
            nav {
            	font-size: .9em;
            	font-style: italic;
            	border-bottom: 1px solid #ddd;
            	padding: 1em 0;
            	}
            nav p {
            	margin: 0;
            	}
    
            /* Typography
            -------------------------------------------------------- */
    
            h1 {
            	margin-top: 0;
            	font-weight: normal;
            	}
            h2 {
            	font-weight: normal;
            	}
            h3 {
            	font-weight: normal;
            	font-style: italic;
            	margin-top:3em;
            	}
            p {
            	margin-top:0;
            	-webkit-hypens:auto;
            	-moz-hypens:auto;
            	hyphens:auto;
            	}
            ul {
            	list-style: square;
            	padding-left:1.2em;
            	}
            ol {
            	padding-left:1.2em;
            	}
            blockquote {
            	margin-left: 1em;
            	padding-left: 1em;
            	border-left: 1px solid #ddd;
            	}
            code {
            	font-family: "Consolas", "Menlo", "Monaco", monospace, serif;
            	font-size: .9em;
            	background: white;
            	}
            a {
            	color: #2484c1;
            	text-decoration: none;
            	}
            a:hover {
            	text-decoration: underline;
            	}
            a img {
            	border:none;
            	}
            h1 a, h1 a:hover {
            	color: #333;
            	text-decoration: none;
            	}
            hr {
            	color : #ddd;
            	height : 1px;
            	margin: 2em 0;
            	border-top : solid 1px #ddd;
            	border-bottom : none;
            	border-left: 0;
            	border-right: 0;
            	}
            p#heart{
            	font-size: 2em;
            	line-height: 1;
            	text-align: center;
            	color: #ccc;
            	}
            .red {
            	color:#B50000;
            	}
            img {
                max-width: 90%;
            }
    
    table {
      margin: 1em auto;
      border: 1px solid #aaa;
      border-collapse: collapse;
    }
    
    th {
      padding:.25em .5em;
      background: #efefef;
      border: 1px solid #ccc;  
    }
    
    td {
      padding:.25em .5em;
      text-align: left;
      border: 1px solid #ccc;
    }
    
            /* Home Page
            --------------------------- */
    
            body#index li {
            	margin-bottom: 1em;
            	}
    
    
            /* iPad
            -------------------------------------------------------- */
            @media only screen and (max-device-width: 1024px) {
            body {
            	font-size: 120%;
            	line-height: 1.4;
            	}
            } /* @iPad */
    
            /* iPhone
            -------------------------------------------------------- */
            @media only screen and (max-device-width: 480px) {
            body {
            	text-align: left;
                padding:.5em;
            	}
            article, footer {
            	width: auto;
            	}
            article {
            	padding: 0 10px;
            	}
            } /* @iPhone */
    
    .lbl {
    }
    .val {
      font-weight: bold;
      text-align: right;
    }
    
    [[bodystyle]]
    </style>
    </head>
    <body>
    <h2>Draft Statistics</h2>
        [[htmltable]]
    </body>
    </html>
    

Options

  • After Success Nothing
    Notification Error
    Log Level Error
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.