diff --git a/index.html b/index.html
index ff7ad73..be1d0f3 100644
--- a/index.html
+++ b/index.html
@@ -34,11 +34,11 @@
<script>
const transitionalWords = [
- "above all", "accordingly", "additionally", "after all", "again", "albeit", "all in all", "all things considered", "also", "alternatively", "although", "altogether", "another key point", "another view is", "as a matter of fact", "as a result", "as an example", "as an illustration", "as can be seen", "as exemplified by", "as follows", "as has been noted", "as shown above", "as soon as", "as well as", "at the present time", "at the same time", "at this instant", "basically", "be that as it may", "because of", "because of this", "besides", "better", "by all means", "by and large", "by contrast", "by the same token", "certainly", "chiefly", "comparatively", "compared with", "consequently", "conversely", "correspondingly", "count", "despite", "different from", "due to", "equally", "equally important", "especially", "even if", "even so", "eventually", "evidence illustrates that", "expressively", "finally moreover", "for example", "for instance", "for one thing", "for the most part", "for the purpose of", "for this reason", "formerly", "forthwith", "frequently", "from time to time", "further", "furthermore", "generally speaking", "given that", "given these points", "hence", "henceforth", "however", "identically", "immediately", "important to realise", "in a moment", "in a word", "in addition", "in any event", "in brief", "in case", "in comparison", "in conclusion", "in contrast", "in detail", "in due time", "in either case", "in essence", "in fact", "in general", "in light of", "in like manner", "in order to", "in other terms", "in other words", "in particular", "in reality", "in short", "in spite of", "in summary", "in that case", "in the event that", "in the final analysis", "in the first place", "in the hope that", "in the long run", "in the meantime", "in the same way", "in this case", "in time", "in view of", "in view of this", "inasmuch as", "including", "indeed", "instead", "it can be seen", "it follows that", "it must be remembered", "lest", "likewise", "mainly", "markedly", "meanwhile", "moreover", "namely", "nevertheless", "next", "nonetheless", "not only", "not to mention", "notably", "notwithstanding", "of course", "on balance", "on the condition that", "on the contrary", "on the negative side", "on the other hand", "on the positive side", "on the whole", "or", "ordinarily", "otherwise", "overall", "owing to", "particularly", "point often overlooked", "prior to", "provided that", "quickly", "rather", "regarding", "regardless", "resulting from", "significantly", "similarly", "since", "so as to", "so long as", "so that", "sooner or later", "specifically", "straightaway", "subsequently", "such as", "surely", "surprisingly", "that implies", "that is to say", "the consequence is", "the consequences are", "the first thing to remember", "the result is", "the results are", "then", "then again", "therefore", "thereupon", "this suggests that", "thus", "to be sure", "to begin with", "to clarify", "to conclude", "to demonstrate", "to emphasise", "to enumerate", "to explain", "to point out", "to put it another way", "to put it differently", "to repeat", "to say nothing of", "to sum up", "to summarise", "to the end that", "together with", "too", "truly", "under those circumstances", "uniquely", "unless", "until now", "up to the present time", "usually", "whenever", "whereas", "while it may be true", "while that may be true", "with attention to", "with this in mind", "with this intention", "without delay", "yet", "whereas"
+ "above all", "accordingly", "additionally", "after all", "again", "albeit", "all in all", "all things considered", "also", "alternatively", "although", "altogether", "another key point", "another view is", "as a matter of fact", "as a result", "as an example", "as an illustration", "as can be seen", "as exemplified by", "as follows", "as has been noted", "as shown above", "as soon as", "as well as", "at the present time", "at the same time", "at this instant", "basically", "be that as it may", "because of", "besides", "better", "by all means", "by and large", "by contrast", "by the same token", "certainly", "chiefly", "comparatively", "compared with", "consequently", "conversely", "correspondingly", "count", "despite", "different from", "due to", "equally", "equally important", "especially", "even if", "even so", "eventually", "evidence illustrates that", "expressively", "finally moreover", "for example", "for instance", "for one thing", "for the most part", "for the purpose of", "for this reason", "formerly", "forthwith", "frequently", "from time to time", "further", "furthermore", "generally speaking", "given that", "given these points", "hence", "henceforth", "however", "identically", "immediately", "important to realise", "in a moment", "in a word", "in addition", "in any event", "in brief", "in case", "in comparison", "in conclusion", "in contrast", "in detail", "in due time", "in either case", "in essence", "in fact", "in general", "in light of", "in like manner", "in order to", "in other terms", "in other words", "in particular", "in reality", "in short", "in spite of", "in summary", "in that case", "in the event that", "in the final analysis", "in the first place", "in the hope that", "in the long run", "in the meantime", "in the same way", "in this case", "in time", "in view of", "in view of this", "inasmuch as", "including", "indeed", "instead", "it can be seen", "it follows that", "it must be remembered", "lest", "likewise", "mainly", "markedly", "meanwhile", "moreover", "namely", "nevertheless", "next", "nonetheless", "not only", "not to mention", "notably", "notwithstanding", "of course", "on balance", "on the condition that", "on the contrary", "on the negative side", "on the other hand", "on the positive side", "on the whole", "or", "ordinarily", "otherwise", "overall", "owing to", "particularly", "point often overlooked", "prior to", "provided that", "quickly", "rather", "regarding", "regardless", "resulting from", "significantly", "similarly", "since", "so as to", "so long as", "so that", "sooner or later", "specifically", "still", "straightaway", "subsequently", "such as", "surely", "surprisingly", "that implies", "that is to say", "the consequence is", "the consequences are", "the first thing to remember", "the result is", "the results are", "then", "then again", "therefore", "thereupon", "this suggests that", "thus", "to be sure", "to begin with", "to clarify", "to conclude", "to demonstrate", "to emphasise", "to enumerate", "to explain", "to point out", "to put it another way", "to put it differently", "to repeat", "to say nothing of", "to sum up", "to summarise", "to the end that", "together with", "too", "truly", "under those circumstances", "uniquely", "unless", "until now", "up to the present time", "usually", "whenever", "whereas", "while it may be true", "while that may be true", "with attention to", "with this in mind", "with this intention", "without delay", "yet", "whereas"
];
const midSentenceOnlyWords = [
- "suggests that", "because", "especially"
+ "suggests that", "because", "especially", "still", "unless", "since", "above all"
];
function escapeHtml(text) {
@@ -47,77 +47,98 @@
return div.innerHTML;
}
+ function getSentencesWithOffsets(text) {
+ const regex = /[^.!?]+[.!?]+[\])'"`’”]*\s*/g;
+ const sentences = [];
+ let match;
+ while ((match = regex.exec(text)) !== null) {
+ sentences.push({ text: match[0], index: match.index });
+ }
+ return sentences;
+ }
+
function checkTransitions() {
const editor = document.getElementById("editor");
const inputText = editor.innerText;
- const rawText = inputText.toLowerCase();
- const sentences = rawText.match(/[^.!?]+[.!?]+/g) || [];
+ const lowerText = inputText.toLowerCase();
+ const sentences = getSentencesWithOffsets(inputText);
let totalCount = 0;
let midCount = 0;
const found = {};
const midFound = {};
+ const highlightSpans = [];
- let highlightedHTML = escapeHtml(inputText);
-
- for (const sentence of sentences) {
- const trimmed = sentence.trim();
- const lower = trimmed.toLowerCase();
+ for (const { text: sentence, index: sentenceStart } of sentences) {
+ const lower = sentence.toLowerCase();
+ // --- (1) Transitional words: start of sentence
for (const phrase of transitionalWords) {
- const phraseRegexStart = new RegExp(`^${phrase}\\b`, "i");
- const phraseRegexAfterSemi = new RegExp(`;\\s*${phrase}\\b`, "gi");
-
- let count = 0;
-
- if (phraseRegexStart.test(lower)) {
- count++;
- }
-
- const semiMatches = lower.match(phraseRegexAfterSemi);
- if (semiMatches) {
- count += semiMatches.length;
+ const startRegex = new RegExp(`^\\s*${phrase}\\b`, "i");
+ const semiRegex = new RegExp(`;\\s*${phrase}\\b`, "gi");
+
+ // Start of sentence
+ const startMatch = lower.match(startRegex);
+ if (startMatch) {
+ const localIndex = lower.indexOf(startMatch[0]);
+ const globalStart = sentenceStart + localIndex;
+ highlightSpans.push({ start: globalStart, end: globalStart + phrase.length });
+ found[phrase] = (found[phrase] || 0) + 1;
+ totalCount++;
}
- if (count > 0) {
- totalCount += count;
- found[phrase] = (found[phrase] || 0) + count;
+ // After semicolon
+ let match;
+ while ((match = semiRegex.exec(lower)) !== null) {
+ const idx = match.index + match[0].lastIndexOf(phrase);
+ const globalStart = sentenceStart + idx;
+ highlightSpans.push({ start: globalStart, end: globalStart + phrase.length });
+ found[phrase] = (found[phrase] || 0) + 1;
+ totalCount++;
}
}
+ // --- (2) Mid-sentence only words
for (const phrase of midSentenceOnlyWords) {
const midRegex = new RegExp(`\\b${phrase}\\b`, "gi");
let match;
while ((match = midRegex.exec(lower)) !== null) {
const before = lower.slice(0, match.index).trim();
- if (before.length > 0 && !before.endsWith(".")) {
- midCount++;
+ const isStart = before.length === 0 || /[.?!]\s*$/.test(before);
+ if (!isStart) {
+ const globalStart = sentenceStart + match.index;
+ highlightSpans.push({ start: globalStart, end: globalStart + phrase.length });
midFound[phrase] = (midFound[phrase] || 0) + 1;
+ midCount++;
}
}
}
}
- for (const phrase of Object.keys(found)) {
- const regex = new RegExp(`\\b(${phrase})\\b`, "gi");
- highlightedHTML = highlightedHTML.replace(regex, `<span class="highlight">$1</span>`);
- }
+ // Build highlighted output
+ const raw = inputText;
+ let output = "";
+ let lastIndex = 0;
+
+ highlightSpans.sort((a, b) => a.start - b.start);
- for (const phrase of Object.keys(midFound)) {
- const regex = new RegExp(`\\b(${phrase})\\b`, "gi");
- highlightedHTML = highlightedHTML.replace(regex, `<span class="highlight">$1</span>`);
+ for (const { start, end } of highlightSpans) {
+ output += escapeHtml(raw.slice(lastIndex, start));
+ output += `<span class="highlight">${escapeHtml(raw.slice(start, end))}</span>`;
+ lastIndex = end;
}
- editor.innerHTML = highlightedHTML;
+ output += escapeHtml(raw.slice(lastIndex));
+ editor.innerHTML = output;
- const combinedCount = totalCount + midCount;
+ const totalMatches = totalCount + midCount;
const percentage = sentences.length
- ? ((combinedCount / sentences.length) * 100).toFixed(1)
+ ? ((totalMatches / sentences.length) * 100).toFixed(1)
: 0;
const result = document.getElementById("result");
- result.innerText = `Transitions: ${combinedCount} (${percentage}%)`;
+ result.innerText = `Transitions: ${totalMatches} (${percentage}%)`;
result.className = (percentage < 10) ? "low" : "high";
const breakdown = document.getElementById("breakdown");
@@ -142,7 +163,6 @@
</script>
-
</body>
</html>