Tuesday, June 8, 2021

safari-web-extension-converter error

$ xcrun safari-web-extension-converter
xcrun: error: unable to find utility "safari-web-extension-converter", not a developer tool or in PATH
Do this first:
sudo xcode-select -s /Applications/Xcode.app

Saturday, May 29, 2021

TypeScript Object is possibly undefined

   .
   .
   .
   
   newHzl[hanzi].pinyin = newHzl[hanzi].pinyin.filter(eachPinyin => pinyinEnglish[eachPinyin]);
} // for loop   
That yields this error:
error: TS2532 [ERROR]: Object is possibly 'undefined'.
        newHzl[hanzi].pinyin = newHzl[hanzi].pinyin.filter(
Got a similar error code on following code, and removed the error by checking if a variable has nothing, if nothing then skip(continue)
for (const [hanzi, { pinyinEnglish }] of Object.entries(hzl)) {
    if (!pinyinEnglish) {
        continue;
    }
    
    for (const pinyin of Object.keys(pinyinEnglish)) {
        if (pinyinEnglish[pinyin].length === 0) {
            delete pinyinEnglish[pinyin];
        }
    }
    
    .
    .
    .    
I tried to do the same solution on the post's first code, but it still yields a compile error of Object is possibly 'undefined'
	.
    .
    .
    
    if (!newHzl[hanzi].pinyin) {
        continue;
    }

    newHzl[hanzi].pinyin = newHzl[hanzi].pinyin.filter(eachPinyin => pinyinEnglish[eachPinyin]);
} // for loop    
The workaround is to introduce a variable so the compiler will not have a hard time inferring the code's control flow:
    .
    .
    .

    const hanziPinyin = newHzl[hanzi].pinyin;
    if (!hanziPinyin) {
        continue;
    }

    newHzl[hanzi].pinyin = hanziPinyin.filter(eachPinyin => pinyinEnglish[eachPinyin]);
} // for loop   
The problem went away

Tuesday, May 18, 2021

JavaScript splitKeep

Helper function for split keep. Reference: https://medium.com/@shemar.gordon32/how-to-split-and-keep-the-delimiter-s-d433fb697c65
String.prototype.splitKeep = function (tokens) {
    const escaped = escapeRegExp(tokens);
    return this.split(new RegExp(`(?=[${escaped}])|(?<=[${escaped}])`, 'g'));
};


// Not built-in yet https://github.com/tc39/proposal-regex-escaping

// Use a good one for the meantime https://stackoverflow.com/questions/3115150/how-to-escape-regular-expression-special-characters-using-javascript
function escapeRegExp(text) {
    return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
Add this when using TypeScript:
declare global {
    interface String {
        splitKeep(tokens: string): string[];
    }
}

Saturday, May 15, 2021

Handling both single click and double click in JavaScript

Live code: https://jsfiddle.net/91zoa2qj/1/

HTML and CSS:
<div id="something">Click or double click me</div>
<hr/>
<div id="good">Click or double click me</div>


<style>
#something, #good {
  background-color: lemonchiffon;
}
</style>

JS:
addGlobalEventListener(
    'click',
    '#something',
    debounceSingleClickOnly(sayHello)
);

addGlobalEventListener(
    'dblclick',
    '#something',
    sayWorld
);

addGlobalEventListener(
    'click',
    '#good',
    debounceSingleClickOnly(sayHello)
);

addGlobalEventListener(
    'dblclick',
    '#good',
    sayWorld
);


    
let counter = 0;
function sayHello({target: {id}}) {
    ++counter;
	console.log(`${counter}. clicked ${id}`);
}


function sayWorld({target: {id}}) {
    ++counter;
	console.log(`${counter}. double-clicked ${id}`);
}


function addGlobalEventListener(type, selector, callback) {
    document.addEventListener(type, (e) => {
        if (e.target.matches(selector)) {
            callback(e);
        }
    });
}


function debounce(func, wait, immediate) {
    let timeout;
    return function () {
        const context = this,
            args = arguments;
        const later = function () {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        const callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
}

function debounceSingleClickOnly(func, timeout = 500) {
    function eventHandler(event) {
        const { detail } = event;
        if (detail > 1) {
            return;
        }

        func.apply(this, arguments);
    }

    return debounce(eventHandler, timeout);
}

Thursday, April 29, 2021

Toggling boolean without using global variable

<div id='_form'>
    Form Inputs
</div>

<div id='_preview' style='display: none'>
    Preview
</div>

<button id='_toggle'>
   Toggle
</button>

<script>
window._toggle.onclick = () => previewFormInputs();
// can also do the following instead of the above
/* window._toggle.onclick = previewFormInputs; */

function setElementVisibility(element, visible) {
    element.style.display = visible ? 'inherit' : 'none';
}

let _previewVisible = false;
function previewFormInputs() {
    _previewVisible = !_previewVisible;
    setElementVisibility(window._form, !_previewVisible);
    setElementVisibility(window._preview, _previewVisible);
}
</script>
Live: https://jsfiddle.net/sewa4jh1/1/
The code above can be improved by combining both the action and the state _previewVisible
<div id='_form'>
    Form Inputs
</div>
 
<div id='_preview' style='display: none'>
    Preview
</div>
 
<button id='_toggle'>
   Toggle
</button>
 
<script>
window._toggle.onclick = () => previewFormInputs();
// can also do the following instead of the above
// _stats.onclick = previewFormInputs;
 
function setElementVisibility(element, visible) {
    element.style.display = visible ? 'inherit' : 'none';
}
 
const previewFormInputs = (function () {
    let previewVisible = false;
     
    return function () {
        previewVisible = !previewVisible;
        setElementVisibility(window._form, !previewVisible);
        setElementVisibility(window._preview, previewVisible);
    }   
})();
</script>
Live: https://jsfiddle.net/sewa4jh1/3/

Sunday, April 25, 2021

Do not be driven nuts by multiple nots


Multiple nots. Complex
    firstname != 'Juan' || lastname != 'Cruz'
English-speak: 
    If your firstname is not Juan or your lastname is not Cruz, therefore you are not Juan Cruz

Don't use the above, it has multiple nots. Use this:

Single not, simple. Translates well to English:
    !(firstname == 'Juan' && lastname == 'Cruz')
English-speak (reading from inside out): 
    If you are Juan Cruz, then don't.
English-speak (reading from left to right): 
    If you are not Juan Cruz, then do.
Postgres tuple test:
    (firstname, lastname) != ('Juan', 'Cruz')
Languages without tuple:
    firstname + ' ' + lastname != 'Juan Cruz'

**Schema (PostgreSQL v13)**

    create table person(firstname text, lastname text);
    
    insert into person(firstname, lastname) values
    ('Juan', 'Cruz'),
    ('Juan', 'Buen'),
    ('Michael', 'Cruz'),
    ('Michael', 'Buen');
    

---

**Query #1**

    select
        firstname, lastname,
        
        firstname != 'Juan' or lastname != 'Cruz' as test1,
        not (firstname = 'Juan' and lastname = 'Cruz') as test2,
        
        (firstname, lastname) != ('Juan', 'Cruz') as test3,
        (firstname || ' ' || lastname) != 'Juan Cruz' as test4
        
    from 
    	person;

| firstname | lastname | test1 | test2 | test3 | test4 |
| --------- | -------- | ----- | ----- | ----- | ----- |
| Juan      | Cruz     | false | false | false | false |
| Juan      | Buen     | true  | true  | true  | true  |
| Michael   | Cruz     | true  | true  | true  | true  |
| Michael   | Buen     | true  | true  | true  | true  |

---

[View on DB Fiddle](https://www.db-fiddle.com/f/nxbuszjT4zgmdj4pGaZeuQ/0)


-------------

Multiple nots. Complex
    firstname != 'Juan' && lastname != 'Cruz'

Single not, simple. Translates well to English:
    !(firstname == 'Juan' || lastname == 'Cruz')
English-speak: 
    If your firstname is Juan or lastname is Cruz, then don't.


**Schema (PostgreSQL v13)**

    create table person(firstname text, lastname text);
    
    insert into person(firstname, lastname) values
    ('Juan', 'Cruz'),
    ('Juan', 'Buen'),
    ('Michael', 'Cruz'),
    ('Michael', 'Buen');
    

---

**Query #1**

    select
        firstname, lastname,
        
        firstname != 'Juan' and lastname != 'Cruz' as test1,
        not (firstname = 'Juan' or lastname = 'Cruz') as test2
        
    from 
    	person;

| firstname | lastname | test1 | test2 |
| --------- | -------- | ----- | ----- |
| Juan      | Cruz     | false | false |
| Juan      | Buen     | false | false |
| Michael   | Cruz     | false | false |
| Michael   | Buen     | true  | true  |

---

[View on DB Fiddle](https://www.db-fiddle.com/f/nxbuszjT4zgmdj4pGaZeuQ/1)


Thursday, April 22, 2021

Intl.Segmenter

It would be awesome if Intl.Segmenter is perfect. For the meantime, retokenize the words that should not be together
function tokenizeZH(text) {
    const segmenter = new Intl.Segmenter('zh', { granularity: 'word' });
    const segments = segmenter.segment(text);

    const words = [];
    for (const { segment /* , index, isWordLike */ } of segments) {
        words.push(segment);
    }

    return words;
}

console.log(tokenizeZH('我不是太清楚'));

Live: https://jsfiddle.net/rgqen1zc/
Output:
["我不是", "太", "清楚"]

我不是 should be 我 不是