Monday, April 19, 2021

Unchecked runtime.lastError: The message port closed before a response was received.

Even if the listener's async code returns true to indicate it is running an async code, the code will still have the error "Unchecked runtime.lastError: The message port closed before a response was received" if the async code is wrongly placed.
The following code produces the error mentioned due to misplaced async
chrome.runtime.onMessage.addListener(async (message /* , sender, sendResponse */) => {
    if (message.action === UPDATE_PAGE) {
        await applyStyleFromSettings();
    }
    // https://stackoverflow.com/questions/53024819/chrome-extension-sendresponse-not-waiting-for-async-function    
    return true;
});
Returning true is not enough, to fully fix the problem, we must place the async code on the code body itself, not on the callback's declaration. Remove the async declaration on the listener, move it to the code's body instead
chrome.runtime.onMessage.addListener((message /*, sender, sendResponse */) => {
    (async () => {
        if (message.action === UPDATE_PAGE) {
            await applyStyleFromSettings();
        }
    })();
    // https://stackoverflow.com/questions/53024819/chrome-extension-sendresponse-not-waiting-for-async-function            
    return true;
});

Saturday, April 17, 2021

TypeScript, array map can't be type-checked

Given this interface:
	interface IHskLevel {
       hanzi: string;
       hsk: number;
    }
This code does not produce compile error (it should):
function* generateHskLevel(): Iterable<IHskLevel> {
    yield* json.map(
        ({ hanzi, pinyin, HSK: hsk }) => ({ hanzi, pinyin, hsk } as IHskLevel)
    );
}
This produces compile error (it should):
function* generateHskLevel(): Iterable<IHskLevel> {
    for (const { hanzi, pinyin, HSK: hsk } of json) {
        yield { hanzi, pinyin, hsk };
    }
}
Error:
error: TS2322 [ERROR]: Type '{ hanzi: string; pinyin: string; hsk: number; }' is not assignable to type 'IHskLevel'.
  Object literal may only specify known properties, and 'pinyin' does not exist in type 'IHskLevel'.
            pinyin,

Wednesday, April 14, 2021

Uncaught (in promise) The message port closed before a response was received

// eslint-disable-next-line no-undef
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
    if (request.dataNeeded === HANZI) {
        (async () => {                                       
            const hzl = await loadHanziListFile();                    
            sendResponse({data: hzl});
        })();
    
        // Need to return true if we are using async code in addListener.
        // Without this line..
        return true;
        // ..we will receive the error: 
        // Uncaught (in promise) The message port closed before a response was received
    }
});

Sunday, April 11, 2021

Service worker registration failed. Cannot read property 'onClicked' of undefined

Given this background.js:
// eslint-disable-next-line no-undef
chrome.action.onClicked.addListener((tab) => {
    console.log("working");

    // eslint-disable-next-line no-undef
    chrome.tabs.sendMessage(
        // tabs[0].id,
        tab.id,
        { action: "CHANGE_COLOR" },
        // eslint-disable-next-line no-unused-vars
        function (response) {}
    );

});

I'm using manifest version 3:
{
    "name": "Chinese word separator",
    "description": "Put spaces between words",
    "version": "1.0",
    "manifest_version": 3,    
    "background" : {
        "service_worker": "background.js"
    },
    "content_scripts": [
        {
            "matches": ["<all_urls>"],
            "css": [],
            "js": ["contentScript.js"]
        }
    ]  
}


And I received this error:

To fix the error, add the action property even if it is empty:

{
    "name": "Chinese word separator",
    "description": "Put spaces between words",
    "version": "1.0",
    "manifest_version": 3,    
    "action": {},
    "background" : {
        "service_worker": "background.js"
    },
    "content_scripts": [
        {
            "matches": ["<all_urls>"],
            "css": [],
            "js": ["contentScript.js"]
        }
    ]  
}
It is advisable to wrap the background.js via a wrapper so you'll get better error message, so if there's no action property in manifest.json..
{
    "name": "Chinese word separator",
    "description": "Put spaces between words",
    "version": "1.0",
    "manifest_version": 3,    
    "background" : {
        "service_worker": "background-wrapper.js"
    },
    "content_scripts": [
        {
            "matches": ["<all_urls>"],
            "css": [],
            "js": ["contentScript.js"]
        }
    ]  
}
..and you wrap the background via wrapper (e.g., background-wrapper.js)..
try {
    // eslint-disable-next-line no-undef
    importScripts("./background.js");
} catch (e) {
    console.error(e);
}
..you will receive a more descriptive error instead:
background-wrapper.js:5 TypeError: Cannot read property 'onClicked' of undefined
    at background.js:1
    at background-wrapper.js:3
(anonymous) @ background-wrapper.js:5
To fix the error, add action on manifest.json

Tuesday, March 9, 2021

Centering horizontally and vertically

<div id="parent">
    <div id="child">hello world</div>
</div>
Centering with margin, it has to be done at child level:
#parent {
    width: 50vw;
    height: 50vh;
    background-color: gray;
    display: flex;
}
#child {
    margin: auto; 
}
Try it at: https://jsfiddle.net/xd7uwgn8/

It's better if it is done at the parent level, the parent should be able to control the child elements:
#parent {
    width: 50vw;
    height: 50vh;
    background-color: gray;
    
    display: flex;
    justify-content: center;
    align-items: center;
}
#child {
}
Try it at: https://jsfiddle.net/xd7uwgn8/2/

Another good thing if it is done at parent level, creating unnecessary div+margin:auto for the content could be avoided:
<div id="parent">
    hello world
</div>
Try it at: https://jsfiddle.net/xd7uwgn8/3/

Tuesday, February 16, 2021

Dvorak navigations - there's no place like home keys

Dvorak keyboard layout is well-known for letting users type commonly used words using just the home keys. And there is no better way to improve that further than making the users use the letters themselves for moving the cursor around.
With the karabiner configuration below, users no longer need to lift their right arm to reach the arrow keys at the rightmost side of their keyboard.
The Karabiner-Element configuration below enable users to use the home keys for cursor navigation for Dvorak users

For best experience, map caps lock to control key, map left control key to esc key.

Not all apps honor the unix-y way of moving the cursor, the Karabiner-Element configuration below will enable it on almost all apps (except Microsoft Office). Microsoft Office apps still interprets Control+B as bold, Control+F as find

I noticed that on online code editors like codepen.io, though they recognize control+F and control+B as right and left arrow respectively, they does not recognize option+control+F as one word to the right, and option+control+B as one word to the left. Worse yet, option+control+F triggers full screen on codesandbox.io, the configuration below solves this problem too.

Up = control+P
Down = control+N
Left = control+B
Right = control+F

option + Right (One word to the right) = option + control+F
option + Left (One word to the left) = option + control+B

delete (hack/delete one character to the left) = control+H
fn+delete (delete one character to the right) = control+D

option + delete (hack/delete one word to the left) = option + control+H
option + fn+delete (delete one word to the right) = option + control+D


command/shift + delete (from the cursor, delete up to end of line) = control+alt+K (did not use control+K, will interfere with terminal's control+K)
no available shortcut key (delete the whole line) = control+alt+U (did not use control+U, will interfere with terminal's control+U)

{
    "description": "Dvorak navigations - no place like home keys",
    "manipulators": [
        {
            "from": {
                "key_code": "v",
                "modifiers": {
                    "mandatory": [
                        "left_control",
                        "left_option"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "end",
                    "modifiers" : [
                        "left_shift"
                    ]

                },
                {
                    "key_code": "delete_or_backspace"
                }
            ],
            "type": "basic"                                
        },
        {
            "from": {
                "key_code": "f",
                "modifiers": {
                    "mandatory": [
                        "left_control",
                        "left_option"                                            
                    ]
                }
            },
            "to": [
                {
                    "key_code": "home"
                },                                    
                {
                    "key_code": "end",
                    "modifiers" : [
                        "left_shift"
                    ]

                },
                {
                    "key_code": "delete_or_backspace"
                }
            ],
            "type": "basic"                                
        },                            
        {
            "from": {
                "key_code": "j",
                "modifiers": {
                    "mandatory": [
                        "left_control"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "delete_or_backspace"
                }
            ],
            "type": "basic"
        },
        {
            "from": {
                "key_code": "j",
                "modifiers": {
                    "mandatory": [
                        "left_control",
                        "left_option"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "delete_or_backspace",
                    "modifiers": [
                        "left_option"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "from": {
                "key_code": "h",
                "modifiers": {
                    "mandatory": [
                        "left_control"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "delete_forward"
                }
            ],
            "type": "basic"
        },
        {
            "from": {
                "key_code": "h",
                "modifiers": {
                    "mandatory": [
                        "left_control",
                        "left_option"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "delete_forward",
                    "modifiers": [
                        "left_option"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "from": {
                "key_code": "l",
                "modifiers": {
                    "mandatory": [
                        "left_control"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "down_arrow"
                }
            ],
            "type": "basic"
        },
        {
            "from": {
                "key_code": "l",
                "modifiers": {
                    "mandatory": [
                        "left_control",
                        "left_shift"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "down_arrow",
                    "modifiers": [
                        "left_shift"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "from": {
                "key_code": "r",
                "modifiers": {
                    "mandatory": [
                        "left_control"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "up_arrow"
                }
            ],
            "type": "basic"
        },
        {
            "from": {
                "key_code": "r",
                "modifiers": {
                    "mandatory": [
                        "left_control",
                        "left_shift"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "up_arrow",
                    "modifiers": [
                        "left_shift"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "from": {
                "key_code": "y",
                "modifiers": {
                    "mandatory": [
                        "left_control",
                        "left_option"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "right_arrow",
                    "modifiers": [
                        "left_option"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "from": {
                "key_code": "y",
                "modifiers": {
                    "mandatory": [
                        "left_control",
                        "left_option",
                        "left_shift"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "right_arrow",
                    "modifiers": [
                        "left_option",
                        "left_shift"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "from": {
                "key_code": "n",
                "modifiers": {
                    "mandatory": [
                        "left_control",
                        "left_option"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "left_arrow",
                    "modifiers": [
                        "left_option"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "from": {
                "key_code": "n",
                "modifiers": {
                    "mandatory": [
                        "left_control",
                        "left_option",
                        "left_shift"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "left_arrow",
                    "modifiers": [
                        "left_option",
                        "left_shift"
                    ]
                }
            ],
            "type": "basic"
        }
    ]
},

Friday, February 5, 2021

create react app + typescript script


% cat ~/.scripts/cra.sh 
#!/bin/zsh

if [ $# -eq 0 ] ; then
	echo "Pass the name of project"
	exit 1
fi

NAME="$1"

yarn dlx create-react-app $NAME --template typescript 

cd $NAME
yarn dlx @yarnpkg/pnpify --sdk vscode

yarn add react-refresh eslint-config-react-app

TO_DECLARE="declare module '@testing-library/react';"
printf $TO_DECLARE >> src/react-app-env.d.ts

code-insiders .