"use strict"; /* global global: false */ var $ = require("jquery"); var ko = require("knockout"); var console = require("console"); var performanceAwareCaller = require("./timed-call.js").timedCall; var toastr = require("toastr"); toastr.options = { "closeButton": false, "debug": false, "positionClass": "toast-bottom-full-width", "target": "#mo-body", "onclick": null, "showDuration": "300", "hideDuration": "1000", "timeOut": "5000", "extendedTimeOut": "1000", "showEasing": "swing", "hideEasing": "linear", "showMethod": "fadeIn", "hideMethod": "fadeOut" }; /* NOTE: translations moved to "plugin" var strings = { 'show preview and send test': 'Visualizza una anteprima e fai un invio di test', // Strings for app.js 'Download': 'Download', 'Test': 'Test', 'Save': 'Salva', 'Downloading...': "Download in corso...", 'Invalid email address': "Indirizzo email invalido", "Test email sent...": "Email di test inviata...", 'Unexpected error talking to server: contact us!': 'Errore di comunicazione con il server: contattaci!', 'Insert here the recipient email address': 'Inserisci qui l\'indirizzo email a cui spedire', "Test email address": "Indirizzo email di test", // viewModel 'Block removed: use undo button to restore it...': 'Blocco eliminato: usa il pulsante annulla per recuperarlo...', 'New block added after the selected one (__pos__)': 'Nuovo blocco aggiunto sotto a quello selezionato (__pos__)', 'New block added at the model bottom (__pos__)': 'Nuovo blocco aggiunto in fondo al modello (__pos__)', // undomain.js 'Undo (#COUNT#)': 'Annulla (#COUNT#)', 'Redo': 'Ripristina', // editor.js 'Selected element has no editable properties': 'L\'elemento selezionato non fornisce proprietà editabili', 'This style is specific for this block: click here to remove the custom style and revert to the theme value': 'Questo stile è specifico di questo blocco: clicca qui per annullare lo stile personalizzato', 'Switch between global and block level styles editing': 'Permette di specificare se si vuole modificare lo stile generale o solamente quello specifico del blocco selezionato', // main.tpl.html 'Undo last operation': 'Annulla ultima operazione', 'Redo last operation': 'Ripeti operazione annullata', 'Show image gallery': 'Visualizza galleria immagini', 'Gallery': 'Galleria', 'Preview': 'Anteprima', 'Show live preview': 'Mostra anteprima live', 'Large screen': 'Schermo grande', 'Tablet': 'Tablet', 'Smartphone': 'Smartphone', 'Show preview and send test': 'Visualizza una anteprima e fai un invio di test', 'Download template': 'Scarica il template', 'Save template': 'Salva il template', 'Saved model is obsolete': 'Modello salvato obsoleto', '

The saved model has been created with a previous, non completely compatible version, of the template

Some content or style in the model COULD BE LOST if you will save

Contact us for more informations!

': '

Il modello salvato è stato creato con una versione precedente del template non del tutto compatibile

Alcuni contenuti o stili del modello POTREBBERO ESSERE PERSI se procederai e deciderai di salvare

Contattaci se hai dei dubbi!

', // TODO this cannot be done in knockout as with uncompatible browsers we don't initialize // 'Usupported browser': 'Browser non compatibile', // '

Your browser is not supported.

Use a different browser or try updaring your browser.

Supported browsers:

': '

Il tuo browser non è supportato.

Accedi con un browser differente o prova ad aggiornare il tuo browser.

Browser supportati:

', // toolbox 'Blocks': 'Blocchi', 'Blocks ready to be added to the template': 'Elenco contenuti aggiungibili al messaggio', 'Content': 'Contenuto', 'Edit content options': 'Modifica opzioni contenuti', 'Style': 'Stile', 'Edit style options': 'Modifica opzioni grafiche', 'Block __name__': 'Blocco __name__', 'Click or drag to add this block to the template': 'Clicca o trascina per aggiungere al messaggio', 'Add': 'Aggiungi', 'By clicking on message parts you will select a block and content options, if any, will show here': 'Cliccando su alcune parti del messaggio selezionerai un blocco e le opzioni contenutistiche, se disponibili, compariranno qui', 'By clicking on message parts you will select a block and style options, if available, will show here': 'Cliccando su alcune parti del messaggio selezionerai un blocco e le opzioni di stile, se disponibili, compariranno qui', 'Click or drag files here': 'Clicca o trascina i file qui!', 'No images uploaded, yet': 'Non hai ancora caricato immagini', 'Show images from the gallery': 'Visualizza le immagini caricate nella tua area', 'Loading...': 'Caricamento...', 'Load gallery': 'Carica galleria', 'Loading gallery...': 'Caricamento in corso...', 'The gallery is empty': 'Nessuna immagine nella galleria', // img-wysiwyg.tmlp 'Remove image': 'Rimuovi immagine', 'Open the image editing tool': 'Avvia strumento modifica immagine', 'Upload a new image': 'Carica una nuova immagine', 'Drop an image here': 'Trascina una immagine qui', 'Drop an image here or click the upload button': 'Trascina una immagine qui o clicca sul pulsante di caricamento', // gallery 'Drag this image and drop it on any template image placeholder': 'Trascina questa immagine sulla posizione in cui vuoi inserirla', 'Gallery:': 'Galleria:', 'Session images': 'Immagini di sessione', 'Recents': 'Recenti', 'Remote gallery': 'Galleria remota', // customstyle 'Customized block.': 'Blocco personalizzato.', // blocks-wysiwyg 'Drop here blocks from the "Blocks" tab': 'Trascina qui i blocchi dalla scheda \'Blocchi\'', // block-wysiwyg 'Drag this handle to move the block': 'Trascina per spostare il blocco altrove', 'Move this block upside': 'Sposta il blocco in su', 'Move this block downside': 'Sposta il blocco in giu', 'Delete block': 'Elimina blocco', 'Duplicate block': 'Duplica blocco', 'Switch block variant': 'Cambia variante blocco', // colorpicker 'Theme Colors,Standard Colors,Web Colors,Theme Colors,Back to Palette,History,No history yet.': 'Colori Tema,Colori Standard,Colori Web,Colori Tema,Torna alla tavolozza,Storico,storico colori vuoto', 'Drop here': 'Rilascia qui', }; */ function initializeEditor(content, blockDefs, thumbPathConverter, galleryUrl) { var viewModel = { galleryRecent: ko.observableArray([]).extend({ paging: 16 }), galleryRemote: ko.observableArray([]).extend({ paging: 16 }), selectedBlock: ko.observable(null), selectedItem: ko.observable(null), selectedTool: ko.observable(0), selectedImageTab: ko.observable(0), dragging: ko.observable(false), draggingImage: ko.observable(false), galleryLoaded: ko.observable(false), showPreviewFrame: ko.observable(false), previewMode: ko.observable('mobile'), showToolbox: ko.observable(true), showTheme: ko.observable(false), showGallery: ko.observable(false), debug: ko.observable(false), contentListeners: ko.observable(0), logoPath: 'dist/img/mosaico32.png', logoUrl: '.', logoAlt: 'mosaico' }; // viewModel.content = content._instrument(ko, content, undefined, true); viewModel.content = content; viewModel.blockDefs = blockDefs; viewModel.notifier = toastr; // Does token substitution in i18next style viewModel.tt = function(key, paramObj) { if (typeof paramObj !== 'undefined') for (var prop in paramObj) if (paramObj.hasOwnProperty(prop)) { key = key.replace(new RegExp('__' + prop + '__', 'g'), paramObj[prop]); } return key; }; // Simply maps to tt: language plugins can override this method to define their own language // handling. // If this method invokes an observable (e.g: viewModel.lang()) then the UI language will automatically // update when the "lang" observable changes. viewModel.t = viewModel.tt; // currently called by editor.html to translate template-defined keys (label, help, descriptions) // the editor always uses the "template" category for that strings. // you can override this method as you like in order to provide translation or change the strings in any way. viewModel.ut = function(category, key) { return key; }; viewModel.templatePath = thumbPathConverter; viewModel.remoteUrlProcessor = function(url) { return url; }; viewModel.remoteFileProcessor = function(fileObj) { if (typeof fileObj.url !== 'undefined') fileObj.url = viewModel.remoteUrlProcessor(fileObj.url); if (typeof fileObj.thumbnailUrl !== 'undefined') fileObj.thumbnailUrl = viewModel.remoteUrlProcessor(fileObj.thumbnailUrl); // deleteUrl? return fileObj; }; // toolbox.tmpl.html viewModel.loadGallery = function() { viewModel.galleryLoaded('loading'); var url = galleryUrl ? galleryUrl : '/upload/'; // retrieve the full list of remote files $.getJSON(url, function(data) { for (var i = 0; i < data.files.length; i++) data.files[i] = viewModel.remoteFileProcessor(data.files[i]); viewModel.galleryLoaded(data.files.length); // TODO do I want this call to return relative paths? Or just absolute paths? viewModel.galleryRemote(data.files.reverse()); }).fail(function() { viewModel.galleryLoaded(false); viewModel.notifier.error(viewModel.t('Unexpected error listing files')); }); }; // img-wysiwyg.tmpl.html viewModel.fileToImage = function(obj, event, ui) { // console.log("fileToImage", obj); return obj.url; }; // block-wysiwyg.tmpl.html viewModel.removeBlock = function(data, parent) { // let's unselect the block if (ko.utils.unwrapObservable(viewModel.selectedBlock) == ko.utils.unwrapObservable(data)) { viewModel.selectBlock(null, true); } var res = parent.blocks.remove(data); // TODO This message should be different depending on undo plugin presence. viewModel.notifier.info(viewModel.t('Block removed: use undo button to restore it...')); return res; }; // block-wysiwyg.tmpl.html viewModel.duplicateBlock = function(index, parent) { var idx = ko.utils.unwrapObservable(index); // Deinstrument/deobserve the object var unwrapped = ko.toJS(ko.utils.unwrapObservable(parent.blocks)[idx]); // We need to remove the id so that a new one will be assigned to the clone if (typeof unwrapped.id !== 'undefined') unwrapped.id = ''; // insert the cloned block parent.blocks.splice(idx + 1, 0, unwrapped); }; // block-wysiwyg.tmpl.html viewModel.moveBlock = function(index, parent, up) { var idx = ko.utils.unwrapObservable(index); var parentBlocks = ko.utils.unwrapObservable(parent.blocks); if ((up && idx > 0) || (!up && idx < parentBlocks.length - 1)) { var destIndex = idx + (up ? -1 : 1); var destBlock = parentBlocks[destIndex]; viewModel.startMultiple(); parent.blocks.splice(destIndex, 1); parent.blocks.splice(idx, 0, destBlock); viewModel.stopMultiple(); } }; // test method, command line use only viewModel.loadDefaultBlocks = function() { // cloning the whole "mainBlocks" object so that undomanager will // see it as a single operation (maybe I could use "startMultiple"/"stopMultiple". var res = ko.toJS(viewModel.content().mainBlocks); res.blocks = []; var input = ko.utils.unwrapObservable(viewModel.blockDefs); for (var i = 0; i < input.length; i++) { var obj = ko.toJS(input[i]); // generating ids for blocks, maybe this would work also leaving it empty. obj.id = 'block_' + i; res.blocks.push(obj); } performanceAwareCaller('setMainBlocks', viewModel.content().mainBlocks._wrap.bind(viewModel.content().mainBlocks, res)); }; // gallery-images.tmpl.html viewModel.addImage = function(img) { var selectedImg = $('#main-wysiwyg-area .selectable-img.selecteditem'); if (selectedImg.length == 1 && typeof img == 'object' && typeof img.url !== 'undefined') { ko.contextFor(selectedImg[0])._src(img.url); return true; } else { return false; } }; // toolbox.tmpl.html viewModel.addBlock = function(obj, event) { // if there is a selected block we try to add the block just after the selected one. var selected = viewModel.selectedBlock(); // search the selected block position. var found; if (selected !== null) { // TODO "mainBlocks" is an hardcoded thing. for (var i = viewModel.content().mainBlocks().blocks().length - 1; i >= 0; i--) { if (viewModel.content().mainBlocks().blocks()[i]() == selected) { found = i; break; } } } var pos; if (typeof found !== 'undefined') { pos = found + 1; viewModel.content().mainBlocks().blocks.splice(pos, 0, obj); viewModel.notifier.info(viewModel.t('New block added after the selected one (__pos__)', { pos: pos })); } else { viewModel.content().mainBlocks().blocks.push(obj); pos = viewModel.content().mainBlocks().blocks().length - 1; viewModel.notifier.info(viewModel.t('New block added at the model bottom (__pos__)', { pos: pos })); } // find the newly added block and select it! var added = viewModel.content().mainBlocks().blocks()[pos](); viewModel.selectBlock(added, true); // prevent click propagation (losing url hash - see #43) return false; }; // Used by stylesheet.js to create multiple styles viewModel.findObjectsOfType = function(data, type) { var res = []; var obj = ko.utils.unwrapObservable(data); for (var prop in obj) if (obj.hasOwnProperty(prop)) { var val = ko.utils.unwrapObservable(obj[prop]); // TODO this is not the right way to deal with "block list" objects. if (prop.match(/Blocks$/)) { var contents = ko.utils.unwrapObservable(val.blocks); for (var i = 0; i < contents.length; i++) { var c = ko.utils.unwrapObservable(contents[i]); if (type === null || ko.utils.unwrapObservable(c.type) == type) res.push(c); } // TODO investigate which condition provide a null value. } else if (typeof val == 'object' && val !== null) { if (type === null || ko.utils.unwrapObservable(val.type) == type) res.push(val); } } return res; }; /* viewModel.placeholderHelper = 'sortable-placeholder'; if (false) { viewModel.placeholderHelper = { element: function(currentItem) { return $('
').removeClass('ui-draggable').addClass('sortable-placeholder').css('position', 'relative').css('width', '100%').css('height', currentItem.css('height')).css('opacity', '.8')[0]; }, update: function(container, p) { return; } }; } */ // Attempt to insert the block in the destination layout during dragging viewModel.placeholderHelper = { element: function(currentItem) { return $(currentItem[0].outerHTML).removeClass('ui-draggable').addClass('sortable-placeholder').css('display', 'block').css('position', 'relative').css('width', '100%').css('height', 'auto').css('opacity', '.8')[0]; }, update: function(container, p) { return; } }; // TODO the undumanager should be pluggable. // Used by "moveBlock" and blocks-wysiwyg.tmpl.html to "merge" drag/drop operations into a single undo/redo op. viewModel.startMultiple = function() { if (typeof viewModel.setUndoModeMerge !== 'undefined') viewModel.setUndoModeMerge(); }; viewModel.stopMultiple = function() { if (typeof viewModel.setUndoModeOnce !== 'undefined') viewModel.setUndoModeOnce(); }; // Used by code generated by editor.js viewModel.localGlobalSwitch = function(prop, globalProp) { var current = prop(); if (current === null) prop(globalProp()); else prop(null); return false; }; // Used by editor and main "converter" to support item selection viewModel.selectItem = function(valueAccessor, item, block) { var val = ko.utils.peekObservable(valueAccessor); if (typeof block !== 'undefined') viewModel.selectBlock(block, false, true); if (val != item) { valueAccessor(item); // On selectItem if we were on "Blocks" toolbox tab we move to "Content" toolbox tab. if (item !== null && viewModel.selectedTool() === 0) viewModel.selectedTool(1); } return false; }.bind(viewModel, viewModel.selectedItem); viewModel.isSelectedItem = function(item) { return viewModel.selectedItem() == item; }; viewModel.selectBlock = function(valueAccessor, item, doNotSelect, doNotUnselectItem) { var val = ko.utils.peekObservable(valueAccessor); if (!doNotUnselectItem) viewModel.selectItem(null); if (val != item) { valueAccessor(item); // hide gallery on block selection viewModel.showGallery(false); if (item !== null && !doNotSelect && viewModel.selectedTool() === 0) viewModel.selectedTool(1); } }.bind(viewModel, viewModel.selectedBlock); // DEBUG viewModel.countSubscriptions = function(model, debug) { var res = 0; for (var prop in model) if (model.hasOwnProperty(prop)) { var p = model[prop]; if (ko.isObservable(p)) { if (typeof p._defaultComputed != 'undefined') { if (typeof debug != 'undefined') console.log(debug + "/" + prop + "/_", p._defaultComputed.getSubscriptionsCount()); res += p._defaultComputed.getSubscriptionsCount(); } if (typeof debug != 'undefined') console.log(debug + "/" + prop + "/-", p.getSubscriptionsCount()); res += p.getSubscriptionsCount(); p = ko.utils.unwrapObservable(p); } if (typeof p == 'object' && p !== null) { var tot = viewModel.countSubscriptions(p, typeof debug != 'undefined' ? debug + '/' + prop + "@" : undefined); if (typeof debug != 'undefined') console.log(debug + "/" + prop + "@", tot); res += tot; } } return res; }; // DEBUG viewModel.loopSubscriptionsCount = function() { var count = viewModel.countSubscriptions(viewModel.content()); global.document.getElementById('subscriptionsCount').innerHTML = count; global.setTimeout(viewModel.loopSubscriptionsCount, 1000); }; viewModel.export = function() { var content = performanceAwareCaller("exportHTML", viewModel.exportHTML); return content; }; function conditional_restore(html) { return html.replace(/]* condition="([^"]*)"[^>]*>([\s\S]*?)<\/replacedcc>/g, function(match, condition, body) { var dd = '(<\/cc>)?/g, '') // restore closing tags (including lost tags) .replace(/><\/cc>/g, '/>') // restore selfclosing tags .replace(//,'') // remove content before start .replace(/.*$/,''); // remove content after end dd += ''; return dd; }); } viewModel.exportHTML = function() { var id = 'exportframe'; $('body').append(''); var frameEl = global.document.getElementById(id); ko.applyBindings(viewModel, frameEl); ko.cleanNode(frameEl); if (viewModel.inline) viewModel.inline(frameEl.contentWindow.document); // Obsolete method didn't work on IE11 when using "HTML5 doctype": // var docType = new XMLSerializer().serializeToString(global.document.doctype); var node = frameEl.contentWindow.document.doctype; var docType = "'; var content = docType + "\n" + frameEl.contentWindow.document.documentElement.outerHTML; ko.removeNode(frameEl); content = content.replace(/