emoji selector - yutarobox

emoji selector - yutarobox

script.js
 const Asearch = (function() {
     var INITPAT, INITSTATE, MAXCHAR;
     INITPAT = 0x80000000; 
     MAXCHAR = 0x100; 
     INITSTATE = [INITPAT, 0, 0, 0];
 
     Asearch.prototype.isupper = function(c) {
       return (c >= 0x41) && (c <= 0x5a);
     };
 
     Asearch.prototype.islower = function(c) {
       return (c >= 0x61) && (c <= 0x7a);
     };
 
     Asearch.prototype.tolower = function(c) {
       if (this.isupper(c)) {
         return c + 0x20;
       } else {
         return c;
       }
     };
 
     Asearch.prototype.toupper = function(c) {
       if (this.islower(c)) {
         return c - 0x20;
       } else {
         return c;
       }
     };
 
     function Asearch(source) {
       var c, i, j, len, mask, ref, ref1;
       this.source = source;
       this.shiftpat = [];
       this.epsilon = 0;
       this.acceptpat = 0;
       mask = INITPAT;
       for (c = i = 0, ref = MAXCHAR; 0 <= ref ? i < ref : i > ref; c = 0 <= ref ? ++i : --i) {
         this.shiftpat[c] = 0;
       }
       ref1 = this.unpack(this.source);
       for (j = 0, len = ref1.length; j < len; j++) {
         c = ref1[j];
         if (c === 0x20) {
           this.epsilon |= mask;
         } else {
           this.shiftpat[c] |= mask;
           this.shiftpat[this.toupper(c)] |= mask;
           this.shiftpat[this.tolower(c)] |= mask;
           mask >>>= 1;
         }
       }
       this.acceptpat = mask;
       return this;
     }
 
     Asearch.prototype.state = function(state, str) {
       var c, i, i0, i1, i2, i3, len, mask, ref;
       if (state == null) {
         state = INITSTATE;
       }
       if (str == null) {
         str = '';
       }
       i0 = state[0];
       i1 = state[1];
       i2 = state[2];
       i3 = state[3];
       ref = this.unpack(str);
       for (i = 0, len = ref.length; i < len; i++) {
         c = ref[i];
         mask = this.shiftpat[c];
         i3 = (i3 & this.epsilon) | ((i3 & mask) >>> 1) | (i2 >>> 1) | i2;
         i2 = (i2 & this.epsilon) | ((i2 & mask) >>> 1) | (i1 >>> 1) | i1;
         i1 = (i1 & this.epsilon) | ((i1 & mask) >>> 1) | (i0 >>> 1) | i0;
         i0 = (i0 & this.epsilon) | ((i0 & mask) >>> 1);
         i1 |= i0 >>> 1;
         i2 |= i1 >>> 1;
         i3 |= i2 >>> 1;
       }
       return [i0, i1, i2, i3];
     };
 
     Asearch.prototype.match = function(str, ambig) {
       var s;
       if (ambig == null) {
         ambig = 0;
       }
       s = this.state(INITSTATE, str);
       if (!(ambig < INITSTATE.length)) {
         ambig = INITSTATE.length - 1;
       }
       return (s[ambig] & this.acceptpat) !== 0;
     };
 
     Asearch.prototype.unpack = function(str) {
       var bytes, c, code, i, len, ref;
       bytes = [];
       ref = str.split('');
       for (i = 0, len = ref.length; i < len; i++) {
         c = ref[i];
         code = c.charCodeAt(0);
         if (code > 0xFF) {
           bytes.push((code & 0xFF00) >>> 8);
         }
         bytes.push(code & 0xFF);
       }
       return bytes;
     };
 
     return Asearch;
 
   })();
 
 
 
 const projectName = scrapbox.Project.name;
 let emojis = [];
 const box = $('<div>').addClass('form-group').css("position", "absolute");
 const container = $('<div>').addClass('dropdown');
 box.append(container);
 let items = $('<ul>').addClass('dropdown-menu');
 container.append(items);
 $('#editor').append(box);
 
 fetch(`/api/pages/${projectName}?limit=10000`, { credentials: 'same-origin'})
 	.then( res => res.text())
     .then( text => {
     	const data = JSON.parse( text );
      	const pages = data.pages;
       	pages.filter( page => (page.image !== null && page.title.match(/^[\w\s\-\+]+$/)))
      		.forEach( page => {
        			emojis.push({
        				name: page.title,
            			path: page.title,
           			icon: `/api/pages/${projectName}/${page.title}/icon`,
             	})
              })
     })
 
 
 scrapbox.PageMenu.addMenu({
     title: 'emoji',
     image: 'https://gyazo.com/d57fea8a143650375af1c8bba1fc1370/raw'
 })
 scrapbox.PageMenu('emoji').addItem({
     title: "load emojis from /jgsemj",
     onClick: () => {
     	 fetch('/api/pages/jgsemj?limit=10000')
       	.then( res => res.text())
        	.then( text => {
         	const data = JSON.parse( text );
           	const pages = data.pages;
            	pages.filter( page => (page.image !== null && page.title.match(/^[\w\s\-\+]+$/)))
             		.forEach( page => {
               			for( let emoji of emojis ) {
                           if( emoji.name === page.title )return;
                     	}
               			emojis.push({
                 			name: page.title,
                    			path: '/jgsemj/' + page.title,
                      		icon: `/api/pages/jgsemj/${page.title}/icon`,
                 		})
               		})
            })
     }
 })      		
 
  // TODO: 様々な文字列が来る場合を考慮する
  
  const taberareloo = ( word, list ) => {
  	const targetWord = word.replace(':', '');
  	const regStr = targetWord.split('').reduce( (pre, cur) => pre + cur + '.*' ).replace('+', '\\+');
   	const reg = RegExp(regStr,'i');
    	return list.filter( item => item.name.match(reg));
  }
  
  const asearched = ( word, list ) => {
  	const targetWord = word.replace(':', '');
   	const a = new Asearch( targetWord );
     const limitCount = Math.floor( targetWord.length/ 4 ) + 1;
     let result = [];
     
     for(let i = 0; i <= limitCount; i++){
     	let matched = list.filter( item => a.match( item.name, i));
      	let notExisted = matched.filter( item => {
       		for( let r of result){
         		if(r.name === item.name){
           			return false;
           		}
         	}
          	return true;
       	})
        	result = [ ...result, ...notExisted];
     }
     
    	return result;
  }
  
  const fizzSearch = ( word, list ) => {
  	const a = asearched( word, list );
   	const b = taberareloo( word, list );
    	const c = b.filter( item => {
     	for( let r of a ){
      		if( r.name == item.name){
        			return false;
        		}
      	}
       	return true;
     })
    	return [...a, ...c];
  }
  
 
  let stack = "";
  const editor = $('#editor');
  
  const open  = () => container.addClass("open"); 
  const close = () => {
  	stack = "";
  	container.removeClass("open");
  }
  
  const replaceText = (text, cursor, emojiPath) => {
  	cursor.focus();
     setTimeout(()=>{
     	for(let i = 0; i < text.length; i++){
         	var ke1 = document.createEvent("Events");
          	ke1.initEvent("keydown", true, true);
          	ke1.keyCode = ke1.which = 8; // Backspace
          	cursor.dispatchEvent(ke1);
         }
         document.execCommand('insertText',null, `[${emojiPath}.icon]` );
         close();
     }, 50)
  }
  
  editor.keydown( e => {
  	const key = e.key;
   	if(key === undefined ) return;
  	if( stack === "" && key !== ":"){
   		close();	
   		return
     };
 
     if ($('.cursor-line').text().trim() == 'code:'
        		|| $('.cursor-line .code-block').length == 1) {
        close()
        return;
     }
     
     if( key === ':' && stack.length !== 0){
     	let name = stack.replace(':', '');
     	for(let emoji of emojis){
      		if( emoji.name === name ){
        			let cursor = $('#text-input')[0];
        			replaceText(stack + ":", cursor, emoji.path);
           		return;
        		}
      	}
       	close()
        	return;
     }
 	const cursor = $('#text-input')[0];
     if( key.match(/^[\w\s\-\:\+]$/) ){
     	stack += e.key;
      	let focused = $(':focus');
       	if(focused.is(items.find('li > a'))){
      		cursor.focus();  		
        	}
     }
   
     if( stack.length === 2 ){
     	if( key === " " ){
      		stack = "";
        		return;
      	}
     	open();
      }
     
     switch(key){
     	case 'Backspace':
      		stack = stack.slice(0, stack.length - 1);
        		if(stack.length === 0){
          		close();
            		return;
          	}
        		break;
         case 'ArrowUp':
         	let focusedUp = $(':focus');
          	if( focusedUp.is(items.find('li > a').eq(0)) ){
           		e.stopPropagation();
             	cursor.focus();
           	}else if( !focusedUp.is(items.find('li > a')) ){
            		close();
              	return;
             }
          	break;
         case 'ArrowDown':
         	let focusedDown = $(':focus');
         	if( !focusedDown.is(items.find('li > a'))) {
         		e.stopPropagation();
           		e.preventDefault();
          		items.find("li > a").eq(0).focus();
             }
             break;
         case 'Escape':
         case 'ArrowLeft':
         case 'ArrowRight': 
         case 'Home':
         case 'End':
         case 'PageUp':
         case 'PageDown':
          	close();
           	break;
         
         case 'Enter':
         	if( stack.length === 1 ){
          		close();
            		break;
          	}
           	let focused = $(':focus');
           	if(!focused.is(items.find('li > a'))){
            		e.stopPropagation();
                 e.preventDefault();
           		items.find('li > a').eq(0).click();
             }
           break;
     }
     
     if( stack.length <= 1 || !key.match(/^[\w\s\:\-\+]$|Backspace/)) return;
     const matchedEmoji = fizzSearch(stack, emojis)
     if( matchedEmoji.length === 0){
     	close();
      	return;
     }
     
     const newItems = $('<ul>').addClass('dropdown-menu');
     
     matchedEmoji.forEach( ( emoji, index) => {
       if( index > 30 ) return;
       const li = $('<li>').addClass('dropdown-item');
       const a  = $('<a>').attr("tabindex", "0");
       const img = $('<img>').attr("src", emoji.icon)
          .addClass("icon").css({ height: "17px", float: "left"});
       const nameTag = $('<div>').text("  :" + emoji.name + ":");
       a.append(img);
       a.append(nameTag);
       li.append(a);
       newItems.append(li);
 	
       a.on('click', () => {
        	cursor.focus();
         replaceText(stack, cursor, emoji.path);
       })
       
       a.on('keypress', ev => {
       	 if(ev.key === "Enter"){
         	ev.preventDefault();
          	ev.stopPropagation();
          	replaceText(stack, cursor, emoji.path);
          }
       })
 	})
  	
   	items.replaceWith(newItems);
   	items = newItems;
     let css = {};
    	cursor.style.cssText.split(';').filter( text => text !== '' )
     	.forEach( text => {
      		const props = text.split(':').map( text => text.replace(' ', '').replace('px', ''));
        		css[props[0]] = props[1];
     	});
     
   	box.css({
    		top: `${parseInt(css.top) + parseInt(css.height) + 3}px`,
      	left: `${css.left}px`,
    	});
  })

#UserScript #絵文字
#20191107 #1107

関連ページとランダムに選ばれたページ