Plugin Directory

Changeset 2083624


Ignore:
Timestamp:
05/08/2019 01:42:30 PM (7 years ago)
Author:
christophesecher
Message:

works with current wordpress version (5.2)

Location:
traveladsnetwork-com
Files:
1 added
6 edited

Legend:

Unmodified
Added
Removed
  • traveladsnetwork-com/tags/1.0/assets/css/pageEdit.css

    r2083453 r2083624  
    2020table#navMenu
    2121{
     22    box-sizing: border-box;
    2223    width:100%;
    2324    position:absolute;
     
    2829    padding:0 20px;
    2930    z-index: 1;
     31
     32    transition: border-radius .3s,
     33                box-shadow .3s;
     34}
     35table#navMenu.flying {
     36    border-radius: 0 0 10px 10px;
     37    box-shadow: 0px 5px 10px 5px #0003;
    3038}
    3139table#navMenu td
  • traveladsnetwork-com/tags/1.0/assets/js/pageEdit.js

    r2083453 r2083624  
    1515
    1616!function($) {
    17     $(document).ready(function() {
    18         // {{{
    19        
    20         /**
    21          * Changes the current focussed word
    22          *
    23          * @param {int}  nb     The keyWord to highlight -> e.g. if you want to highlight the 5th keyword, nb=4
    24          * @param {bool} scroll Should the screen scroll automatically to this keyword?
    25          */
    26         function tan_changeFocus(nb=0, scroll=true)
    27         {
    28             // {{{
    29             nb = nb || 0;
    30 
    31             nb = (nb+$('#travel_editor mark.TANKeyWord').length)%$('#travel_editor mark.TANKeyWord').length
    32 
    33             currentFocus = nb;
    34             elem = $('#travel_editor mark.TANKeyWord')[nb];
    35 
    36             $('#navMenu_keyWord').val(elem.innerHTML.trim());
    37 
    38             $('#travel_editor mark.TANKeyWord').removeClass('current');
    39             $(elem).addClass('current');
    40 
    41             $('#navMenu_currentKeyWord').text(nb+1);
    42 
    43             if($(elem).hasClass('active'))
    44             {
    45                 $('div#navMenu_buttonOnOff').addClass('active');
    46             }
    47             else
    48             {
    49                 $('div#navMenu_buttonOnOff').removeClass('active');
    50             }
    51 
    52            
    53             $('select#navMenu_merchantSelect').val($(elem).attr('data-adv') || $('select#navMenu_merchantSelect option')[0].value);
    54 
    55             if(scroll)
    56             {
    57                 $('html, body').clearQueue();
    58                 $('html, body').animate({
    59                     scrollTop: $(elem).offset().top-$(window).height()/2
    60                 }, 500);
    61             }
    62             // }}}
    63         }
    64 
    65         function setWpEditorContent(content)
    66         {
    67             // {{{
    68             if(!window.editorName)
    69                 window.editorName = (!!wp.data)?'gutenberg':
    70                              (!!tinymce || !!tinyMCE)?'tinymce':
    71                              '';
    72 
    73             switch(editorName)
    74             {
    75                 case 'gutenberg':
    76                     wp.data.dispatch( 'core/editor' ).resetBlocks([]);
    77 
    78                     var editedContent = wp.data.select( "core/editor" ).getEditedPostContent();
    79                     var newBlocks = wp.blocks.parse( content );
    80                     wp.data.dispatch( "core/editor" ).insertBlocks( newBlocks );
    81 
    82                     return true;
    83                 case 'tinymce':
    84 
    85                     if(!!tinymce.activeEditor)
    86                         tinymce.activeEditor.setContent( content )
    87                     else if(!!tinyMCE.activeEditor)
    88                         tinyMCE.activeEditor.setContent( content )
    89 
    90                     $('#content').val($('#hidden_travel_editor').html());
    91 
    92                     return true;
    93             }
    94 
    95             return false;
    96             // }}}
    97         }
    98 
    99         function getWpEditorContent()
    100         {
    101             // {{{
    102             if(!window.editorName)
    103                 window.editorName = (!!wp.data)?'gutenberg':
    104                              (!!tinymce || !!tinyMCE)?'tinymce':
    105                              '';
    106 
    107             switch(editorName)
    108             {
    109                 case 'gutenberg':
    110                     return wp.data.select('core/editor').getEditedPostContent();
    111                 case 'tinymce':
    112                     if($('#content').attr('aria-hidden') === 'true') // text editor
    113                         return (window.tinymce || window.tinyMCE).activeEditor.getContent();
    114                     else
    115                         return $('#content').val().replace(/\n/g, '<br/>');
    116             }
    117 
    118             return '';
    119             // }}}
    120         }
    121 
    122 
    123         /**
    124          * Finds the position of a keyWord
    125          *
    126          * @param {DomNode}    e         The Dom node representing the keyword, has to be a `<mark class='TANKeyWord'>`
    127          * @param {jQueryNode} container The container in which the keyWords are. travel editor by default.
    128          *
    129          * @return {number} the position of this specific keyword, or 0 if not found
    130          */
    131 
    132         function tan_domToNum(e, container=$('#travel_editor'))
    133         {
    134             // {{{
    135             list = container.find(' mark.TANKeyWord');
    136             for(var i=0 ; i<list.length ; i++)
    137                 if(list[i] === e)
    138                     return i;
    139             return 0;
    140             // }}}
    141         }
    142 
    143         /**
    144          * function called when a keyWord is clicked
    145          *
    146          * @param {DomNode} e The clicked keyWord, has to be a `<mark class='TANKeyWord'>`
    147          */
    148         function tan_clickMark(e)
    149         {
    150             tan_changeFocus(tan_domToNum(e));
    151         }
    152 
    153         /**
    154          * Toggle the specified keyWord on and off
    155          *
    156          * @param {number} foc  The keyword to activate, the currently focussed keyword by default, pass -1 to toggle all, -2 to toggle all custom keywords
    157          * @param {bool}   flag Weather the keyWord should be activated or deactivated. Toggle by default
    158          */
    159         function tan_activateKeyWord(foc = currentFocus, flag = undefined)
    160         {
    161             // {{{
    162             if(foc < 0)
    163             {
    164                 let classes = ['TANKeyWord'];
    165                 if(foc === -2)
    166                     classes.push('custom');
    167 
    168                 let classesStr = classes.map(v=>'.'+v).join('');
    169 
    170                 f = $('#travel_editor mark.active'+classesStr).length
    171                     - $('#travel_editor mark'+classesStr+':not(.active)').length
    172 
    173 
    174                 f = f<0;
    175                
    176                 $('#travel_editor mark'+classesStr).each(function() {
    177                     tan_activateKeyWord(tan_domToNum(this), f);
    178                 });
    179 
    180                 return;
    181             }
    182 
    183             elem = $('#travel_editor mark.TANKeyWord').eq(foc);
    184 
    185             if(elem.hasClass('active') === flag)
    186                 return;
    187 
    188             if(flag === undefined)
    189                 elem.toggleClass('active');
    190             else
    191                 elem.toggleClass('active', flag);
    192 
    193             elem.attr('data-adv', $('#navMenu_merchantSelect').find('option:selected').val());
    194 
    195 
    196             if(foc === currentFocus) $('#navMenu_buttonOnOff').toggleClass('active', elem.hasClass('active'));
    197 
    198             // }}}
    199         }
    200 
    201         var totalReplaced = 0;
    202        
    203         /**
    204          * Surround the keyWords by <mark>, to highlighting it
    205          *
    206          * @param {RegExp} target regular expression matching the targeted keywords
    207          */
    208         jQuery.fn.tan_highlightKeywords = function(target)
    209         {
    210             // {{{
    211             //
    212             // Get all text nodes:
    213             //
    214             excludeNodes = ['SCRIPT', 'STYLE', 'H1', 'H2', 'H3', 'H4'];
    215             var $textNodes = this
    216                                 .find("*")
    217                                 .andSelf()
    218                                 .contents()
    219                                 .filter(function() {
    220                                     return  this.nodeType === 3
    221                                          && !$(this).parent("a").length
    222                                          && !$(this).parent(excludeNodes.join(',')).length
    223                                          && !$(this).parent('.TANKeyWord').length
    224                                          && !$(this).parent('.TAN_link').length
    225                                          && $(this).text().trim() !== '';
    226                                 });
    227 
    228             //
    229             // Iterate through the text nodes,
    230             // highlighting the keyWords
    231             //
    232 
    233             let totalReplaced = 0;
    234             $textNodes.each(function(index, element) {
    235 
    236                 match = $(element).text().match(target);
    237 
    238                 if(match === null) return;
    239                 var i=0;
    240                 var finalStr = $(element).text().replace(target, function(word) {
    241                     var originalWord = match[i];
    242                     i++;
    243                     totalReplaced++;
    244 
    245                     let tmp = addedKeyWords.filter(v=>v[0].format() === originalWord.format())
    246                    
    247                     let c = '';
    248                     let s = originalWord;
    249                     if(tmp.length)
    250                     {
    251                         c = ' custom'
    252                         s = tmp[0][1];
    253                     }
    254 
    255                     return `<mark class='TANKeyWord`+c+`'
    256                                   data-search='`+s+`'
    257                                   data-adv=`+$('#navMenu_merchantSelect').find('option:selected').val()+`>`
    258                               + originalWord
    259                               + `</mark>`;
    260                 });
    261 
    262                 $(element).replaceWith(finalStr);
    263             });
    264 
    265             $('.TANKeyWord').each(function(i) {
    266                
    267                 this.onclick = function() {
    268                     tan_changeFocus(tan_domToNum(this));
    269                 };
    270             });
    271            
    272             // }}}
    273         }
    274 
    275         /**
    276          * copy content from the travel ads network editor to the tiny MCE
    277          */
    278         function copy_travel2wp()
    279         {
    280             // {{{
    281 
    282             $('#hidden_travel_editor').html(($('div#travel_editor').html()));
    283 
    284             $('#hidden_travel_editor mark.TANKeyWord').each(function() {
    285                 $(this).replaceWith(this.outerHTML.replace(/\bmark\b/, 'span'))
    286             });
    287 
    288             $('#hidden_travel_editor span.TANKeyWord').each(function() {
    289                 $(this).removeClass('current');
    290                 $(this).removeClass('TANKeyWord');
    291 
    292                 $(this).addClass('TAN_link');
    293 
    294                 if($(this).hasClass('custom'))
    295                     $(this).addClass('TAN_link_custom');
    296             });
    297 
    298             $('#hidden_travel_editor section.tan_asset').each(function(i) {
    299                 $(this).find('script').siblings().remove();
    300             });
    301 
    302             setWpEditorContent( $('#hidden_travel_editor').html() );
    303 
    304             $('div#travel_editor section.tan_asset script').remove();
    305 
    306             $('#hidden_travel_editor').empty();
    307 
    308             // }}}
    309         }
    310        
    311         /**
    312          * Copy the content from the wordpress editor to the travel editor
    313          *
    314          * @param {string} content The content to put in the travel editor
    315          */
    316         function copy_wp2travel()
    317         {
    318             // {{{
    319             content = getWpEditorContent();
    320 
    321 
    322             if(  $('div.mce-tinymce').css('display') === 'none' // normal mode
    323               && $('wp-editor-area').css('display') === 'none') // text mode
    324             {
    325                 $('#travel_editor').html('<h1 style="text-align:center">The visual Editor is not supported for now...</h1>'+
    326                                         '<h2 style="text-align:center">You can switch to autmatic mode!</h2>');
    327                 $('#navMenu').toggle(false);
    328                 $('#navMenuOffset').toggle(false);
    329                 return;
    330             }
    331             $('#navMenu').toggle(true);
    332             $('#navMenuOffset').toggle(true);
    333 
    334             totalReplaced = 0;
    335             $('#travel_editor').empty();
    336             $('#travel_editor').html(content);
    337             $('#travel_editor span.TAN_link').each(function() {
    338 
    339                 $(this).replaceWith(this.outerHTML.replace(/\bspan\b/, 'mark'))
    340             });
    341 
    342             $('#travel_editor mark.TAN_link').each(function() {
    343                 $(this).removeClass('TAN_link_custom');
    344                 $(this).removeClass('TAN_link');
    345 
    346                 $(this).addClass('TANKeyWord');
    347             });
    348 
    349 
    350             $("#travel_editor").tan_highlightKeywords(createNewRegexp(keyWordsRegExp, addedKeyWords.map(v=>v[0])));
    351            
    352             console.log('here');
    353             tan_changeFocus(currentFocus, false);
    354             console.log('not here');
    355             $('#navMenu_totalKeyWord').text($('#travel_editor mark.TANKeyWord').length);
    356             // }}}
    357         }
    358 
    359         /**
    360          * Transforms the google places API response to a easier to use object
    361          *
    362          * @param {Object} googlePlace The object returned by the google places API
    363          *                              -> ```
    364          *                                 {
    365          *                                  address_coponents: <array of addresses>,
    366          *                                  geometry:          <geometry, lat, long>,
    367          *                                  types:             <array of types>,
    368          *                                  name:              <name>
    369          *                                 }
    370          *                                 ```
    371          *
    372          * @return {Object} An easier to use array
    373          *                   -> ```
    374          *                      {
    375          *                          country:     <country name>,
    376          *                          city:        <city name>,
    377          *                          route:       <route name>,
    378          *                          searchText:  <text to search on the OTA's calls>,
    379          *                          lat:         <latitude>,
    380          *                          lng:         <longitude>,
    381          *                      }
    382          *                     
    383          *                      ```
    384          */
    385         function googlePlaceFormat(googlePlace)
    386         {
    387             // {{{
    388             let out = {};
    389 
    390             let cityWrongNames = {
    391                 'Krung Thep Maha Nakhon': 'Bangkok',
    392                 'Krong Siem Reap': 'Siem Reap'
    393             };
    394 
    395             let address = googlePlace.address_components.filter(v=>  !/^[0-9]*$/.test(v.long_name)      // exclude only numbers
    396                                                                   && !/^[0-9]/.test(v.long_name));      // and  beginning with number
    397 
    398             let country             = address.filter(v=>v.types.contains('country')),
    399                 administrativeAreas = address.filter(v=>v.types.some(t=>/administrative_area_level_\d+/.test(t))),
    400                 city                = address.filter(v=>v.types.contains('locality', 'postal_town')),
    401                 subLocalAreas       = address.filter(v=>v.types.some(t=>/sublocality_level_\d+/.test(t))),
    402                 route               = address.filter(v=>v.types.contains('route'));
    403 
    404             out = {
    405                 'lat': googlePlace.geometry.location.lat(),
    406                 'lng': googlePlace.geometry.location.lng()
    407             };
    408 
    409             if(country.length)
    410             {
    411                 out.country=country[0].long_name;
    412                 if(city.length)
    413                 {
    414                     out.city=city[0].long_name;
    415 
    416                     if(route.length)
    417                         out.route = route[0].long_name
    418                     else if(subLocalAreas.length)
    419                         out.route = subLocalAreas[0].long_name
    420                 }
    421                 else if(administrativeAreas.length)
    422                     out.city = administrativeAreas[0].long_name;
    423             }
    424 
    425             out.city = cityWrongNames[out.city] || out.city;
    426            
    427            
    428             if(!googlePlace.types.contains('lodging', 'premise'))   // is not lodging nor premise
    429                 out.searchText = (out.route||'')+', '+(out.city||'')+', '+(out.country||'');
    430             else
    431                 out.searchText = (googlePlace.name||'')+', '+(out.city||'')+', '+(out.country||'');
    432            
    433             out.searchText = out.searchText.replace(/^[, ]+/, '');
    434 
    435             return out;
    436             // }}}
    437         }
    438 
    439         /**
    440          * Adds the custom keyWords to the original regexp
    441          *
    442          * @param {RegExp} original The original regexp conaining all the default keywords
    443          * @param {Array}  keyWords A list of the new keyWords
    444          */
    445         function createNewRegexp(original, keyWords)
    446         {
    447             // {{{
    448             //
    449             // transforming the old regexp to string
    450             //
    451             let newRegExp = original.toString().replace(/^[^\/]*\/|\/[^/]*$/g, '');
    452            
    453             //
    454             // adding the new keyWords
    455             //
    456             for(let i=0 ; i<keyWords.length ; i++)
    457             {
    458                 newRegExp += '|';
    459                 if(/^[a-z]/i.test(keyWords[i])) // begin with latin alphabet
    460                     newRegExp += '\\b';
    461 
    462                 newRegExp += keyWords[i].replace(/([\[\]\(\)\+\/\*\?])/g, '\\$1');  // making the word regex proof
    463 
    464                 if(/[a-z]$/i.test(keyWords[i])) // finish with latin alphabet
    465                     newRegExp += '\\b';
    466 
    467                 newRegExp += '|';
    468                 if(/^[a-z]/i.test(keyWords[i])) // begin with latin alphabet
    469                     newRegExp += '\\b';
    470 
    471                 newRegExp += keyWords[i].format().replace(/([\[\]\(\)\+\/\*\?])/g, '\\$1'); // making the word regex proof
    472 
    473                 if(/[a-z]$/i.test(keyWords[i])) // finish with latin alphabet
    474                     newRegExp += '\\b';
    475 
    476             }
    477 
    478             return new RegExp(newRegExp, 'gi');
    479             // }}}
    480         }
    481        
    482         /**
    483          * initialize the google places field
    484          */
    485         function initAutocomplete()
    486         {
    487             // {{{
    488             //
    489             // avoids to trigger the page reload on enter
    490             //
    491             $('#googleField').on('keydown', function(e){if(e.keyCode===13) e.preventDefault()});
    492 
    493             googleField = new google.maps.places.Autocomplete($('#googleField')[0],
    494                                   {types: ['establishment', 'geocode']});
    495 
    496             googleField.setFields(['address_components',
    497                        'geometry',
    498                        'types',
    499                        'name']);
    500 
    501             googleField.addListener('place_changed', fillInAddress);
    502             // }}}
    503         }
    504        
    505         /**
    506          * Callback, listen to google places field
    507          */
    508         function fillInAddress()
    509         {
    510             // {{{
    511             var place = googleField.getPlace();
    512 
    513             $('#googleField').addClass('kwAdd');
    514             let kw = $('#googleField').val().split(/, ?/)[0]
    515 
    516             if(lastSel[0])
    517             {
    518                 let start = $(lastSel[1].startContainer).parents('mark.TANKeyWord');
    519                 let end = $(lastSel[1].endContainer).parents('mark.TANKeyWord');
    520 
    521                 lastSel[1].deleteContents();
    522                 lastSel[1].insertNode(document.createTextNode(' '+kw+' '));
    523 
    524                 start.replaceWith(function() { return this.innerHTML });
    525                 end.replaceWith(function() { return this.innerHTML });
    526             }
    527 
    528             if(  !addedKeyWords.map(v=>v[0].format()).contains(kw.format())
    529               && (  !kw.match(keyWordsRegExp)
    530                  || kw.match(keyWordsRegExp)[0] !== kw ))   // kw not present in predefined keywords
    531                 addedKeyWords.push([kw, googlePlaceFormat(place).searchText]);
    532 
    533             if(addedKeyWords.length)
    534                 $('#newKWs').html('<br/>Your keywords:<br/> '+addedKeyWords.map(v=>'<span class=newKW>'+v[0]+'</span>').join(' '));
    535 
    536 
    537             $('span.newKW').click(function() {
    538                 let kw = this.innerHTML;
    539 
    540                 addedKeyWords = addedKeyWords.filter(v=>v[0] !== kw);
    541                 $('.TANKeyWord').each(function() {
    542                     if(kw.format() === this.innerText.format())
    543                         $(this).replaceWith(this.innerHTML);
    544                 });
    545                 $(this).remove();
    546             });
    547 
    548 
    549             $("#travel_editor").tan_highlightKeywords(createNewRegexp(keyWordsRegExp, addedKeyWords.map(v=>v[0])));
    550             // }}}
    551         }
    552 
    553         //
    554         // setting up the listeners
    555         //
    556 
    557         $('div#navMenu_buttonOnOff').click(function(){
    558             tan_activateKeyWord();
    559         });
    560 
    561         $('div[aria-label="TravelAdsNetwork Manual"]').click(function() {
    562             // {{{
    563             if($('#travelads_sectionid').css('display') === 'none')
    564             {
    565                 setTimeout(function() {
    566                     $('html, body').animate({
    567                         scrollTop: $('#travelads_sectionid').offset().top - 100
    568                     }, 500);
    569                 }, 300);
    570             }
    571             // }}}
    572         });
    573 
    574         $('#navMenu_merchantSelect').change(function() {
    575             $('.TANKeyWord.current').attr('data-adv', $(this).find('option:selected').val())
    576             tan_activateKeyWord(undefined, true);
    577         });
    578 
    579         $('#navMenuprevWord').click(function() {
    580             tan_changeFocus(currentFocus-1);
    581         });
    582         $('#navMenunextWord').click(function() {
    583             tan_changeFocus(currentFocus+1);
    584         });
    585 
    586         $('#publish').click(function() {
    587             copy_travel2wp();
    588         });
    589 
    590        
    591         let stop=setInterval(function() {
    592             // {{{
    593             if(!(window.tinyMCE || window.tinymce).editors)
    594                 return;
    595 
    596             clearInterval(stop);
    597 
    598             $('#travelads_sectionid').on('mouseenter', function() {
    599                 if(!travelFocused)
    600                 {
    601                     travelFocused = true;
    602                     copy_wp2travel();
    603                 }
    604 
    605                 let editors = ['#content', '.editor-writing-flow']
    606                           .concat([...(window.tinyMCE || window.tinymce).editors]
    607                               .map(v=>(!!v.container && '#'+v.container.getAttribute('id') || '')))
    608                           .filter(v=>!!v)
    609                           .join(', ');
    610 
    611                 $(editors).each(function() {
    612                     this.onmouseenter = function() {
    613                         if(travelFocused)
    614                             copy_travel2wp();
    615                         travelFocused = false;
    616                     };
    617                 });
    618             });
    619 
    620             let content = getWpEditorContent();
    621            
    622             var parser = new DOMParser();
    623             data = parser.parseFromString('<html>'+content.replace(/&nbsp;/g, '<br/>')+'</html>', "text/xml");
    624            
    625             [...data.getElementsByClassName('TAN_link_custom')].forEach(function(elem) {
    626                 let flag=-2;
    627                 addedKeyWords.forEach(function([kw], index) {
    628                     if(kw.format() === elem.innerHTML.format())
    629                         if(kw.format() !== kw)
    630                             flag = index;
    631                         else
    632                             flag = -1;
    633                 });
    634                
    635                 switch(flag)
    636                 {
    637                     case -2:
    638                         addedKeyWords.push([elem.innerHTML, elem.getAttribute('data-search')]);
    639                         break;
    640                     case -1:
    641                         break;
    642                     default:
    643                         addedKeyWords[flag] = [elem.innerHTML, elem.getAttribute('data-search')];
    644                 }
    645             });
    646 
    647             if(addedKeyWords.length)
    648                 $('#newKWs').html('<br/>Your keywords:<br/> '+addedKeyWords.map(v=>'<span class=newKW>'+v[0]+'</span>').join(', '));
    649             // }}}
    650         }, 500);
    651 
    652        
    653         let ctrlDown = false;
    654         let shiftDown = false;
    655         let lastSel = [];
    656        
    657         /**
    658          * Fast mode, allow to go through the keywords
    659          * with the keyboard, much faster than the mouse
    660          */
    661         $('#navMenu_keyWord').on('keydown', function(e) {
    662             // {{{
    663             let code = e.keyCode;
    664             let sel = $("#navMenu_merchantSelect");
    665             switch(code)
    666             {
    667                 case 39:    // right
    668                     tan_changeFocus(currentFocus+1);
    669                     break;
    670                 case 37:
    671                     tan_changeFocus(currentFocus-1);
    672                     break;  // left
    673                 case 38:    // up
    674                     sel.val((sel.find(':selected').prev('option')[0] || sel.find('option:last-child')[0]).value);
    675                     sel.trigger('change');
    676                     break; 
    677                 case 40:    // down
    678                     sel.val((sel.find(':selected + option')[0] || sel.find('option:first-child')[0]).value);
    679                     sel.trigger('change');
    680                     break;
    681                 case 32: tan_activateKeyWord(ctrlDown?-1:shiftDown?-2:currentFocus); break;             // space
    682                 case 13: tan_activateKeyWord(ctrlDown?-1:shiftDown?-2:currentFocus); break;             // enter
    683 
    684                 case 17: ctrlDown=true; break;
    685                 case 16: shiftDown=true; break;
    686             }
    687 
    688             e.preventDefault();
    689             // }}}
    690         })
    691         $('#navMenu_keyWord').on('keyup', function(e) {
    692             // {{{
    693             if(e.keyCode === 17)
    694                 ctrlDown=false;
    695             if(e.keyCode === 16)
    696                 shiftDown=false;
    697             // }}}
    698         });
    699 
    700         $('#travel_editor, #navMenu tr:last-child').on('mouseup', function(e) {
    701             // {{{
    702             setTimeout(function() {
    703                 if(  !$(e.target).is("#navMenu_merchantSelect")
    704                   && !getSelectionHtml()[0]
    705                   && !$('#googleField').is(':focus'))
    706                     $('#navMenu_keyWord')[0].focus();
    707             }, 100);
    708             // }}}
    709         })
    710 
    711         $('#travel_editor').on('mouseup', function(e) {
    712             // {{{
    713             let sel = getSelectionHtml();
    714             if(!sel[0])
    715                 return;
    716 
    717             lastSel = sel;
    718             $(lastSel[1]).parents('mark.TANKeyWord').replaceWith(function() {
    719                 console.log('this', this);
    720                 return this.innerHTML;
    721             })
    722             sel = sel[0];
    723 
    724             $('#googleField').val(sel.replace(/<[^>]*>/g, ''));
    725             $('#googleField')[0].focus();
    726             // }}}
    727         });
    728 
    729         document.addEventListener('scroll', function (e) {
    730             // {{{
    731             let diff = $(window).scrollTop() - $('#travelads_sectionid .inside').offset().top + 32;
    732 
    733             if(diff >= 0)
    734             {
    735                 $('#navMenu').css({
    736                     position: 'fixed',
    737                     left: $('#travelads_sectionid').offset().left,
    738                     top: $('#wpadminbar').height(),
    739 
    740                     width: $('#travelads_sectionid').width()+2,
    741                 });
    742 
    743                 $('.pac-container.pac-logo').css({
    744                     top:      $('#googleField').offset().top + $('#googleField').height() + 8,
    745                 });
    746 
    747             }
    748             else
    749             {
    750                 $('#navMenu').css({
    751                     position: '',
    752                     left: '',
    753                     top: '',
    754                     width: ''
    755                 });
    756             }
    757             // }}}
    758         }, true);
    759        
    760         initAutocomplete();
    761 
    762 
    763         //
    764         // creation of the Regex (list of keywords) and initializations
    765         //
    766         data=keyWords;
    767         var to_replace = '';
    768         for(var i=0 ; i<data.length ; i++)
    769             to_replace += '\\b'+data[i]+'\\b|';
    770 
    771         to_replace = to_replace.slice(0, -1);
    772 
    773         keyWordsRegExp = new RegExp(to_replace, 'gi')
    774 
    775 
    776         //
    777         // Open the dialog by default
    778         //
    779         setTimeout(function() {
    780             $('#travelads_sectionid.closed button').click();
    781         }, 2000);
    782 
    783         // }}}
    784     });
     17    $(document).ready(function() {
     18        // {{{
     19       
     20        /**
     21         * Changes the current focussed word
     22         *
     23         * @param {int}  nb     The keyWord to highlight -> e.g. if you want to highlight the 5th keyword, nb=4
     24         * @param {bool} scroll Should the screen scroll automatically to this keyword?
     25         */
     26        function tan_changeFocus(nb=0, scroll=true)
     27        {
     28            // {{{
     29            nb = nb || 0;
     30
     31            nb = (nb+$('#travel_editor mark.TANKeyWord').length)%$('#travel_editor mark.TANKeyWord').length
     32
     33            currentFocus = nb;
     34            elem = $('#travel_editor mark.TANKeyWord')[nb];
     35
     36            $('#navMenu_keyWord').val(elem.innerHTML.trim());
     37
     38            $('#travel_editor mark.TANKeyWord').removeClass('current');
     39            $(elem).addClass('current');
     40
     41            $('#navMenu_currentKeyWord').text(nb+1);
     42
     43            if($(elem).hasClass('active'))
     44            {
     45                $('div#navMenu_buttonOnOff').addClass('active');
     46            }
     47            else
     48            {
     49                $('div#navMenu_buttonOnOff').removeClass('active');
     50            }
     51
     52           
     53            $('select#navMenu_merchantSelect').val($(elem).attr('data-adv') || $('select#navMenu_merchantSelect option')[0].value);
     54
     55            if(scroll)
     56            {
     57                switch( window.editorName ) {
     58                    case 'gutenberg':
     59                        $('.edit-post-layout__content').clearQueue();
     60                        $('.edit-post-layout__content').animate({
     61                            scrollTop: $(elem).offset().top-$('.edit-post-visual-editor').offset().top-$(window).height()/2
     62                        }, 500);
     63                        break;
     64                    case 'tinymce':
     65                        $('html, body').clearQueue();
     66                        $('html, body').animate({
     67                            scrollTop: $(elem).offset().top-$(window).height()/2
     68                        }, 500);
     69                        break;
     70                }
     71            }
     72            // }}}
     73        }
     74
     75        function setWpEditorContent(content)
     76        {
     77            // {{{
     78            if(!window.editorName)
     79                window.editorName = (!!wp.data)?'gutenberg':
     80                             (!!tinymce || !!tinyMCE)?'tinymce':
     81                             '';
     82
     83            switch(editorName)
     84            {
     85                case 'gutenberg':
     86                    wp.data.dispatch( 'core/editor' ).resetBlocks([]);
     87
     88                    var editedContent = wp.data.select( "core/editor" ).getEditedPostContent();
     89                    var newBlocks = wp.blocks.parse( content );
     90                    wp.data.dispatch( "core/editor" ).insertBlocks( newBlocks );
     91
     92                    return true;
     93                case 'tinymce':
     94
     95                    if(!!tinymce.activeEditor)
     96                        tinymce.activeEditor.setContent( content )
     97                    else if(!!tinyMCE.activeEditor)
     98                        tinyMCE.activeEditor.setContent( content )
     99
     100                    $('#content').val($('#hidden_travel_editor').html());
     101
     102                    return true;
     103            }
     104
     105            return false;
     106            // }}}
     107        }
     108
     109        function getWpEditorContent()
     110        {
     111            // {{{
     112            if(!window.editorName)
     113                window.editorName = (!!wp.data)?'gutenberg':
     114                             (!!tinymce || !!tinyMCE)?'tinymce':
     115                             '';
     116
     117            switch(editorName)
     118            {
     119                case 'gutenberg':
     120                    return wp.data.select('core/editor').getEditedPostContent();
     121                case 'tinymce':
     122                    if($('#content').attr('aria-hidden') === 'true') // text editor
     123                        return (window.tinymce || window.tinyMCE).activeEditor.getContent();
     124                    else
     125                        return $('#content').val().replace(/\n/g, '<br/>');
     126            }
     127
     128            return '';
     129            // }}}
     130        }
     131
     132
     133        /**
     134         * Finds the position of a keyWord
     135         *
     136         * @param {DomNode}    e         The Dom node representing the keyword, has to be a `<mark class='TANKeyWord'>`
     137         * @param {jQueryNode} container The container in which the keyWords are. travel editor by default.
     138         *
     139         * @return {number} the position of this specific keyword, or 0 if not found
     140         */
     141
     142        function tan_domToNum(e, container=$('#travel_editor'))
     143        {
     144            // {{{
     145            list = container.find(' mark.TANKeyWord');
     146            for(var i=0 ; i<list.length ; i++)
     147                if(list[i] === e)
     148                    return i;
     149            return 0;
     150            // }}}
     151        }
     152
     153        /**
     154         * function called when a keyWord is clicked
     155         *
     156         * @param {DomNode} e The clicked keyWord, has to be a `<mark class='TANKeyWord'>`
     157         */
     158        function tan_clickMark(e)
     159        {
     160            tan_changeFocus(tan_domToNum(e));
     161        }
     162
     163        /**
     164         * Toggle the specified keyWord on and off
     165         *
     166         * @param {number} foc  The keyword to activate, the currently focussed keyword by default, pass -1 to toggle all, -2 to toggle all custom keywords
     167         * @param {bool}   flag Weather the keyWord should be activated or deactivated. Toggle by default
     168         */
     169        function tan_activateKeyWord(foc = currentFocus, flag = undefined)
     170        {
     171            // {{{
     172            if(foc < 0)
     173            {
     174                let classes = ['TANKeyWord'];
     175                if(foc === -2)
     176                    classes.push('custom');
     177
     178                let classesStr = classes.map(v=>'.'+v).join('');
     179
     180                f = $('#travel_editor mark.active'+classesStr).length
     181                    - $('#travel_editor mark'+classesStr+':not(.active)').length
     182
     183
     184                f = f<0;
     185               
     186                $('#travel_editor mark'+classesStr).each(function() {
     187                    tan_activateKeyWord(tan_domToNum(this), f);
     188                });
     189
     190                return;
     191            }
     192
     193            elem = $('#travel_editor mark.TANKeyWord').eq(foc);
     194
     195            if(elem.hasClass('active') === flag)
     196                return;
     197
     198            if(flag === undefined)
     199                elem.toggleClass('active');
     200            else
     201                elem.toggleClass('active', flag);
     202
     203            elem.attr('data-adv', $('#navMenu_merchantSelect').find('option:selected').val());
     204
     205
     206            if(foc === currentFocus) $('#navMenu_buttonOnOff').toggleClass('active', elem.hasClass('active'));
     207
     208            // }}}
     209        }
     210
     211        var totalReplaced = 0;
     212       
     213        /**
     214         * Surround the keyWords by <mark>, to highlighting it
     215         *
     216         * @param {RegExp} target regular expression matching the targeted keywords
     217         */
     218        jQuery.fn.tan_highlightKeywords = function(target)
     219        {
     220            // {{{
     221            //
     222            // Get all text nodes:
     223            //
     224            excludeNodes = ['SCRIPT', 'STYLE', 'H1', 'H2', 'H3', 'H4'];
     225            var $textNodes = this
     226                                .find("*")
     227                                .andSelf()
     228                                .contents()
     229                                .filter(function() {
     230                                    return  this.nodeType === 3
     231                                         && !$(this).parent("a").length
     232                                         && !$(this).parent(excludeNodes.join(',')).length
     233                                         && !$(this).parent('.TANKeyWord').length
     234                                         && !$(this).parent('.TAN_link').length
     235                                         && $(this).text().trim() !== '';
     236                                });
     237
     238            //
     239            // Iterate through the text nodes,
     240            // highlighting the keyWords
     241            //
     242
     243            let totalReplaced = 0;
     244            $textNodes.each(function(index, element) {
     245
     246                match = $(element).text().match(target);
     247
     248                if(match === null) return;
     249                var i=0;
     250                var finalStr = $(element).text().replace(target, function(word) {
     251                    var originalWord = match[i];
     252                    i++;
     253                    totalReplaced++;
     254
     255                    let tmp = addedKeyWords.filter(v=>v[0].format() === originalWord.format())
     256                   
     257                    let c = '';
     258                    let s = originalWord;
     259                    if(tmp.length)
     260                    {
     261                        c = ' custom'
     262                        s = tmp[0][1];
     263                    }
     264
     265                    return `<mark class='TANKeyWord`+c+`'
     266                                  data-search='`+s+`'
     267                                  data-adv=`+$('#navMenu_merchantSelect').find('option:selected').val()+`>`
     268                              + originalWord
     269                              + `</mark>`;
     270                });
     271
     272                $(element).replaceWith(finalStr);
     273            });
     274
     275            $('.TANKeyWord').each(function(i) {
     276               
     277                this.onclick = function() {
     278                    tan_changeFocus(tan_domToNum(this));
     279                };
     280            });
     281           
     282            // }}}
     283        }
     284
     285        /**
     286         * copy content from the travel ads network editor to the tiny MCE
     287         */
     288        function copy_travel2wp()
     289        {
     290            // {{{
     291
     292            $('#hidden_travel_editor').html(($('div#travel_editor').html()));
     293
     294            $('#hidden_travel_editor mark.TANKeyWord').each(function() {
     295                $(this).replaceWith(this.outerHTML.replace(/\bmark\b/, 'span'))
     296            });
     297
     298            $('#hidden_travel_editor span.TANKeyWord').each(function() {
     299                $(this).removeClass('current');
     300                $(this).removeClass('TANKeyWord');
     301
     302                $(this).addClass('TAN_link');
     303
     304                if($(this).hasClass('custom'))
     305                    $(this).addClass('TAN_link_custom');
     306            });
     307
     308            $('#hidden_travel_editor section.tan_asset').each(function(i) {
     309                $(this).find('script').siblings().remove();
     310            });
     311
     312            setWpEditorContent( $('#hidden_travel_editor').html() );
     313
     314            $('div#travel_editor section.tan_asset script').remove();
     315
     316            $('#hidden_travel_editor').empty();
     317
     318            // }}}
     319        }
     320       
     321        /**
     322         * Copy the content from the wordpress editor to the travel editor
     323         *
     324         * @param {string} content The content to put in the travel editor
     325         */
     326        function copy_wp2travel()
     327        {
     328            // {{{
     329            content = getWpEditorContent();
     330
     331
     332            if(  $('div.mce-tinymce').css('display') === 'none' // normal mode
     333              && $('wp-editor-area').css('display') === 'none') // text mode
     334            {
     335                $('#travel_editor').html('<h1 style="text-align:center">The visual Editor is not supported for now...</h1>'+
     336                                        '<h2 style="text-align:center">You can switch to autmatic mode!</h2>');
     337                $('#navMenu').toggle(false);
     338                $('#navMenuOffset').toggle(false);
     339                return;
     340            }
     341            $('#navMenu').toggle(true);
     342            $('#navMenuOffset').toggle(true);
     343
     344             totalReplaced = 0;
     345            $('#travel_editor').empty();
     346            $('#travel_editor').html(content);
     347            $('#travel_editor span.TAN_link').each(function() {
     348
     349                $(this).replaceWith(this.outerHTML.replace(/\bspan\b/, 'mark'))
     350            });
     351
     352            $('#travel_editor mark.TAN_link').each(function() {
     353                $(this).removeClass('TAN_link_custom');
     354                $(this).removeClass('TAN_link');
     355
     356                $(this).addClass('TANKeyWord');
     357            });
     358
     359
     360            $("#travel_editor").tan_highlightKeywords(createNewRegexp(keyWordsRegExp, addedKeyWords.map(v=>v[0])));
     361           
     362            tan_changeFocus(currentFocus, false);
     363            $('#navMenu_totalKeyWord').text($('#travel_editor mark.TANKeyWord').length);
     364            // }}}
     365        }
     366
     367        /**
     368         * Transforms the google places API response to a easier to use object
     369         *
     370         * @param {Object} googlePlace The object returned by the google places API
     371         *                              -> ```
     372         *                                 {
     373         *                                     address_coponents: <array of addresses>,
     374         *                                     geometry:          <geometry, lat, long>,
     375         *                                     types:             <array of types>,
     376         *                                     name:              <name>
     377         *                                 }
     378         *                                 ```
     379         *
     380         * @return {Object} An easier to use array
     381         *                   -> ```
     382         *                      {
     383         *                          country:     <country name>,
     384         *                          city:        <city name>,
     385         *                          route:       <route name>,
     386         *                          searchText:  <text to search on the OTA's calls>,
     387         *                          lat:         <latitude>,
     388         *                          lng:         <longitude>,
     389         *                      }
     390         *                     
     391         *                      ```
     392         */
     393        function googlePlaceFormat(googlePlace)
     394        {
     395            // {{{
     396            let out = {};
     397
     398            let cityWrongNames = {
     399                'Krung Thep Maha Nakhon': 'Bangkok',
     400                'Krong Siem Reap': 'Siem Reap'
     401            };
     402
     403            let address = googlePlace.address_components.filter(v=>  !/^[0-9]*$/.test(v.long_name)        // exclude only numbers
     404                                                                  && !/^[0-9]/.test(v.long_name));      // and  beginning with number
     405
     406            let country             = address.filter(v=>v.types.contains('country')),
     407                administrativeAreas = address.filter(v=>v.types.some(t=>/administrative_area_level_\d+/.test(t))),
     408                city                = address.filter(v=>v.types.contains('locality', 'postal_town')),
     409                subLocalAreas       = address.filter(v=>v.types.some(t=>/sublocality_level_\d+/.test(t))),
     410                route               = address.filter(v=>v.types.contains('route'));
     411
     412            out = {
     413                'lat': googlePlace.geometry.location.lat(),
     414                'lng': googlePlace.geometry.location.lng()
     415            };
     416
     417            if(country.length)
     418            {
     419                out.country=country[0].long_name;
     420                if(city.length)
     421                {
     422                    out.city=city[0].long_name;
     423
     424                    if(route.length)
     425                        out.route = route[0].long_name
     426                    else if(subLocalAreas.length)
     427                        out.route = subLocalAreas[0].long_name
     428                }
     429                else if(administrativeAreas.length)
     430                    out.city = administrativeAreas[0].long_name;
     431            }
     432
     433            out.city = cityWrongNames[out.city] || out.city;
     434           
     435           
     436            if(!googlePlace.types.contains('lodging', 'premise'))    // is not lodging nor premise
     437                out.searchText = (out.route||'')+', '+(out.city||'')+', '+(out.country||'');
     438            else
     439                out.searchText = (googlePlace.name||'')+', '+(out.city||'')+', '+(out.country||'');
     440           
     441            out.searchText = out.searchText.replace(/^[, ]+/, '');
     442
     443            return out;
     444            // }}}
     445        }
     446
     447        /**
     448         * Adds the custom keyWords to the original regexp
     449         *
     450         * @param {RegExp} original The original regexp conaining all the default keywords
     451         * @param {Array}  keyWords A list of the new keyWords
     452         */
     453        function createNewRegexp(original, keyWords)
     454        {
     455            // {{{
     456            //
     457            // transforming the old regexp to string
     458            //
     459            let newRegExp = original.toString().replace(/^[^\/]*\/|\/[^/]*$/g, '');
     460           
     461            //
     462            // adding the new keyWords
     463            //
     464            for(let i=0 ; i<keyWords.length ; i++)
     465            {
     466                newRegExp += '|';
     467                if(/^[a-z]/i.test(keyWords[i])) // begin with latin alphabet
     468                    newRegExp += '\\b';
     469
     470                newRegExp += keyWords[i].replace(/([\[\]\(\)\+\/\*\?])/g, '\\$1');    // making the word regex proof
     471
     472                if(/[a-z]$/i.test(keyWords[i])) // finish with latin alphabet
     473                    newRegExp += '\\b';
     474
     475                newRegExp += '|';
     476                if(/^[a-z]/i.test(keyWords[i])) // begin with latin alphabet
     477                    newRegExp += '\\b';
     478
     479                newRegExp += keyWords[i].format().replace(/([\[\]\(\)\+\/\*\?])/g, '\\$1');    // making the word regex proof
     480
     481                if(/[a-z]$/i.test(keyWords[i])) // finish with latin alphabet
     482                    newRegExp += '\\b';
     483
     484            }
     485
     486            return new RegExp(newRegExp, 'gi');
     487            // }}}
     488        }
     489       
     490        /**
     491         * initialize the google places field
     492         */
     493        function initAutocomplete()
     494        {
     495            // {{{
     496            //
     497            // avoids to trigger the page reload on enter
     498            //
     499            $('#googleField').on('keydown', function(e){if(e.keyCode===13) e.preventDefault()});
     500
     501            googleField = new google.maps.places.Autocomplete($('#googleField')[0],
     502                                  {types: ['establishment', 'geocode']});
     503
     504            googleField.setFields(['address_components',
     505                       'geometry',
     506                       'types',
     507                       'name']);
     508
     509            googleField.addListener('place_changed', fillInAddress);
     510            // }}}
     511        }
     512       
     513        /**
     514         * Callback, listen to google places field
     515         */
     516        function fillInAddress()
     517        {
     518            // {{{
     519            var place = googleField.getPlace();
     520
     521            $('#googleField').addClass('kwAdd');
     522            let kw = $('#googleField').val().split(/, ?/)[0]
     523
     524            if(lastSel[0])
     525            {
     526                let start = $(lastSel[1].startContainer).parents('mark.TANKeyWord');
     527                let end = $(lastSel[1].endContainer).parents('mark.TANKeyWord');
     528
     529                lastSel[1].deleteContents();
     530                lastSel[1].insertNode(document.createTextNode(' '+kw+' '));
     531
     532                start.replaceWith(function() { return this.innerHTML });
     533                end.replaceWith(function() { return this.innerHTML });
     534            }
     535
     536            if(  !addedKeyWords.map(v=>v[0].format()).contains(kw.format())
     537              && (  !kw.match(keyWordsRegExp)
     538                 || kw.match(keyWordsRegExp)[0] !== kw ))    // kw not present in predefined keywords
     539                addedKeyWords.push([kw, googlePlaceFormat(place).searchText]);
     540
     541            if(addedKeyWords.length)
     542                $('#newKWs').html('<br/>Your keywords:<br/> '+addedKeyWords.map(v=>'<span class=newKW>'+v[0]+'</span>').join(' '));
     543
     544
     545            $('span.newKW').click(function() {
     546                let kw = this.innerHTML;
     547
     548                addedKeyWords = addedKeyWords.filter(v=>v[0] !== kw);
     549                $('.TANKeyWord').each(function() {
     550                    if(kw.format() === this.innerText.format())
     551                        $(this).replaceWith(this.innerHTML);
     552                });
     553                $(this).remove();
     554            });
     555
     556
     557            $("#travel_editor").tan_highlightKeywords(createNewRegexp(keyWordsRegExp, addedKeyWords.map(v=>v[0])));
     558            // }}}
     559        }
     560
     561        //
     562        // setting up the listeners
     563        //
     564
     565        $('div#navMenu_buttonOnOff').click(function(){
     566            tan_activateKeyWord();
     567        });
     568
     569        $('div[aria-label="TravelAdsNetwork Manual"]').click(function() {
     570            // {{{
     571            if($('#travelads_sectionid').css('display') === 'none')
     572            {
     573                setTimeout(function() {
     574                    $('html, body').animate({
     575                        scrollTop: $('#travelads_sectionid').offset().top - 100
     576                    }, 500);
     577                }, 300);
     578            }
     579            // }}}
     580        });
     581
     582        $('#navMenu_merchantSelect').change(function() {
     583            $('.TANKeyWord.current').attr('data-adv', $(this).find('option:selected').val())
     584            tan_activateKeyWord(undefined, true);
     585        });
     586
     587        $('#navMenuprevWord').click(function() {
     588            tan_changeFocus(currentFocus-1);
     589        });
     590        $('#navMenunextWord').click(function() {
     591            tan_changeFocus(currentFocus+1);
     592        });
     593
     594        $('#publish').click(function() {
     595            copy_travel2wp();
     596        });
     597
     598       
     599        let stop=setInterval(function() {
     600            // {{{
     601            if(!(window.tinyMCE || window.tinymce).editors)
     602                return;
     603
     604            clearInterval(stop);
     605
     606            $('#travelads_sectionid').on('mouseenter', function() {
     607                if(!travelFocused)
     608                {
     609                    travelFocused = true;
     610                    copy_wp2travel();
     611                }
     612
     613                let editors = ['#content', '.editor-writing-flow']
     614                          .concat([...(window.tinyMCE || window.tinymce).editors]
     615                              .map(v=>(!!v.container && '#'+v.container.getAttribute('id') || '')))
     616                          .filter(v=>!!v)
     617                          .join(', ');
     618
     619                $(editors).each(function() {
     620                    this.onmouseenter = function() {
     621                        if(travelFocused)
     622                            copy_travel2wp();
     623                        travelFocused = false;
     624                    };
     625                });
     626            });
     627
     628            let content = getWpEditorContent();
     629           
     630            var parser = new DOMParser();
     631            data = parser.parseFromString('<html>'+content.replace(/&nbsp;/g, '<br/>')+'</html>', "text/xml");
     632           
     633            [...data.getElementsByClassName('TAN_link_custom')].forEach(function(elem) {
     634                let flag=-2;
     635                addedKeyWords.forEach(function([kw], index) {
     636                    if(kw.format() === elem.innerHTML.format())
     637                        if(kw.format() !== kw)
     638                            flag = index;
     639                        else
     640                            flag = -1;
     641                });
     642               
     643                switch(flag)
     644                {
     645                    case -2:
     646                        addedKeyWords.push([elem.innerHTML, elem.getAttribute('data-search')]);
     647                        break;
     648                    case -1:
     649                        break;
     650                    default:
     651                        addedKeyWords[flag] = [elem.innerHTML, elem.getAttribute('data-search')];
     652                }
     653            });
     654
     655            if(addedKeyWords.length)
     656                $('#newKWs').html('<br/>Your keywords:<br/> '+addedKeyWords.map(v=>'<span class=newKW>'+v[0]+'</span>').join(', '));
     657            // }}}
     658        }, 500);
     659
     660       
     661        let ctrlDown = false;
     662        let shiftDown = false;
     663        let lastSel = [];
     664       
     665        /**
     666         * Fast mode, allow to go through the keywords
     667         * with the keyboard, much faster than the mouse
     668         */
     669        $('#navMenu_keyWord').on('keydown', function(e) {
     670            // {{{
     671            let code = e.keyCode;
     672            let sel = $("#navMenu_merchantSelect");
     673            switch(code)
     674            {
     675                case 39:     // right
     676                    tan_changeFocus(currentFocus+1);
     677                    break;
     678                case 37:
     679                    tan_changeFocus(currentFocus-1);
     680                    break;    // left
     681                case 38:    // up
     682                    sel.val((sel.find(':selected').prev('option')[0] || sel.find('option:last-child')[0]).value);
     683                    sel.trigger('change');
     684                    break;   
     685                case 40:     // down
     686                    sel.val((sel.find(':selected + option')[0] || sel.find('option:first-child')[0]).value);
     687                    sel.trigger('change');
     688                    break;
     689                case 32: tan_activateKeyWord(ctrlDown?-1:shiftDown?-2:currentFocus); break;             // space
     690                case 13: tan_activateKeyWord(ctrlDown?-1:shiftDown?-2:currentFocus); break;             // enter
     691
     692                case 17: ctrlDown=true; break;
     693                case 16: shiftDown=true; break;
     694            }
     695
     696            e.preventDefault();
     697            // }}}
     698        })
     699        $('#navMenu_keyWord').on('keyup', function(e) {
     700            // {{{
     701            if(e.keyCode === 17)
     702                ctrlDown=false;
     703            if(e.keyCode === 16)
     704                shiftDown=false;
     705            // }}}
     706        });
     707
     708        $('#travel_editor, #navMenu tr:last-child').on('mouseup', function(e) {
     709            // {{{
     710            setTimeout(function() {
     711                if(  !$(e.target).is("#navMenu_merchantSelect")
     712                  && !getSelectionHtml()[0]
     713                  && !$('#googleField').is(':focus'))
     714                    $('#navMenu_keyWord')[0].focus();
     715            }, 100);
     716            // }}}
     717        })
     718
     719        $('#travel_editor').on('mouseup', function(e) {
     720            // {{{
     721            let sel = getSelectionHtml();
     722            if(!sel[0])
     723                return;
     724
     725            lastSel = sel;
     726            $(lastSel[1]).parents('mark.TANKeyWord').replaceWith(function() {
     727                return this.innerHTML;
     728            })
     729            sel = sel[0];
     730
     731            $('#googleField').val(sel.replace(/<[^>]*>/g, ''));
     732            $('#googleField')[0].focus();
     733            // }}}
     734        });
     735
     736        document.addEventListener('scroll', function (e) {
     737            // {{{
     738           
     739            let topOffset = $('#wpadminbar').height() +
     740                            (window.editorName==='gutenberg'?$('.edit-post-header').outerHeight():0) +
     741                            ((tmp=$('.components-notice')).length?[...tmp].reduce((a,v)=>a+$(v).outerHeight(),0):0),
     742                diff = $(window).scrollTop() - $('#travelads_sectionid .inside').offset().top + topOffset;
     743
     744            if(diff >= 0)
     745            {
     746                $('#navMenu').toggleClass('flying', true);
     747
     748                $('#navMenu').css({
     749                    position: 'fixed',
     750                    left: $('#travelads_sectionid').offset().left,
     751                    top: topOffset,
     752
     753                    width: $('#travelads_sectionid').width()+2,
     754                });
     755
     756                $('.pac-container.pac-logo').css({
     757                    top:      $('#googleField').offset().top + $('#googleField').height() + 8,
     758                });
     759
     760            }
     761            else
     762            {
     763                $('#navMenu').toggleClass('flying', false);
     764                $('#navMenu').css({
     765                    position: '',
     766                    left: '',
     767                    top: '',
     768                    width: ''
     769                });
     770            }
     771            // }}}
     772        }, true);
     773       
     774        initAutocomplete();
     775
     776
     777        //
     778        // creation of the Regex (list of keywords) and initializations
     779        //
     780        data=keyWords;
     781        var to_replace = '';
     782        for(var i=0 ; i<data.length ; i++)
     783            to_replace += '\\b'+data[i]+'\\b|';
     784
     785        to_replace = to_replace.slice(0, -1);
     786
     787        keyWordsRegExp = new RegExp(to_replace, 'gi')
     788
     789
     790        //
     791        // Open the dialog by default
     792        //
     793        setTimeout(function() {
     794            $('#travelads_sectionid.closed button').click();
     795        }, 2000);
     796
     797        // }}}
     798    });
    785799}(jQuery);
  • traveladsnetwork-com/tags/1.0/readme.txt

    r2083453 r2083624  
    22Contributors: christophesecher
    33Tags: travel, affiliate plugin, affiliate link, deeplinking, deeplinks, travel booking, bloggers, merchant, travel content, travel, Travel Ads Network, Traveladsnetwork, content monetization, creating purchase intent, monetize your audience data, Convert Content into Revenue
    4 Tags: travel affiliate plugin affiliate link deeplinking deeplinks travel booking bloggers merchant travel content travel Travel Ads Network Traveladsnetwork content monetization creating purchase intent monetize your audience data Convert Content into Revenue
    54Requires at least: 4.9.0
    6 Tested up to: 4.9.8
     5Tested up to: 5.2
    76Requires PHP: 7.3
    87Stable tag: trunk
  • traveladsnetwork-com/trunk/assets/css/pageEdit.css

    r2083453 r2083624  
    2020table#navMenu
    2121{
     22    box-sizing: border-box;
    2223    width:100%;
    2324    position:absolute;
     
    2829    padding:0 20px;
    2930    z-index: 1;
     31
     32    transition: border-radius .3s,
     33                box-shadow .3s;
     34}
     35table#navMenu.flying {
     36    border-radius: 0 0 10px 10px;
     37    box-shadow: 0px 5px 10px 5px #0003;
    3038}
    3139table#navMenu td
  • traveladsnetwork-com/trunk/assets/js/pageEdit.js

    r2083453 r2083624  
    1515
    1616!function($) {
    17     $(document).ready(function() {
    18         // {{{
    19        
    20         /**
    21          * Changes the current focussed word
    22          *
    23          * @param {int}  nb     The keyWord to highlight -> e.g. if you want to highlight the 5th keyword, nb=4
    24          * @param {bool} scroll Should the screen scroll automatically to this keyword?
    25          */
    26         function tan_changeFocus(nb=0, scroll=true)
    27         {
    28             // {{{
    29             nb = nb || 0;
    30 
    31             nb = (nb+$('#travel_editor mark.TANKeyWord').length)%$('#travel_editor mark.TANKeyWord').length
    32 
    33             currentFocus = nb;
    34             elem = $('#travel_editor mark.TANKeyWord')[nb];
    35 
    36             $('#navMenu_keyWord').val(elem.innerHTML.trim());
    37 
    38             $('#travel_editor mark.TANKeyWord').removeClass('current');
    39             $(elem).addClass('current');
    40 
    41             $('#navMenu_currentKeyWord').text(nb+1);
    42 
    43             if($(elem).hasClass('active'))
    44             {
    45                 $('div#navMenu_buttonOnOff').addClass('active');
    46             }
    47             else
    48             {
    49                 $('div#navMenu_buttonOnOff').removeClass('active');
    50             }
    51 
    52            
    53             $('select#navMenu_merchantSelect').val($(elem).attr('data-adv') || $('select#navMenu_merchantSelect option')[0].value);
    54 
    55             if(scroll)
    56             {
    57                 $('html, body').clearQueue();
    58                 $('html, body').animate({
    59                     scrollTop: $(elem).offset().top-$(window).height()/2
    60                 }, 500);
    61             }
    62             // }}}
    63         }
    64 
    65         function setWpEditorContent(content)
    66         {
    67             // {{{
    68             if(!window.editorName)
    69                 window.editorName = (!!wp.data)?'gutenberg':
    70                              (!!tinymce || !!tinyMCE)?'tinymce':
    71                              '';
    72 
    73             switch(editorName)
    74             {
    75                 case 'gutenberg':
    76                     wp.data.dispatch( 'core/editor' ).resetBlocks([]);
    77 
    78                     var editedContent = wp.data.select( "core/editor" ).getEditedPostContent();
    79                     var newBlocks = wp.blocks.parse( content );
    80                     wp.data.dispatch( "core/editor" ).insertBlocks( newBlocks );
    81 
    82                     return true;
    83                 case 'tinymce':
    84 
    85                     if(!!tinymce.activeEditor)
    86                         tinymce.activeEditor.setContent( content )
    87                     else if(!!tinyMCE.activeEditor)
    88                         tinyMCE.activeEditor.setContent( content )
    89 
    90                     $('#content').val($('#hidden_travel_editor').html());
    91 
    92                     return true;
    93             }
    94 
    95             return false;
    96             // }}}
    97         }
    98 
    99         function getWpEditorContent()
    100         {
    101             // {{{
    102             if(!window.editorName)
    103                 window.editorName = (!!wp.data)?'gutenberg':
    104                              (!!tinymce || !!tinyMCE)?'tinymce':
    105                              '';
    106 
    107             switch(editorName)
    108             {
    109                 case 'gutenberg':
    110                     return wp.data.select('core/editor').getEditedPostContent();
    111                 case 'tinymce':
    112                     if($('#content').attr('aria-hidden') === 'true') // text editor
    113                         return (window.tinymce || window.tinyMCE).activeEditor.getContent();
    114                     else
    115                         return $('#content').val().replace(/\n/g, '<br/>');
    116             }
    117 
    118             return '';
    119             // }}}
    120         }
    121 
    122 
    123         /**
    124          * Finds the position of a keyWord
    125          *
    126          * @param {DomNode}    e         The Dom node representing the keyword, has to be a `<mark class='TANKeyWord'>`
    127          * @param {jQueryNode} container The container in which the keyWords are. travel editor by default.
    128          *
    129          * @return {number} the position of this specific keyword, or 0 if not found
    130          */
    131 
    132         function tan_domToNum(e, container=$('#travel_editor'))
    133         {
    134             // {{{
    135             list = container.find(' mark.TANKeyWord');
    136             for(var i=0 ; i<list.length ; i++)
    137                 if(list[i] === e)
    138                     return i;
    139             return 0;
    140             // }}}
    141         }
    142 
    143         /**
    144          * function called when a keyWord is clicked
    145          *
    146          * @param {DomNode} e The clicked keyWord, has to be a `<mark class='TANKeyWord'>`
    147          */
    148         function tan_clickMark(e)
    149         {
    150             tan_changeFocus(tan_domToNum(e));
    151         }
    152 
    153         /**
    154          * Toggle the specified keyWord on and off
    155          *
    156          * @param {number} foc  The keyword to activate, the currently focussed keyword by default, pass -1 to toggle all, -2 to toggle all custom keywords
    157          * @param {bool}   flag Weather the keyWord should be activated or deactivated. Toggle by default
    158          */
    159         function tan_activateKeyWord(foc = currentFocus, flag = undefined)
    160         {
    161             // {{{
    162             if(foc < 0)
    163             {
    164                 let classes = ['TANKeyWord'];
    165                 if(foc === -2)
    166                     classes.push('custom');
    167 
    168                 let classesStr = classes.map(v=>'.'+v).join('');
    169 
    170                 f = $('#travel_editor mark.active'+classesStr).length
    171                     - $('#travel_editor mark'+classesStr+':not(.active)').length
    172 
    173 
    174                 f = f<0;
    175                
    176                 $('#travel_editor mark'+classesStr).each(function() {
    177                     tan_activateKeyWord(tan_domToNum(this), f);
    178                 });
    179 
    180                 return;
    181             }
    182 
    183             elem = $('#travel_editor mark.TANKeyWord').eq(foc);
    184 
    185             if(elem.hasClass('active') === flag)
    186                 return;
    187 
    188             if(flag === undefined)
    189                 elem.toggleClass('active');
    190             else
    191                 elem.toggleClass('active', flag);
    192 
    193             elem.attr('data-adv', $('#navMenu_merchantSelect').find('option:selected').val());
    194 
    195 
    196             if(foc === currentFocus) $('#navMenu_buttonOnOff').toggleClass('active', elem.hasClass('active'));
    197 
    198             // }}}
    199         }
    200 
    201         var totalReplaced = 0;
    202        
    203         /**
    204          * Surround the keyWords by <mark>, to highlighting it
    205          *
    206          * @param {RegExp} target regular expression matching the targeted keywords
    207          */
    208         jQuery.fn.tan_highlightKeywords = function(target)
    209         {
    210             // {{{
    211             //
    212             // Get all text nodes:
    213             //
    214             excludeNodes = ['SCRIPT', 'STYLE', 'H1', 'H2', 'H3', 'H4'];
    215             var $textNodes = this
    216                                 .find("*")
    217                                 .andSelf()
    218                                 .contents()
    219                                 .filter(function() {
    220                                     return  this.nodeType === 3
    221                                          && !$(this).parent("a").length
    222                                          && !$(this).parent(excludeNodes.join(',')).length
    223                                          && !$(this).parent('.TANKeyWord').length
    224                                          && !$(this).parent('.TAN_link').length
    225                                          && $(this).text().trim() !== '';
    226                                 });
    227 
    228             //
    229             // Iterate through the text nodes,
    230             // highlighting the keyWords
    231             //
    232 
    233             let totalReplaced = 0;
    234             $textNodes.each(function(index, element) {
    235 
    236                 match = $(element).text().match(target);
    237 
    238                 if(match === null) return;
    239                 var i=0;
    240                 var finalStr = $(element).text().replace(target, function(word) {
    241                     var originalWord = match[i];
    242                     i++;
    243                     totalReplaced++;
    244 
    245                     let tmp = addedKeyWords.filter(v=>v[0].format() === originalWord.format())
    246                    
    247                     let c = '';
    248                     let s = originalWord;
    249                     if(tmp.length)
    250                     {
    251                         c = ' custom'
    252                         s = tmp[0][1];
    253                     }
    254 
    255                     return `<mark class='TANKeyWord`+c+`'
    256                                   data-search='`+s+`'
    257                                   data-adv=`+$('#navMenu_merchantSelect').find('option:selected').val()+`>`
    258                               + originalWord
    259                               + `</mark>`;
    260                 });
    261 
    262                 $(element).replaceWith(finalStr);
    263             });
    264 
    265             $('.TANKeyWord').each(function(i) {
    266                
    267                 this.onclick = function() {
    268                     tan_changeFocus(tan_domToNum(this));
    269                 };
    270             });
    271            
    272             // }}}
    273         }
    274 
    275         /**
    276          * copy content from the travel ads network editor to the tiny MCE
    277          */
    278         function copy_travel2wp()
    279         {
    280             // {{{
    281 
    282             $('#hidden_travel_editor').html(($('div#travel_editor').html()));
    283 
    284             $('#hidden_travel_editor mark.TANKeyWord').each(function() {
    285                 $(this).replaceWith(this.outerHTML.replace(/\bmark\b/, 'span'))
    286             });
    287 
    288             $('#hidden_travel_editor span.TANKeyWord').each(function() {
    289                 $(this).removeClass('current');
    290                 $(this).removeClass('TANKeyWord');
    291 
    292                 $(this).addClass('TAN_link');
    293 
    294                 if($(this).hasClass('custom'))
    295                     $(this).addClass('TAN_link_custom');
    296             });
    297 
    298             $('#hidden_travel_editor section.tan_asset').each(function(i) {
    299                 $(this).find('script').siblings().remove();
    300             });
    301 
    302             setWpEditorContent( $('#hidden_travel_editor').html() );
    303 
    304             $('div#travel_editor section.tan_asset script').remove();
    305 
    306             $('#hidden_travel_editor').empty();
    307 
    308             // }}}
    309         }
    310        
    311         /**
    312          * Copy the content from the wordpress editor to the travel editor
    313          *
    314          * @param {string} content The content to put in the travel editor
    315          */
    316         function copy_wp2travel()
    317         {
    318             // {{{
    319             content = getWpEditorContent();
    320 
    321 
    322             if(  $('div.mce-tinymce').css('display') === 'none' // normal mode
    323               && $('wp-editor-area').css('display') === 'none') // text mode
    324             {
    325                 $('#travel_editor').html('<h1 style="text-align:center">The visual Editor is not supported for now...</h1>'+
    326                                         '<h2 style="text-align:center">You can switch to autmatic mode!</h2>');
    327                 $('#navMenu').toggle(false);
    328                 $('#navMenuOffset').toggle(false);
    329                 return;
    330             }
    331             $('#navMenu').toggle(true);
    332             $('#navMenuOffset').toggle(true);
    333 
    334             totalReplaced = 0;
    335             $('#travel_editor').empty();
    336             $('#travel_editor').html(content);
    337             $('#travel_editor span.TAN_link').each(function() {
    338 
    339                 $(this).replaceWith(this.outerHTML.replace(/\bspan\b/, 'mark'))
    340             });
    341 
    342             $('#travel_editor mark.TAN_link').each(function() {
    343                 $(this).removeClass('TAN_link_custom');
    344                 $(this).removeClass('TAN_link');
    345 
    346                 $(this).addClass('TANKeyWord');
    347             });
    348 
    349 
    350             $("#travel_editor").tan_highlightKeywords(createNewRegexp(keyWordsRegExp, addedKeyWords.map(v=>v[0])));
    351            
    352             console.log('here');
    353             tan_changeFocus(currentFocus, false);
    354             console.log('not here');
    355             $('#navMenu_totalKeyWord').text($('#travel_editor mark.TANKeyWord').length);
    356             // }}}
    357         }
    358 
    359         /**
    360          * Transforms the google places API response to a easier to use object
    361          *
    362          * @param {Object} googlePlace The object returned by the google places API
    363          *                              -> ```
    364          *                                 {
    365          *                                  address_coponents: <array of addresses>,
    366          *                                  geometry:          <geometry, lat, long>,
    367          *                                  types:             <array of types>,
    368          *                                  name:              <name>
    369          *                                 }
    370          *                                 ```
    371          *
    372          * @return {Object} An easier to use array
    373          *                   -> ```
    374          *                      {
    375          *                          country:     <country name>,
    376          *                          city:        <city name>,
    377          *                          route:       <route name>,
    378          *                          searchText:  <text to search on the OTA's calls>,
    379          *                          lat:         <latitude>,
    380          *                          lng:         <longitude>,
    381          *                      }
    382          *                     
    383          *                      ```
    384          */
    385         function googlePlaceFormat(googlePlace)
    386         {
    387             // {{{
    388             let out = {};
    389 
    390             let cityWrongNames = {
    391                 'Krung Thep Maha Nakhon': 'Bangkok',
    392                 'Krong Siem Reap': 'Siem Reap'
    393             };
    394 
    395             let address = googlePlace.address_components.filter(v=>  !/^[0-9]*$/.test(v.long_name)      // exclude only numbers
    396                                                                   && !/^[0-9]/.test(v.long_name));      // and  beginning with number
    397 
    398             let country             = address.filter(v=>v.types.contains('country')),
    399                 administrativeAreas = address.filter(v=>v.types.some(t=>/administrative_area_level_\d+/.test(t))),
    400                 city                = address.filter(v=>v.types.contains('locality', 'postal_town')),
    401                 subLocalAreas       = address.filter(v=>v.types.some(t=>/sublocality_level_\d+/.test(t))),
    402                 route               = address.filter(v=>v.types.contains('route'));
    403 
    404             out = {
    405                 'lat': googlePlace.geometry.location.lat(),
    406                 'lng': googlePlace.geometry.location.lng()
    407             };
    408 
    409             if(country.length)
    410             {
    411                 out.country=country[0].long_name;
    412                 if(city.length)
    413                 {
    414                     out.city=city[0].long_name;
    415 
    416                     if(route.length)
    417                         out.route = route[0].long_name
    418                     else if(subLocalAreas.length)
    419                         out.route = subLocalAreas[0].long_name
    420                 }
    421                 else if(administrativeAreas.length)
    422                     out.city = administrativeAreas[0].long_name;
    423             }
    424 
    425             out.city = cityWrongNames[out.city] || out.city;
    426            
    427            
    428             if(!googlePlace.types.contains('lodging', 'premise'))   // is not lodging nor premise
    429                 out.searchText = (out.route||'')+', '+(out.city||'')+', '+(out.country||'');
    430             else
    431                 out.searchText = (googlePlace.name||'')+', '+(out.city||'')+', '+(out.country||'');
    432            
    433             out.searchText = out.searchText.replace(/^[, ]+/, '');
    434 
    435             return out;
    436             // }}}
    437         }
    438 
    439         /**
    440          * Adds the custom keyWords to the original regexp
    441          *
    442          * @param {RegExp} original The original regexp conaining all the default keywords
    443          * @param {Array}  keyWords A list of the new keyWords
    444          */
    445         function createNewRegexp(original, keyWords)
    446         {
    447             // {{{
    448             //
    449             // transforming the old regexp to string
    450             //
    451             let newRegExp = original.toString().replace(/^[^\/]*\/|\/[^/]*$/g, '');
    452            
    453             //
    454             // adding the new keyWords
    455             //
    456             for(let i=0 ; i<keyWords.length ; i++)
    457             {
    458                 newRegExp += '|';
    459                 if(/^[a-z]/i.test(keyWords[i])) // begin with latin alphabet
    460                     newRegExp += '\\b';
    461 
    462                 newRegExp += keyWords[i].replace(/([\[\]\(\)\+\/\*\?])/g, '\\$1');  // making the word regex proof
    463 
    464                 if(/[a-z]$/i.test(keyWords[i])) // finish with latin alphabet
    465                     newRegExp += '\\b';
    466 
    467                 newRegExp += '|';
    468                 if(/^[a-z]/i.test(keyWords[i])) // begin with latin alphabet
    469                     newRegExp += '\\b';
    470 
    471                 newRegExp += keyWords[i].format().replace(/([\[\]\(\)\+\/\*\?])/g, '\\$1'); // making the word regex proof
    472 
    473                 if(/[a-z]$/i.test(keyWords[i])) // finish with latin alphabet
    474                     newRegExp += '\\b';
    475 
    476             }
    477 
    478             return new RegExp(newRegExp, 'gi');
    479             // }}}
    480         }
    481        
    482         /**
    483          * initialize the google places field
    484          */
    485         function initAutocomplete()
    486         {
    487             // {{{
    488             //
    489             // avoids to trigger the page reload on enter
    490             //
    491             $('#googleField').on('keydown', function(e){if(e.keyCode===13) e.preventDefault()});
    492 
    493             googleField = new google.maps.places.Autocomplete($('#googleField')[0],
    494                                   {types: ['establishment', 'geocode']});
    495 
    496             googleField.setFields(['address_components',
    497                        'geometry',
    498                        'types',
    499                        'name']);
    500 
    501             googleField.addListener('place_changed', fillInAddress);
    502             // }}}
    503         }
    504        
    505         /**
    506          * Callback, listen to google places field
    507          */
    508         function fillInAddress()
    509         {
    510             // {{{
    511             var place = googleField.getPlace();
    512 
    513             $('#googleField').addClass('kwAdd');
    514             let kw = $('#googleField').val().split(/, ?/)[0]
    515 
    516             if(lastSel[0])
    517             {
    518                 let start = $(lastSel[1].startContainer).parents('mark.TANKeyWord');
    519                 let end = $(lastSel[1].endContainer).parents('mark.TANKeyWord');
    520 
    521                 lastSel[1].deleteContents();
    522                 lastSel[1].insertNode(document.createTextNode(' '+kw+' '));
    523 
    524                 start.replaceWith(function() { return this.innerHTML });
    525                 end.replaceWith(function() { return this.innerHTML });
    526             }
    527 
    528             if(  !addedKeyWords.map(v=>v[0].format()).contains(kw.format())
    529               && (  !kw.match(keyWordsRegExp)
    530                  || kw.match(keyWordsRegExp)[0] !== kw ))   // kw not present in predefined keywords
    531                 addedKeyWords.push([kw, googlePlaceFormat(place).searchText]);
    532 
    533             if(addedKeyWords.length)
    534                 $('#newKWs').html('<br/>Your keywords:<br/> '+addedKeyWords.map(v=>'<span class=newKW>'+v[0]+'</span>').join(' '));
    535 
    536 
    537             $('span.newKW').click(function() {
    538                 let kw = this.innerHTML;
    539 
    540                 addedKeyWords = addedKeyWords.filter(v=>v[0] !== kw);
    541                 $('.TANKeyWord').each(function() {
    542                     if(kw.format() === this.innerText.format())
    543                         $(this).replaceWith(this.innerHTML);
    544                 });
    545                 $(this).remove();
    546             });
    547 
    548 
    549             $("#travel_editor").tan_highlightKeywords(createNewRegexp(keyWordsRegExp, addedKeyWords.map(v=>v[0])));
    550             // }}}
    551         }
    552 
    553         //
    554         // setting up the listeners
    555         //
    556 
    557         $('div#navMenu_buttonOnOff').click(function(){
    558             tan_activateKeyWord();
    559         });
    560 
    561         $('div[aria-label="TravelAdsNetwork Manual"]').click(function() {
    562             // {{{
    563             if($('#travelads_sectionid').css('display') === 'none')
    564             {
    565                 setTimeout(function() {
    566                     $('html, body').animate({
    567                         scrollTop: $('#travelads_sectionid').offset().top - 100
    568                     }, 500);
    569                 }, 300);
    570             }
    571             // }}}
    572         });
    573 
    574         $('#navMenu_merchantSelect').change(function() {
    575             $('.TANKeyWord.current').attr('data-adv', $(this).find('option:selected').val())
    576             tan_activateKeyWord(undefined, true);
    577         });
    578 
    579         $('#navMenuprevWord').click(function() {
    580             tan_changeFocus(currentFocus-1);
    581         });
    582         $('#navMenunextWord').click(function() {
    583             tan_changeFocus(currentFocus+1);
    584         });
    585 
    586         $('#publish').click(function() {
    587             copy_travel2wp();
    588         });
    589 
    590        
    591         let stop=setInterval(function() {
    592             // {{{
    593             if(!(window.tinyMCE || window.tinymce).editors)
    594                 return;
    595 
    596             clearInterval(stop);
    597 
    598             $('#travelads_sectionid').on('mouseenter', function() {
    599                 if(!travelFocused)
    600                 {
    601                     travelFocused = true;
    602                     copy_wp2travel();
    603                 }
    604 
    605                 let editors = ['#content', '.editor-writing-flow']
    606                           .concat([...(window.tinyMCE || window.tinymce).editors]
    607                               .map(v=>(!!v.container && '#'+v.container.getAttribute('id') || '')))
    608                           .filter(v=>!!v)
    609                           .join(', ');
    610 
    611                 $(editors).each(function() {
    612                     this.onmouseenter = function() {
    613                         if(travelFocused)
    614                             copy_travel2wp();
    615                         travelFocused = false;
    616                     };
    617                 });
    618             });
    619 
    620             let content = getWpEditorContent();
    621            
    622             var parser = new DOMParser();
    623             data = parser.parseFromString('<html>'+content.replace(/&nbsp;/g, '<br/>')+'</html>', "text/xml");
    624            
    625             [...data.getElementsByClassName('TAN_link_custom')].forEach(function(elem) {
    626                 let flag=-2;
    627                 addedKeyWords.forEach(function([kw], index) {
    628                     if(kw.format() === elem.innerHTML.format())
    629                         if(kw.format() !== kw)
    630                             flag = index;
    631                         else
    632                             flag = -1;
    633                 });
    634                
    635                 switch(flag)
    636                 {
    637                     case -2:
    638                         addedKeyWords.push([elem.innerHTML, elem.getAttribute('data-search')]);
    639                         break;
    640                     case -1:
    641                         break;
    642                     default:
    643                         addedKeyWords[flag] = [elem.innerHTML, elem.getAttribute('data-search')];
    644                 }
    645             });
    646 
    647             if(addedKeyWords.length)
    648                 $('#newKWs').html('<br/>Your keywords:<br/> '+addedKeyWords.map(v=>'<span class=newKW>'+v[0]+'</span>').join(', '));
    649             // }}}
    650         }, 500);
    651 
    652        
    653         let ctrlDown = false;
    654         let shiftDown = false;
    655         let lastSel = [];
    656        
    657         /**
    658          * Fast mode, allow to go through the keywords
    659          * with the keyboard, much faster than the mouse
    660          */
    661         $('#navMenu_keyWord').on('keydown', function(e) {
    662             // {{{
    663             let code = e.keyCode;
    664             let sel = $("#navMenu_merchantSelect");
    665             switch(code)
    666             {
    667                 case 39:    // right
    668                     tan_changeFocus(currentFocus+1);
    669                     break;
    670                 case 37:
    671                     tan_changeFocus(currentFocus-1);
    672                     break;  // left
    673                 case 38:    // up
    674                     sel.val((sel.find(':selected').prev('option')[0] || sel.find('option:last-child')[0]).value);
    675                     sel.trigger('change');
    676                     break; 
    677                 case 40:    // down
    678                     sel.val((sel.find(':selected + option')[0] || sel.find('option:first-child')[0]).value);
    679                     sel.trigger('change');
    680                     break;
    681                 case 32: tan_activateKeyWord(ctrlDown?-1:shiftDown?-2:currentFocus); break;             // space
    682                 case 13: tan_activateKeyWord(ctrlDown?-1:shiftDown?-2:currentFocus); break;             // enter
    683 
    684                 case 17: ctrlDown=true; break;
    685                 case 16: shiftDown=true; break;
    686             }
    687 
    688             e.preventDefault();
    689             // }}}
    690         })
    691         $('#navMenu_keyWord').on('keyup', function(e) {
    692             // {{{
    693             if(e.keyCode === 17)
    694                 ctrlDown=false;
    695             if(e.keyCode === 16)
    696                 shiftDown=false;
    697             // }}}
    698         });
    699 
    700         $('#travel_editor, #navMenu tr:last-child').on('mouseup', function(e) {
    701             // {{{
    702             setTimeout(function() {
    703                 if(  !$(e.target).is("#navMenu_merchantSelect")
    704                   && !getSelectionHtml()[0]
    705                   && !$('#googleField').is(':focus'))
    706                     $('#navMenu_keyWord')[0].focus();
    707             }, 100);
    708             // }}}
    709         })
    710 
    711         $('#travel_editor').on('mouseup', function(e) {
    712             // {{{
    713             let sel = getSelectionHtml();
    714             if(!sel[0])
    715                 return;
    716 
    717             lastSel = sel;
    718             $(lastSel[1]).parents('mark.TANKeyWord').replaceWith(function() {
    719                 console.log('this', this);
    720                 return this.innerHTML;
    721             })
    722             sel = sel[0];
    723 
    724             $('#googleField').val(sel.replace(/<[^>]*>/g, ''));
    725             $('#googleField')[0].focus();
    726             // }}}
    727         });
    728 
    729         document.addEventListener('scroll', function (e) {
    730             // {{{
    731             let diff = $(window).scrollTop() - $('#travelads_sectionid .inside').offset().top + 32;
    732 
    733             if(diff >= 0)
    734             {
    735                 $('#navMenu').css({
    736                     position: 'fixed',
    737                     left: $('#travelads_sectionid').offset().left,
    738                     top: $('#wpadminbar').height(),
    739 
    740                     width: $('#travelads_sectionid').width()+2,
    741                 });
    742 
    743                 $('.pac-container.pac-logo').css({
    744                     top:      $('#googleField').offset().top + $('#googleField').height() + 8,
    745                 });
    746 
    747             }
    748             else
    749             {
    750                 $('#navMenu').css({
    751                     position: '',
    752                     left: '',
    753                     top: '',
    754                     width: ''
    755                 });
    756             }
    757             // }}}
    758         }, true);
    759        
    760         initAutocomplete();
    761 
    762 
    763         //
    764         // creation of the Regex (list of keywords) and initializations
    765         //
    766         data=keyWords;
    767         var to_replace = '';
    768         for(var i=0 ; i<data.length ; i++)
    769             to_replace += '\\b'+data[i]+'\\b|';
    770 
    771         to_replace = to_replace.slice(0, -1);
    772 
    773         keyWordsRegExp = new RegExp(to_replace, 'gi')
    774 
    775 
    776         //
    777         // Open the dialog by default
    778         //
    779         setTimeout(function() {
    780             $('#travelads_sectionid.closed button').click();
    781         }, 2000);
    782 
    783         // }}}
    784     });
     17    $(document).ready(function() {
     18        // {{{
     19       
     20        /**
     21         * Changes the current focussed word
     22         *
     23         * @param {int}  nb     The keyWord to highlight -> e.g. if you want to highlight the 5th keyword, nb=4
     24         * @param {bool} scroll Should the screen scroll automatically to this keyword?
     25         */
     26        function tan_changeFocus(nb=0, scroll=true)
     27        {
     28            // {{{
     29            nb = nb || 0;
     30
     31            nb = (nb+$('#travel_editor mark.TANKeyWord').length)%$('#travel_editor mark.TANKeyWord').length
     32
     33            currentFocus = nb;
     34            elem = $('#travel_editor mark.TANKeyWord')[nb];
     35
     36            $('#navMenu_keyWord').val(elem.innerHTML.trim());
     37
     38            $('#travel_editor mark.TANKeyWord').removeClass('current');
     39            $(elem).addClass('current');
     40
     41            $('#navMenu_currentKeyWord').text(nb+1);
     42
     43            if($(elem).hasClass('active'))
     44            {
     45                $('div#navMenu_buttonOnOff').addClass('active');
     46            }
     47            else
     48            {
     49                $('div#navMenu_buttonOnOff').removeClass('active');
     50            }
     51
     52           
     53            $('select#navMenu_merchantSelect').val($(elem).attr('data-adv') || $('select#navMenu_merchantSelect option')[0].value);
     54
     55            if(scroll)
     56            {
     57                switch( window.editorName ) {
     58                    case 'gutenberg':
     59                        $('.edit-post-layout__content').clearQueue();
     60                        $('.edit-post-layout__content').animate({
     61                            scrollTop: $(elem).offset().top-$('.edit-post-visual-editor').offset().top-$(window).height()/2
     62                        }, 500);
     63                        break;
     64                    case 'tinymce':
     65                        $('html, body').clearQueue();
     66                        $('html, body').animate({
     67                            scrollTop: $(elem).offset().top-$(window).height()/2
     68                        }, 500);
     69                        break;
     70                }
     71            }
     72            // }}}
     73        }
     74
     75        function setWpEditorContent(content)
     76        {
     77            // {{{
     78            if(!window.editorName)
     79                window.editorName = (!!wp.data)?'gutenberg':
     80                             (!!tinymce || !!tinyMCE)?'tinymce':
     81                             '';
     82
     83            switch(editorName)
     84            {
     85                case 'gutenberg':
     86                    wp.data.dispatch( 'core/editor' ).resetBlocks([]);
     87
     88                    var editedContent = wp.data.select( "core/editor" ).getEditedPostContent();
     89                    var newBlocks = wp.blocks.parse( content );
     90                    wp.data.dispatch( "core/editor" ).insertBlocks( newBlocks );
     91
     92                    return true;
     93                case 'tinymce':
     94
     95                    if(!!tinymce.activeEditor)
     96                        tinymce.activeEditor.setContent( content )
     97                    else if(!!tinyMCE.activeEditor)
     98                        tinyMCE.activeEditor.setContent( content )
     99
     100                    $('#content').val($('#hidden_travel_editor').html());
     101
     102                    return true;
     103            }
     104
     105            return false;
     106            // }}}
     107        }
     108
     109        function getWpEditorContent()
     110        {
     111            // {{{
     112            if(!window.editorName)
     113                window.editorName = (!!wp.data)?'gutenberg':
     114                             (!!tinymce || !!tinyMCE)?'tinymce':
     115                             '';
     116
     117            switch(editorName)
     118            {
     119                case 'gutenberg':
     120                    return wp.data.select('core/editor').getEditedPostContent();
     121                case 'tinymce':
     122                    if($('#content').attr('aria-hidden') === 'true') // text editor
     123                        return (window.tinymce || window.tinyMCE).activeEditor.getContent();
     124                    else
     125                        return $('#content').val().replace(/\n/g, '<br/>');
     126            }
     127
     128            return '';
     129            // }}}
     130        }
     131
     132
     133        /**
     134         * Finds the position of a keyWord
     135         *
     136         * @param {DomNode}    e         The Dom node representing the keyword, has to be a `<mark class='TANKeyWord'>`
     137         * @param {jQueryNode} container The container in which the keyWords are. travel editor by default.
     138         *
     139         * @return {number} the position of this specific keyword, or 0 if not found
     140         */
     141
     142        function tan_domToNum(e, container=$('#travel_editor'))
     143        {
     144            // {{{
     145            list = container.find(' mark.TANKeyWord');
     146            for(var i=0 ; i<list.length ; i++)
     147                if(list[i] === e)
     148                    return i;
     149            return 0;
     150            // }}}
     151        }
     152
     153        /**
     154         * function called when a keyWord is clicked
     155         *
     156         * @param {DomNode} e The clicked keyWord, has to be a `<mark class='TANKeyWord'>`
     157         */
     158        function tan_clickMark(e)
     159        {
     160            tan_changeFocus(tan_domToNum(e));
     161        }
     162
     163        /**
     164         * Toggle the specified keyWord on and off
     165         *
     166         * @param {number} foc  The keyword to activate, the currently focussed keyword by default, pass -1 to toggle all, -2 to toggle all custom keywords
     167         * @param {bool}   flag Weather the keyWord should be activated or deactivated. Toggle by default
     168         */
     169        function tan_activateKeyWord(foc = currentFocus, flag = undefined)
     170        {
     171            // {{{
     172            if(foc < 0)
     173            {
     174                let classes = ['TANKeyWord'];
     175                if(foc === -2)
     176                    classes.push('custom');
     177
     178                let classesStr = classes.map(v=>'.'+v).join('');
     179
     180                f = $('#travel_editor mark.active'+classesStr).length
     181                    - $('#travel_editor mark'+classesStr+':not(.active)').length
     182
     183
     184                f = f<0;
     185               
     186                $('#travel_editor mark'+classesStr).each(function() {
     187                    tan_activateKeyWord(tan_domToNum(this), f);
     188                });
     189
     190                return;
     191            }
     192
     193            elem = $('#travel_editor mark.TANKeyWord').eq(foc);
     194
     195            if(elem.hasClass('active') === flag)
     196                return;
     197
     198            if(flag === undefined)
     199                elem.toggleClass('active');
     200            else
     201                elem.toggleClass('active', flag);
     202
     203            elem.attr('data-adv', $('#navMenu_merchantSelect').find('option:selected').val());
     204
     205
     206            if(foc === currentFocus) $('#navMenu_buttonOnOff').toggleClass('active', elem.hasClass('active'));
     207
     208            // }}}
     209        }
     210
     211        var totalReplaced = 0;
     212       
     213        /**
     214         * Surround the keyWords by <mark>, to highlighting it
     215         *
     216         * @param {RegExp} target regular expression matching the targeted keywords
     217         */
     218        jQuery.fn.tan_highlightKeywords = function(target)
     219        {
     220            // {{{
     221            //
     222            // Get all text nodes:
     223            //
     224            excludeNodes = ['SCRIPT', 'STYLE', 'H1', 'H2', 'H3', 'H4'];
     225            var $textNodes = this
     226                                .find("*")
     227                                .andSelf()
     228                                .contents()
     229                                .filter(function() {
     230                                    return  this.nodeType === 3
     231                                         && !$(this).parent("a").length
     232                                         && !$(this).parent(excludeNodes.join(',')).length
     233                                         && !$(this).parent('.TANKeyWord').length
     234                                         && !$(this).parent('.TAN_link').length
     235                                         && $(this).text().trim() !== '';
     236                                });
     237
     238            //
     239            // Iterate through the text nodes,
     240            // highlighting the keyWords
     241            //
     242
     243            let totalReplaced = 0;
     244            $textNodes.each(function(index, element) {
     245
     246                match = $(element).text().match(target);
     247
     248                if(match === null) return;
     249                var i=0;
     250                var finalStr = $(element).text().replace(target, function(word) {
     251                    var originalWord = match[i];
     252                    i++;
     253                    totalReplaced++;
     254
     255                    let tmp = addedKeyWords.filter(v=>v[0].format() === originalWord.format())
     256                   
     257                    let c = '';
     258                    let s = originalWord;
     259                    if(tmp.length)
     260                    {
     261                        c = ' custom'
     262                        s = tmp[0][1];
     263                    }
     264
     265                    return `<mark class='TANKeyWord`+c+`'
     266                                  data-search='`+s+`'
     267                                  data-adv=`+$('#navMenu_merchantSelect').find('option:selected').val()+`>`
     268                              + originalWord
     269                              + `</mark>`;
     270                });
     271
     272                $(element).replaceWith(finalStr);
     273            });
     274
     275            $('.TANKeyWord').each(function(i) {
     276               
     277                this.onclick = function() {
     278                    tan_changeFocus(tan_domToNum(this));
     279                };
     280            });
     281           
     282            // }}}
     283        }
     284
     285        /**
     286         * copy content from the travel ads network editor to the tiny MCE
     287         */
     288        function copy_travel2wp()
     289        {
     290            // {{{
     291
     292            $('#hidden_travel_editor').html(($('div#travel_editor').html()));
     293
     294            $('#hidden_travel_editor mark.TANKeyWord').each(function() {
     295                $(this).replaceWith(this.outerHTML.replace(/\bmark\b/, 'span'))
     296            });
     297
     298            $('#hidden_travel_editor span.TANKeyWord').each(function() {
     299                $(this).removeClass('current');
     300                $(this).removeClass('TANKeyWord');
     301
     302                $(this).addClass('TAN_link');
     303
     304                if($(this).hasClass('custom'))
     305                    $(this).addClass('TAN_link_custom');
     306            });
     307
     308            $('#hidden_travel_editor section.tan_asset').each(function(i) {
     309                $(this).find('script').siblings().remove();
     310            });
     311
     312            setWpEditorContent( $('#hidden_travel_editor').html() );
     313
     314            $('div#travel_editor section.tan_asset script').remove();
     315
     316            $('#hidden_travel_editor').empty();
     317
     318            // }}}
     319        }
     320       
     321        /**
     322         * Copy the content from the wordpress editor to the travel editor
     323         *
     324         * @param {string} content The content to put in the travel editor
     325         */
     326        function copy_wp2travel()
     327        {
     328            // {{{
     329            content = getWpEditorContent();
     330
     331
     332            if(  $('div.mce-tinymce').css('display') === 'none' // normal mode
     333              && $('wp-editor-area').css('display') === 'none') // text mode
     334            {
     335                $('#travel_editor').html('<h1 style="text-align:center">The visual Editor is not supported for now...</h1>'+
     336                                        '<h2 style="text-align:center">You can switch to autmatic mode!</h2>');
     337                $('#navMenu').toggle(false);
     338                $('#navMenuOffset').toggle(false);
     339                return;
     340            }
     341            $('#navMenu').toggle(true);
     342            $('#navMenuOffset').toggle(true);
     343
     344             totalReplaced = 0;
     345            $('#travel_editor').empty();
     346            $('#travel_editor').html(content);
     347            $('#travel_editor span.TAN_link').each(function() {
     348
     349                $(this).replaceWith(this.outerHTML.replace(/\bspan\b/, 'mark'))
     350            });
     351
     352            $('#travel_editor mark.TAN_link').each(function() {
     353                $(this).removeClass('TAN_link_custom');
     354                $(this).removeClass('TAN_link');
     355
     356                $(this).addClass('TANKeyWord');
     357            });
     358
     359
     360            $("#travel_editor").tan_highlightKeywords(createNewRegexp(keyWordsRegExp, addedKeyWords.map(v=>v[0])));
     361           
     362            tan_changeFocus(currentFocus, false);
     363            $('#navMenu_totalKeyWord').text($('#travel_editor mark.TANKeyWord').length);
     364            // }}}
     365        }
     366
     367        /**
     368         * Transforms the google places API response to a easier to use object
     369         *
     370         * @param {Object} googlePlace The object returned by the google places API
     371         *                              -> ```
     372         *                                 {
     373         *                                     address_coponents: <array of addresses>,
     374         *                                     geometry:          <geometry, lat, long>,
     375         *                                     types:             <array of types>,
     376         *                                     name:              <name>
     377         *                                 }
     378         *                                 ```
     379         *
     380         * @return {Object} An easier to use array
     381         *                   -> ```
     382         *                      {
     383         *                          country:     <country name>,
     384         *                          city:        <city name>,
     385         *                          route:       <route name>,
     386         *                          searchText:  <text to search on the OTA's calls>,
     387         *                          lat:         <latitude>,
     388         *                          lng:         <longitude>,
     389         *                      }
     390         *                     
     391         *                      ```
     392         */
     393        function googlePlaceFormat(googlePlace)
     394        {
     395            // {{{
     396            let out = {};
     397
     398            let cityWrongNames = {
     399                'Krung Thep Maha Nakhon': 'Bangkok',
     400                'Krong Siem Reap': 'Siem Reap'
     401            };
     402
     403            let address = googlePlace.address_components.filter(v=>  !/^[0-9]*$/.test(v.long_name)        // exclude only numbers
     404                                                                  && !/^[0-9]/.test(v.long_name));      // and  beginning with number
     405
     406            let country             = address.filter(v=>v.types.contains('country')),
     407                administrativeAreas = address.filter(v=>v.types.some(t=>/administrative_area_level_\d+/.test(t))),
     408                city                = address.filter(v=>v.types.contains('locality', 'postal_town')),
     409                subLocalAreas       = address.filter(v=>v.types.some(t=>/sublocality_level_\d+/.test(t))),
     410                route               = address.filter(v=>v.types.contains('route'));
     411
     412            out = {
     413                'lat': googlePlace.geometry.location.lat(),
     414                'lng': googlePlace.geometry.location.lng()
     415            };
     416
     417            if(country.length)
     418            {
     419                out.country=country[0].long_name;
     420                if(city.length)
     421                {
     422                    out.city=city[0].long_name;
     423
     424                    if(route.length)
     425                        out.route = route[0].long_name
     426                    else if(subLocalAreas.length)
     427                        out.route = subLocalAreas[0].long_name
     428                }
     429                else if(administrativeAreas.length)
     430                    out.city = administrativeAreas[0].long_name;
     431            }
     432
     433            out.city = cityWrongNames[out.city] || out.city;
     434           
     435           
     436            if(!googlePlace.types.contains('lodging', 'premise'))    // is not lodging nor premise
     437                out.searchText = (out.route||'')+', '+(out.city||'')+', '+(out.country||'');
     438            else
     439                out.searchText = (googlePlace.name||'')+', '+(out.city||'')+', '+(out.country||'');
     440           
     441            out.searchText = out.searchText.replace(/^[, ]+/, '');
     442
     443            return out;
     444            // }}}
     445        }
     446
     447        /**
     448         * Adds the custom keyWords to the original regexp
     449         *
     450         * @param {RegExp} original The original regexp conaining all the default keywords
     451         * @param {Array}  keyWords A list of the new keyWords
     452         */
     453        function createNewRegexp(original, keyWords)
     454        {
     455            // {{{
     456            //
     457            // transforming the old regexp to string
     458            //
     459            let newRegExp = original.toString().replace(/^[^\/]*\/|\/[^/]*$/g, '');
     460           
     461            //
     462            // adding the new keyWords
     463            //
     464            for(let i=0 ; i<keyWords.length ; i++)
     465            {
     466                newRegExp += '|';
     467                if(/^[a-z]/i.test(keyWords[i])) // begin with latin alphabet
     468                    newRegExp += '\\b';
     469
     470                newRegExp += keyWords[i].replace(/([\[\]\(\)\+\/\*\?])/g, '\\$1');    // making the word regex proof
     471
     472                if(/[a-z]$/i.test(keyWords[i])) // finish with latin alphabet
     473                    newRegExp += '\\b';
     474
     475                newRegExp += '|';
     476                if(/^[a-z]/i.test(keyWords[i])) // begin with latin alphabet
     477                    newRegExp += '\\b';
     478
     479                newRegExp += keyWords[i].format().replace(/([\[\]\(\)\+\/\*\?])/g, '\\$1');    // making the word regex proof
     480
     481                if(/[a-z]$/i.test(keyWords[i])) // finish with latin alphabet
     482                    newRegExp += '\\b';
     483
     484            }
     485
     486            return new RegExp(newRegExp, 'gi');
     487            // }}}
     488        }
     489       
     490        /**
     491         * initialize the google places field
     492         */
     493        function initAutocomplete()
     494        {
     495            // {{{
     496            //
     497            // avoids to trigger the page reload on enter
     498            //
     499            $('#googleField').on('keydown', function(e){if(e.keyCode===13) e.preventDefault()});
     500
     501            googleField = new google.maps.places.Autocomplete($('#googleField')[0],
     502                                  {types: ['establishment', 'geocode']});
     503
     504            googleField.setFields(['address_components',
     505                       'geometry',
     506                       'types',
     507                       'name']);
     508
     509            googleField.addListener('place_changed', fillInAddress);
     510            // }}}
     511        }
     512       
     513        /**
     514         * Callback, listen to google places field
     515         */
     516        function fillInAddress()
     517        {
     518            // {{{
     519            var place = googleField.getPlace();
     520
     521            $('#googleField').addClass('kwAdd');
     522            let kw = $('#googleField').val().split(/, ?/)[0]
     523
     524            if(lastSel[0])
     525            {
     526                let start = $(lastSel[1].startContainer).parents('mark.TANKeyWord');
     527                let end = $(lastSel[1].endContainer).parents('mark.TANKeyWord');
     528
     529                lastSel[1].deleteContents();
     530                lastSel[1].insertNode(document.createTextNode(' '+kw+' '));
     531
     532                start.replaceWith(function() { return this.innerHTML });
     533                end.replaceWith(function() { return this.innerHTML });
     534            }
     535
     536            if(  !addedKeyWords.map(v=>v[0].format()).contains(kw.format())
     537              && (  !kw.match(keyWordsRegExp)
     538                 || kw.match(keyWordsRegExp)[0] !== kw ))    // kw not present in predefined keywords
     539                addedKeyWords.push([kw, googlePlaceFormat(place).searchText]);
     540
     541            if(addedKeyWords.length)
     542                $('#newKWs').html('<br/>Your keywords:<br/> '+addedKeyWords.map(v=>'<span class=newKW>'+v[0]+'</span>').join(' '));
     543
     544
     545            $('span.newKW').click(function() {
     546                let kw = this.innerHTML;
     547
     548                addedKeyWords = addedKeyWords.filter(v=>v[0] !== kw);
     549                $('.TANKeyWord').each(function() {
     550                    if(kw.format() === this.innerText.format())
     551                        $(this).replaceWith(this.innerHTML);
     552                });
     553                $(this).remove();
     554            });
     555
     556
     557            $("#travel_editor").tan_highlightKeywords(createNewRegexp(keyWordsRegExp, addedKeyWords.map(v=>v[0])));
     558            // }}}
     559        }
     560
     561        //
     562        // setting up the listeners
     563        //
     564
     565        $('div#navMenu_buttonOnOff').click(function(){
     566            tan_activateKeyWord();
     567        });
     568
     569        $('div[aria-label="TravelAdsNetwork Manual"]').click(function() {
     570            // {{{
     571            if($('#travelads_sectionid').css('display') === 'none')
     572            {
     573                setTimeout(function() {
     574                    $('html, body').animate({
     575                        scrollTop: $('#travelads_sectionid').offset().top - 100
     576                    }, 500);
     577                }, 300);
     578            }
     579            // }}}
     580        });
     581
     582        $('#navMenu_merchantSelect').change(function() {
     583            $('.TANKeyWord.current').attr('data-adv', $(this).find('option:selected').val())
     584            tan_activateKeyWord(undefined, true);
     585        });
     586
     587        $('#navMenuprevWord').click(function() {
     588            tan_changeFocus(currentFocus-1);
     589        });
     590        $('#navMenunextWord').click(function() {
     591            tan_changeFocus(currentFocus+1);
     592        });
     593
     594        $('#publish').click(function() {
     595            copy_travel2wp();
     596        });
     597
     598       
     599        let stop=setInterval(function() {
     600            // {{{
     601            if(!(window.tinyMCE || window.tinymce).editors)
     602                return;
     603
     604            clearInterval(stop);
     605
     606            $('#travelads_sectionid').on('mouseenter', function() {
     607                if(!travelFocused)
     608                {
     609                    travelFocused = true;
     610                    copy_wp2travel();
     611                }
     612
     613                let editors = ['#content', '.editor-writing-flow']
     614                          .concat([...(window.tinyMCE || window.tinymce).editors]
     615                              .map(v=>(!!v.container && '#'+v.container.getAttribute('id') || '')))
     616                          .filter(v=>!!v)
     617                          .join(', ');
     618
     619                $(editors).each(function() {
     620                    this.onmouseenter = function() {
     621                        if(travelFocused)
     622                            copy_travel2wp();
     623                        travelFocused = false;
     624                    };
     625                });
     626            });
     627
     628            let content = getWpEditorContent();
     629           
     630            var parser = new DOMParser();
     631            data = parser.parseFromString('<html>'+content.replace(/&nbsp;/g, '<br/>')+'</html>', "text/xml");
     632           
     633            [...data.getElementsByClassName('TAN_link_custom')].forEach(function(elem) {
     634                let flag=-2;
     635                addedKeyWords.forEach(function([kw], index) {
     636                    if(kw.format() === elem.innerHTML.format())
     637                        if(kw.format() !== kw)
     638                            flag = index;
     639                        else
     640                            flag = -1;
     641                });
     642               
     643                switch(flag)
     644                {
     645                    case -2:
     646                        addedKeyWords.push([elem.innerHTML, elem.getAttribute('data-search')]);
     647                        break;
     648                    case -1:
     649                        break;
     650                    default:
     651                        addedKeyWords[flag] = [elem.innerHTML, elem.getAttribute('data-search')];
     652                }
     653            });
     654
     655            if(addedKeyWords.length)
     656                $('#newKWs').html('<br/>Your keywords:<br/> '+addedKeyWords.map(v=>'<span class=newKW>'+v[0]+'</span>').join(', '));
     657            // }}}
     658        }, 500);
     659
     660       
     661        let ctrlDown = false;
     662        let shiftDown = false;
     663        let lastSel = [];
     664       
     665        /**
     666         * Fast mode, allow to go through the keywords
     667         * with the keyboard, much faster than the mouse
     668         */
     669        $('#navMenu_keyWord').on('keydown', function(e) {
     670            // {{{
     671            let code = e.keyCode;
     672            let sel = $("#navMenu_merchantSelect");
     673            switch(code)
     674            {
     675                case 39:     // right
     676                    tan_changeFocus(currentFocus+1);
     677                    break;
     678                case 37:
     679                    tan_changeFocus(currentFocus-1);
     680                    break;    // left
     681                case 38:    // up
     682                    sel.val((sel.find(':selected').prev('option')[0] || sel.find('option:last-child')[0]).value);
     683                    sel.trigger('change');
     684                    break;   
     685                case 40:     // down
     686                    sel.val((sel.find(':selected + option')[0] || sel.find('option:first-child')[0]).value);
     687                    sel.trigger('change');
     688                    break;
     689                case 32: tan_activateKeyWord(ctrlDown?-1:shiftDown?-2:currentFocus); break;             // space
     690                case 13: tan_activateKeyWord(ctrlDown?-1:shiftDown?-2:currentFocus); break;             // enter
     691
     692                case 17: ctrlDown=true; break;
     693                case 16: shiftDown=true; break;
     694            }
     695
     696            e.preventDefault();
     697            // }}}
     698        })
     699        $('#navMenu_keyWord').on('keyup', function(e) {
     700            // {{{
     701            if(e.keyCode === 17)
     702                ctrlDown=false;
     703            if(e.keyCode === 16)
     704                shiftDown=false;
     705            // }}}
     706        });
     707
     708        $('#travel_editor, #navMenu tr:last-child').on('mouseup', function(e) {
     709            // {{{
     710            setTimeout(function() {
     711                if(  !$(e.target).is("#navMenu_merchantSelect")
     712                  && !getSelectionHtml()[0]
     713                  && !$('#googleField').is(':focus'))
     714                    $('#navMenu_keyWord')[0].focus();
     715            }, 100);
     716            // }}}
     717        })
     718
     719        $('#travel_editor').on('mouseup', function(e) {
     720            // {{{
     721            let sel = getSelectionHtml();
     722            if(!sel[0])
     723                return;
     724
     725            lastSel = sel;
     726            $(lastSel[1]).parents('mark.TANKeyWord').replaceWith(function() {
     727                return this.innerHTML;
     728            })
     729            sel = sel[0];
     730
     731            $('#googleField').val(sel.replace(/<[^>]*>/g, ''));
     732            $('#googleField')[0].focus();
     733            // }}}
     734        });
     735
     736        document.addEventListener('scroll', function (e) {
     737            // {{{
     738           
     739            let topOffset = $('#wpadminbar').height() +
     740                            (window.editorName==='gutenberg'?$('.edit-post-header').outerHeight():0) +
     741                            ((tmp=$('.components-notice')).length?[...tmp].reduce((a,v)=>a+$(v).outerHeight(),0):0),
     742                diff = $(window).scrollTop() - $('#travelads_sectionid .inside').offset().top + topOffset;
     743
     744            if(diff >= 0)
     745            {
     746                $('#navMenu').toggleClass('flying', true);
     747
     748                $('#navMenu').css({
     749                    position: 'fixed',
     750                    left: $('#travelads_sectionid').offset().left,
     751                    top: topOffset,
     752
     753                    width: $('#travelads_sectionid').width()+2,
     754                });
     755
     756                $('.pac-container.pac-logo').css({
     757                    top:      $('#googleField').offset().top + $('#googleField').height() + 8,
     758                });
     759
     760            }
     761            else
     762            {
     763                $('#navMenu').toggleClass('flying', false);
     764                $('#navMenu').css({
     765                    position: '',
     766                    left: '',
     767                    top: '',
     768                    width: ''
     769                });
     770            }
     771            // }}}
     772        }, true);
     773       
     774        initAutocomplete();
     775
     776
     777        //
     778        // creation of the Regex (list of keywords) and initializations
     779        //
     780        data=keyWords;
     781        var to_replace = '';
     782        for(var i=0 ; i<data.length ; i++)
     783            to_replace += '\\b'+data[i]+'\\b|';
     784
     785        to_replace = to_replace.slice(0, -1);
     786
     787        keyWordsRegExp = new RegExp(to_replace, 'gi')
     788
     789
     790        //
     791        // Open the dialog by default
     792        //
     793        setTimeout(function() {
     794            $('#travelads_sectionid.closed button').click();
     795        }, 2000);
     796
     797        // }}}
     798    });
    785799}(jQuery);
  • traveladsnetwork-com/trunk/readme.txt

    r1969530 r2083624  
    22Contributors: christophesecher
    33Tags: travel, affiliate plugin, affiliate link, deeplinking, deeplinks, travel booking, bloggers, merchant, travel content, travel, Travel Ads Network, Traveladsnetwork, content monetization, creating purchase intent, monetize your audience data, Convert Content into Revenue
    4 Tags: travel affiliate plugin affiliate link deeplinking deeplinks travel booking bloggers merchant travel content travel Travel Ads Network Traveladsnetwork content monetization creating purchase intent monetize your audience data Convert Content into Revenue
    54Requires at least: 4.9.0
    6 Tested up to: 4.9.8
     5Tested up to: 5.2
    76Requires PHP: 7.3
    87Stable tag: trunk
Note: See TracChangeset for help on using the changeset viewer.