mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 18:10:18 +05:30
128 lines
4.0 KiB
JavaScript
128 lines
4.0 KiB
JavaScript
/**
|
|
* Writing Analytics — pure computation engine
|
|
* No DOM dependencies. Exported analyze(text) returns a metrics object.
|
|
*/
|
|
|
|
const STOP_WORDS = new Set([
|
|
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been',
|
|
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would',
|
|
'could', 'should', 'to', 'of', 'in', 'for', 'on', 'with',
|
|
'at', 'by', 'from', 'as', 'and', 'or', 'but', 'if', 'it',
|
|
'its', 'this', 'that', 'these', 'those', 'i', 'me', 'my',
|
|
'we', 'our', 'you', 'your', 'he', 'him', 'his', 'she', 'her',
|
|
'they', 'them', 'their', 'not', 'no', 'so', 'than', 'too',
|
|
'very', 'also', 'just', 'about', 'up', 'out', 'what', 'which', 'who'
|
|
]);
|
|
|
|
function countSyllables(word) {
|
|
word = word.toLowerCase().replace(/(?:[^laeiouy]es|ed|[^laeiouy]e)$/, '');
|
|
word = word.replace(/^y/, '');
|
|
return word.match(/[aeiouy]{1,2}/gi)?.length || 1;
|
|
}
|
|
|
|
function extractWords(text) {
|
|
return text.match(/[a-zA-Z]+(?:['-][a-zA-Z]+)*/g) || [];
|
|
}
|
|
|
|
function getReadabilityLabel(score) {
|
|
if (score >= 90) return 'Very Easy';
|
|
if (score >= 70) return 'Easy';
|
|
if (score >= 50) return 'Standard';
|
|
if (score >= 30) return 'Difficult';
|
|
return 'Very Difficult';
|
|
}
|
|
|
|
function analyze(text) {
|
|
if (!text || !text.trim()) {
|
|
return {
|
|
wordCount: 0,
|
|
sentenceCount: 0,
|
|
paragraphCount: 0,
|
|
fleschEase: 0,
|
|
fleschGrade: 0,
|
|
readabilityLabel: 'N/A',
|
|
readingTime: 0,
|
|
speakingTime: 0,
|
|
uniqueWordCount: 0,
|
|
lexicalDiversity: 0,
|
|
avgSentenceLength: 0,
|
|
longestSentence: '',
|
|
longestSentenceLength: 0,
|
|
topWords: []
|
|
};
|
|
}
|
|
|
|
const words = extractWords(text);
|
|
const wordCount = words.length;
|
|
|
|
const sentences = text.split(/[.!?]+/).map(s => s.trim()).filter(Boolean);
|
|
const sentenceCount = Math.max(sentences.length, 1);
|
|
|
|
const paragraphs = text.split(/\n\s*\n/).map(p => p.trim()).filter(Boolean);
|
|
const paragraphCount = Math.max(paragraphs.length, 1);
|
|
|
|
let totalSyllables = 0;
|
|
for (const w of words) {
|
|
totalSyllables += countSyllables(w);
|
|
}
|
|
|
|
const fleschEase = Math.round((206.835 - 1.015 * (wordCount / sentenceCount) - 84.6 * (totalSyllables / wordCount)) * 10) / 10;
|
|
const fleschGrade = Math.round((0.39 * (wordCount / sentenceCount) + 11.8 * (totalSyllables / wordCount) - 15.59) * 10) / 10;
|
|
const readabilityLabel = getReadabilityLabel(fleschEase);
|
|
|
|
const readingTime = Math.ceil(wordCount / 200);
|
|
const speakingTime = Math.ceil(wordCount / 130);
|
|
|
|
const uniqueWords = new Set(words.map(w => w.toLowerCase()));
|
|
const uniqueWordCount = uniqueWords.size;
|
|
const lexicalDiversity = wordCount > 0 ? Math.round((uniqueWordCount / wordCount) * 1000) / 10 : 0;
|
|
|
|
const avgSentenceLength = Math.round((wordCount / sentenceCount) * 10) / 10;
|
|
|
|
let longestSentence = '';
|
|
let longestSentenceLength = 0;
|
|
for (const s of sentences) {
|
|
const sWords = extractWords(s);
|
|
if (sWords.length > longestSentenceLength) {
|
|
longestSentenceLength = sWords.length;
|
|
longestSentence = s.trim();
|
|
}
|
|
}
|
|
|
|
if (longestSentence.length > 80) {
|
|
longestSentence = longestSentence.substring(0, 80) + '...';
|
|
}
|
|
|
|
const wordFreq = {};
|
|
for (const w of words) {
|
|
const lower = w.toLowerCase();
|
|
if (!STOP_WORDS.has(lower) && lower.length > 1) {
|
|
wordFreq[lower] = (wordFreq[lower] || 0) + 1;
|
|
}
|
|
}
|
|
|
|
const topWords = Object.entries(wordFreq)
|
|
.sort((a, b) => b[1] - a[1])
|
|
.slice(0, 10)
|
|
.map(([word, count]) => ({ word, count }));
|
|
|
|
return {
|
|
wordCount,
|
|
sentenceCount,
|
|
paragraphCount,
|
|
fleschEase,
|
|
fleschGrade,
|
|
readabilityLabel,
|
|
readingTime,
|
|
speakingTime,
|
|
uniqueWordCount,
|
|
lexicalDiversity,
|
|
avgSentenceLength,
|
|
longestSentence,
|
|
longestSentenceLength,
|
|
topWords
|
|
};
|
|
}
|
|
|
|
module.exports = { analyze };
|