Edit File by line
/home/barbar84/public_h.../wp-inclu.../js
File: mce-view.js
/**
[0] Fix | Delete
* @output wp-includes/js/mce-view.js
[1] Fix | Delete
*/
[2] Fix | Delete
[3] Fix | Delete
/* global tinymce */
[4] Fix | Delete
[5] Fix | Delete
/*
[6] Fix | Delete
* The TinyMCE view API.
[7] Fix | Delete
*
[8] Fix | Delete
* Note: this API is "experimental" meaning that it will probably change
[9] Fix | Delete
* in the next few releases based on feedback from 3.9.0.
[10] Fix | Delete
* If you decide to use it, please follow the development closely.
[11] Fix | Delete
*
[12] Fix | Delete
* Diagram
[13] Fix | Delete
*
[14] Fix | Delete
* |- registered view constructor (type)
[15] Fix | Delete
* | |- view instance (unique text)
[16] Fix | Delete
* | | |- editor 1
[17] Fix | Delete
* | | | |- view node
[18] Fix | Delete
* | | | |- view node
[19] Fix | Delete
* | | | |- ...
[20] Fix | Delete
* | | |- editor 2
[21] Fix | Delete
* | | | |- ...
[22] Fix | Delete
* | |- view instance
[23] Fix | Delete
* | | |- ...
[24] Fix | Delete
* |- registered view
[25] Fix | Delete
* | |- ...
[26] Fix | Delete
*/
[27] Fix | Delete
( function( window, wp, shortcode, $ ) {
[28] Fix | Delete
'use strict';
[29] Fix | Delete
[30] Fix | Delete
var views = {},
[31] Fix | Delete
instances = {};
[32] Fix | Delete
[33] Fix | Delete
wp.mce = wp.mce || {};
[34] Fix | Delete
[35] Fix | Delete
/**
[36] Fix | Delete
* wp.mce.views
[37] Fix | Delete
*
[38] Fix | Delete
* A set of utilities that simplifies adding custom UI within a TinyMCE editor.
[39] Fix | Delete
* At its core, it serves as a series of converters, transforming text to a
[40] Fix | Delete
* custom UI, and back again.
[41] Fix | Delete
*/
[42] Fix | Delete
wp.mce.views = {
[43] Fix | Delete
[44] Fix | Delete
/**
[45] Fix | Delete
* Registers a new view type.
[46] Fix | Delete
*
[47] Fix | Delete
* @param {string} type The view type.
[48] Fix | Delete
* @param {Object} extend An object to extend wp.mce.View.prototype with.
[49] Fix | Delete
*/
[50] Fix | Delete
register: function( type, extend ) {
[51] Fix | Delete
views[ type ] = wp.mce.View.extend( _.extend( extend, { type: type } ) );
[52] Fix | Delete
},
[53] Fix | Delete
[54] Fix | Delete
/**
[55] Fix | Delete
* Unregisters a view type.
[56] Fix | Delete
*
[57] Fix | Delete
* @param {string} type The view type.
[58] Fix | Delete
*/
[59] Fix | Delete
unregister: function( type ) {
[60] Fix | Delete
delete views[ type ];
[61] Fix | Delete
},
[62] Fix | Delete
[63] Fix | Delete
/**
[64] Fix | Delete
* Returns the settings of a view type.
[65] Fix | Delete
*
[66] Fix | Delete
* @param {string} type The view type.
[67] Fix | Delete
*
[68] Fix | Delete
* @return {Function} The view constructor.
[69] Fix | Delete
*/
[70] Fix | Delete
get: function( type ) {
[71] Fix | Delete
return views[ type ];
[72] Fix | Delete
},
[73] Fix | Delete
[74] Fix | Delete
/**
[75] Fix | Delete
* Unbinds all view nodes.
[76] Fix | Delete
* Runs before removing all view nodes from the DOM.
[77] Fix | Delete
*/
[78] Fix | Delete
unbind: function() {
[79] Fix | Delete
_.each( instances, function( instance ) {
[80] Fix | Delete
instance.unbind();
[81] Fix | Delete
} );
[82] Fix | Delete
},
[83] Fix | Delete
[84] Fix | Delete
/**
[85] Fix | Delete
* Scans a given string for each view's pattern,
[86] Fix | Delete
* replacing any matches with markers,
[87] Fix | Delete
* and creates a new instance for every match.
[88] Fix | Delete
*
[89] Fix | Delete
* @param {string} content The string to scan.
[90] Fix | Delete
* @param {tinymce.Editor} editor The editor.
[91] Fix | Delete
*
[92] Fix | Delete
* @return {string} The string with markers.
[93] Fix | Delete
*/
[94] Fix | Delete
setMarkers: function( content, editor ) {
[95] Fix | Delete
var pieces = [ { content: content } ],
[96] Fix | Delete
self = this,
[97] Fix | Delete
instance, current;
[98] Fix | Delete
[99] Fix | Delete
_.each( views, function( view, type ) {
[100] Fix | Delete
current = pieces.slice();
[101] Fix | Delete
pieces = [];
[102] Fix | Delete
[103] Fix | Delete
_.each( current, function( piece ) {
[104] Fix | Delete
var remaining = piece.content,
[105] Fix | Delete
result, text;
[106] Fix | Delete
[107] Fix | Delete
// Ignore processed pieces, but retain their location.
[108] Fix | Delete
if ( piece.processed ) {
[109] Fix | Delete
pieces.push( piece );
[110] Fix | Delete
return;
[111] Fix | Delete
}
[112] Fix | Delete
[113] Fix | Delete
// Iterate through the string progressively matching views
[114] Fix | Delete
// and slicing the string as we go.
[115] Fix | Delete
while ( remaining && ( result = view.prototype.match( remaining ) ) ) {
[116] Fix | Delete
// Any text before the match becomes an unprocessed piece.
[117] Fix | Delete
if ( result.index ) {
[118] Fix | Delete
pieces.push( { content: remaining.substring( 0, result.index ) } );
[119] Fix | Delete
}
[120] Fix | Delete
[121] Fix | Delete
result.options.editor = editor;
[122] Fix | Delete
instance = self.createInstance( type, result.content, result.options );
[123] Fix | Delete
text = instance.loader ? '.' : instance.text;
[124] Fix | Delete
[125] Fix | Delete
// Add the processed piece for the match.
[126] Fix | Delete
pieces.push( {
[127] Fix | Delete
content: instance.ignore ? text : '<p data-wpview-marker="' + instance.encodedText + '">' + text + '</p>',
[128] Fix | Delete
processed: true
[129] Fix | Delete
} );
[130] Fix | Delete
[131] Fix | Delete
// Update the remaining content.
[132] Fix | Delete
remaining = remaining.slice( result.index + result.content.length );
[133] Fix | Delete
}
[134] Fix | Delete
[135] Fix | Delete
// There are no additional matches.
[136] Fix | Delete
// If any content remains, add it as an unprocessed piece.
[137] Fix | Delete
if ( remaining ) {
[138] Fix | Delete
pieces.push( { content: remaining } );
[139] Fix | Delete
}
[140] Fix | Delete
} );
[141] Fix | Delete
} );
[142] Fix | Delete
[143] Fix | Delete
content = _.pluck( pieces, 'content' ).join( '' );
[144] Fix | Delete
return content.replace( /<p>\s*<p data-wpview-marker=/g, '<p data-wpview-marker=' ).replace( /<\/p>\s*<\/p>/g, '</p>' );
[145] Fix | Delete
},
[146] Fix | Delete
[147] Fix | Delete
/**
[148] Fix | Delete
* Create a view instance.
[149] Fix | Delete
*
[150] Fix | Delete
* @param {string} type The view type.
[151] Fix | Delete
* @param {string} text The textual representation of the view.
[152] Fix | Delete
* @param {Object} options Options.
[153] Fix | Delete
* @param {boolean} force Recreate the instance. Optional.
[154] Fix | Delete
*
[155] Fix | Delete
* @return {wp.mce.View} The view instance.
[156] Fix | Delete
*/
[157] Fix | Delete
createInstance: function( type, text, options, force ) {
[158] Fix | Delete
var View = this.get( type ),
[159] Fix | Delete
encodedText,
[160] Fix | Delete
instance;
[161] Fix | Delete
[162] Fix | Delete
if ( text.indexOf( '[' ) !== -1 && text.indexOf( ']' ) !== -1 ) {
[163] Fix | Delete
// Looks like a shortcode? Remove any line breaks from inside of shortcodes
[164] Fix | Delete
// or autop will replace them with <p> and <br> later and the string won't match.
[165] Fix | Delete
text = text.replace( /\[[^\]]+\]/g, function( match ) {
[166] Fix | Delete
return match.replace( /[\r\n]/g, '' );
[167] Fix | Delete
});
[168] Fix | Delete
}
[169] Fix | Delete
[170] Fix | Delete
if ( ! force ) {
[171] Fix | Delete
instance = this.getInstance( text );
[172] Fix | Delete
[173] Fix | Delete
if ( instance ) {
[174] Fix | Delete
return instance;
[175] Fix | Delete
}
[176] Fix | Delete
}
[177] Fix | Delete
[178] Fix | Delete
encodedText = encodeURIComponent( text );
[179] Fix | Delete
[180] Fix | Delete
options = _.extend( options || {}, {
[181] Fix | Delete
text: text,
[182] Fix | Delete
encodedText: encodedText
[183] Fix | Delete
} );
[184] Fix | Delete
[185] Fix | Delete
return instances[ encodedText ] = new View( options );
[186] Fix | Delete
},
[187] Fix | Delete
[188] Fix | Delete
/**
[189] Fix | Delete
* Get a view instance.
[190] Fix | Delete
*
[191] Fix | Delete
* @param {(string|HTMLElement)} object The textual representation of the view or the view node.
[192] Fix | Delete
*
[193] Fix | Delete
* @return {wp.mce.View} The view instance or undefined.
[194] Fix | Delete
*/
[195] Fix | Delete
getInstance: function( object ) {
[196] Fix | Delete
if ( typeof object === 'string' ) {
[197] Fix | Delete
return instances[ encodeURIComponent( object ) ];
[198] Fix | Delete
}
[199] Fix | Delete
[200] Fix | Delete
return instances[ $( object ).attr( 'data-wpview-text' ) ];
[201] Fix | Delete
},
[202] Fix | Delete
[203] Fix | Delete
/**
[204] Fix | Delete
* Given a view node, get the view's text.
[205] Fix | Delete
*
[206] Fix | Delete
* @param {HTMLElement} node The view node.
[207] Fix | Delete
*
[208] Fix | Delete
* @return {string} The textual representation of the view.
[209] Fix | Delete
*/
[210] Fix | Delete
getText: function( node ) {
[211] Fix | Delete
return decodeURIComponent( $( node ).attr( 'data-wpview-text' ) || '' );
[212] Fix | Delete
},
[213] Fix | Delete
[214] Fix | Delete
/**
[215] Fix | Delete
* Renders all view nodes that are not yet rendered.
[216] Fix | Delete
*
[217] Fix | Delete
* @param {boolean} force Rerender all view nodes.
[218] Fix | Delete
*/
[219] Fix | Delete
render: function( force ) {
[220] Fix | Delete
_.each( instances, function( instance ) {
[221] Fix | Delete
instance.render( null, force );
[222] Fix | Delete
} );
[223] Fix | Delete
},
[224] Fix | Delete
[225] Fix | Delete
/**
[226] Fix | Delete
* Update the text of a given view node.
[227] Fix | Delete
*
[228] Fix | Delete
* @param {string} text The new text.
[229] Fix | Delete
* @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
[230] Fix | Delete
* @param {HTMLElement} node The view node to update.
[231] Fix | Delete
* @param {boolean} force Recreate the instance. Optional.
[232] Fix | Delete
*/
[233] Fix | Delete
update: function( text, editor, node, force ) {
[234] Fix | Delete
var instance = this.getInstance( node );
[235] Fix | Delete
[236] Fix | Delete
if ( instance ) {
[237] Fix | Delete
instance.update( text, editor, node, force );
[238] Fix | Delete
}
[239] Fix | Delete
},
[240] Fix | Delete
[241] Fix | Delete
/**
[242] Fix | Delete
* Renders any editing interface based on the view type.
[243] Fix | Delete
*
[244] Fix | Delete
* @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
[245] Fix | Delete
* @param {HTMLElement} node The view node to edit.
[246] Fix | Delete
*/
[247] Fix | Delete
edit: function( editor, node ) {
[248] Fix | Delete
var instance = this.getInstance( node );
[249] Fix | Delete
[250] Fix | Delete
if ( instance && instance.edit ) {
[251] Fix | Delete
instance.edit( instance.text, function( text, force ) {
[252] Fix | Delete
instance.update( text, editor, node, force );
[253] Fix | Delete
} );
[254] Fix | Delete
}
[255] Fix | Delete
},
[256] Fix | Delete
[257] Fix | Delete
/**
[258] Fix | Delete
* Remove a given view node from the DOM.
[259] Fix | Delete
*
[260] Fix | Delete
* @param {tinymce.Editor} editor The TinyMCE editor instance the view node is in.
[261] Fix | Delete
* @param {HTMLElement} node The view node to remove.
[262] Fix | Delete
*/
[263] Fix | Delete
remove: function( editor, node ) {
[264] Fix | Delete
var instance = this.getInstance( node );
[265] Fix | Delete
[266] Fix | Delete
if ( instance ) {
[267] Fix | Delete
instance.remove( editor, node );
[268] Fix | Delete
}
[269] Fix | Delete
}
[270] Fix | Delete
};
[271] Fix | Delete
[272] Fix | Delete
/**
[273] Fix | Delete
* A Backbone-like View constructor intended for use when rendering a TinyMCE View.
[274] Fix | Delete
* The main difference is that the TinyMCE View is not tied to a particular DOM node.
[275] Fix | Delete
*
[276] Fix | Delete
* @param {Object} options Options.
[277] Fix | Delete
*/
[278] Fix | Delete
wp.mce.View = function( options ) {
[279] Fix | Delete
_.extend( this, options );
[280] Fix | Delete
this.initialize();
[281] Fix | Delete
};
[282] Fix | Delete
[283] Fix | Delete
wp.mce.View.extend = Backbone.View.extend;
[284] Fix | Delete
[285] Fix | Delete
_.extend( wp.mce.View.prototype, /** @lends wp.mce.View.prototype */{
[286] Fix | Delete
[287] Fix | Delete
/**
[288] Fix | Delete
* The content.
[289] Fix | Delete
*
[290] Fix | Delete
* @type {*}
[291] Fix | Delete
*/
[292] Fix | Delete
content: null,
[293] Fix | Delete
[294] Fix | Delete
/**
[295] Fix | Delete
* Whether or not to display a loader.
[296] Fix | Delete
*
[297] Fix | Delete
* @type {Boolean}
[298] Fix | Delete
*/
[299] Fix | Delete
loader: true,
[300] Fix | Delete
[301] Fix | Delete
/**
[302] Fix | Delete
* Runs after the view instance is created.
[303] Fix | Delete
*/
[304] Fix | Delete
initialize: function() {},
[305] Fix | Delete
[306] Fix | Delete
/**
[307] Fix | Delete
* Returns the content to render in the view node.
[308] Fix | Delete
*
[309] Fix | Delete
* @return {*}
[310] Fix | Delete
*/
[311] Fix | Delete
getContent: function() {
[312] Fix | Delete
return this.content;
[313] Fix | Delete
},
[314] Fix | Delete
[315] Fix | Delete
/**
[316] Fix | Delete
* Renders all view nodes tied to this view instance that are not yet rendered.
[317] Fix | Delete
*
[318] Fix | Delete
* @param {string} content The content to render. Optional.
[319] Fix | Delete
* @param {boolean} force Rerender all view nodes tied to this view instance. Optional.
[320] Fix | Delete
*/
[321] Fix | Delete
render: function( content, force ) {
[322] Fix | Delete
if ( content != null ) {
[323] Fix | Delete
this.content = content;
[324] Fix | Delete
}
[325] Fix | Delete
[326] Fix | Delete
content = this.getContent();
[327] Fix | Delete
[328] Fix | Delete
// If there's nothing to render an no loader needs to be shown, stop.
[329] Fix | Delete
if ( ! this.loader && ! content ) {
[330] Fix | Delete
return;
[331] Fix | Delete
}
[332] Fix | Delete
[333] Fix | Delete
// We're about to rerender all views of this instance, so unbind rendered views.
[334] Fix | Delete
force && this.unbind();
[335] Fix | Delete
[336] Fix | Delete
// Replace any left over markers.
[337] Fix | Delete
this.replaceMarkers();
[338] Fix | Delete
[339] Fix | Delete
if ( content ) {
[340] Fix | Delete
this.setContent( content, function( editor, node ) {
[341] Fix | Delete
$( node ).data( 'rendered', true );
[342] Fix | Delete
this.bindNode.call( this, editor, node );
[343] Fix | Delete
}, force ? null : false );
[344] Fix | Delete
} else {
[345] Fix | Delete
this.setLoader();
[346] Fix | Delete
}
[347] Fix | Delete
},
[348] Fix | Delete
[349] Fix | Delete
/**
[350] Fix | Delete
* Binds a given node after its content is added to the DOM.
[351] Fix | Delete
*/
[352] Fix | Delete
bindNode: function() {},
[353] Fix | Delete
[354] Fix | Delete
/**
[355] Fix | Delete
* Unbinds a given node before its content is removed from the DOM.
[356] Fix | Delete
*/
[357] Fix | Delete
unbindNode: function() {},
[358] Fix | Delete
[359] Fix | Delete
/**
[360] Fix | Delete
* Unbinds all view nodes tied to this view instance.
[361] Fix | Delete
* Runs before their content is removed from the DOM.
[362] Fix | Delete
*/
[363] Fix | Delete
unbind: function() {
[364] Fix | Delete
this.getNodes( function( editor, node ) {
[365] Fix | Delete
this.unbindNode.call( this, editor, node );
[366] Fix | Delete
}, true );
[367] Fix | Delete
},
[368] Fix | Delete
[369] Fix | Delete
/**
[370] Fix | Delete
* Gets all the TinyMCE editor instances that support views.
[371] Fix | Delete
*
[372] Fix | Delete
* @param {Function} callback A callback.
[373] Fix | Delete
*/
[374] Fix | Delete
getEditors: function( callback ) {
[375] Fix | Delete
_.each( tinymce.editors, function( editor ) {
[376] Fix | Delete
if ( editor.plugins.wpview ) {
[377] Fix | Delete
callback.call( this, editor );
[378] Fix | Delete
}
[379] Fix | Delete
}, this );
[380] Fix | Delete
},
[381] Fix | Delete
[382] Fix | Delete
/**
[383] Fix | Delete
* Gets all view nodes tied to this view instance.
[384] Fix | Delete
*
[385] Fix | Delete
* @param {Function} callback A callback.
[386] Fix | Delete
* @param {boolean} rendered Get (un)rendered view nodes. Optional.
[387] Fix | Delete
*/
[388] Fix | Delete
getNodes: function( callback, rendered ) {
[389] Fix | Delete
this.getEditors( function( editor ) {
[390] Fix | Delete
var self = this;
[391] Fix | Delete
[392] Fix | Delete
$( editor.getBody() )
[393] Fix | Delete
.find( '[data-wpview-text="' + self.encodedText + '"]' )
[394] Fix | Delete
.filter( function() {
[395] Fix | Delete
var data;
[396] Fix | Delete
[397] Fix | Delete
if ( rendered == null ) {
[398] Fix | Delete
return true;
[399] Fix | Delete
}
[400] Fix | Delete
[401] Fix | Delete
data = $( this ).data( 'rendered' ) === true;
[402] Fix | Delete
[403] Fix | Delete
return rendered ? data : ! data;
[404] Fix | Delete
} )
[405] Fix | Delete
.each( function() {
[406] Fix | Delete
callback.call( self, editor, this, this /* back compat */ );
[407] Fix | Delete
} );
[408] Fix | Delete
} );
[409] Fix | Delete
},
[410] Fix | Delete
[411] Fix | Delete
/**
[412] Fix | Delete
* Gets all marker nodes tied to this view instance.
[413] Fix | Delete
*
[414] Fix | Delete
* @param {Function} callback A callback.
[415] Fix | Delete
*/
[416] Fix | Delete
getMarkers: function( callback ) {
[417] Fix | Delete
this.getEditors( function( editor ) {
[418] Fix | Delete
var self = this;
[419] Fix | Delete
[420] Fix | Delete
$( editor.getBody() )
[421] Fix | Delete
.find( '[data-wpview-marker="' + this.encodedText + '"]' )
[422] Fix | Delete
.each( function() {
[423] Fix | Delete
callback.call( self, editor, this );
[424] Fix | Delete
} );
[425] Fix | Delete
} );
[426] Fix | Delete
},
[427] Fix | Delete
[428] Fix | Delete
/**
[429] Fix | Delete
* Replaces all marker nodes tied to this view instance.
[430] Fix | Delete
*/
[431] Fix | Delete
replaceMarkers: function() {
[432] Fix | Delete
this.getMarkers( function( editor, node ) {
[433] Fix | Delete
var selected = node === editor.selection.getNode();
[434] Fix | Delete
var $viewNode;
[435] Fix | Delete
[436] Fix | Delete
if ( ! this.loader && $( node ).text() !== tinymce.DOM.decode( this.text ) ) {
[437] Fix | Delete
editor.dom.setAttrib( node, 'data-wpview-marker', null );
[438] Fix | Delete
return;
[439] Fix | Delete
}
[440] Fix | Delete
[441] Fix | Delete
$viewNode = editor.$(
[442] Fix | Delete
'<div class="wpview wpview-wrap" data-wpview-text="' + this.encodedText + '" data-wpview-type="' + this.type + '" contenteditable="false"></div>'
[443] Fix | Delete
);
[444] Fix | Delete
[445] Fix | Delete
editor.undoManager.ignore( function() {
[446] Fix | Delete
editor.$( node ).replaceWith( $viewNode );
[447] Fix | Delete
} );
[448] Fix | Delete
[449] Fix | Delete
if ( selected ) {
[450] Fix | Delete
setTimeout( function() {
[451] Fix | Delete
editor.undoManager.ignore( function() {
[452] Fix | Delete
editor.selection.select( $viewNode[0] );
[453] Fix | Delete
editor.selection.collapse();
[454] Fix | Delete
} );
[455] Fix | Delete
} );
[456] Fix | Delete
}
[457] Fix | Delete
} );
[458] Fix | Delete
},
[459] Fix | Delete
[460] Fix | Delete
/**
[461] Fix | Delete
* Removes all marker nodes tied to this view instance.
[462] Fix | Delete
*/
[463] Fix | Delete
removeMarkers: function() {
[464] Fix | Delete
this.getMarkers( function( editor, node ) {
[465] Fix | Delete
editor.dom.setAttrib( node, 'data-wpview-marker', null );
[466] Fix | Delete
} );
[467] Fix | Delete
},
[468] Fix | Delete
[469] Fix | Delete
/**
[470] Fix | Delete
* Sets the content for all view nodes tied to this view instance.
[471] Fix | Delete
*
[472] Fix | Delete
* @param {*} content The content to set.
[473] Fix | Delete
* @param {Function} callback A callback. Optional.
[474] Fix | Delete
* @param {boolean} rendered Only set for (un)rendered nodes. Optional.
[475] Fix | Delete
*/
[476] Fix | Delete
setContent: function( content, callback, rendered ) {
[477] Fix | Delete
if ( _.isObject( content ) && ( content.sandbox || content.head || content.body.indexOf( '<script' ) !== -1 ) ) {
[478] Fix | Delete
this.setIframes( content.head || '', content.body, callback, rendered );
[479] Fix | Delete
} else if ( _.isString( content ) && content.indexOf( '<script' ) !== -1 ) {
[480] Fix | Delete
this.setIframes( '', content, callback, rendered );
[481] Fix | Delete
} else {
[482] Fix | Delete
this.getNodes( function( editor, node ) {
[483] Fix | Delete
content = content.body || content;
[484] Fix | Delete
[485] Fix | Delete
if ( content.indexOf( '<iframe' ) !== -1 ) {
[486] Fix | Delete
content += '<span class="mce-shim"></span>';
[487] Fix | Delete
}
[488] Fix | Delete
[489] Fix | Delete
editor.undoManager.transact( function() {
[490] Fix | Delete
node.innerHTML = '';
[491] Fix | Delete
node.appendChild( _.isString( content ) ? editor.dom.createFragment( content ) : content );
[492] Fix | Delete
editor.dom.add( node, 'span', { 'class': 'wpview-end' } );
[493] Fix | Delete
} );
[494] Fix | Delete
[495] Fix | Delete
callback && callback.call( this, editor, node );
[496] Fix | Delete
}, rendered );
[497] Fix | Delete
}
[498] Fix | Delete
},
[499] Fix | Delete
12
It is recommended that you Edit text format, this type of Fix handles quite a lot in one request
Function