TinyMCE plugin not showing - plugins

My plugin doesnt show up in tinymce even though I can see it getting called. Anyone see what Ive missed?
(function () {
tinymce.PluginManager.requireLangPack('prettifier');
tinymce.create('tinymce.plugins.PrettifierPlugin', {
init: function (ed, url) {
debugger;
// ed.addCommand('mcePrettifier', function () {
// ed.windowManager.open({
// file: url + '/dialog.htm',
// width: 320 + ed.getLang('prettifier.delta_width', 0),
// height: 120 + ed.getLang('prettifier.delta_height', 0),
// inline: 1
// });
// });
// ed.addButton('prettifier', {
// title: 'prettifier.desc',
// cmd: 'mcePrettifier',
// image: url + '/img/prettifier.gif'
// });
ed.addButton('prettifier', {
title: 'prettifier.desc',
cmd: 'mcePrettifier',
image: url + '/img/prettifier.gif',
onclick: function () {
ed.focus(),
ed.selection.setContent('<pre class="prettifier">' + ed.selection.getContent() + '</pre>');
}
});
},
createControl: function (n, cm) {
return null;
},
getInfo: function () {
return {
longname: 'Code Prettifier plugin',
author: 'blah',
authorurl: 'http://blah.com',
infourl: 'http://blah.com',
version: "1.0"
};
}
});
debugger;
tinymce.PluginManager.add('prettifier', tinymce.plugins.PrettifierPlugin);
})();
Thanks

Assuming that you've included it correctly in both the plugins element and a *theme_advanced_buttons* config item then I suspect the problem is a JavaScript error in your plugin.
In the code you've included, you have a comma instead of a semi-colon at the end of ed.focus() in the onclick for the button.

Related

Code migration from tinymce 4 to tinymce 5 - problem with action function (true / false)

I have a problem with migrating the plugin from tinymce 4 to tinymka 5. The console tells me "Uncaught (in promise) TypeError: btn.active is not a function"
I can not find an equivalent for tinymce 5. Can someone replace it?
Code below:
tinymce.PluginManager.add('phonelink', function(editor, url) {
// Add a button that opens a window
var linkText = "";
var linkTitle = "";
var link = "";
// tinymce.DOM.loadCSS(url + '/css/phonelink.css');
editor.ui.registry.addButton('phonelink2', {
text: 'asddas',
icon: 'image-options',
onSetup: updateOnSelect,
onAction: onClickPhoneButton
});
// Adds a menu item to the tools menu
editor.ui.registry.addMenuItem('phonelink', {
text: 'asddas',
icon: 'image-options',
context: 'tools',
onAction: onClickPhoneButton,
onSetup: updateOnSelect
});
function onClickPhoneButton(){
editor.windowManager.open({
title: '123213123',
body: {
type: 'panel',
items: [
{type: 'input', name: 'phone', label: 'Teléfono', value: link},
{type: 'input', name: 'showtext', label: 'Texto a mostrar', value: linkText},
{type: 'input', name: 'title', label: 'Título', value: linkTitle}
]
},
buttons: [
{
text: 'Close',
type: 'cancel',
onclick: 'close'
},
{
type: 'submit',
name: 'submitButton',
text: 'Stwórz',
primary: true
}
],
onAction: function(e) {
alert('Toggle menu item clicked');
},
onSubmit: function(e) {
var data = e.getData();
var hrefLink = '<a title="' + data .title + '" href="tel:+34' + data .phone + '">' + data .showtext + '</a>';
if(link !== ''){
editor.setContent(hrefLink);
}else{
editor.insertContent(hrefLink);
}
e.close();
}
});
}
function updateOnSelect() {
var btn = this;
const editorEventCallback = function (e) {
var node = editor.selection.getNode();
var isTelLink = node.href !== undefined && node.href.indexOf('tel:+') !== -1
btn.active(isTelLink);
if(isTelLink){
link = node.href;
link = link.replace("tel:+34", "");
linkTitle = node.title;
linkText = node.text;
}
};
editor.on('NodeChange', editorEventCallback);
return function (btn) {
editor.off('NodeChange', editorEventCallback);
}
}
});
I searched the documentation for a replacement, but found nothing.
TinyMCE 5 no longer passes the button and menu instance via this. Instead it passes an API instance as the first parameter, so you'll want to change your updateOnSelect function to something like this:
function updateOnSelect(api) {
const editorEventCallback = function (e) {
var node = editor.selection.getNode();
var isTelLink = node.href !== undefined && node.href.indexOf('tel:+') !== -1
api.setActive(isTelLink);
if(isTelLink){
link = node.href;
link = link.replace("tel:+34", "");
linkTitle = node.title;
linkText = node.text;
}
};
editor.on('NodeChange', editorEventCallback);
return function (btn) {
editor.off('NodeChange', editorEventCallback);
}
}
You'll note var btn = this has been removed and that the API to set an item as active is setActive instead of active. This can be found in the documentation here: https://www.tiny.cloud/docs/ui-components/typesoftoolbarbuttons/#togglebutton and https://www.tiny.cloud/docs/ui-components/menuitems/#togglemenuitems (see the API section in both links).
In the above, you may have noticed both reference "Toggle" items. That's another change in TinyMCE 5, as different types of buttons/menu items have a separate registration API. So you'll also need to swap to using editor.ui.registry.addToggleButton and editor.ui.registry.addToggleMenuItem. More details about that can be found here if needed: https://www.tiny.cloud/docs/migration-from-4x/#customtoolbarbuttons
Here's an example fiddle showing the changes mentioned above: https://fiddle.tiny.cloud/B5haab.
Hopefully that helps!

jQuery file upload Resuming file uploads

I am using the Jquery file upload basic-plus.html. I am not sure How to use the Resumable uploading functionality in the below code.
I see something like this according to the documentation but not sure How to use that with the basic-plus.html
$('#fileupload').fileupload({
maxChunkSize: 10000000, // 10 MB
add: function (e, data) {
var that = this;
$.getJSON('server/php/', {file: data.files[0].name}, function (result) {
var file = result.file;
data.uploadedBytes = file && file.size;
$.blueimp.fileupload.prototype
.options.add.call(that, e, data);
});
}
});
I tried something like below. But didn't work.
basic-plus.html
$(function () {
'use strict';
// Change this to the location of your server-side upload handler:
var url = window.location.hostname === 'blueimp.github.io' ?
'//jquery-file-upload.appspot.com/' : 'server/php/',
uploadButton = $('<button/>')
.addClass('btn btn-primary')
.prop('disabled', true)
.text('Processing...')
.on('click', function () {
var $this = $(this),
data = $this.data();
$.getJSON('server/php/', {file: data.files[0].name}, function (result) {
console.log('come here');
console.log(result);
var file = result.file;
data.uploadedBytes = file && file.size;
//$.blueimp.fileupload.prototype.options.add.call($this, data);
});
$this
.off('click')
.text('Abort')
.on('click', function () {
$this.remove();
data.abort();
});
data.submit().always(function () {
$this.remove();
});
});
$('#fileupload').fileupload({
url: url,
dataType: 'json',
autoUpload: false,
//multipart: false,
acceptFileTypes: /(\.|\/)(avi|flv|wmv|mpg|mp4|mov|3gp|m4v)$/i,
maxFileSize: 969932800, // 900 MB
maxChunkSize: 2000000, // 2 MB
// Enable image resizing, except for Android and Opera,
// which actually support image resizing, but fail to
// send Blob objects via XHR requests:
disableImageResize: /Android(?!.*Chrome)|Opera/
.test(window.navigator.userAgent),
previewMaxWidth: 100,
previewMaxHeight: 100,
previewCrop: true,
}).on('fileuploadadd', function (e, data) {
data.context = $('<div/>').appendTo('#files');
$.each(data.files, function (index, file) {
var node = $('<p/>').append($('<span/>').text(file.name));
console.log(node);
if (!index) {
node
.append('<br>')
.append(uploadButton.clone(true).data(data));
}
node.appendTo(data.context);
});
}).on('fileuploadprocessalways', function (e, data) {
var index = data.index,
file = data.files[index],
node = $(data.context.children()[index]);
if (file.preview) {
node
.prepend('<br>')
.prepend(file.preview);
}
if (file.error) {
node
.append('<br>')
.append($('<span class="text-danger"/>').text(file.error));
}
if (index + 1 === data.files.length) {
data.context.find('button')
.text('Upload')
.prop('disabled', !!data.files.error);
}
}).on('fileuploadprogressall', function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#progress .progress-bar').css(
'width',
progress + '%'
);
}).on('fileuploaddone', function (e, data) {
$.each(data.result.files, function (index, file) {
if (file.url) {
var link = $('<a>')
.attr('target', '_blank')
.prop('href', file.url);
$(data.context.children()[index])
.wrap(link);
} else if (file.error) {
var error = $('<span class="text-danger"/>').text(file.error);
$(data.context.children()[index])
.append('<br>')
.append(error);
}
});
}).on('fileuploadfail', function (e, data) {
$.each(data.files, function (index) {
var error = $('<span class="text-danger"/>').text('File upload failed.');
$(data.context.children()[index])
.append('<br>')
.append(error);
});
}).prop('disabled', !$.support.fileInput)
.parent().addClass($.support.fileInput ? undefined : 'disabled');
});

Scoping issues while using context menu

I'm following the docs here to add a context menu item to my grid. The issue is that from the scope of getContextMenuItems (in the example), I'm unable to access any other methods or variables in my component. Is this possible? Example below:
private varIWantToAccess: boolean = false;
function getContextMenuItems(params) {
var result = [
{ // custom item
name: 'Alert ' + params.value,
action: function ()
{
window.alert('Alerting about ' + params.value);
this.varIWantToAccess = true; // Builds fine, but throws a run time exception, since this "this" context is different than the one that has "varIWantToAccess"
}
},
....
return result;
}
Thanks!
You can add the reference to this in grid's context -
this.gridOptions.context = {
thisComponent : this
};
And then, thisComponent can be access as below -
private getContextMenuItems(params) {
console.log(params);
var result = [
{ // custom item
name: 'Sample',
action: function () {params.context.thisComponent.callMe(); },
icon: '<i class="fa fa-pencil" />'
}];
return result;
}
Same can be done for any other call backs like cellRenderer.
I assume that you are speaking of an Angular 2 or 4 component using TypeScript.
If so then use fat arrow to connect to your function.
Example:
gridOptions.getContextMenuItems = () => this.getContextMenuItems();
This should provide you the scope you need.
You need to provide parent context property to the item.
Sample context menu item:
{
name: 'BreakoutReport',
action: function () {
this.context.isDrillDownData = false;
this.context.isBreakOutReport = true;
this.context.populateBreakOutGrid();
},
cssClasses: ['redFont', 'bold'],
disabled: !params.value.drillDownReport,
context: params.context
}
Here, this.context has access to all the parent functions.
Remember, this context needs to be set in grid options first and then can be transferred to context menu items.
1st step: set context in gridOptions
getGridOption() {
return {
getContextMenuItems: this.getContextMenu,
context: this//pass parent context
};
}
2nd step: pass context to context menu subitems
getContextMenu(params) {
const result = [
{
name: 'Drilldown Report',
action: function () {
this.context.populateDrillDownReport();//access parent context using this.context inside the function.
},
cssClasses: ['redFont', 'bold'],
disabled: !params.value.drillDownReport,
context: params.context//pass parent context
},
'separator',
'copy',
'copyWithHeaders'];
return result;
}
you can just amend your getContextMenuItems
getContextMenuItems = (params) => {
var result = [
{
name: 'Activate ' + params.value,
action: function () {
window.alert('Activated Successfully ');
},
cssClasses: ['redFont', 'bold'],
},
{
name: 'Details',
action: () => {
this.router.navigate(['container-authorization/listing/distributor-container-store/details']);
},
cssClasses: ['redFont', 'bold']
},
}
method to fat arrow method like below.

Protractor/Jasmine send REST Call when a test failed

I am using Protractor and Jasmine to test my hybrid mobile app, which works fine. I'd like to create an incident on my Team Foundation Server (TFS), when a test fails. Therefore, I have to send an REST-Call to the Api, which also works fine in my Angular App. But it does not work, when I am inside my test environment.
My Code:
var BrowsePage = require('./browse.page');
var tfsIncident = require('./tfsIncident_service');
var request = require('request');
describe('Testing the browse state', function () {
var browsePage = new BrowsePage();
var specsArray = [];
var reporterCurrentSpec = {
specDone: function (result) {
if (result.status === 'failed') {
var mappedResult = tfsIncident.create(result);
console.log(mappedResult); //This works so far, but then it crashes
var options = {
method: 'PATCH', //THis Method requiered the API
url: 'MY_COOL_API_ENDPOINT',
headers: {
'Authorization': 'Basic ' + btoa('USERNAME' + ':' + 'PASSWORD'),
'Content-Type': 'application/json-patch+json'
},
body: mappedResult
};
function callback(error, response, body) {
if (!error && response.statusCode == 200) {
var info = JSON.parse(body);
console.log(response);
console.log(info);
}
}
request(options, callback);
}
}
};
jasmine.getEnv().addReporter(reporterCurrentSpec);
//This test passes
it('should be able to take display the heading', function () {
expect(browsePage.headline.isPresent()).toBe(true);
});
// Test is supposed to fail
it('should be able to fail', function () {
expect(browsePage.headline).toBe(1);
});
// Test is supposed to fail as well
it('should be able to fail too', function () {
expect(browsePage.headline).toBe(2);
});
});
So the problem is, that my only console output is (after the console.log(mappedResult)): E/launcher - BUG: launcher exited with 1 tasks remaining
So I have no idea, why this does not work.
Any help appreciated.
Edit
Protractor: 5.0.0
Appium Desktop Client: 1.4.16.1
Chromedriver: 2.27
Windows 10 64 Bit
Jasmine: 2.4.1
I finally got my problem solved. The problem was caused by ignoring the promises by jasmine. I had to add a .controllFlow().wait() to my protractor.promise
The following code works fine:
var BrowsePage = require('./browse.page');
describe('Testing the browse state', function () {
var browsePage = new BrowsePage();
var reporterCurrentSpec = {
specDone: function (result) {
if (result.status === 'failed') {
//Mapping of the result
var incident = [
{
op: 'add',
path: '/fields/System.Title',
value: 'Test: ' + result.fullName + ' failed'
},
{
op: 'add',
path: '/fields/System.Description',
value: result.failedExpectations[0].message
},
{
op: 'add',
path: '/fields/Microsoft.VSTS.Common.Priority',
value: '1'
},
{
op: 'add',
path: '/fields/System.AssignedTo',
value: 'Name Lastname <e#mail.com>'
}
];
protractor.promise.controlFlow().wait(create(incident)).then(function (done) { //The magic happens in this line
console.log("test done from specDone:" + done);
});
}
}
};
jasmine.getEnv().addReporter(reporterCurrentSpec); //Add new Jasmine-Reporter
function create(incident) {
var request = require('request');
var defer = protractor.promise.defer(); //new promise
request({
url: 'https://MY_COOL_ENDPOINT.COM',
method: "PATCH",
json: true, // <--Very important!!!
headers: {
'Authorization': 'Basic ' + new Buffer('USERNAME' + ':' + 'PASSWORD').toString('base64'),
'Content-Type': 'application/json-patch+json'
},
body: incident
}, function (error, response, body) {
console.log(error);
console.log(response.statusCode);
console.log(body.id); //Id of created incident on TFS
defer.fulfill({
statusCode: response.statusCode
}); //resolve the promise
});
return defer.promise; //return promise here
}
it('should be able to display the heading', function () {
expect(browsePage.headline.isPresent()).toBe(true);
});
it('should be able to fail', function () {
expect(browsePage.headline.isPresent()).toBe(false);
});
it('should be able to fail 2', function () {
expect(browsePage.headline.isPresent()).toBe(false);
});
});
Attention
When the test suite is done and the last promise is not resolved at this moment, the last incident is not created. I'll try to work around by adding to the last test a browser.sleep(5000); so that the create(incident) function gets more time to finish.
Thanks to this StackOverflow answer for helping me.

How to test slide-box in protractor

I have a slide-box described in one of my protractor tests; I can find the box and can get properties (i.e. 'how many') but how do I cycle through the boxes so I can test verify the display, e.g.
profilepage.slides.next()
expect(profilepage.slide.slideTitle = 'Credentials'
profilepage.slides.next()
expect(profilepage.slide.slideTitle = "Info"
etc.
Controller:
.controller('ProfileCtrl', function ($scope, ProfileService) {
$scope.data = {
numViewableSlides: 0,
slideIndex: 0,
initialInstruction: true,
secondInstruction: false, slides: [
{
'template': 'templates/slidebox/credentials.html',
'viewable': true
},
{
'template': 'templates/slidebox/contactinfo.html',
'viewable': true
},
{
'template': 'templates/slidebox/employeeinfo.html',
'viewable': true
},
{
'template': 'templates/slidebox/assignmentinfo.html',
'viewable': true
}
]
}
. . .
Template:
<ion-slide-box on-slide-changed="slideChanged(index)" show-pager="true">
<ion-slide ng-repeat="slide in data.slides | filter:{viewable : true}">
<div ng-include src="slide.template"></div>
</ion-slide>
</ion-slide-box>
Page Object:
profilepage.prototype = Object.create({}, {
backButton: {
get: function () {
return element(by.css('ion-ios7-arrow-back'));
}
},
slides: {
get: function () {
return element.all(by.repeater('slide in data.slides'));
}
},
slideTitle: {
get: function (id) {
element.all(by.repeater('slide in data.slides')).then(function (slidelist) {
var titleElement = slidelist[id].element(by.css('#slideName'));
return titleElement.getText();
});
}
},
. . .
Spec:
describe('Profile', function () {
var ppage = new profilepage();
beforeEach(function () {
browser.ignoreSynchronization = false;
});
it('should have correct lastname and have four slides on profile page', function () {
expect(browser.getCurrentUrl()).toEqual('http://localhost:8100/#/profile');
expect(ppage.lastname).toBe('Smith,');
expect(ppage.slides.count()).toEqual(4);
browser.sleep(1000);
});
it('should slide all the pages', function(){
expect(browser.getCurrentUrl()).toEqual('http://localhost:8100/#/profile');
// SLIDE EACH PAGE ABOUT HERE <------------
browser.sleep(1000);
})
The idea is to use ionic's $ionicSlideBoxDelegate from within the spec file. For that we'll need to make it accessible globally:
var addProtractorSlideBox, nextSlide;
addProtractorSlideBox = function() {
return browser.addMockModule("services", function() {
return angular.module("services").run(function($ionicSlideBoxDelegate) {
return window._$ionicSlideBoxDelegate = $ionicSlideBoxDelegate;
});
});
};
nextSlide = function() {
return browser.executeScript('_$ionicSlideBoxDelegate.next()');
};
...
beforeEach(function() {
...
addProtractorSlideBox();
...
});
it('...', function() {
...
nextSlide();
...
})
This pattern is very useful for other ionic/angular services.