TinyMCE: wordcount plugin is not counting special characters as word - tinymce

I have added the plugin wordcount to count the number of words entered in my TinyMCE texteditor.
plugins: "wordcount",
wordcount_cleanregex: /[.(),;:!?%#$?\x27\x22_+=\\/\-]*/g
It is counting letters and numbers but when I am giving a special character , it is not counting them.
for e.g ----
Hi I am 18 year old (for this it is giving me count 6)
Hi I am ## year old (for this it is giving me count 5)
Any idea what I need to do. I tried to remove:
%#$ from wordcount_cleanregex , but it didn't work.

Your issue is not with the wordcount_cleanregex setting but rather with the wordcount_countregex setting:
https://www.tinymce.com/docs/plugins/wordcount/#wordcount_countregex
If you look at the default one you will see why its skipping the characters that it is. Here is the exact regex:
https://regex101.com/r/wL4fL1/1
If you tweak that regex you can get it to count your ## as a word.
Note: There is some core cleaning that is done within the wordcount plugin that is done regardless of your configuration settings. In TinyMCE 4.4.1 it looks like this:
if (tx) {
tx = tx.replace(/\.\.\./g, ' '); // convert ellipses to spaces
tx = tx.replace(/<.[^<>]*?>/g, ' ').replace(/ | /gi, ' '); // remove html tags and space chars
// deal with html entities
tx = tx.replace(/(\w+)(&#?[a-z0-9]+;)+(\w+)/i, "$1$3").replace(/&.+?;/g, ' ');
tx = tx.replace(cleanre, ''); // remove numbers and punctuation
var wordArray = tx.match(countre);
if (wordArray) {
tc = wordArray.length;
}
}
...so some core things are still stripped from your content regardless of what you put in the wordcount_cleanregex and wordcount_countregex. If you want to change this core behavior you will need to modify the plugin's source code.

here is my wordcount plugin which I am adding externally , The function getCount returns the number of words perfectly when I run this function seprately on my .aspx page as javascript function, but when I am running under this plugin it is only counting letters (it is not counting any number/special characters)
tinymce.PluginManager.add('wordcount', function(editor) {
function update() {
editor.theme.panel.find('#wordcount').text(['Words: {0}', getCount()]);
}
editor.on('init', function() {
var statusbar = editor.theme.panel && editor.theme.panel.find('#statusbar')[0];
if (statusbar) {
tinymce.util.Delay.setEditorTimeout(editor, function() {
statusbar.insert({
type: 'label',
name: 'wordcount',
text: ['Words: {0}', getCount()],
classes: 'wordcount',
disabled: editor.settings.readonly
}, 0);
editor.on('setcontent beforeaddundo', update);
editor.on('keyup', function(e) {
if (e.keyCode == 32) {
update();
}
});
}, 0);
}
});
getCount = function () {
var body = editor.getBody().innerHTML;
text1 = body.replace(/<[^>]+>/g, '');
s = text1.replace(/ /g, ' ');
s = s.replace(/(^\s*)|(\s*$)/gi, "");//exclude start and end white-space
s = s.replace(/[ ]{2,}/gi, " ");//2 or more space to 1
s = s.replace(/\n /, "\n"); // exclude newline with a start spacing
return s.split(' ').length;
};
});

Related

How to get and replace the word on the left of the cursor

For a word add-ins in javascript, a simple use case is to get the word on the left of the cursor and to replace it in upper case.
For example, if | is the cursor:
Hello world| will become Hello WORLD|
Hello| world will become HELLO| world
Is it possible to perform this example with the Word.Range class? For example, to expand the range until a space like this fictive code:
Word.run(function (context) {
var selection = context.document.getSelection();
var cursor = selection.getRange('Start');
// Fictive: how to expand the range to the left until a space?
var range = cursor.expandToLeftUntil(' ');
range.load("text");
var html = range.getHtml();
await context.sync();
var textToReplace = html.value.toUpperCase();
// Replace the text
range.insertText(textToReplace, 'Replace');
await context.sync();
});
Or is there any other solution?
A possible strategy is to use the search method to get a RangeCollection of all the words in the document (or body or paragraph, etc.). Then get a reference to the current selected range (where the cursor is). Then loop through the collection and call the Range.compareLocationWith method to find the range that is "AdjacentBefore" the currently selected range.
I was trying to do a similar thing. At least, when a selection is empty get the word nearby the cursor. I hope their would be some API function, but that's not the case.
I started out with the answer/idea of Rick Kirkham (thanks!). I couldn't get to search method to work to get a list of words. Using split on a space worked fine though.
Instead of select like I do you could modify the the text.
If you don't want to get close-by but only after you should alter the function to check 'InsideStart' (in that scenario you would like to go to the previous word, so i-1).
Word.run(async (context) => {
let cursorOrSelection = context.document.getSelection();
cursorOrSelection.load();
await context.sync();
// if the cursor is empty we make a selection of the Word close-by
// this behaviour is done automatically when you insert a comment in Word
if (cursorOrSelection.isEmpty) {
console.log("Empty selection, cursor.");
// get the paragraph closest to the cursor.
const paragraph = cursorOrSelection.paragraphs.getFirst();
const allWordsInParagraph = paragraph.split([" "], true /* trimDelimiters*/, true /* trimSpaces */);
allWordsInParagraph.load();
await context.sync();
// compare the cursorRange with the ranges of individual words in the paragraph.
let compareRanges = [];
allWordsInParagraph.items.forEach( item => {
compareRanges.push({
compare: cursorOrSelection.compareLocationWith(item),
range: item
});
});
await context.sync();
// walk through all the words and compare the location relation with the cursor
// were the location relation changes, the word is near the cursor.
let previousLocationRelation = null;
let wordClosestToCursorRange = null;
for (let i = 0; i < compareRanges.length; i++) {
const locationRelation = compareRanges[i].compare.value;
console.log(locationRelation);
// if first entry is Before, we are at the beginning
if(i==0 && locationRelation === 'Before') {
wordClosestToCursorRange = compareRanges[i].range;
// jump out
break;
}
else {
if(previousLocationRelation && locationRelation != previousLocationRelation) {
// first "edge" we find.
// console.log('-- edge');
// if first edge we encounter is Before
// we need the previous one (could be after)
if(locationRelation === 'Before') {
wordClosestToCursorRange = compareRanges[i-1].range;
}
else {
// we are inside the word or end of the word
// Inside, InsideStart, InsideEnd
wordClosestToCursorRange = compareRanges[i].range;
}
// jump out we are only interested in the first edge
break;
}
}
previousLocationRelation = locationRelation;
}
wordClosestToCursorRange.select();
}
return context.sync();
})
.catch(function (error) {
console.log(error.message)
})

TinyMCE v4 testing with Jest not returning Editor instance

I am attempting to write some tests in Jest that require the use of TinyMCE. I am getting back what seems to be a valid tinymce object (since tinymce.majorVersion displays 4) but when I perform tinymce.editors[0] I get undefined.
This is my js/Jest code:
describe('My Test', () => {
var editor = null;
var BODY = 'editor-body';
var tinyMceSettings = {
elements = BODY,
plugins = 'myplugin',
init_instance_callback: function(editor) {
editor.setContent(Utils.HTML);
};
};
tinymce.init(tinyMceSettings);
editor = tinymce.get(BODY);
// tinymce.majorVersion prints '4'
// tinymce.editors[0] gives me undefined
});
My Utils.js file contains some HTML to testwith:
const HTML =
"<div id='editor-body' class='editor-body-parts'>" +
"<p data-type='header'>" +
"<span class='element'>TIME</span>" +
"<span class='element'>PERSON</span>" +
"<span class='element'>ADDRESS</span>" +
"</p> +
"</div>";
Any help would be great.
Jest is not a "proper" browser so TinyMCE likely won't actually initialize against it. I would suspect that the init() call is simply not working due to that and while the global tinymce variable exists simply by loading the TinyMCE script the editors[] array would be empty unless a proper init() completes.

Get all bookmark IDs from Word Document

I want to scan an entire document containing text with the styles Heading 1, Heading 2, normal text and several bullet points / other text (which is basically a tech report). After scanning I want to extract bookmarks assigned to "Heading 2" elements, which also act as sub heading titles within the report.
getBookmarks() is defined in the Preview / Beta API which works if my cursor is placed on the "Heading 2" element, as seen below:
async function getBookmarks() {
Word.run(function(context) {
var range = context.document.getSelection();
var bkmrk = range.getBookmarks(true, true);
return context.sync().then(function() {
console.log("The bookmarks read from the document was: " + bkmrk.value);
});
}).catch(function(error) {
console.log("Error: " + JSON.stringify(error));
if (error instanceof OfficeExtension.Error) {
console.log("Debug info: " + JSON.stringify(error.debugInfo));
}
});
}
I've managed to scan the entire document and obtain the "style" attribute as well, as seen from the example code on API documentation:
async function getParagraphAll() { await Word.run(async (context) => {
// Gets the complete sentence (as range) associated with the insertion point.
let paragraphs = context.document.body.paragraphs
paragraphs.load("text, style");
await context.sync();
// Expands the range to the end of the paragraph to get all the complete sentences.
let completeParagraph = paragraphs.items[0]
.getRange()
.expandTo(
context.document
.getSelection()
.paragraphs.getFirst()
.getRange("End")
)
paragraphs.load("text, style, hyperlink");
await context.sync();
for (let i = 0; i < paragraphs.items.length; i++) {
console.log(paragraphs.items[i].style);
//let range = paragraphs.items[i].getRange() - Why is this not working ?
//let bkmrk = range.getBookmarks(true, false) - This doesnt get me the bookmark while its in
//the loop scanning the entire document. Is it because it fails on "Normal" style?
// Should I filter out "Normal" and only run "getBookmarks" on "Heading" style ?
console.log(paragraphs.items[i].style);
} }); }
I've made the reference to Libraries available in the preview API link: https://appsforoffice.microsoft.com/lib/beta/hosted/office.js
I'm struggling to understand why I can get the bookmark at cursor level but when I want to get it for the entire document, it just displays
do context.sync() before loading any property. There is no need for
load.

Is it possible to set TextDocument as dirty programatically in VSCode?

Is it possible to set a TextDocument as dirty programatically in VSCode? Something like
openedDocument.setDirty()
There isn't a direct way to do it; TextDocument.isDirty is a read-only property.
However, I put together a workaround that sets isDirty by making an edit that has no effect (tested with VSCode 1.37.1):
// Set the dirty bit on 'textEditor'. This is meant to be called as a
// text editor command.
async function setDirty(textEditor: TextEditor, editBuilder: TextEditorEdit)
: Promise<void>
{
// The strategy here is to make a change that has no effect. If the
// document has text in it, we can replace some text with itself
// (simply inserting an empty string does not work). We prefer to
// edit text at the end of the file in order to minimize spurious
// recomputation by analyzers.
// Try to replace the last line.
if (textEditor.document.lineCount >= 2) {
const lineNumber = textEditor.document.lineCount-2;
const lastLineRange = new Range(
new Position(lineNumber, 0),
new Position(lineNumber+1, 0));
const lastLineText = textEditor.document.getText(lastLineRange);
editBuilder.replace(lastLineRange, lastLineText);
return;
}
// Try to replace the first character.
const range = new Range(new Position(0, 0), new Position(0, 1));
const text = textEditor.document.getText(range);
if (text.length > 0) {
editBuilder.replace(range, text);
return;
}
// With an empty file, we first add a character and then remove it.
// This has to be done as two edits, which can cause the cursor to
// visibly move and then return, but we can at least combine them
// into a single undo step.
await textEditor.edit(
(innerEditBuilder: TextEditorEdit) => {
innerEditBuilder.replace(range, " ");
},
{ undoStopBefore: true, undoStopAfter: false });
await textEditor.edit(
(innerEditBuilder: TextEditorEdit) => {
innerEditBuilder.replace(range, "");
},
{ undoStopBefore: false, undoStopAfter: true });
}
In your activate function, hook it up with something like:
context.subscriptions.push(
commands.registerTextEditorCommand("extension.setDirty", setDirty));

Can anybody help me to resolve ng-grid filtering in a multi row Grid

Here is the plunker: http://plnkr.co/edit/fGVVOOIwvf4GrEj3XtJ6?p=preview
I have created a multi row grid and I would like to filter the grid data for each column header.
If I put an input box outside the grid it is working fine. If I put an input box inside the column header filtering is not working
Please see the code and help me for this.
Use this filterBar plugin. (I cannot take credit for this, I do not remember where I found it.)
Plugin
var filterBarPlugin = {
init: function(scope, grid) {
filterBarPlugin.scope = scope;
filterBarPlugin.grid = grid;
$scope.$watch(function() {
var searchQuery = "";
angular.forEach(filterBarPlugin.scope.columns, function(col) {
if (col.visible && col.filterText) {
var filterText = (col.filterText.indexOf('*') == 0 ? col.filterText.replace('*', '') : "^" + col.filterText) + ";";
searchQuery += col.displayName + ": " + filterText;
}
});
return searchQuery;
}, function(searchQuery) {
filterBarPlugin.scope.$parent.filterText = searchQuery;
filterBarPlugin.grid.searchProvider.evalFilter();
});
},
scope: undefined,
grid: undefined,
};
Change your header cell input ng-model to: col.filterText
<input type="text" placeholder="MY NAME" ng-model="col.filterText" ng-change="activateFilter()"/>
Add plugin to gridOptions
...
plugins: [filterBarPlugin],
...
Updated Plunker: Plunker
I know this is old, but in case someone else ends up here...
Here is a google group thread discussing this: https://groups.google.com/forum/#!topic/angular/lhu5Fbs97G4
and within that discussion you can find the following plnkr which does what you are wanting (and which I think the above answer references):
http://plnkr.co/edit/c8mHmAXattallFRzXSaG?p=preview