Initial commit

This commit is contained in:
Ben Goldsworthy 2017-01-12 23:10:07 +00:00
commit 28e6ddf404
1083 changed files with 191734 additions and 0 deletions

View file

@ -0,0 +1,93 @@
/* jshint devel: true, onevar: false */
/* global tinyMCE, AtD_restore_if_proofreading, AtD_check, AtD_unbind_proofreader_listeners,
AtD, AtD_bind_proofreader_listeners, AtD_check_when
*/
/* the AtD/jQuery and AtD/TinyMCE plugins check if this variable exists and increment it when a proofread check happens */
var AtD_proofread_click_count = 0;
/* This is function called when the publish/update button is pressed */
function AtD_submit_check( e ) {
/* User has already checked their document... no need to hold up their submit */
if (AtD_proofread_click_count > 0) {
return;
}
/* Let's not submit the form, shall we? */
e.stopImmediatePropagation();
e.preventDefault();
/* We'll call the AtD function based on which editor is currently active */
if ( typeof(tinyMCE) !== 'undefined' && tinyMCE.activeEditor && !tinyMCE.activeEditor.isHidden() ) {
/* Woo! We're running tinyMCE! */
tinyMCE.activeEditor.execCommand('mceWritingImprovementTool', AtD_submit_check_callback);
} else {
/* Go Go HTML editor! */
AtD_restore_if_proofreading();
AtD_check( AtD_submit_check_callback );
}
}
/* This is the callback function that runs after the publish/update button is pressed */
function AtD_submit_check_callback(count) {
count = Number( count || 0 );
AtD_unbind_proofreader_listeners();
if ( 0 === count || 1 < AtD_proofread_click_count ) {
/* if no errors were found, submit form */
AtD_update_post();
} else if ( -1 === count ) {
/* If there was an error, alert the user and submit form */
alert( AtD.getLang('message_server_error', 'There was a problem communicating with the Proofreading service. Try again in one minute.') );
AtD_update_post();
} else {
var original_post_status = jQuery('#original_post_status').val();
/* Okay, the user has tried to publish/update already but there are still errors. Ask them what to do */
var message;
if ( original_post_status === 'publish' ) {
message = AtD.getLang('dialog_confirm_post_publish', 'The proofreader has suggestions for this post. Are you sure you want to publish it?\n\nPress OK to publish your post, or Cancel to view the suggestions and edit your post.');
} else {
message = AtD.getLang('dialog_confirm_post_update', 'The proofreader has suggestions for this post. Are you sure you want to update it?\n\nPress OK to update your post, or Cancel to view the suggestions and edit your post.');
}
if ( confirm( message ) ) {
AtD_update_post();
} else {
AtD_bind_proofreader_listeners();
AtD_kill_autoproofread();
}
/* Let's do some interface clean-up */
jQuery('#publish').removeClass('button-primary-disabled');
jQuery('#ajax-loading').hide();
}
}
/* Stop the proofreader from doing its auto proofread thing (activated when the proofread button is clicked) */
function AtD_kill_autoproofread() {
jQuery('#publish').unbind('click.AtD_submit_check');
}
/* a function to force the post to be submitted */
function AtD_update_post() {
if ( typeof(tinyMCE) === 'undefined' || !tinyMCE.activeEditor || tinyMCE.activeEditor.isHidden() ) {
AtD_restore_if_proofreading();
}
jQuery('#publish').unbind('click.AtD_submit_check').click();
}
/* init the autoproofread options */
jQuery( document ).ready( function($){
var orig_status = $('#original_post_status').val();
/* check if auto-check is enabled && if #content exists */
if ( typeof AtD_check_when !== 'undefined' && $('#content').length &&
( ( orig_status !== 'publish' && AtD_check_when.onpublish ) ||
( ( orig_status === 'publish' || orig_status === 'schedule' ) && AtD_check_when.onupdate ) ) ) {
$('#publish').bind( 'click.AtD_submit_check', AtD_submit_check );
}
});

View file

@ -0,0 +1,267 @@
/* jshint devel: true, onevar: false, smarttabs: true */
/* global AtD, QTags, AtD_l10n_r0ar, edButtons, edButton, switchEditors, AtD_unbind_proofreader_listeners */
/* exported AtD_unbind_proofreader_listeners */
var AtD_qtbutton, autosave;
/* convienence method to restore the text area from the preview div */
function AtD_restore_text_area() {
var content;
/* swap the preview div for the textarea, notice how I have to restore the appropriate class/id/style attributes */
if( jQuery('#atd-content').get(0) ) {
AtD.remove('atd-content');
content = jQuery('#atd-content').html();
} else {
AtD.remove('content');
content = jQuery('#content').html();
}
if ( navigator.appName === 'Microsoft Internet Explorer' ) {
content = content.replace(/<BR.*?class.*?atd_remove_me.*?>/gi, '\n');
}
// Remove emoji replacement images
if ( typeof window.twemoji !== 'undefined' ) {
content = content.replace( /<img [^>]+>/g, function( img ) {
// The images should have class containing 'emoji'
if ( img.indexOf( 'emoji' ) !== -1 ) {
var alt = img.match( /alt="([^"]+)"/ );
if ( alt && alt[1] && window.twemoji.test( alt[1] ) ) {
return alt[1];
}
}
return img;
});
}
// jQuery('#content').replaceWith( AtD.content_canvas );
jQuery('#content').val( content.replace(/\&lt\;/g, '<').replace(/\&gt\;/g, '>').replace(/\&amp;/g, '&') )
.height(AtD.height)
.show();
jQuery('#atd-content').remove();
if ( AtD_qtbutton ) {
/* change the link text back to its original label */
jQuery(AtD_qtbutton).val( AtD.getLang('button_proofread', 'proofread') );
jQuery(AtD_qtbutton).css({ 'color' : '#464646' });
/* enable the toolbar buttons */
jQuery( AtD_qtbutton ).siblings('input').andSelf().attr( 'disabled', false );
}
/* restore autosave */
if ( AtD.autosave !== undefined ) {
if ( window.wp && window.wp.autosave && window.wp.autosave.server ) {
window.wp.autosave.local.resume && window.wp.autosave.local.resume();
window.wp.autosave.server.resume && window.wp.autosave.server.resume();
} else {
autosave = AtD.autosave;
}
}
}
// add the AtD button properly to quicktags
if ( typeof(QTags) !== 'undefined' && QTags.addButton ) {
jQuery(document).ready(function(){
QTags.addButton( 'AtD', AtD_l10n_r0ar.button_proofread, AtD_check );
});
} else {
edButtons[edButtons.length] = new edButton('ed_AtD', 'AtD', '', '', '');
jQuery(document).ready(function($){
$('#ed_AtD').replaceWith('<input type="button" id="ed_AtD" accesskey="" class="ed_button" onclick="AtD_check(this);" value="' + AtD_l10n_r0ar.button_proofread + '" />');
});
}
function AtD_restore_if_proofreading() {
if ( AtD_qtbutton && jQuery(AtD_qtbutton).val() === AtD.getLang('button_edit_text', 'edit text') ) {
AtD_restore_text_area();
}
}
function AtD_unbind_proofreader_listeners() {
jQuery('#save-post, #post-preview, #publish, #edButtonPreview').unbind('focus', AtD_restore_if_proofreading );
jQuery('#add_poll, #add_image, #add_video, #add_audio, #add_media').unbind('click', AtD_restore_if_proofreading );
jQuery('#post').unbind('submit', AtD_restore_if_proofreading );
}
function AtD_bind_proofreader_listeners() {
jQuery('#save-post, #post-preview, #publish, #edButtonPreview').focus( AtD_restore_if_proofreading );
jQuery('#add_poll, #add_image, #add_video, #add_audio, #add_media').click( AtD_restore_if_proofreading );
jQuery('#post').submit( AtD_restore_if_proofreading );
}
/* where the magic happens, checks the spelling or restores the form */
function AtD_check(button) {
var callback, divHeight;
if ( jQuery.isFunction( button ) ) {
callback = button;
if ( !AtD_qtbutton ) {
AtD_qtbutton = jQuery( '#qt_content_AtD, #ed_AtD' ).get( 0 );
}
} else {
if ( !button.id ) {
button = button[0];
}
AtD_qtbutton = button;
}
if ( !jQuery('#content').size() ) {
if ( 'undefined' !== typeof callback ) {
callback( 0 );
}
AtD_restore_if_proofreading();
return;
}
/* If the text of the link says edit comment, then restore the textarea so the user can edit the text */
if ( jQuery(AtD_qtbutton).val() === AtD.getLang('button_edit_text', 'edit text') ) {
AtD_restore_text_area();
} else {
// Disable editor expand/scroll
if ( window.editorExpand && jQuery( '#postdivrich' ).hasClass( 'wp-editor-expand' ) ) {
AtD.wpEditorExpand = true;
// window.editorExpand.off && window.editorExpand.off();
} else {
AtD.wpEditorExpand = false;
}
/* initialize some of the stuff related to this plugin */
if ( ! AtD.height ) {
AtD.height = jQuery('#content').height();
AtD_bind_proofreader_listeners();
/* make it so clicking the Visual button works when AtD is active */
jQuery('#edButtonPreview').attr( 'onclick', null ).click( function() {
AtD_restore_if_proofreading();
switchEditors.go( 'content', 'tinymce' );
});
/* saved the textarea as we need to restore the original one for the toolbar to continue to function properly */
AtD.content_canvas = jQuery('#content');
/* store the autosave, we're going to make it empty during spellcheck to prevent auto saved text from being
over written with empty text */
AtD.autosave = autosave;
} else {
// Update the height
AtD.height = jQuery('#content').height();
}
/* set the spell check link to a link that lets the user edit the text */
/* disable the button to prevent a race condition where content is deleted if proofread is clicked with a check
in progress. */
jQuery(AtD_qtbutton).css({ 'color' : 'red' }).val( AtD.getLang('button_edit_text', 'edit text') ).attr('disabled', true);
/* replace the div */
var $replacement,
$textarea = jQuery('#content'),
text = $textarea.val().replace( /\&/g, '&amp;' ).replace( /</g, '&lt;' ).replace( /\>/g, '&gt;' ),
fontFamily = $textarea.css('font-family'),
fontSize = $textarea.css('font-size'),
lineHeight = $textarea.css('line-height');
if ( navigator.appName === 'Microsoft Internet Explorer' ) {
text = text.replace( /[\n\r\f]/gm, '<BR class="atd_remove_me">' );
}
$replacement = jQuery( '<div class="input" id="atd-content">' + text + '</div>' );
$textarea.after( $replacement ).hide();
divHeight = AtD.height;
// AtD disables resizing of the Text editor, normalize the size of the replacement div.
if ( divHeight < 200 ) {
divHeight = 200;
} else if ( divHeight > 1000 ) {
divHeight = 1000;
}
var toolBarHeight = jQuery('#ed_toolbar').height();
$replacement.css( {
overflow: 'auto',
'background-color': 'white',
color: 'black',
'white-space': 'pre-wrap',
padding: '10px',
'font-family': fontFamily || 'Consolas, Monaco, monospace',
'font-size': fontSize || '13px',
'line-height': lineHeight || '1.5',
height: divHeight,
'margin-top': toolBarHeight+7+'px'
} );
/* kill autosave... :) */
if ( window.wp && window.wp.autosave && window.wp.autosave.server ) {
window.wp.autosave.local.suspend && window.wp.autosave.local.suspend();
window.wp.autosave.server.suspend && window.wp.autosave.server.suspend();
} else {
autosave = function() { };
}
/* disable the toolbar buttons */
jQuery( AtD_qtbutton ).siblings('input').andSelf().attr( 'disabled', true ); // using .arrt instead of .prop so it's compat with older WP and jQuery
/* check the writing in the textarea */
AtD.check('atd-content', {
success: function(errorCount) {
if ( Number( errorCount ) === 0 && typeof callback !== 'function' ) {
alert( AtD.getLang('message_no_errors_found', 'No writing errors were found') );
}
AtD_restore_if_proofreading();
},
ready: function(errorCount) {
jQuery(AtD_qtbutton).attr('disabled', false);
if ( typeof callback === 'function' ) {
callback( errorCount );
}
},
error: function() {
jQuery(AtD_qtbutton).attr('disabled', false);
if ( typeof callback === 'function' ) {
callback( -1 );
} else {
alert( AtD.getLang('message_server_error', 'There was a problem communicating with the Proofreading service. Try again in one minute.') );
}
AtD_restore_if_proofreading();
},
editSelection: function(element) {
var text = prompt( AtD.getLang('dialog_replace_selection', 'Replace selection with:'), element.text() );
if ( text != null ) {
element.replaceWith( text );
}
},
explain: function(url) {
var left = (screen.width / 2) - (480 / 2);
var top = (screen.height / 2) - (380 / 2);
window.open( url, '', 'width=480,height=380,toolbar=0,status=0,resizable=0,location=0,menuBar=0,left=' + left + ',top=' + top).focus();
},
ignore: function(word) {
jQuery.ajax({
type : 'GET',
url : AtD.rpc_ignore + encodeURI( word ).replace( /&/g, '%26'),
format : 'raw',
error : function(XHR, status, error) {
if ( AtD.callback_f !== undefined && AtD.callback_f.error !== undefined ) {
AtD.callback_f.error(status + ': ' + error);
}
}
});
}
});
}
}

View file

@ -0,0 +1,122 @@
/* AtD error styles */
.hiddenSpellError
{
border-bottom: 2px solid red;
cursor: default;
}
.hiddenGrammarError
{
border-bottom: 2px solid green;
cursor: default;
}
.hiddenSuggestion
{
border-bottom: 2px solid blue;
cursor: default;
}
/* Menu styles derived from:
* jquery.spellchecker.js - a simple jQuery Spell Checker
* Copyright (c) 2008 Richard Willis
* MIT license : http://www.opensource.org/licenses/mit-license.php
* Project : http://jquery-spellchecker.googlecode.com
*/
#suggestmenu
{
min-width: 122px;
background: #ebeaeb;
position: absolute;
display: none;
z-index: 9999;
overflow: none;
margin-top: 1px;
text-align: right;
font-size: 11px;
font-family: Tahoma, Verdana, Arial, Helvetica;
}
#suggestmenu strong
{
background: #cccccc;
font-weight: bold;
padding:3px 6px 3px 6px;
display:block;
border:1px solid #dddddd;
border-bottom: 1px solid #aaaaaa;
color: black;
}
#suggestmenu em
{
text-align:center;
padding:3px 6px 3px 6px;
display:block;
border-top:1px solid #ccc;
border-right:1px solid #ccc;
}
#suggestmenu a, #suggestmenu a:visited
{
background: #ebeaeb;
border-right:1px solid #dddddd;
border-left:1px solid #dddddd;
padding:3px 6px 3px 6px;
display:block;
margin:0px;
text-decoration:none;
color: black;
outline:none
}
#suggestmenu a.first, #suggestmenu a.first:visited
{
border-top:1px solid #dddddd;
}
.spell_sep_bottom
{
border-bottom: 1px solid #dddddd;
}
.spell_sep_top
{
border-top: 1px solid #aaaaaa;
}
#suggestmenu a:hover
{
color:#000;
background: #f5f5f5;
}
#suggestmenu .foot
{
border-top:1px solid #aaaaaa;
background:#fff
}
#suggestmenu .foot a, #suggestmenu .foot a:visited
{
outline:none
}
/* TinyMCE 4.0 */
div.mce-atd-menu-title.mce-disabled {
padding: 3px 12px 0;
}
div.mce-atd-menu-title.mce-disabled:hover,
div.mce-atd-menu-title.mce-disabled:hover span.mce-text {
background: none;
color: #888;
cursor: default;
}
div.mce-atd-menu-title.mce-disabled span.mce-text {
font-weight: bold;
color: #888;
}

View file

@ -0,0 +1 @@
.hiddenSpellError{border-bottom:2px solid red;cursor:default}.hiddenGrammarError{border-bottom:2px solid green;cursor:default}.hiddenSuggestion{border-bottom:2px solid #00f;cursor:default}#suggestmenu{min-width:122px;background:#ebeaeb;position:absolute;display:none;z-index:9999;overflow:none;margin-top:1px;text-align:right;font-size:11px;font-family:Tahoma,Verdana,Arial,Helvetica}#suggestmenu strong{background:#ccc;font-weight:700;padding:3px 6px;display:block;border:1px solid #ddd;border-bottom:1px solid #aaa;color:#000}#suggestmenu em{text-align:center;padding:3px 6px;display:block;border-top:1px solid #ccc;border-right:1px solid #ccc}#suggestmenu a,#suggestmenu a:visited{background:#ebeaeb;border-right:1px solid #ddd;border-left:1px solid #ddd;padding:3px 6px;display:block;margin:0;text-decoration:none;color:#000;outline:0}#suggestmenu a.first,#suggestmenu a.first:visited{border-top:1px solid #ddd}.spell_sep_bottom{border-bottom:1px solid #ddd}.spell_sep_top{border-top:1px solid #aaa}#suggestmenu a:hover{color:#000;background:#f5f5f5}#suggestmenu .foot{border-top:1px solid #aaa;background:#fff}#suggestmenu .foot a,#suggestmenu .foot a:visited{outline:0}div.mce-atd-menu-title.mce-disabled{padding:3px 12px 0}div.mce-atd-menu-title.mce-disabled:hover,div.mce-atd-menu-title.mce-disabled:hover span.mce-text{background:0 0;color:#888;cursor:default}div.mce-atd-menu-title.mce-disabled span.mce-text{font-weight:700;color:#888}

View file

@ -0,0 +1,640 @@
/*
* atd.core.js - A building block to create a front-end for AtD
* Author : Raphael Mudge, Automattic
* License : LGPL
* Project : http://www.afterthedeadline.com/developers.slp
* Contact : raffi@automattic.com
*/
/* jshint sub: true, devel: true, onevar: false, smarttabs: true */
/* exported EXPORTED_SYMBOLS, atd_sprintf */
/* EXPORTED_SYMBOLS is set so this file can be a JavaScript Module */
var EXPORTED_SYMBOLS = ['AtDCore'];
function AtDCore() {
/* these are the categories of errors AtD should ignore */
this.ignore_types = ['Bias Language', 'Cliches', 'Complex Expression', 'Diacritical Marks', 'Double Negatives', 'Hidden Verbs', 'Jargon Language', 'Passive voice', 'Phrases to Avoid', 'Redundant Expression'];
/* these are the phrases AtD should ignore */
this.ignore_strings = {};
/* Localized strings */
// Back-compat, not used
this.i18n = {};
}
/*
* Internationalization Functions
*/
AtDCore.prototype.getLang = function( key, defaultk ) {
return ( window.AtD_l10n_r0ar && window.AtD_l10n_r0ar[key] ) || defaultk;
};
AtDCore.prototype.addI18n = function( obj ) {
// Back-compat
window.AtD_l10n_r0ar = obj;
};
/*
* Setters
*/
AtDCore.prototype.setIgnoreStrings = function(string) {
var parent = this;
this.map(string.split(/,\s*/g), function(string) {
parent.ignore_strings[string] = 1;
});
};
AtDCore.prototype.showTypes = function(string) {
var show_types = string.split(/,\s*/g);
var types = {};
/* set some default types that we want to make optional */
/* grammar checker options */
types['Double Negatives'] = 1;
types['Hidden Verbs'] = 1;
types['Passive voice'] = 1;
types['Bias Language'] = 1;
/* style checker options */
types['Cliches'] = 1;
types['Complex Expression'] = 1;
types['Diacritical Marks'] = 1;
types['Jargon Language'] = 1;
types['Phrases to Avoid'] = 1;
types['Redundant Expression'] = 1;
var ignore_types = [];
this.map(show_types, function(string) {
types[string] = undefined;
});
this.map(this.ignore_types, function(string) {
if (types[string] !== undefined) {
ignore_types.push(string);
}
});
this.ignore_types = ignore_types;
};
/*
* Error Parsing Code
*/
AtDCore.prototype.makeError = function(error_s, tokens, type, seps/*, pre*/) {
var struct = {};
struct.type = type;
struct.string = error_s;
struct.tokens = tokens;
if (new RegExp('\\b' + error_s + '\\b').test(error_s)) {
struct.regexp = new RegExp('(?!'+error_s+'<)\\b' + error_s.replace(/\s+/g, seps) + '\\b');
}
else if (new RegExp(error_s + '\\b').test(error_s)) {
struct.regexp = new RegExp('(?!'+error_s+'<)' + error_s.replace(/\s+/g, seps) + '\\b');
}
else if (new RegExp('\\b' + error_s).test(error_s)) {
struct.regexp = new RegExp('(?!'+error_s+'<)\\b' + error_s.replace(/\s+/g, seps));
}
else {
struct.regexp = new RegExp('(?!'+error_s+'<)' + error_s.replace(/\s+/g, seps));
}
struct.used = false; /* flag whether we've used this rule or not */
return struct;
};
AtDCore.prototype.addToErrorStructure = function(errors, list, type, seps) {
var parent = this;
this.map(list, function(error) {
var tokens = error['word'].split(/\s+/);
var pre = error['pre'];
var first = tokens[0];
if (errors['__' + first] === undefined) {
errors['__' + first] = {};
errors['__' + first].pretoks = {};
errors['__' + first].defaults = [];
}
if (pre === '') {
errors['__' + first].defaults.push(parent.makeError(error['word'], tokens, type, seps, pre));
} else {
if (errors['__' + first].pretoks['__' + pre] === undefined) {
errors['__' + first].pretoks['__' + pre] = [];
}
errors['__' + first].pretoks['__' + pre].push(parent.makeError(error['word'], tokens, type, seps, pre));
}
});
};
AtDCore.prototype.buildErrorStructure = function(spellingList, enrichmentList, grammarList) {
var seps = this._getSeparators();
var errors = {};
this.addToErrorStructure(errors, spellingList, 'hiddenSpellError', seps);
this.addToErrorStructure(errors, grammarList, 'hiddenGrammarError', seps);
this.addToErrorStructure(errors, enrichmentList, 'hiddenSuggestion', seps);
return errors;
};
AtDCore.prototype._getSeparators = function() {
var re = '', i;
var str = '"s!#$%&()*+,./:;<=>?@[\\]^_{|}';
// Build word separator regexp
for (i=0; i<str.length; i++) {
re += '\\' + str.charAt(i);
}
return '(?:(?:[\xa0' + re + '])|(?:\\-\\-))+';
};
AtDCore.prototype.processXML = function(responseXML) {
/* types of errors to ignore */
var types = {};
this.map(this.ignore_types, function(type) {
types[type] = 1;
});
/* save suggestions in the editor object */
this.suggestions = [];
/* process through the errors */
var errors = responseXML.getElementsByTagName('error');
/* words to mark */
var grammarErrors = [];
var spellingErrors = [];
var enrichment = [];
for (var i = 0; i < errors.length; i++) {
if (errors[i].getElementsByTagName('string').item(0).firstChild !== null) {
var errorString = errors[i].getElementsByTagName('string').item(0).firstChild.data;
var errorType = errors[i].getElementsByTagName('type').item(0).firstChild.data;
var errorDescription = errors[i].getElementsByTagName('description').item(0).firstChild.data;
var errorContext;
if (errors[i].getElementsByTagName('precontext').item(0).firstChild !== null) {
errorContext = errors[i].getElementsByTagName('precontext').item(0).firstChild.data;
} else {
errorContext = '';
}
/* create a hashtable with information about the error in the editor object, we will use this later
to populate a popup menu with information and suggestions about the error */
if (this.ignore_strings[errorString] === undefined) {
var suggestion = {};
suggestion['description'] = errorDescription;
suggestion['suggestions'] = [];
/* used to find suggestions when a highlighted error is clicked on */
suggestion['matcher'] = new RegExp('^' + errorString.replace(/\s+/, this._getSeparators()) + '$');
suggestion['context'] = errorContext;
suggestion['string'] = errorString;
suggestion['type'] = errorType;
this.suggestions.push(suggestion);
if (errors[i].getElementsByTagName('suggestions').item(0) !== null) {
var suggestions = errors[i].getElementsByTagName('suggestions').item(0).getElementsByTagName('option');
for (var j = 0; j < suggestions.length; j++) {
suggestion['suggestions'].push(suggestions[j].firstChild.data);
}
}
/* setup the more info url */
if (errors[i].getElementsByTagName('url').item(0) !== null) {
var errorUrl = errors[i].getElementsByTagName('url').item(0).firstChild.data;
suggestion['moreinfo'] = errorUrl + '&theme=tinymce';
}
if (types[errorDescription] === undefined) {
if (errorType === 'suggestion') {
enrichment.push({ word: errorString, pre: errorContext });
}
if (errorType === 'grammar') {
grammarErrors.push({ word: errorString, pre: errorContext });
}
}
if (errorType === 'spelling' || errorDescription === 'Homophone') {
spellingErrors.push({ word: errorString, pre: errorContext });
}
if (errorDescription === 'Cliches') {
suggestion['description'] = 'Clichés'; /* done here for backwards compatability with current user settings */
}
if (errorDescription === 'Spelling') {
suggestion['description'] = this.getLang('menu_title_spelling', 'Spelling');
}
if (errorDescription === 'Repeated Word') {
suggestion['description'] = this.getLang('menu_title_repeated_word', 'Repeated Word');
}
if (errorDescription === 'Did you mean...') {
suggestion['description'] = this.getLang('menu_title_confused_word', 'Did you mean...');
}
} // end if ignore[errorString] == undefined
} // end if
} // end for loop
var errorStruct;
var ecount = spellingErrors.length + grammarErrors.length + enrichment.length;
if (ecount > 0) {
errorStruct = this.buildErrorStructure(spellingErrors, enrichment, grammarErrors);
} else {
errorStruct = undefined;
}
/* save some state in this object, for retrieving suggestions later */
return { errors: errorStruct, count: ecount, suggestions: this.suggestions };
};
AtDCore.prototype.findSuggestion = function(element) {
var text = element.innerHTML;
var context = ( this.getAttrib(element, 'pre') + '' ).replace(/[\\,!\\?\\."\s]/g, '');
if (this.getAttrib(element, 'pre') === undefined) {
alert(element.innerHTML);
}
var errorDescription;
var len = this.suggestions.length;
for (var i = 0; i < len; i++) {
if ((context === '' || context === this.suggestions[i]['context']) && this.suggestions[i]['matcher'].test(text)) {
errorDescription = this.suggestions[i];
break;
}
}
return errorDescription;
};
/*
* TokenIterator class
*/
function TokenIterator(tokens) {
this.tokens = tokens;
this.index = 0;
this.count = 0;
this.last = 0;
}
TokenIterator.prototype.next = function() {
var current = this.tokens[this.index];
this.count = this.last;
this.last += current.length + 1;
this.index++;
/* strip single quotes from token, AtD does this when presenting errors */
if (current !== '') {
if (current[0] === '\'') {
current = current.substring(1, current.length);
}
if (current[current.length - 1] === '\'') {
current = current.substring(0, current.length - 1);
}
}
return current;
};
TokenIterator.prototype.hasNext = function() {
return this.index < this.tokens.length;
};
TokenIterator.prototype.hasNextN = function(n) {
return (this.index + n) < this.tokens.length;
};
TokenIterator.prototype.skip = function(m, n) {
this.index += m;
this.last += n;
if (this.index < this.tokens.length) {
this.count = this.last - this.tokens[this.index].length;
}
};
TokenIterator.prototype.getCount = function() {
return this.count;
};
TokenIterator.prototype.peek = function(n) {
var peepers = [];
var end = this.index + n;
for (var x = this.index; x < end; x++) {
peepers.push(this.tokens[x]);
}
return peepers;
};
/*
* code to manage highlighting of errors
*/
AtDCore.prototype.markMyWords = function(container_nodes, errors) {
var seps = new RegExp(this._getSeparators()),
nl = [],
ecount = 0, /* track number of highlighted errors */
parent = this,
bogus = this._isTinyMCE ? ' data-mce-bogus="1"' : '',
emptySpan = '<span class="mceItemHidden"' + bogus + '>&nbsp;</span>',
textOnlyMode;
/**
* Split a text node into an ordered list of siblings:
* - text node to the left of the match
* - the element replacing the match
* - text node to the right of the match
*
* We have to leave the text to the left and right of the match alone
* in order to prevent XSS
*
* @return array
*/
function splitTextNode( textnode, regexp, replacement ) {
var text = textnode.nodeValue,
index = text.search( regexp ),
match = text.match( regexp ),
captured = [],
cursor;
if ( index < 0 || ! match.length ) {
return [ textnode ];
}
if ( index > 0 ) {
// capture left text node
captured.push( document.createTextNode( text.substr( 0, index ) ) );
}
// capture the replacement of the matched string
captured.push( parent.create( match[0].replace( regexp, replacement ) ) );
cursor = index + match[0].length;
if ( cursor < text.length ) {
// capture right text node
captured.push( document.createTextNode( text.substr( cursor ) ) );
}
return captured;
}
function _isInPre( node ) {
if ( node ) {
while ( node.parentNode ) {
if ( node.nodeName === 'PRE' ) {
return true;
}
node = node.parentNode;
}
}
return false;
}
/* Collect all text nodes */
/* Our goal--ignore nodes that are already wrapped */
this._walk( container_nodes, function( n ) {
if ( n.nodeType === 3 && ! parent.isMarkedNode( n ) && ! _isInPre( n ) ) {
nl.push( n );
}
});
/* walk through the relevant nodes */
var iterator;
this.map( nl, function( n ) {
var v;
if ( n.nodeType === 3 ) {
v = n.nodeValue; /* we don't want to mangle the HTML so use the actual encoded string */
var tokens = n.nodeValue.split( seps ); /* split on the unencoded string so we get access to quotes as " */
var previous = '';
var doReplaces = [];
iterator = new TokenIterator(tokens);
while ( iterator.hasNext() ) {
var token = iterator.next();
var current = errors['__' + token];
var defaults;
if ( current !== undefined && current.pretoks !== undefined ) {
defaults = current.defaults;
current = current.pretoks['__' + previous];
var done = false;
var prev, curr;
prev = v.substr(0, iterator.getCount());
curr = v.substr(prev.length, v.length);
var checkErrors = function( error ) {
if ( error !== undefined && ! error.used && foundStrings[ '__' + error.string ] === undefined && error.regexp.test( curr ) ) {
foundStrings[ '__' + error.string ] = 1;
doReplaces.push([ error.regexp, '<span class="'+error.type+'" pre="'+previous+'"' + bogus + '>$&</span>' ]);
error.used = true;
done = true;
}
}; // jshint ignore:line
var foundStrings = {};
if (current !== undefined) {
previous = previous + ' ';
parent.map(current, checkErrors);
}
if (!done) {
previous = '';
parent.map(defaults, checkErrors);
}
}
previous = token;
} // end while
/* do the actual replacements on this span */
if ( doReplaces.length > 0 ) {
var newNode = n;
for ( var x = 0; x < doReplaces.length; x++ ) {
var regexp = doReplaces[x][0], result = doReplaces[x][1];
/* it's assumed that this function is only being called on text nodes (nodeType == 3), the iterating is necessary
because eventually the whole thing gets wrapped in an mceItemHidden span and from there it's necessary to
handle each node individually. */
var bringTheHurt = function( node ) {
var span, splitNodes;
if ( node.nodeType === 3 ) {
ecount++;
/* sometimes IE likes to ignore the space between two spans, solution is to insert a placeholder span with
a non-breaking space. The markup removal code substitutes this span for a space later */
if ( parent.isIE() && node.nodeValue.length > 0 && node.nodeValue.substr(0, 1) === ' ' ) {
return parent.create( emptySpan + node.nodeValue.substr( 1, node.nodeValue.length - 1 ).replace( regexp, result ), false );
} else {
if ( textOnlyMode ) {
return parent.create( node.nodeValue.replace( regexp, result ), false );
}
span = parent.create( '<span />' );
if ( typeof textOnlyMode === 'undefined' ) {
// cache this to avoid adding / removing nodes unnecessarily
textOnlyMode = typeof span.appendChild !== 'function';
if ( textOnlyMode ) {
parent.remove( span );
return parent.create( node.nodeValue.replace( regexp, result ), false );
}
}
// "Visual" mode
splitNodes = splitTextNode( node, regexp, result );
for ( var i = 0; i < splitNodes.length; i++ ) {
span.appendChild( splitNodes[i] );
}
node = span;
return node;
}
}
else {
var contents = parent.contents(node);
for ( var y = 0; y < contents.length; y++ ) {
if ( contents[y].nodeType === 3 && regexp.test( contents[y].nodeValue ) ) {
var nnode;
if ( parent.isIE() && contents[y].nodeValue.length > 0 && contents[y].nodeValue.substr(0, 1) === ' ') {
nnode = parent.create( emptySpan + contents[y].nodeValue.substr( 1, contents[y].nodeValue.length - 1 ).replace( regexp, result ), true );
} else {
nnode = parent.create( contents[y].nodeValue.replace( regexp, result ), true );
}
parent.replaceWith( contents[y], nnode );
parent.removeParent( nnode );
ecount++;
return node; /* we did a replacement so we can call it quits, errors only get used once */
}
}
return node;
}
}; // jshint ignore:line
newNode = bringTheHurt(newNode);
}
parent.replaceWith(n, newNode);
}
}
});
return ecount;
};
AtDCore.prototype._walk = function(elements, f) {
var i;
for (i = 0; i < elements.length; i++) {
f.call(f, elements[i]);
this._walk(this.contents(elements[i]), f);
}
};
AtDCore.prototype.removeWords = function(node, w) {
var count = 0;
var parent = this;
this.map(this.findSpans(node).reverse(), function(n) {
if (n && (parent.isMarkedNode(n) || parent.hasClass(n, 'mceItemHidden') || parent.isEmptySpan(n)) ) {
if (n.innerHTML === '&nbsp;') {
var nnode = document.createTextNode(' '); /* hax0r */
parent.replaceWith(n, nnode);
} else if (!w || n.innerHTML === w) {
parent.removeParent(n);
count++;
}
}
});
return count;
};
AtDCore.prototype.isEmptySpan = function(node) {
return (this.getAttrib(node, 'class') === '' && this.getAttrib(node, 'style') === '' && this.getAttrib(node, 'id') === '' && !this.hasClass(node, 'Apple-style-span') && this.getAttrib(node, 'mce_name') === '');
};
AtDCore.prototype.isMarkedNode = function(node) {
return (this.hasClass(node, 'hiddenGrammarError') || this.hasClass(node, 'hiddenSpellError') || this.hasClass(node, 'hiddenSuggestion'));
};
/*
* Context Menu Helpers
*/
AtDCore.prototype.applySuggestion = function(element, suggestion) {
if (suggestion === '(omit)') {
this.remove(element);
}
else {
var node = this.create(suggestion);
this.replaceWith(element, node);
this.removeParent(node);
}
};
/*
* Check for an error
*/
AtDCore.prototype.hasErrorMessage = function(xmlr) {
return (xmlr !== undefined && xmlr.getElementsByTagName('message').item(0) !== null);
};
AtDCore.prototype.getErrorMessage = function(xmlr) {
return xmlr.getElementsByTagName('message').item(0);
};
/* this should always be an error, alas... not practical */
AtDCore.prototype.isIE = function() {
return navigator.appName === 'Microsoft Internet Explorer';
};
// TODO: this doesn't seem used anywhere in AtD, moved here from install_atd_l10n.js for eventual back-compat
/* a quick poor man's sprintf */
function atd_sprintf(format, values) {
var result = format;
for (var x = 0; x < values.length; x++) {
result = result.replace(new RegExp('%' + (x + 1) + '\\$', 'g'), values[x]);
}
return result;
}

View file

@ -0,0 +1,122 @@
/* AtD error styles */
.hiddenSpellError
{
border-bottom: 2px solid red;
cursor: default;
}
.hiddenGrammarError
{
border-bottom: 2px solid green;
cursor: default;
}
.hiddenSuggestion
{
border-bottom: 2px solid blue;
cursor: default;
}
/* Menu styles derived from:
* jquery.spellchecker.js - a simple jQuery Spell Checker
* Copyright (c) 2008 Richard Willis
* MIT license : http://www.opensource.org/licenses/mit-license.php
* Project : http://jquery-spellchecker.googlecode.com
*/
#suggestmenu
{
min-width: 122px;
background: #ebeaeb;
position: absolute;
display: none;
z-index: 9999;
overflow: none;
margin-top: 1px;
text-align: left;
font-size: 11px;
font-family: Tahoma, Verdana, Arial, Helvetica;
}
#suggestmenu strong
{
background: #cccccc;
font-weight: bold;
padding:3px 6px 3px 6px;
display:block;
border:1px solid #dddddd;
border-bottom: 1px solid #aaaaaa;
color: black;
}
#suggestmenu em
{
text-align:center;
padding:3px 6px 3px 6px;
display:block;
border-top:1px solid #ccc;
border-left:1px solid #ccc;
}
#suggestmenu a, #suggestmenu a:visited
{
background: #ebeaeb;
border-left:1px solid #dddddd;
border-right:1px solid #dddddd;
padding:3px 6px 3px 6px;
display:block;
margin:0px;
text-decoration:none;
color: black;
outline:none
}
#suggestmenu a.first, #suggestmenu a.first:visited
{
border-top:1px solid #dddddd;
}
.spell_sep_bottom
{
border-bottom: 1px solid #dddddd;
}
.spell_sep_top
{
border-top: 1px solid #aaaaaa;
}
#suggestmenu a:hover
{
color:#000;
background: #f5f5f5;
}
#suggestmenu .foot
{
border-top:1px solid #aaaaaa;
background:#fff
}
#suggestmenu .foot a, #suggestmenu .foot a:visited
{
outline:none
}
/* TinyMCE 4.0 */
div.mce-atd-menu-title.mce-disabled {
padding: 3px 12px 0;
}
div.mce-atd-menu-title.mce-disabled:hover,
div.mce-atd-menu-title.mce-disabled:hover span.mce-text {
background: none;
color: #888;
cursor: default;
}
div.mce-atd-menu-title.mce-disabled span.mce-text {
font-weight: bold;
color: #888;
}

View file

@ -0,0 +1 @@
.hiddenSpellError{border-bottom:2px solid red;cursor:default}.hiddenGrammarError{border-bottom:2px solid green;cursor:default}.hiddenSuggestion{border-bottom:2px solid #00f;cursor:default}#suggestmenu{min-width:122px;background:#ebeaeb;position:absolute;display:none;z-index:9999;overflow:none;margin-top:1px;text-align:left;font-size:11px;font-family:Tahoma,Verdana,Arial,Helvetica}#suggestmenu strong{background:#ccc;font-weight:700;padding:3px 6px;display:block;border:1px solid #ddd;border-bottom:1px solid #aaa;color:#000}#suggestmenu em{text-align:center;padding:3px 6px;display:block;border-top:1px solid #ccc;border-left:1px solid #ccc}#suggestmenu a,#suggestmenu a:visited{background:#ebeaeb;border-left:1px solid #ddd;border-right:1px solid #ddd;padding:3px 6px;display:block;margin:0;text-decoration:none;color:#000;outline:0}#suggestmenu a.first,#suggestmenu a.first:visited{border-top:1px solid #ddd}.spell_sep_bottom{border-bottom:1px solid #ddd}.spell_sep_top{border-top:1px solid #aaa}#suggestmenu a:hover{color:#000;background:#f5f5f5}#suggestmenu .foot{border-top:1px solid #aaa;background:#fff}#suggestmenu .foot a,#suggestmenu .foot a:visited{outline:0}div.mce-atd-menu-title.mce-disabled{padding:3px 12px 0}div.mce-atd-menu-title.mce-disabled:hover,div.mce-atd-menu-title.mce-disabled:hover span.mce-text{background:0 0;color:#888;cursor:default}div.mce-atd-menu-title.mce-disabled span.mce-text{font-weight:700;color:#888}

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

View file

@ -0,0 +1,128 @@
<?php
/*
* Display the configuration options for AtD
*/
/*
* A convienence function to display the HTML for an AtD option
*/
function AtD_print_option( $name, $value, $options ) {
// Attribute-safe version of $name
$attr_name = sanitize_title($name); // Using sanitize_title since there's no comparable function for attributes
?>
<input type="checkbox" id="atd_<?php echo esc_attr( $attr_name ) ?>" name="<?php echo esc_attr( $options['name'] ); ?>[<?php echo esc_attr( $name ); ?>]" value="1" <?php checked( '1', isset( $options[$name] ) ? $options[$name] : false ); ?>> <label for="atd_<?php echo esc_attr( $attr_name ); ?>"><?php echo esc_html( $value ); ?></label>
<?php
}
/*
* Save AtD options
*/
function AtD_process_options_update() {
$user = wp_get_current_user();
if ( ! $user || $user->ID == 0 )
return;
AtD_update_options( $user->ID, 'AtD_options' );
AtD_update_options( $user->ID, 'AtD_check_when' );
AtD_update_options( $user->ID, 'AtD_guess_lang' );
}
/*
* Display the various AtD options
*/
function AtD_display_options_form() {
/* grab our user and validate their existence */
$user = wp_get_current_user();
if ( ! $user || $user->ID == 0 )
return;
$options_show_types = AtD_get_options( $user->ID, 'AtD_options' );
$options_check_when = AtD_get_options( $user->ID, 'AtD_check_when' );
$options_guess_lang = AtD_get_options( $user->ID, 'AtD_guess_lang' );
?>
<table class="form-table">
<tr valign="top">
<th scope="row"> <a id="atd"></a> <?php _e( 'Proofreading', 'jetpack' ); ?></th>
<td>
<p><?php _e( 'Automatically proofread content when:', 'jetpack' ); ?>
<p><?php
AtD_print_option( 'onpublish', __('a post or page is first published', 'jetpack'), $options_check_when );
echo '<br />';
AtD_print_option( 'onupdate', __('a post or page is updated', 'jetpack'), $options_check_when );
?></p>
<p style="font-weight: bold"><?php _e('English Options', 'jetpack'); ?></p>
<p><?php _e('Enable proofreading for the following grammar and style rules when writing posts and pages:', 'jetpack'); ?></p>
<p><?php
AtD_print_option( 'Bias Language', __('Bias Language', 'jetpack'), $options_show_types );
echo '<br />';
AtD_print_option( 'Cliches', __('Clich&eacute;s', 'jetpack'), $options_show_types );
echo '<br />';
AtD_print_option( 'Complex Expression', __('Complex Phrases', 'jetpack'), $options_show_types );
echo '<br />';
AtD_print_option( 'Diacritical Marks', __('Diacritical Marks', 'jetpack'), $options_show_types );
echo '<br />';
AtD_print_option( 'Double Negative', __('Double Negatives', 'jetpack'), $options_show_types );
echo '<br />';
AtD_print_option( 'Hidden Verbs', __('Hidden Verbs', 'jetpack'), $options_show_types );
echo '<br />';
AtD_print_option( 'Jargon Language', __('Jargon', 'jetpack'), $options_show_types );
echo '<br />';
AtD_print_option( 'Passive voice', __('Passive Voice', 'jetpack'), $options_show_types );
echo '<br />';
AtD_print_option( 'Phrases to Avoid', __('Phrases to Avoid', 'jetpack'), $options_show_types );
echo '<br />';
AtD_print_option( 'Redundant Expression', __('Redundant Phrases', 'jetpack'), $options_show_types );
?></p>
<p><?php printf( __( '<a href="%s" target="_blank">Learn more</a> about these options.', 'jetpack' ), 'http://support.wordpress.com/proofreading/' );
?></p>
<p style="font-weight: bold"><?php _e( 'Language', 'jetpack' ); ?></p>
<p><?php
_e( 'The proofreader supports English, French, German, Portuguese, and Spanish. Your user interface language (see above) is the default proofreading language.', 'jetpack' );
?></p>
<p><?php
AtD_print_option( 'true', __('Use automatically detected language to proofread posts and pages', 'jetpack' ), $options_guess_lang );
?></p>
<?php
}
/*
* Returns an array of AtD user options specified by $name
*/
function AtD_get_options( $user_id, $name ) {
$options_raw = AtD_get_setting( $user_id, $name, 'single' );
$options = array();
$options['name'] = $name;
if ( $options_raw )
foreach ( explode( ',', $options_raw ) as $option )
$options[ $option ] = 1;
return $options;
}
/*
* Saves set of user options specified by $name from POST data
*/
function AtD_update_options( $user_id, $name ) {
/* We should probably run $_POST[name] through an esc_*() function... */
if ( isset( $_POST[$name] ) && is_array( $_POST[$name] ) ) {
$copy = array_map( 'strip_tags', array_keys( $_POST[$name] ) );
AtD_update_setting( $user_id, AtD_sanitize( $name ), implode( ',', $copy ) );
} else {
AtD_update_setting( $user_id, AtD_sanitize( $name ), '');
}
return;
}

View file

@ -0,0 +1,148 @@
<?php
/*
* Called by the TinyMCE plugin when Ignore Always is clicked (setup as an action through admin-ajax.php)
*/
function AtD_ignore_call() {
if ( ! AtD_is_allowed() )
return;
$user = wp_get_current_user();
if ( ! $user || $user->ID == 0 )
return;
check_admin_referer( 'atd_ignore' );
$ignores = explode( ',', AtD_get_setting( $user->ID, 'AtD_ignored_phrases') );
array_push( $ignores, $_GET['phrase'] );
$ignores = array_filter( array_map( 'strip_tags', $ignores ) );
AtD_update_setting( $user->ID, 'AtD_ignored_phrases', implode( ',', $ignores ) );
header( 'Content-Type: text/xml' );
echo '<success></success>';
die();
}
/*
* Called when a POST occurs, used to save AtD ignored phrases
*/
function AtD_process_unignore_update() {
if ( ! AtD_is_allowed() )
return;
if ( ! isset( $_POST['AtD_ignored_phrases'] ) )
return;
$user = wp_get_current_user();
if ( ! $user || $user->ID == 0 )
return;
$ignores = array_filter( array_map( 'strip_tags', explode( ',', $_POST['AtD_ignored_phrases'] ) ) );
AtD_update_setting( $user->ID, 'AtD_ignored_phrases', join( ',', $ignores ) );
}
/*
* Display the AtD unignore form on a page
*/
function AtD_display_unignore_form() {
if ( ! AtD_is_allowed() )
return;
$user = wp_get_current_user();
if ( ! $user || $user->ID == 0 )
return;
$ignores = AtD_get_setting( $user->ID, 'AtD_ignored_phrases' );
?>
<script>
function atd_show_phrases( ignored )
{
var element = jQuery( '#atd_ignores' ),
items = [],
delLink;
ignored.sort();
element.empty();
for ( var i = 0; i < ignored.length; i++ ) {
if ( ignored[i].length > 0 ) {
delLink = jQuery( '<span id="atd_' + i + '">&nbsp;</span>' );
delLink
.text( delLink.text() + ignored[i] )
.prepend( jQuery( '<a class="ntdelbutton">X</a>' ).data( 'ignored', ignored[i] ) );
element.append( delLink ).append( '<br />' );
}
}
}
function atd_unignore( phrase ) {
/* get the ignored values and remove the unwanted phrase */
var ignored = jQuery( '#AtD_ignored_phrases' ).val().split( /,/g );
ignored = jQuery.map(ignored, function(value, index) { return value == phrase ? null : value; });
jQuery( '#AtD_ignored_phrases' ).val( ignored.join(',') );
/* update the UI */
atd_show_phrases( ignored );
/* show a nifty message to the user */
jQuery( '#AtD_message' ).show();
}
function atd_ignore () {
/* get the ignored values and update the hidden field */
var ignored = jQuery( '#AtD_ignored_phrases' ).val().split( /,/g );
jQuery.map(jQuery( '#AtD_add_ignore' ).val().split(/,\s*/g), function(value, index) { ignored.push(value); });
jQuery( '#AtD_ignored_phrases' ).val( ignored.join(',') );
/* update the UI */
atd_show_phrases( ignored );
jQuery( '#AtD_add_ignore' ).val('');
/* show that nifteroo messaroo to the useroo */
jQuery( '#AtD_message' ).show();
}
function atd_ignore_init() {
jQuery( '#AtD_message' ).hide();
jQuery( '#atd_ignores' ).on( 'click', 'a', function() {
atd_unignore( jQuery(this).data( 'ignored' ) );
return false;
} );
atd_show_phrases( jQuery( '#AtD_ignored_phrases' ).val().split( /,/g ) );
}
/* document.ready() does not execute in IE6 unless it's at the bottom of the page. oi! */
if (navigator.appName == 'Microsoft Internet Explorer')
setTimeout( atd_ignore_init, 2500 );
else
jQuery( document ).ready( atd_ignore_init );
</script>
<input type="hidden" name="AtD_ignored_phrases" id="AtD_ignored_phrases" value="<?php echo esc_attr( $ignores ); ?>">
<p style="font-weight: bold"><?php _e( 'Ignored Phrases', 'jetpack' ); ?></p>
<p><?php _e( 'Identify words and phrases to ignore while proofreading your posts and pages:', 'jetpack' ); ?></p>
<p><input type="text" id="AtD_add_ignore" name="AtD_add_ignore"> <input type="button" value="<?php _e( 'Add', 'jetpack' ); ?>" onclick="javascript:atd_ignore()"></p>
<div class="tagchecklist" id="atd_ignores"></div>
<div class="plugin-update-tr" id="AtD_message" style="display: none">
<div class="update-message"><strong><?php _e( 'Be sure to click "Update Profile" at the bottom of the screen to save your changes.', 'jetpack' ); ?></strong></div>
</div>
</td>
</tr>
</table>
<?php
}

View file

@ -0,0 +1,436 @@
/*
* jquery.atd.js - jQuery powered writing check with After the Deadline
* Author : Raphael Mudge, Automattic Inc.
* License : LGPL or MIT License (take your pick)
* Project : http://www.afterthedeadline.com/development.slp
* Contact : raffi@automattic.com
*
* Derived from:
*
* jquery.spellchecker.js - a simple jQuery Spell Checker
* Copyright (c) 2008 Richard Willis
* MIT license : http://www.opensource.org/licenses/mit-license.php
* Project : http://jquery-spellchecker.googlecode.com
* Contact : willis.rh@gmail.com
*/
/* jshint onevar: false, sub: true, smarttabs: true */
/* global AtDCore, AtD_proofread_click_count, CSSHttpRequest, ActiveXObject */
var AtD =
{
rpc : '', /* see the proxy.php that came with the AtD/TinyMCE plugin */
rpc_css : 'http://www.polishmywriting.com/atd-jquery/server/proxycss.php?data=', /* you may use this, but be nice! */
rpc_css_lang : 'en',
api_key : '',
i18n : {}, // Back-compat
listener : {}
};
AtD.getLang = function( key, defaultk ) {
return ( window.AtD_l10n_r0ar && window.AtD_l10n_r0ar[key] ) || defaultk;
};
AtD.addI18n = function( obj ) {
// Back-compat
window.AtD_l10n_r0ar = obj;
};
AtD.setIgnoreStrings = function(string) {
AtD.core.setIgnoreStrings(string);
};
AtD.showTypes = function(string) {
AtD.core.showTypes(string);
};
AtD.checkCrossAJAX = function(container_id, callback_f) {
/* checks if a global var for click stats exists and increments it if it does... */
if (typeof AtD_proofread_click_count !== 'undefined') {
AtD_proofread_click_count++;
}
AtD.callback_f = callback_f; /* remember the callback for later */
AtD.remove(container_id);
var container = jQuery('#' + container_id);
var text = jQuery.trim(container.html());
text = text.replace(/\&lt;/g, '<').replace(/\&gt;/g, '>').replace(/\&amp;/g, '&');
text = encodeURIComponent( text.replace( /\%/g, '%25' ) ); /* % not being escaped here creates problems, I don't know why. */
/* do some sanity checks based on the browser */
if ((text.length > 2000 && navigator.appName === 'Microsoft Internet Explorer') || text.length > 7800) {
if (callback_f !== undefined && callback_f.error !== undefined) {
callback_f.error('Maximum text length for this browser exceeded');
}
return;
}
/* do some cross-domain AJAX action with CSSHttpRequest */
CSSHttpRequest.get(AtD.rpc_css + text + '&lang=' + AtD.rpc_css_lang + '&nocache=' + (new Date().getTime()), function(response) {
/* do some magic to convert the response into an XML document */
var xml;
if (navigator.appName === 'Microsoft Internet Explorer') {
xml = new ActiveXObject('Microsoft.XMLDOM');
xml.async = false;
xml.loadXML(response);
} else {
xml = (new DOMParser()).parseFromString(response, 'text/xml');
}
/* check for and display error messages from the server */
if (AtD.core.hasErrorMessage(xml)) {
if (AtD.callback_f !== undefined && AtD.callback_f.error !== undefined) {
AtD.callback_f.error(AtD.core.getErrorMessage(xml));
}
return;
}
/* highlight the errors */
AtD.container = container_id;
var count = Number( AtD.processXML(container_id, xml) );
if (AtD.callback_f !== undefined && AtD.callback_f.ready !== undefined) {
AtD.callback_f.ready(count);
}
if (count === 0 && AtD.callback_f !== undefined && AtD.callback_f.success !== undefined) {
AtD.callback_f.success(count);
}
AtD.counter = count;
AtD.count = count;
});
};
/* check a div for any incorrectly spelled words */
AtD.check = function(container_id, callback_f) {
/* checks if a global var for click stats exists and increments it if it does... */
if (typeof AtD_proofread_click_count !== 'undefined') {
AtD_proofread_click_count++;
}
AtD.callback_f = callback_f; /* remember the callback for later */
AtD.remove(container_id);
var container = jQuery('#' + container_id);
var text = jQuery.trim(container.html());
text = text.replace(/\&lt;/g, '<').replace(/\&gt;/g, '>').replace(/\&amp;/g, '&');
text = encodeURIComponent( text ); /* re-escaping % is not necessary here. don't do it */
jQuery.ajax({
type : 'POST',
url : AtD.rpc + '/checkDocument',
data : 'key=' + AtD.api_key + '&data=' + text,
format : 'raw',
dataType : (jQuery.browser.msie) ? 'text' : 'xml',
error : function(XHR, status, error) {
if (AtD.callback_f !== undefined && AtD.callback_f.error !== undefined) {
AtD.callback_f.error(status + ': ' + error);
}
},
success : function(data) {
/* apparently IE likes to return XML as plain text-- work around from:
http://docs.jquery.com/Specifying_the_Data_Type_for_AJAX_Requests */
var xml;
if (typeof data === 'string') {
xml = new ActiveXObject('Microsoft.XMLDOM');
xml.async = false;
xml.loadXML(data);
}
else {
xml = data;
}
if (AtD.core.hasErrorMessage(xml)) {
if (AtD.callback_f !== undefined && AtD.callback_f.error !== undefined) {
AtD.callback_f.error(AtD.core.getErrorMessage(xml));
}
return;
}
/* on with the task of processing and highlighting errors */
AtD.container = container_id;
var count = Number( AtD.processXML(container_id, xml) );
if (AtD.callback_f !== undefined && AtD.callback_f.ready !== undefined) {
AtD.callback_f.ready(count);
}
if (count === 0 && AtD.callback_f !== undefined && AtD.callback_f.success !== undefined) {
AtD.callback_f.success(count);
}
AtD.counter = count;
AtD.count = count;
}
});
};
AtD.remove = function(container_id) {
AtD._removeWords(container_id, null);
};
AtD.clickListener = function(event) {
if (AtD.core.isMarkedNode(event.target)) {
AtD.suggest(event.target);
}
};
AtD.processXML = function(container_id, responseXML) {
var results = AtD.core.processXML(responseXML);
if (results.count > 0) {
results.count = AtD.core.markMyWords(jQuery('#' + container_id).contents(), results.errors);
}
jQuery('#' + container_id).unbind('click', AtD.clickListener);
jQuery('#' + container_id).click(AtD.clickListener);
return results.count;
};
AtD.useSuggestion = function(word) {
this.core.applySuggestion(AtD.errorElement, word);
AtD.counter --;
if (AtD.counter === 0 && AtD.callback_f !== undefined && AtD.callback_f.success !== undefined) {
AtD.callback_f.success(AtD.count);
}
};
AtD.editSelection = function() {
var parent = AtD.errorElement.parent();
if (AtD.callback_f !== undefined && AtD.callback_f.editSelection !== undefined) {
AtD.callback_f.editSelection(AtD.errorElement);
}
if (AtD.errorElement.parent() !== parent) {
AtD.counter --;
if (AtD.counter === 0 && AtD.callback_f !== undefined && AtD.callback_f.success !== undefined) {
AtD.callback_f.success(AtD.count);
}
}
};
AtD.ignoreSuggestion = function() {
AtD.core.removeParent(AtD.errorElement);
AtD.counter --;
if (AtD.counter === 0 && AtD.callback_f !== undefined && AtD.callback_f.success !== undefined) {
AtD.callback_f.success(AtD.count);
}
};
AtD.ignoreAll = function(container_id) {
var target = AtD.errorElement.text();
var removed = AtD._removeWords(container_id, target);
AtD.counter -= removed;
if (AtD.counter === 0 && AtD.callback_f !== undefined && AtD.callback_f.success !== undefined) {
AtD.callback_f.success(AtD.count);
}
if (AtD.callback_f !== undefined && AtD.callback_f.ignore !== undefined) {
AtD.callback_f.ignore(target);
AtD.core.setIgnoreStrings(target);
}
};
AtD.explainError = function() {
if (AtD.callback_f !== undefined && AtD.callback_f.explain !== undefined) {
AtD.callback_f.explain(AtD.explainURL);
}
};
AtD.suggest = function(element) {
/* construct the menu if it doesn't already exist */
var suggest;
if (jQuery('#suggestmenu').length === 0) {
suggest = jQuery('<div id="suggestmenu"></div>');
suggest.prependTo('body');
} else {
suggest = jQuery('#suggestmenu');
suggest.hide();
}
/* find the correct suggestions object */
var errorDescription = AtD.core.findSuggestion(element);
/* build up the menu y0 */
AtD.errorElement = jQuery(element);
suggest.empty();
if (errorDescription === undefined) {
suggest.append('<strong>' + AtD.getLang('menu_title_no_suggestions', 'No suggestions') + '</strong>');
} else if (errorDescription['suggestions'].length === 0) {
suggest.append('<strong>' + errorDescription['description'] + '</strong>');
} else {
suggest.append('<strong>' + errorDescription['description'] + '</strong>');
for (var i = 0; i < errorDescription['suggestions'].length; i++) {
(function(sugg) {
suggest.append('<a href="javascript:AtD.useSuggestion(\'' + sugg.replace(/'/, '\\\'') + '\')">' + sugg + '</a>');
})(errorDescription['suggestions'][i]); // jshint ignore:line
}
}
/* do the explain menu if configured */
if (AtD.callback_f !== undefined && AtD.callback_f.explain !== undefined && errorDescription['moreinfo'] !== undefined) {
suggest.append('<a href="javascript:AtD.explainError()" class="spell_sep_top">' + AtD.getLang('menu_option_explain', 'Explain...') + '</a>');
AtD.explainURL = errorDescription['moreinfo'];
}
/* do the ignore option */
suggest.append('<a href="javascript:AtD.ignoreSuggestion()" class="spell_sep_top">' + AtD.getLang('menu_option_ignore_once', 'Ignore suggestion') + '</a>');
/* add the edit in place and ignore always option */
if (AtD.callback_f !== undefined && AtD.callback_f.editSelection !== undefined) {
if (AtD.callback_f !== undefined && AtD.callback_f.ignore !== undefined) {
suggest.append('<a href="javascript:AtD.ignoreAll(\'' + AtD.container + '\')">' + AtD.getLang('menu_option_ignore_always', 'Ignore always') + '</a>');
} else {
suggest.append('<a href="javascript:AtD.ignoreAll(\'' + AtD.container + '\')">' + AtD.getLang('menu_option_ignore_all', 'Ignore all') + '</a>');
}
suggest.append('<a href="javascript:AtD.editSelection(\'' + AtD.container + '\')" class="spell_sep_bottom spell_sep_top">' + AtD.getLang('menu_option_edit_selection', 'Edit Selection...') + '</a>');
}
else {
if (AtD.callback_f !== undefined && AtD.callback_f.ignore !== undefined) {
suggest.append('<a href="javascript:AtD.ignoreAll(\'' + AtD.container + '\')" class="spell_sep_bottom">' + AtD.getLang('menu_option_ignore_always', 'Ignore always') + '</a>');
} else {
suggest.append('<a href="javascript:AtD.ignoreAll(\'' + AtD.container + '\')" class="spell_sep_bottom">' + AtD.getLang('menu_option_ignore_all', 'Ignore all') + '</a>');
}
}
/* show the menu */
var pos = jQuery(element).offset();
var width = jQuery(element).width();
/* a sanity check for Internet Explorer--my favorite browser in every possible way */
if (width > 100) {
width = 50;
}
jQuery(suggest).css({ left: (pos.left + width) + 'px', top: pos.top + 'px' });
jQuery(suggest).fadeIn(200);
/* bind events to make the menu disappear when the user clicks outside of it */
AtD.suggestShow = true;
setTimeout(function() {
jQuery('body').bind('click', function() {
if (!AtD.suggestShow) {
jQuery('#suggestmenu').fadeOut(200);
}
});
}, 1);
setTimeout(function() {
AtD.suggestShow = false;
}, 2);
};
AtD._removeWords = function(container_id, w) {
return this.core.removeWords(jQuery('#' + container_id), w);
};
/*
* Set prototypes used by AtD Core UI
*/
AtD.initCoreModule = function() {
var core = new AtDCore();
core.hasClass = function(node, className) {
return jQuery(node).hasClass(className);
};
core.map = jQuery.map;
core.contents = function(node) {
return jQuery(node).contents();
};
core.replaceWith = function(old_node, new_node) {
return jQuery(old_node).replaceWith(new_node);
};
core.findSpans = function(parent) {
return jQuery.makeArray(parent.find('span'));
};
core.create = function(string/*, isTextNode*/) {
// replace out all tags with &-equivalents so that we preserve tag text.
string = string.replace(/\&/g, '&amp;');
string = string.replace(/</g, '&lt;').replace(/\>/g, '&gt;');
// find all instances of AtD-created spans
var matches = string.match(/\&lt;span class="hidden\w+?" pre="[^"]*"\&gt;.*?\&lt;\/span\&gt;/g);
var x;
// ... and fix the tags in those substrings.
if (matches) {
for (x = 0; x < matches.length; x++) {
string = string.replace(matches[x], matches[x].replace(/\&lt;/gi, '<').replace(/\&gt;/gi, '>'));
}
}
if (core.isIE()) {
// and... one more round of corrections for our friends over at the Internet Explorer
matches = string.match(/\&lt;span class="mceItemHidden"\&gt;\&amp;nbsp;\&lt;\/span&gt;/g, string);
//|&lt;BR.*?class.*?atd_remove_me.*?\&gt;/gi, string);
if (matches) {
for (x = 0; x < matches.length; x++) {
string = string.replace(matches[x], matches[x].replace(/\&lt;/gi, '<').replace(/\&gt;/gi, '>').replace(/\&amp;/gi, '&'));
}
}
}
var node = jQuery('<span class="mceItemHidden"></span>');
node.html(string);
return node;
};
core.remove = function(node) {
return jQuery(node).remove();
};
core.removeParent = function(node) {
/* unwrap exists in jQuery 1.4+ only. Thankfully because replaceWith as-used here won't work in 1.4 */
if (jQuery(node).unwrap) {
return jQuery(node).contents().unwrap();
} else {
return jQuery(node).replaceWith(jQuery(node).html());
}
};
core.getAttrib = function(node, name) {
return jQuery(node).attr(name);
};
return core;
};
AtD.core = AtD.initCoreModule();

View file

@ -0,0 +1,115 @@
<?php
/*
* This script redirects AtD AJAX requests to the AtD service
*/
/**
* Returns array with headers in $response[0] and body in $response[1]
* Based on a function from Akismet
*/
function AtD_http_post( $request, $host, $path, $port = 80 ) {
$http_args = array(
'body' => $request,
'headers' => array(
'Content-Type' => 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' ),
'Host' => $host,
'User-Agent' => 'AtD/0.1'
),
'httpversion' => '1.0',
/**
* Change the timeout time for AtD post.
*
* @module after-the-deadline
*
* @since 1.2.3
*
* @param int $var Timeout time in seconds, default 15.
*/
'timeout' => apply_filters( 'atd_http_post_timeout', 15 ),
);
// Handle non-standard ports being passed in.
if ( ( 80 !== $port ) && is_numeric( $port ) && ( intval( $port ) > 0 ) ) {
$host .= ':' . intval( $port );
}
// Strip any / off the begining so we can add it back and protect against SSRF
$path = ltrim( $path, '/' );
$AtD_url = set_url_scheme( "http://{$host}/{$path}" );
$response = wp_remote_post( $AtD_url, $http_args );
$code = (int) wp_remote_retrieve_response_code( $response );
if ( is_wp_error( $response ) ) {
/**
* Fires when there is a post error to AtD.
*
* @module after-the-deadline
*
* @since 1.2.3
*
* @param int|string http-error The error that AtD runs into.
*/
do_action( 'atd_http_post_error', 'http-error' );
return array();
} elseif ( 200 != $code ) {
/** This action is documented in modules/after-the-deadline/proxy.php */
do_action( 'atd_http_post_error', $code );
}
return array(
wp_remote_retrieve_headers( $response ),
wp_remote_retrieve_body( $response ),
);
}
/*
* This function is called as an action handler to admin-ajax.php
*/
function AtD_redirect_call() {
if ( $_SERVER['REQUEST_METHOD'] === 'POST' )
$postText = trim( file_get_contents( 'php://input' ) );
check_admin_referer( 'proxy_atd' );
$url = $_GET['url'];
/**
* Change the AtD service domain.
*
* @module after-the-deadline
*
* @since 1.2.3
*
* @param string $var The URL for AtD service domain, default is service.afterthedeadline.com.
*/
$service = apply_filters( 'atd_service_domain', 'service.afterthedeadline.com' );
$user = wp_get_current_user();
$atd_lang = get_locale();
// If we're on WPCOM, this function should be available.
if ( function_exists( 'get_user_lang_code' ) ) {
$atd_lang = get_user_lang_code( $user->ID );
}
if ( ! empty( $atd_lang ) ) {
if ( strpos($atd_lang, 'pt') !== false )
$service = 'pt.service.afterthedeadline.com';
else if ( strpos($atd_lang, 'de') !== false )
$service = 'de.service.afterthedeadline.com';
else if ( strpos($atd_lang, 'es') !== false )
$service = 'es.service.afterthedeadline.com';
else if ( strpos($atd_lang, 'fr') !== false )
$service = 'fr.service.afterthedeadline.com';
}
$guess = strcmp( AtD_get_setting( $user->ID, 'AtD_guess_lang' ), "true" ) == 0 ? "true" : "false";
$data = AtD_http_post( $postText . "&guess=$guess", defined('ATD_HOST') ? ATD_HOST : $service, $url, defined('ATD_PORT') ? ATD_PORT : 80 );
header( 'Content-Type: text/xml' );
if ( ! empty( $data[1] ) )
echo $data[1];
die();
}

View file

@ -0,0 +1,124 @@
/* This file was automatically generated on Mar 20 2014 23:59:38 */
/* AtD error styles */
.hiddenSpellError
{
border-bottom: 2px solid red;
cursor: default;
}
.hiddenGrammarError
{
border-bottom: 2px solid green;
cursor: default;
}
.hiddenSuggestion
{
border-bottom: 2px solid blue;
cursor: default;
}
/* Menu styles derived from:
* jquery.spellchecker.js - a simple jQuery Spell Checker
* Copyright (c) 2008 Richard Willis
* MIT license : http://www.opensource.org/licenses/mit-license.php
* Project : http://jquery-spellchecker.googlecode.com
*/
#suggestmenu
{
min-width: 122px;
background: #ebeaeb;
position: absolute;
display: none;
z-index: 9999;
overflow: none;
margin-top: 1px;
text-align: right;
font-size: 11px;
font-family: Tahoma, Verdana, Arial, Helvetica;
}
#suggestmenu strong
{
background: #cccccc;
font-weight: bold;
padding:3px 6px 3px 6px;
display:block;
border:1px solid #dddddd;
border-bottom: 1px solid #aaaaaa;
color: black;
}
#suggestmenu em
{
text-align:center;
padding:3px 6px 3px 6px;
display:block;
border-top:1px solid #ccc;
border-right:1px solid #ccc;
}
#suggestmenu a, #suggestmenu a:visited
{
background: #ebeaeb;
border-right:1px solid #dddddd;
border-left:1px solid #dddddd;
padding:3px 6px 3px 6px;
display:block;
margin:0px;
text-decoration:none;
color: black;
outline:none
}
#suggestmenu a.first, #suggestmenu a.first:visited
{
border-top:1px solid #dddddd;
}
.spell_sep_bottom
{
border-bottom: 1px solid #dddddd;
}
.spell_sep_top
{
border-top: 1px solid #aaaaaa;
}
#suggestmenu a:hover
{
color:#000;
background: #f5f5f5;
}
#suggestmenu .foot
{
border-top:1px solid #aaaaaa;
background:#fff
}
#suggestmenu .foot a, #suggestmenu .foot a:visited
{
outline:none
}
/* TinyMCE 4.0 */
div.mce-atd-menu-title.mce-disabled {
padding: 3px 12px 0;
}
div.mce-atd-menu-title.mce-disabled:hover,
div.mce-atd-menu-title.mce-disabled:hover span.mce-text {
background: none;
color: #888;
cursor: default;
}
div.mce-atd-menu-title.mce-disabled span.mce-text {
font-weight: bold;
color: #888;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

View file

@ -0,0 +1,18 @@
.hiddenSpellError
{
border-bottom: 2px solid red;
cursor: default;
}
.hiddenGrammarError
{
border-bottom: 2px solid green;
cursor: default;
}
.hiddenSuggestion
{
border-bottom: 2px solid blue;
cursor: default;
}

View file

@ -0,0 +1 @@
.hiddenSpellError{border-bottom:2px solid red;cursor:default}.hiddenGrammarError{border-bottom:2px solid green;cursor:default}.hiddenSuggestion{border-bottom:2px solid #00f;cursor:default}

View file

@ -0,0 +1,479 @@
/*
* TinyMCE Writing Improvement Tool Plugin
* Author: Raphael Mudge (raffi@automattic.com)
*
* http://www.afterthedeadline.com
*
* Distributed under the LGPL
*
* Derived from:
* $Id: editor_plugin_src.js 425 2007-11-21 15:17:39Z spocke $
*
* @author Moxiecode
* @copyright Copyright (C) 2004-2008, Moxiecode Systems AB, All rights reserved.
*
* Moxiecode Spell Checker plugin released under the LGPL with TinyMCE
*/
/* jshint onevar: false, sub: true, devel: true */
/* global tinymce, AtDCore, AtD_proofread_click_count */
(function()
{
var each = tinymce.each, DOM = tinymce.DOM, core;
function getLang( key, defaultStr ) {
return ( window.AtD_l10n_r0ar && window.AtD_l10n_r0ar[key] ) || defaultStr;
}
tinymce.create('tinymce.plugins.AfterTheDeadlinePlugin',
{
getInfo : function()
{
return {
longname : 'After The Deadline',
author : 'Raphael Mudge',
authorurl : 'http://blog.afterthedeadline.com',
infourl : 'http://www.afterthedeadline.com',
version : tinymce.majorVersion + '.' + tinymce.minorVersion
};
},
/* initializes the functions used by the AtD Core UI Module */
initAtDCore : function(editor/*, plugin*/)
{
var core = new AtDCore();
core.map = each;
core.getAttrib = function(node, key)
{
return editor.dom.getAttrib(node, key);
};
core.findSpans = function(parent)
{
if (!parent) {
return editor.dom.select('span');
} else {
return editor.dom.select('span', parent);
}
};
core.hasClass = function(node, className)
{
return editor.dom.hasClass(node, className);
};
core.contents = function(node)
{
return node.childNodes;
};
core.replaceWith = function(old_node, new_node)
{
return editor.dom.replace(new_node, old_node);
};
core.create = function(node_html)
{
return editor.dom.create('span', { 'class': 'mceItemHidden' }, node_html);
};
core.removeParent = function(node)
{
editor.dom.remove(node, 1);
return node;
};
core.remove = function(node)
{
editor.dom.remove(node);
};
core.setIgnoreStrings(editor.getParam('atd_ignore_strings', [] ).join(','));
core.showTypes(editor.getParam('atd_show_types', ''));
return core;
},
/* called when the plugin is initialized */
init : function(ed, url)
{
if ( typeof(AtDCore) === 'undefined' ) {
return;
}
var plugin = this;
var editor = ed;
this.url = url;
this.editor = ed;
core = ed.core = this.initAtDCore(editor, plugin);
/* look at the atd_ignore variable and put that stuff into a hash */
var ignore = tinymce.util.Cookie.getHash('atd_ignore');
if (!ignore) {
ignore = {};
}
/* add a command to request a document check and process the results. */
editor.addCommand('mceWritingImprovementTool', function(callback)
{
/* checks if a global var for click stats exists and increments it if it does... */
if (typeof AtD_proofread_click_count !== 'undefined') {
AtD_proofread_click_count++;
}
/* create the nifty spinny thing that says "hizzo, I'm doing something fo realz" */
plugin.editor.setProgressState(1);
/* remove the previous errors */
plugin._removeWords();
/* send request to our service */
plugin.sendRequest('checkDocument', ed.getContent({ format: 'raw' }), function(data, request/*, someObject*/)
{
/* turn off the spinning thingie */
plugin.editor.setProgressState(0);
/* if the server is not accepting requests, let the user know */
if ( request.status !== 200 || request.responseText.substr(1, 4) === 'html' || !request.responseXML )
{
ed.windowManager.alert(
getLang( 'message_server_error', 'There was a problem communicating with the Proofreading service. Try again in one minute.' ),
callback ? function() { callback( 0 ); } : function() {}
);
return;
}
/* check to see if things are broken first and foremost */
if (request.responseXML.getElementsByTagName('message').item(0) != null)
{
ed.windowManager.alert(
request.responseXML.getElementsByTagName('message').item(0).firstChild.data,
callback ? function() { callback( 0 ); } : function() {}
);
return;
}
var results = core.processXML(request.responseXML);
var ecount = 0;
if (results.count > 0)
{
ecount = plugin.markMyWords(results.errors);
ed.suggestions = results.suggestions;
}
if (ecount === 0 && (!callback || callback === undefined)) {
ed.windowManager.alert( getLang('message_no_errors_found', 'No writing errors were found.') );
} else if (callback) {
callback(ecount);
}
});
});
/* load cascading style sheet for this plugin */
editor.onInit.add(function()
{
/* loading the content.css file, why? I have no clue */
if (editor.settings.content_css !== false)
{
editor.dom.loadCSS(editor.getParam('atd_css_url', url + '/css/content.css'));
}
});
/* again showing a menu, I have no clue what */
editor.onClick.add(plugin._showMenu, plugin);
/* we're showing some sort of menu, no idea what */
editor.onContextMenu.add(plugin._showMenu, plugin);
/* strip out the markup before the contents is serialized (and do it on a copy of the markup so we don't affect the user experience) */
editor.onPreProcess.add(function(sender, object)
{
var dom = sender.dom;
each(dom.select('span', object.node).reverse(), function(n)
{
if (n && (dom.hasClass(n, 'hiddenGrammarError') || dom.hasClass(n, 'hiddenSpellError') || dom.hasClass(n, 'hiddenSuggestion') || dom.hasClass(n, 'mceItemHidden') || (!dom.getAttrib(n, 'class') && !dom.getAttrib(n, 'style') && !dom.getAttrib(n, 'id') && !dom.hasClass(n, 'Apple-style-span') && !dom.getAttrib(n, 'mce_name'))))
{
dom.remove(n, 1);
}
});
});
/* cleanup the HTML before executing certain commands */
editor.onBeforeExecCommand.add(function(editor, command)
{
if (command === 'mceCodeEditor')
{
plugin._removeWords();
}
else if (command === 'mceFullScreen')
{
plugin._done();
}
});
ed.addButton('AtD', {
title: getLang( 'button_proofread_tooltip', 'Proofread Writing' ),
image: ed.getParam('atd_button_url', url + '/atdbuttontr.gif'),
cmd: 'mceWritingImprovementTool'
});
},
_removeWords : function(w)
{
var ed = this.editor, dom = ed.dom, se = ed.selection, b = se.getBookmark();
ed.core.removeWords(undefined, w);
/* force a rebuild of the DOM... even though the right elements are stripped, the DOM is still organized
as if the span were there and this breaks my code */
dom.setHTML(dom.getRoot(), dom.getRoot().innerHTML);
se.moveToBookmark(b);
},
markMyWords : function(errors)
{
var ed = this.editor;
var se = ed.selection, b = se.getBookmark();
var ecount = ed.core.markMyWords(ed.core.contents(this.editor.getBody()), errors);
se.moveToBookmark(b);
return ecount;
},
_showMenu : function(ed, e)
{
var t = this;
ed = t.editor; // not clear why this overwrites the function parameter
var m = t._menu, p1, dom = ed.dom, vp = dom.getViewPort(ed.getWin());
if (!m)
{
p1 = DOM.getPos(ed.getContentAreaContainer());
//p2 = DOM.getPos(ed.getContainer());
m = ed.controlManager.createDropMenu('spellcheckermenu',
{
offset_x : p1.x,
offset_y : p1.y,
'class' : 'mceNoIcons'
});
t._menu = m;
}
if (ed.core.isMarkedNode(e.target))
{
/* remove these other lame-o elements */
m.removeAll();
/* find the correct suggestions object */
var errorDescription = ed.core.findSuggestion(e.target);
if (!errorDescription)
{
m.add({ title: getLang( 'menu_title_no_suggestions', 'No suggestions' ), 'class': 'mceMenuItemTitle' }).setDisabled(1);
}
else if (errorDescription['suggestions'].length === 0)
{
m.add({ title: errorDescription['description'], 'class' : 'mceMenuItemTitle' }).setDisabled(1);
}
else
{
m.add({ title : errorDescription['description'], 'class' : 'mceMenuItemTitle' }).setDisabled(1);
for (var i = 0; i < errorDescription['suggestions'].length; i++)
{
(function(sugg)
{
m.add({
title : sugg,
onclick : function()
{
ed.core.applySuggestion(e.target, sugg);
t._checkDone();
}
});
})(errorDescription['suggestions'][i]); // jshint ignore:line
}
m.addSeparator();
}
if (errorDescription && errorDescription['moreinfo'])
{
(function(url)
{
m.add({
title : getLang( 'menu_option_explain', 'Explain...' ),
onclick : function()
{
ed.windowManager.open({
url : url,
width : 480,
height : 380,
inline : true
}, { theme_url : this.url });
}
});
})(errorDescription['moreinfo']);
m.addSeparator();
}
m.add({
title : getLang( 'menu_option_ignore_once', 'Ignore suggestion' ),
onclick : function()
{
dom.remove(e.target, 1);
t._checkDone();
}
});
if (String(this.editor.getParam('atd_ignore_enable', 'false')) === 'true')
{
m.add({
title : getLang( 'menu_option_ignore_always', 'Ignore always' ),
onclick : function()
{
var url = t.editor.getParam('atd_ignore_rpc_url', '{backend}');
if (url === '{backend}')
{
/* Default scheme is to save ignore preferences in a cookie */
var ignore = tinymce.util.Cookie.getHash('atd_ignore');
if (!ignore) { ignore = {}; }
ignore[e.target.innerHTML] = 1;
tinymce.util.Cookie.setHash('atd_ignore', ignore, new Date( (new Date().getTime()) + 157680000000) );
}
else
{
/* Plugin is configured to send ignore preferences to server, do that */
var id = t.editor.getParam('atd_rpc_id', '12345678');
tinymce.util.XHR.send({
url : url + encodeURI(e.target.innerHTML).replace(/&/g, '%26') + '&key=' + id,
content_type : 'text/xml',
async : true,
type : 'GET',
success : function(/* type, req, o */)
{
/* do nothing */
},
error : function( type, req, o )
{
alert( 'Ignore preference save failed\n' + type + '\n' + req.status + '\nAt: ' + o.url );
}
});
/* update atd_ignore_strings with the new value */
t.editor.core.setIgnoreStrings(e.target.innerHTML); /* this does an update */
}
t._removeWords(e.target.innerHTML);
t._checkDone();
}
});
}
else
{
m.add({
title : getLang( 'menu_option_ignore_all', 'Ignore all' ),
onclick : function()
{
t._removeWords(e.target.innerHTML);
t._checkDone();
}
});
}
/* show the menu please */
ed.selection.select(e.target);
p1 = dom.getPos(e.target);
m.showMenu(p1.x, p1.y + e.target.offsetHeight - vp.y);
return tinymce.dom.Event.cancel(e);
}
else
{
m.hideMenu();
}
},
/* loop through editor DOM, call _done if no mce tags exist. */
_checkDone : function()
{
var t = this, ed = t.editor, dom = ed.dom, o;
each(dom.select('span'), function(n)
{
if (n && dom.hasClass(n, 'mceItemHidden'))
{
o = true;
return false;
}
});
if (!o)
{
t._done();
}
},
/* remove all tags, hide the menu, and fire a dom change event */
_done : function()
{
var plugin = this;
plugin._removeWords();
if (plugin._menu)
{
plugin._menu.hideMenu();
}
plugin.editor.nodeChanged();
},
sendRequest : function(file, data, success)
{
var id = this.editor.getParam('atd_rpc_id', '12345678');
var url = this.editor.getParam('atd_rpc_url', '{backend}');
var plugin = this;
if (url === '{backend}' || id === '12345678')
{
this.editor.setProgressState(0);
alert('Please specify: atd_rpc_url and atd_rpc_id');
return;
}
tinymce.util.XHR.send({
url : url + '/' + file,
content_type : 'text/xml',
type : 'POST',
data : 'data=' + encodeURI(data).replace(/&/g, '%26') + '&key=' + id,
async : true,
success : success,
error : function( type, req, o )
{
plugin.editor.setProgressState(0);
alert( type + '\n' + req.status + '\nAt: ' + o.url );
}
});
}
});
// Register plugin
tinymce.PluginManager.add('AtD', tinymce.plugins.AfterTheDeadlinePlugin);
})();

View file

@ -0,0 +1,427 @@
/* global tinymce */
/*
* TinyMCE Writing Improvement Tool Plugin
* Author: Raphael Mudge (raffi@automattic.com)
*
* Updated for TinyMCE 4.0
*
* http://www.afterthedeadline.com
*
* Distributed under the LGPL
*
* Derived from:
* $Id: editor_plugin_src.js 425 2007-11-21 15:17:39Z spocke $
*
* @author Moxiecode
* @copyright Copyright (C) 2004-2008, Moxiecode Systems AB, All rights reserved.
*
* Moxiecode Spell Checker plugin released under the LGPL with TinyMCE
*/
tinymce.PluginManager.add( 'AtD', function( editor ) {
var suggestionsMenu, started, atdCore, dom,
each = tinymce.each;
/* initializes the functions used by the AtD Core UI Module */
function initAtDCore() {
atdCore = new window.AtDCore();
atdCore.map = each;
atdCore._isTinyMCE = true;
atdCore.getAttrib = function( node, key ) {
return dom.getAttrib( node, key );
};
atdCore.findSpans = function( parent ) {
if ( parent === undefined ) {
return dom.select('span');
} else {
return dom.select( 'span', parent );
}
};
atdCore.hasClass = function( node, className ) {
return dom.hasClass( node, className );
};
atdCore.contents = function( node ) {
return node.childNodes;
};
atdCore.replaceWith = function( old_node, new_node ) {
return dom.replace( new_node, old_node );
};
atdCore.create = function( node_html ) {
return dom.create( 'span', { 'class': 'mceItemHidden', 'data-mce-bogus': 1 }, node_html );
};
atdCore.removeParent = function( node ) {
dom.remove( node, true );
return node;
};
atdCore.remove = function( node ) {
dom.remove( node );
};
atdCore.setIgnoreStrings( editor.getParam( 'atd_ignore_strings', [] ).join(',') );
atdCore.showTypes( editor.getParam( 'atd_show_types', '' ) );
}
function getLang( key, defaultStr ) {
return ( window.AtD_l10n_r0ar && window.AtD_l10n_r0ar[key] ) || defaultStr;
}
function isMarkedNode( node ) {
return ( node.className && /\bhidden(GrammarError|SpellError|Suggestion)\b/.test( node.className ) );
}
function markMyWords( errors ) {
return atdCore.markMyWords( atdCore.contents( editor.getBody() ), errors );
}
// If no more suggestions, finish.
function checkIfFinished() {
if ( ! editor.dom.select('span.hiddenSpellError, span.hiddenGrammarError, span.hiddenSuggestion').length ) {
if ( suggestionsMenu ) {
suggestionsMenu.hideMenu();
}
finish();
}
}
function ignoreWord( target, word, all ) {
var dom = editor.dom;
if ( all ) {
each( editor.dom.select( 'span.hiddenSpellError, span.hiddenGrammarError, span.hiddenSuggestion' ), function( node ) {
var text = node.innerText || node.textContent;
if ( text === word ) {
dom.remove( node, true );
}
});
} else {
dom.remove( target, true );
}
checkIfFinished();
}
// Called when the user clicks "Finish" or when no more suggestions left.
// Removes all remaining spans and fires custom event.
function finish() {
var node,
dom = editor.dom,
regex = new RegExp( 'mceItemHidden|hidden(((Grammar|Spell)Error)|Suggestion)' ),
nodes = dom.select('span'),
i = nodes.length;
while ( i-- ) { // reversed
node = nodes[i];
if ( node.className && regex.test( node.className ) ) {
dom.remove( node, true );
}
}
// Rebuild the DOM so AtD core can find the text nodes
editor.setContent( editor.getContent({ format: 'raw' }), { format: 'raw' } );
started = false;
editor.nodeChanged();
editor.fire('SpellcheckEnd');
}
function sendRequest( file, data, success ) {
var id = editor.getParam( 'atd_rpc_id', '12345678' ),
url = editor.getParam( 'atd_rpc_url', '{backend}' );
if ( url === '{backend}' || id === '12345678' ) {
window.alert( 'Please specify: atd_rpc_url and atd_rpc_id' );
return;
}
// create the nifty spinny thing that says "hizzo, I'm doing something fo realz"
editor.setProgressState( true );
tinymce.util.XHR.send({
url: url + '/' + file,
content_type: 'text/xml',
type: 'POST',
data: 'data=' + encodeURI( data ).replace( /&/g, '%26' ) + '&key=' + id,
success: success,
error: function( type, req, o ) {
editor.setProgressState();
window.alert( type + '\n' + req.status + '\nAt: ' + o.url );
}
});
}
function storeIgnoredStrings( /*text*/ ) {
// Store in sessionStorage?
}
function setAlwaysIgnore( text ) {
var url = editor.getParam( 'atd_ignore_rpc_url' );
if ( ! url || url === '{backend}' ) {
// Store ignored words for this session only
storeIgnoredStrings( text );
} else {
// Plugin is configured to send ignore preferences to server, do that
tinymce.util.XHR.send({
url: url + encodeURIComponent( text ) + '&key=' + editor.getParam( 'atd_rpc_id', '12345678' ),
content_type: 'text/xml',
type: 'GET',
error: function() {
storeIgnoredStrings( text );
}
});
}
// Update atd_ignore_strings with the new value
atdCore.setIgnoreStrings( text );
}
// Create the suggestions menu
function showSuggestions( target ) {
var pos, root, targetPos,
items = [],
text = target.innerText || target.textContent,
errorDescription = atdCore.findSuggestion( target );
if ( ! errorDescription ) {
items.push({
text: getLang( 'menu_title_no_suggestions', 'No suggestions' ),
classes: 'atd-menu-title',
disabled: true
});
} else {
items.push({
text: errorDescription.description,
classes: 'atd-menu-title',
disabled: true
});
if ( errorDescription.suggestions.length ) {
items.push({ text: '-' }); // separator
each( errorDescription.suggestions, function( suggestion ) {
items.push({
text: suggestion,
onclick: function() {
atdCore.applySuggestion( target, suggestion );
checkIfFinished();
}
});
});
}
}
if ( errorDescription && errorDescription.moreinfo ) {
items.push({ text: '-' }); // separator
items.push({
text: getLang( 'menu_option_explain', 'Explain...' ),
onclick : function() {
editor.windowManager.open({
title: getLang( 'menu_option_explain', 'Explain...' ),
url: errorDescription.moreinfo,
width: 480,
height: 380,
inline: true
});
}
});
}
items.push.apply( items, [
{ text: '-' }, // separator
{ text: getLang( 'menu_option_ignore_once', 'Ignore suggestion' ), onclick: function() {
ignoreWord( target, text );
}}
]);
if ( editor.getParam( 'atd_ignore_enable' ) ) {
items.push({
text: getLang( 'menu_option_ignore_always', 'Ignore always' ),
onclick: function() {
setAlwaysIgnore( text );
ignoreWord( target, text, true );
}
});
} else {
items.push({
text: getLang( 'menu_option_ignore_all', 'Ignore all' ),
onclick: function() {
ignoreWord( target, text, true );
}
});
}
// Render menu
suggestionsMenu = new tinymce.ui.Menu({
items: items,
context: 'contextmenu',
onautohide: function( event ) {
if ( isMarkedNode( event.target ) ) {
event.preventDefault();
}
},
onhide: function() {
suggestionsMenu.remove();
suggestionsMenu = null;
}
});
suggestionsMenu.renderTo( document.body );
// Position menu
pos = tinymce.DOM.getPos( editor.getContentAreaContainer() );
targetPos = editor.dom.getPos( target );
root = editor.dom.getRoot();
// Adjust targetPos for scrolling in the editor
if ( root.nodeName === 'BODY' ) {
targetPos.x -= root.ownerDocument.documentElement.scrollLeft || root.scrollLeft;
targetPos.y -= root.ownerDocument.documentElement.scrollTop || root.scrollTop;
} else {
targetPos.x -= root.scrollLeft;
targetPos.y -= root.scrollTop;
}
pos.x += targetPos.x;
pos.y += targetPos.y;
suggestionsMenu.moveTo( pos.x, pos.y + target.offsetHeight );
}
// Init everything
editor.on( 'init', function() {
if ( typeof window.AtDCore === 'undefined' ) {
return;
}
// Set dom and atdCore
dom = editor.dom;
initAtDCore();
// add a command to request a document check and process the results.
editor.addCommand( 'mceWritingImprovementTool', function( callback ) {
var results,
errorCount = 0;
if ( typeof callback !== 'function' ) {
callback = function(){};
}
// checks if a global var for click stats exists and increments it if it does...
if ( typeof window.AtD_proofread_click_count !== 'undefined' ) {
window.AtD_proofread_click_count++;
}
// remove the previous errors
if ( started ) {
finish();
return;
}
// send request to our service
sendRequest( 'checkDocument', editor.getContent({ format: 'raw' }), function( data, request ) {
// turn off the spinning thingie
editor.setProgressState();
// if the server is not accepting requests, let the user know
if ( request.status !== 200 || request.responseText.substr( 1, 4 ) === 'html' || ! request.responseXML ) {
editor.windowManager.alert(
getLang( 'message_server_error', 'There was a problem communicating with the Proofreading service. Try again in one minute.' ),
callback(0)
);
return;
}
// check to see if things are broken first and foremost
if ( request.responseXML.getElementsByTagName('message').item(0) !== null ) {
editor.windowManager.alert(
request.responseXML.getElementsByTagName('message').item(0).firstChild.data,
callback(0)
);
return;
}
results = atdCore.processXML( request.responseXML );
if ( results.count > 0 ) {
errorCount = markMyWords( results.errors );
}
if ( ! errorCount ) {
editor.windowManager.alert( getLang( 'message_no_errors_found', 'No writing errors were found.' ) );
} else {
started = true;
editor.fire('SpellcheckStart');
}
callback( errorCount );
});
});
if ( editor.settings.content_css !== false ) {
// CSS for underlining suggestions
dom.addStyle( '.hiddenSpellError{border-bottom:2px solid red;cursor:default;}' +
'.hiddenGrammarError{border-bottom:2px solid green;cursor:default;}' +
'.hiddenSuggestion{border-bottom:2px solid blue;cursor:default;}' );
}
// Menu z-index > DFW
tinymce.DOM.addStyle( 'div.mce-floatpanel{z-index:150100 !important;}' );
// Click on misspelled word
editor.on( 'click', function( event ) {
if ( isMarkedNode( event.target ) ) {
event.preventDefault();
editor.selection.select( event.target );
// Create the suggestions menu
showSuggestions( event.target );
}
});
});
editor.addMenuItem( 'spellchecker', {
text: getLang( 'button_proofread_tooltip', 'Proofread Writing' ),
context: 'tools',
cmd: 'mceWritingImprovementTool',
onPostRender: function() {
var self = this;
editor.on('SpellcheckStart SpellcheckEnd', function() {
self.active( started );
});
}
});
editor.addButton( 'spellchecker', {
tooltip: getLang( 'button_proofread_tooltip', 'Proofread Writing' ),
cmd: 'mceWritingImprovementTool',
onPostRender: function() {
var self = this;
editor.on( 'SpellcheckStart SpellcheckEnd', function() {
self.active( started );
});
}
});
editor.on( 'remove', function() {
if ( suggestionsMenu ) {
suggestionsMenu.remove();
suggestionsMenu = null;
}
});
});