tag:blogger.com,1999:blog-41734506201823571672024-03-14T03:03:33.634+08:00class Programmer implements ANiceHumble, Person {Code as we know it. Open source stuff goes hereIEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.comBlogger335125tag:blogger.com,1999:blog-4173450620182357167.post-88839788610162265762023-03-15T17:24:00.004+08:002023-05-09T08:53:09.256+08:00ESLint wish<pre class="brush: java; highlight:[4,4]">
const y = new Set([10,20,30]);
const z = new Set([7,8,9]);
[1,2,3,4].forEach(y.add, z); // oops you added to z instead
console.log([...y]);
console.log([...z]);
// can't fault those who prefer this. I mean this line, not javascript's this :D
[1,2,3,4].forEach(Set.prototype.add, y);
console.log([...y]);
console.log('---');
console.log([...z]);
</pre>
If you are used to other language that automatically pass the <strong>this</strong> to the callback, you might want to be more explicit in JavaScript on passing which function you want to be called instead
<br/>
<br/>
Wish ESLint has a warning for passing callback like in line 4
<pre class="console">
[10, 20, 30]
[7, 8, 9, 1, 2, 3, 4]
---
[10, 20, 30, 1, 2, 3, 4]
[7, 8, 9, 1, 2, 3, 4]
</pre>IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-22384394985142665352023-01-30T15:45:00.011+08:002023-01-30T21:03:15.413+08:00Improvement to Chinese words separator's Parallel Text highlighting for Safari<p>Translated words that are split apart are now all highlighted</p><p>More details: <a href="https://www.anicehumble.com/2023/01/browsers-translation-mechanism.html">https://www.anicehumble.com/2023/01/browsers-translation-mechanism.html</a></p><p><a href="https://juejin.cn/post/7025868886914400293">https://juejin.cn/post/7025868886914400293</a></p><p></p><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjr3fvJPuXb960k4LwcBQj-AP7D44iVpSOad_4BiDp9nPpoz75BLbnf_0qL5QpvZveSHBYGdmU56w8XC9It7s5h7RRbyjssyXo9YnWtrZsvWb3XZMivtCuBC-bw2g89RDmiv79tauji0wX3QdXsegfNrtpuOWjoNxI55dw8DIQzs7pV3HqXHdJBrzNXTw" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="824" data-original-width="1252" src="https://blogger.googleusercontent.com/img/a/AVvXsEjr3fvJPuXb960k4LwcBQj-AP7D44iVpSOad_4BiDp9nPpoz75BLbnf_0qL5QpvZveSHBYGdmU56w8XC9It7s5h7RRbyjssyXo9YnWtrZsvWb3XZMivtCuBC-bw2g89RDmiv79tauji0wX3QdXsegfNrtpuOWjoNxI55dw8DIQzs7pV3HqXHdJBrzNXTw=s16000" /></a></div><br /><br /></div><div class="separator" style="clear: both; text-align: left;">Here's another example: <a href="https://czbooks.net/n/c8dbnf/cllgb6e?chapterNumber=2">https://czbooks.net/n/c8dbnf/cllgb6e?chapterNumber=2</a></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjrzbcflP_fAzizwPFw-KIUDRn-j10EKFY5y4p-clT61e_7Nb75Ma4ZGdLf_1ARSkmXYc_7tTYzEoAr6sOTuFkPB-MCpLfjOqx1Wrw9-omSNszyN1QFxBH_14RoZezHNfNozdIabthv9Sp4WaOKw7ECWchrFGrlkXySDXYkUp7mjQ8Y0zU83zfDr-g22w" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="1050" data-original-width="1152" src="https://blogger.googleusercontent.com/img/a/AVvXsEjrzbcflP_fAzizwPFw-KIUDRn-j10EKFY5y4p-clT61e_7Nb75Ma4ZGdLf_1ARSkmXYc_7tTYzEoAr6sOTuFkPB-MCpLfjOqx1Wrw9-omSNszyN1QFxBH_14RoZezHNfNozdIabthv9Sp4WaOKw7ECWchrFGrlkXySDXYkUp7mjQ8Y0zU83zfDr-g22w=s16000" /></a></div><br /><br /></div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: left;">More advanced example:</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjzJSLL02khKBQKhjYnVVrcyD9WImH0hrQNmcOnYnHly-f0f2_ySIvV90UCjhLAVGrVWVOtkUH2zy64ncsbArkSk1lFMZSiTKQQwJW6wre1wZMaIwHLCURmE-8HwZZ5e_fUCN7RS4zYMNCWwvm2z11OU6bg8aVhinVFxj5pqSN2upJ42aOf5yUuo6Ed0A" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="1046" data-original-width="1159" src="https://blogger.googleusercontent.com/img/a/AVvXsEjzJSLL02khKBQKhjYnVVrcyD9WImH0hrQNmcOnYnHly-f0f2_ySIvV90UCjhLAVGrVWVOtkUH2zy64ncsbArkSk1lFMZSiTKQQwJW6wre1wZMaIwHLCURmE-8HwZZ5e_fUCN7RS4zYMNCWwvm2z11OU6bg8aVhinVFxj5pqSN2upJ42aOf5yUuo6Ed0A=s16000" /></a></div><br /><div style="text-align: left;"><br /></div><div style="text-align: left;">More example:</div><div style="text-align: left;"><br /></div><div style="text-align: left;"><a href="https://www.zhihu.com/question/19562698">https://www.zhihu.com/question/19562698</a></div><div style="text-align: left;"><br /></div><div style="text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiUoTb5HhMq4CRvuUVsrvyAFVP04hLB_nRlCekzPGbcoAKSNRMtKV87DhRyVFC7ZXK0XQsnTtQWZirbr_w5szCwICAe4UjBnwWfGDczk9-IoBOxrbgkmaaV0HRIZX6dPcM4c9n6gP7_UZZ_4NWMBi4RSP_urAccr9RnciISPmVYFft9lMQpTiyJjPQg5A" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="1049" data-original-width="1026" src="https://blogger.googleusercontent.com/img/a/AVvXsEiUoTb5HhMq4CRvuUVsrvyAFVP04hLB_nRlCekzPGbcoAKSNRMtKV87DhRyVFC7ZXK0XQsnTtQWZirbr_w5szCwICAe4UjBnwWfGDczk9-IoBOxrbgkmaaV0HRIZX6dPcM4c9n6gP7_UZZ_4NWMBi4RSP_urAccr9RnciISPmVYFft9lMQpTiyJjPQg5A=s16000" /></a></div><br /><br /></div></div></div><br /></div></div><p></p>IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-1932186605526160662023-01-27T08:20:00.012+08:002023-01-27T09:22:09.038+08:00Browsers translation mechanism<p>
Safari can translate across tag boundaries. The phrase 先开发 (先 = first, 开发 = develop. in English is <b>develop first</b>), is split and the subject is placed between the the words that were split, i.e., <b>develop</b> <em>something something here</em> <b>first</b>
</p>
<br />
<p>
</p><div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpW21AMQ9ESCs7BS0I0DiMRqlX4FqEj94F68V1GWXpgHU0QzQR_teuFjYWVVCOYM1sI5ZHu3s4q4nULtOSgZVCuCC_gHR33mMXQ3wsq8s1Vrd-TUfY0NwSqaQT_U_WPmD3kW88W1NA7QCffUbKGVOHCD1-mZHndPoYaP3zd23d6vzFwNS545XP9swTwA/s1600/safari-translation-across-tags.png" style="display: block; margin-left: 1em; margin-right: 1em; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="704" data-original-width="1158" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpW21AMQ9ESCs7BS0I0DiMRqlX4FqEj94F68V1GWXpgHU0QzQR_teuFjYWVVCOYM1sI5ZHu3s4q4nULtOSgZVCuCC_gHR33mMXQ3wsq8s1Vrd-TUfY0NwSqaQT_U_WPmD3kW88W1NA7QCffUbKGVOHCD1-mZHndPoYaP3zd23d6vzFwNS545XP9swTwA/s1600/safari-translation-across-tags.png" /></a></div>
<p></p>
<br />
<p>Chrome can only translate tag by tag. Each one of its translation is contained in the same tag the source language are contained in originally</p>
<p>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNG7Itn1F18ZPzXAC9u14CtcT9aIOO4sgupAz_qyzo99Q3WUXDiytv2mrAu0hS7FTPRQnivkqPmvxG8u3N_wtRFwXySbuXR3zIBChGpZVoqFX-7dbvvprNgC5fQhJKIF6FrS4AI9YfMBttTY3SZVXkdC9yaunob64xdncEECWCOfb9sswwllqkOVYmSw/s1600/chrome-translation-by-tag.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" data-original-height="786" data-original-width="1279" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNG7Itn1F18ZPzXAC9u14CtcT9aIOO4sgupAz_qyzo99Q3WUXDiytv2mrAu0hS7FTPRQnivkqPmvxG8u3N_wtRFwXySbuXR3zIBChGpZVoqFX-7dbvvprNgC5fQhJKIF6FrS4AI9YfMBttTY3SZVXkdC9yaunob64xdncEECWCOfb9sswwllqkOVYmSw/s1600/chrome-translation-by-tag.png"/></a></div>
</p>
<br/>
<p>
Safari's advantage is that it can re-arrange the translation across tags, making the translation sound more natural than Chrome.
Chinese words separator app highlighter granularity is by phrase, it stops at commas and periods, each phrase/sentence is contained in a tag
<br/>
<br/>
Chrome's advantage, though sounding unnatural English, is that you can get a feel of the grammar of Chinese language and how its sentence is structured
</p>
<br />
<p>
<a href="https://apps.apple.com/app/chinese-words-separator/id1598790017" target="_blank">Chinese words separator for Safari</a>
<br />
<br/>
<a href="https://chrome.google.com/webstore/detail/chinese-words-separator/gacfacdpfimbkgcnlegknnmcccjgcbnp" target="_blank">Chinese words separator for Chrome</a>
</p>
IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-44607745048879339482022-07-01T14:20:00.001+08:002023-03-15T08:26:25.156+08:00Preventing accidental alert and console.log on productionUse this regular expression to find stray console.log and alert:
<pre class="console">^[^\/]\s+console\.group|^console\.group
^[^\/]\s+console\.log|^console\.log
^[^\/]\s+alert|^alert
</pre><chinese-words-separator--notification-area class="xcws-notif-host" id="chinese-words-separator--notification-area"><div><style>
.xcws-notif-host div {
all: revert;
}
.xcws-notif-host > div {
position: fixed;
top: 0px;
right: 4px;
max-width: 480px;
display: flex;
flex-direction: column;
z-index: 99999999999;
}
.xcws-notif-host > div > .xcws-hz-notification {
position:relative;
padding: 4px 8px;
color: white;
font-family: "Helvetica";
font-size: 12pt;
border-radius: 5px;
margin: 5px 0px 3px 5px;
opacity: 0;
left: 250px;
white-space: pre-wrap;
box-shadow: 3px 3px 5px 3px rgba(128,128,128,0.5);
}
.xcws-notif-host > div > .xcws-hz-notification > div > .xcws-hz-learned {
text-align: left;
color: white;
}
.xcws-notif-host > div .xcws-hz-pair-info {
display: block;
display: flex;
justify-content: space-between;
white-space: pre;
align-items: baseline;
}
.xcws-notif-host > div .xcws-hz-pair-info > div {
margin: 0;
color: white;
}
@-webkit-keyframes fadeInFromNone {
0% {
display: none;
opacity: 0;
}
5% {
display: block;
opacity: 1;
left: 0px;
}
75% {
display: block;
opacity: 1;
left: 0px;
}
100% {
display: block;
opacity: 0;
left: 250px;
}
}
.xcws-notif-host > div .xcws-hz--fade_out {
-webkit-animation: fadeInFromNone 5s ease-out;
}
.xcws-notif-host > div .xcws-hz--move_element_up {
-webkit-animation: fadeInFromNone 5s ease-out;
}
.xcws-notif-host > div .xcws-hz--do_after_goan{
opacity: 1;
overflow-y: hidden;
visibility: hidden;
left: 0px ;
-webkit-animation: xcws-hz---after_goan 0.5s;
}
@keyframes xcws-hz---after_goan {
0% {
visibility:hidden;
overflow-y: hidden;
}
100% {
padding: 0px;
height: 0px;
}
}
.xcws-notif-host > div > .xcws-hz-notification.xcws-hz-notification-type--success {
background: green;
}
.xcws-notif-host > div > .xcws-hz-notification.xcws-hz-notification-type--error {
background: orangered;
}
.xcws-notif-host > div > .xcws-hz-notification.xcws-hz-notification-type--info {
background: dodgerblue;
}
</style></div></chinese-words-separator--notification-area>IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-10906254525398950532022-06-11T10:30:00.006+08:002022-06-11T10:32:07.814+08:00Multiple nots is knotty to readSimplifying multiple nots:
<pre class="brush: java">
const isVariant = e.match(VARIANT_TESTER);
const matchingFirstHanzi = isVariant?.[2];
const matchingSecondHanzi = isVariant?.[4];
// knotty read:
// const needToShow
// = !isVariant || (matchingFirstHanzi !== hanzi && matchingSecondHanzi !== hanzi);
// De-Morgan'd, easy to read:
// const needToHide = isVariant && (matchingFirstHanzi === hanzi || matchingSecondHanzi === hanzi);
const needToHide = isVariant && [matchingFirstHanzi, matchingSecondHanzi].includes(hanzi);
const needToShow = !needToHide;
</pre>IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-76087928369752580062021-11-18T08:51:00.005+08:002021-11-18T08:51:34.277+08:00Horizontal scrollbar on textarea when text exceeds textarea's widthThis works on Chrome:
<pre class="brush: html">
white-space: nowrap;
</pre>
<br/>
However, the above does not work on Safari. In Safari, use this instead:
<pre class="brush: html">
white-space: pre;
overflow-x: auto;
word-wrap: normal;
</pre>
<br/>
<a href="https://stackoverflow.com/questions/657795/how-to-remove-word-wrap-from-textarea/25556711#25556711">https://stackoverflow.com/questions/657795/how-to-remove-word-wrap-from-textarea/25556711#25556711</a>IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-11406368611507204272021-11-14T17:45:00.007+08:002021-11-15T21:43:11.244+08:00Split keep function. Lookbehind, match, loopCommon code:
<pre class="brush: java">function escapeRegExp(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
</pre>
Lookbehind:
<pre class="brush: java">String.prototype.splitKeep = function (tokens) {
const escaped = escapeRegExp(tokens);
return this.split(new RegExp(`(?=[${escaped}])|(?<=[${escaped}])`, "g"));
};
</pre>
Match. Safari does not support lookbehind, use match approach:
<pre class="brush: java">String.prototype.splitKeep = function (tokens) {
const tokensEscaped = tokens
.split("")
.map((s) => escapeRegExp(s))
.join("|");
const wordMatch = `[^${escapeRegExp(tokens)}]+`;
return this.match(new RegExp(tokensEscaped + "|" + wordMatch, "g"));
};
</pre>
Loop-based approach:
<pre class="brush: java">String.prototype.splitKeep = function (tokens) {
let toPush = "";
const splitList = [];
for (const c of this) {
if (tokens.includes(c)) {
if (toPush.length > 0) {
splitList.push(toPush);
}
splitList.push(c);
toPush = "";
continue;
}
toPush += c;
}
if (toPush.length > 0) {
splitList.push(toPush);
}
return splitList;
};
</pre>
Benchmark code:
<pre class="brush: java">
console.time("test");
for (let i = 0; i < 10000; ++i) {
"pin1yin1".splitKeep("12345 ");
}
console.timeEnd("test");
</pre>
Results:
<pre class="console">
% node splitkeep-lookbehind.js
test: 19.35ms
% node splitkeep-lookbehind.js
test: 18.951ms
% node splitkeep-match.js
test: 54.635ms
% node splitkeep-match.js
test: 51.998ms
% node splitkeep-loop.js
test: 14.647ms
% node splitkeep-loop.js
test: 13.035ms
</pre>
IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-53684411619112591342021-10-02T08:37:00.002+08:002021-10-02T08:44:32.980+08:00Magic of 1s and 0s<p>
Instead of this:
</p>
<pre class="brush: java">
const VARIANT_CHAR = /variant of \p{Script=Han}/u;
// 'variant of chineseCharacterHere' sorts last
sort((a, b) =>
VARIANT_CHAR.test(a.definitions.join(' '))
&& VARIANT_CHAR.test(b.definitions.join(' ')) ?
0
: VARIANT_CHAR.test(a.definitions.join(' ')) ?
1
: VARIANT_CHAR.test(b.definitions.join(' ')) ?
-1
:
0
)
</pre>
<p>
Just do this:
</p>
<pre class="brush: java">
const VARIANT_CHAR = /variant of \p{Script=Han}/u;
// 'variant of chineseCharacterHere' sorts last
sort((a, b) =>
VARIANT_CHAR.test(a.definitions.join(' '))
-
VARIANT_CHAR.test(b.definitions.join(' '))
)
</pre>IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-31687421533701577492021-06-08T10:44:00.002+08:002021-06-08T10:44:49.022+08:00safari-web-extension-converter error<pre class="console">
$ xcrun safari-web-extension-converter
xcrun: error: unable to find utility "safari-web-extension-converter", not a developer tool or in PATH
</pre>
Do this first:
<pre class="console">
sudo xcode-select -s /Applications/Xcode.app
</pre>IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com5tag:blogger.com,1999:blog-4173450620182357167.post-55591134898394010252021-05-29T11:45:00.003+08:002021-05-29T11:49:39.146+08:00TypeScript Object is possibly undefined<pre class="brush: java">
.
.
.
newHzl[hanzi].pinyin = newHzl[hanzi].pinyin.filter(eachPinyin => pinyinEnglish[eachPinyin]);
} // for loop
</pre>
That yields this error:
<pre class="console">
error: TS2532 [ERROR]: Object is possibly 'undefined'.
newHzl[hanzi].pinyin = newHzl[hanzi].pinyin.filter(
</pre>
Got a similar error code on following code, and removed the error by checking if a variable has nothing, if nothing then skip(continue)
<pre class="brush: java">
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];
}
}
.
.
.
</pre>
I tried to do the same solution on the post's first code, but it still yields a compile error of <strong>Object is possibly 'undefined'</strong>
<pre class="brush: java">
.
.
.
if (!newHzl[hanzi].pinyin) {
continue;
}
newHzl[hanzi].pinyin = newHzl[hanzi].pinyin.filter(eachPinyin => pinyinEnglish[eachPinyin]);
} // for loop
</pre>
The workaround is to introduce a variable so the compiler will not have a hard time inferring the code's control flow:
<pre class="brush: java">
.
.
.
const hanziPinyin = newHzl[hanzi].pinyin;
if (!hanziPinyin) {
continue;
}
newHzl[hanzi].pinyin = hanziPinyin.filter(eachPinyin => pinyinEnglish[eachPinyin]);
} // for loop
</pre>
The problem went away
IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-87299813773255044822021-05-18T21:56:00.003+08:002021-11-14T17:08:07.371+08:00JavaScript splitKeepHelper function for split keep. Reference: <a href='https://medium.com/@shemar.gordon32/how-to-split-and-keep-the-delimiter-s-d433fb697c65'>https://medium.com/@shemar.gordon32/how-to-split-and-keep-the-delimiter-s-d433fb697c65</a>
<pre class="brush: java">
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, "\\$&");
}
</pre>
Add this when using TypeScript:
<pre class="brush: java">
declare global {
interface String {
splitKeep(tokens: string): string[];
}
}
</pre>
<strong>November 14</strong>
For browsers that the regex component don't have <a href="https://caniuse.com/js-regexp-lookbehind">lookbehind</a> capability yet, use match method:
<pre class="brush: java">
String.prototype.splitKeep = function (tokens) {
const tokensEscaped = tokens
.split('')
.map((s) => escapeRegExp(s))
.join('|');
const wordMatch = `[^${escapeRegExp(tokens)}]+`;
return this.match(new RegExp(tokensEscaped + '|' + wordMatch, 'g'));
};
</pre>IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-36690848134492868832021-05-15T09:59:00.012+08:002021-05-15T10:02:00.807+08:00Handling both single click and double click in JavaScriptLive code: <a href='https://jsfiddle.net/91zoa2qj/1/'>https://jsfiddle.net/91zoa2qj/1/</a>
<br/>
<br/>
HTML and CSS:
<pre class="brush: html">
<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>
</pre>
JS:
<pre class="brush: java">
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);
}
</pre>IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-3788421782683687252021-04-29T14:58:00.016+08:002021-05-15T12:43:58.046+08:00Toggling boolean without using global variable<pre class="brush: java">
<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>
</pre>
Live: <a href='https://jsfiddle.net/sewa4jh1/1/'>https://jsfiddle.net/sewa4jh1/1/</a>
<br/>
The code above can be improved by combining both the action and the state _previewVisible
<pre class="brush: java; highlight: [22,23,25]">
<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>
</pre>
Live: <a href='https://jsfiddle.net/sewa4jh1/3/'>https://jsfiddle.net/sewa4jh1/3/</a>
IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-7202589092083335592021-04-25T10:49:00.002+08:002021-04-25T10:50:31.252+08:00Do not be driven nuts by multiple nots<pre class="console">
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)
</pre>IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-67511083942017438812021-04-22T17:00:00.005+08:002021-04-22T17:05:01.830+08:00Intl.SegmenterIt would be awesome if Intl.Segmenter is perfect. For the meantime, retokenize the words that should not be together
<pre class="brush: java">
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('我不是太清楚'));
</pre>
<br/>
Live: <a href='https://jsfiddle.net/rgqen1zc/'>https://jsfiddle.net/rgqen1zc/</a>
<br/>
Output:
<pre class="console">
["我不是", "太", "清楚"]
</pre>
<br/>
<strong>我不是</strong> should be <strong>我 不是</strong>
IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-9432598697371010352021-04-19T09:16:00.002+08:002021-04-24T22:55:47.235+08:00Unchecked runtime.lastError: The message port closed before a response was received.Even if the listener's async code <a href="https://www.anicehumble.com/2021/04/uncaught-in-promise-message-port-closed.html">returns true</a> to indicate it is running an async code, the code will still have the error <strong>"Unchecked runtime.lastError: The message port closed before a response was received"</strong> if the async code is wrongly placed.
<br/>
The following code produces the error mentioned due to misplaced async
<pre class="brush: java; highlight: [1,6]">
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;
});
</pre>
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 from the listener, move it to the code's body instead
<pre class="brush: java; highlight: [1,2, 8]">
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;
});
</pre>IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-63918211760390127802021-04-17T12:32:00.007+08:002021-05-15T13:01:23.858+08:00TypeScript, array map can't be type-checkedGiven this interface:
<pre class="brush: java">
interface IHskLevel {
hanzi: string;
hsk: number;
}
</pre>
This code does not produce compile error (it should):
<pre class="brush: java">
function* generateHskLevel(): Iterable<IHskLevel> {
yield* json.map(
({ hanzi, pinyin, HSK: hsk }) => ({ hanzi, pinyin, hsk })
);
}
</pre>
This produces compile error (it should):
<pre class="brush: java">
function* generateHskLevel(): Iterable<IHskLevel> {
for (const { hanzi, pinyin, HSK: hsk } of json) {
yield { hanzi, pinyin, hsk };
}
}
</pre>
Error:
<pre class="console">
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,
</pre>
IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-1496883823007170592021-04-14T17:17:00.005+08:002021-04-14T17:17:50.914+08:00Uncaught (in promise) The message port closed before a response was received<pre class="brush: java">
// 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
}
});
</pre>IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-77332032850635168532021-04-11T18:13:00.012+08:002021-04-15T14:19:13.147+08:00Service worker registration failed. Cannot read property 'onClicked' of undefinedGiven this background.js:
<pre class="brush: java">// 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) {}
);
});
</pre>
I'm using manifest version 3:
<pre class="brush: java">{
"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"]
}
]
}
</pre>
<br />
<br />
And I received this error:
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXbzSpKedRANvzrcKgatf9ZhkrhXLyh3QX8YI9_v7EMAQdgCTiTb0q-VJHcCuGe3pir7vBsjGLBINqU4120yCsVBfkyqpBLPhijKl2h6eG5dxGIyQi9oT_5a2KIOyzPVmfPWNHvAbQTO4s/s0/Screen+Shot+2021-04-11+at+6.09.08+PM.png" style="display: block; margin-left: 1em; margin-right: 1em; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="271" data-original-width="358" height="303" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXbzSpKedRANvzrcKgatf9ZhkrhXLyh3QX8YI9_v7EMAQdgCTiTb0q-VJHcCuGe3pir7vBsjGLBINqU4120yCsVBfkyqpBLPhijKl2h6eG5dxGIyQi9oT_5a2KIOyzPVmfPWNHvAbQTO4s/w400-h303/Screen+Shot+2021-04-11+at+6.09.08+PM.png" width="400" /></a></div>
<p>
To fix the error, add the <strong>action</strong> property even if it is empty:
</p>
<pre class="brush: java; highlight: [6]">{
"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"]
}
]
}
</pre>
It is <a href="https://twitter.com/DotProto/status/1381318310956990466">advisable</a> to wrap the background.js via a wrapper so you'll get better error message, so if there's no <b>action</b> property in manifest.json..
<pre class="brush: java">{
"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"]
}
]
}
</pre>
..and you wrap the background via wrapper (e.g., background-wrapper.js).. <br />
<pre class="brush: java">try {
// eslint-disable-next-line no-undef
importScripts("./background.js");
} catch (e) {
console.error(e);
}
</pre>
..you will receive a more descriptive error instead:
<pre class="console">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
</pre>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXW8y8lBa0ttCAKbGhaBWBozNvwPA3ibpLpwBuACW-xXB1InWwguOUwsQboQK7B1wBaouWiIfdFyK4rYpTGSk3rtI9O8oC75kdi5zIa6nLLS0E24JwRhOxQqu2SAni24pl3z0EF8Tmspo4/s0/Screen+Shot+2021-04-12+at+9.19.08+AM.png" style="display: block; margin-left: 1em; margin-right: 1em; padding: 1em 0px; text-align: center;"><img alt="" border="0" data-original-height="168" data-original-width="1244" height="86" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXW8y8lBa0ttCAKbGhaBWBozNvwPA3ibpLpwBuACW-xXB1InWwguOUwsQboQK7B1wBaouWiIfdFyK4rYpTGSk3rtI9O8oC75kdi5zIa6nLLS0E24JwRhOxQqu2SAni24pl3z0EF8Tmspo4/w640-h86/Screen+Shot+2021-04-12+at+9.19.08+AM.png" width="640" /></a></div>
To fix the error, add <strong>action</strong> on manifest.jsonIEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-60416841303771875282021-03-09T21:50:00.002+08:002021-03-09T21:51:51.016+08:00Centering horizontally and vertically<pre class="brush: html">
<div id="parent">
<div id="child">hello world</div>
</div>
</pre>
Centering with margin, it has to be done at child level:
<pre class="brush: html; highlight: [5,8]">
#parent {
width: 50vw;
height: 50vh;
background-color: gray;
display: flex;
}
#child {
margin: auto;
}
</pre>
Try it at: <a href="https://jsfiddle.net/xd7uwgn8/">https://jsfiddle.net/xd7uwgn8/</a>
<br/>
<br/>
It's better if it is done at the parent level, the parent should be able to control the child elements:
<pre class="brush: html; highlight: [6,7,8]">
#parent {
width: 50vw;
height: 50vh;
background-color: gray;
display: flex;
justify-content: center;
align-items: center;
}
#child {
}
</pre>
Try it at: <a href='https://jsfiddle.net/xd7uwgn8/2/'>https://jsfiddle.net/xd7uwgn8/2/</a>
<br/>
<br/>
Another good thing if it is done at parent level, creating unnecessary div+margin:auto for the content could be avoided:
<pre class="brush: html">
<div id="parent">
hello world
</div>
</pre>
Try it at: <a href='https://jsfiddle.net/xd7uwgn8/3/'>https://jsfiddle.net/xd7uwgn8/3/</a>IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-81828940026646904892021-02-16T21:22:00.034+08:002021-03-25T13:58:06.590+08:00Dvorak navigations - there's no place like home keysDvorak 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.<br/>
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.
<br/>
The Karabiner-Element configuration below enable users to use the home keys for cursor navigation for Dvorak users<br/>
<br/>
For best experience, map <strong>caps lock</strong> to <strong>control</strong> key, map <strong>left control</strong> key to <strong>esc</strong> key.<br/>
<br />
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
<br/>
<br/>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.
<br/>
<br />
<div>Up = control+P</div>
<div>Down = control+N</div>
<div>Left = control+B</div>
<div>Right = control+F</div>
<div><br /></div>
<div>option + Right (One word to the right) = option + control+F</div>
<div>option + Left (One word to the left) = option + control+B</div>
<div><br /></div>
<div>delete (hack/delete one character to the left) = control+H</div>
<div>fn+delete (delete one character to the right) = control+D</div>
<div><br /></div>
<div>option + delete (hack/delete one word to the left) = option + control+H</div>
<div>option + fn+delete (delete one word to the right) = option + control+D</div>
<br/>
<br/>
<div>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)</div>
<div>no available shortcut key (delete the whole line) = control+alt+U (did not use control+U, will interfere with terminal's control+U)</div>
<br/>
<pre class="console">
{
"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"
}
]
},
</pre>IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-6486688616283599852021-02-08T16:25:00.007+08:002021-02-08T16:28:40.682+08:00Error on VS Code: Git: git@github.com: Permission denied (publickey)To solve that problem, type this in terminal:
<pre class="console">
% ssh-add
</pre>
Solution source: <a href="https://superuser.com/questions/360686/what-exactly-does-ssh-add-do">https://superuser.com/questions/360686/what-exactly-does-ssh-add-do</a>
IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-25663408423013682632021-02-05T18:48:00.003+08:002021-02-08T16:28:04.952+08:00create react app + typescript script<pre class="console">
% 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 .
</pre>IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-75962062500369365882021-01-29T10:38:00.001+08:002021-01-29T10:38:11.589+08:00Failed to load config "react-app" to extend from.Solution:
<pre class="console">
$ yarn add react-refresh eslint-config-react-app
</pre>IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0tag:blogger.com,1999:blog-4173450620182357167.post-37233895170251500162020-08-18T21:42:00.007+08:002020-08-18T21:46:02.479+08:00Avoiding name clashes when using HoC in React<div>
After watching Michael Jackson's video on <a href='https://www.youtube.com/watch?v=BcVAq3YFiuc'>HoC vs render props</a>, I noticed that the problems in HoC he mentioned can be avoided, albeit the component authors have to make their component's property names be dynamic. The component's property name(s) have to be explicitly set.
<br/><br/>
To cut to the chase, here's one implementation: <a href='https://codesandbox.io/s/compassionate-curie-i1x7w'>https://codesandbox.io/s/compassionate-curie-i1x7w</a>
</div>
<br/>
App.js
<pre class="brush: java">
import React from "react";
import "./styles.css";
import { withMouse } from "./Mouse";
import { withCat } from "./Cat";
const withMouseProp = "mouse";
const withCatProp = "spawnDate";
class App extends React.Component {
render() {
const mouseProp = this.props[withMouseProp];
const catProp = this.props[withCatProp];
const { message } = this.props;
return (
<div className="App">
<h1>{message}</h1>
<h2>Start editing to see some magic happen!</h2>
mouse is at {mouseProp.x} {mouseProp.y}
<br />
Cat spawn date: {catProp}
</div>
);
}
}
export default withMouse(
withCat(App, withCatProp, withMouseProp),
withMouseProp
);
</pre>
Mouse.js
<pre class="brush: java">
import React from "react";
export function withMouse(Component, mousePropName) {
return class extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (event) = {
this.setState({
x: event.clientX,
y: event.clientY
});
};
render() {
const withPropName = { ...this.props, [mousePropName]: this.state };
return (
<div onMouseMove={this.handleMouseMove}>
<Component {...withPropName} />
</div>
);
}
};
}
</pre>
Cat.js
<pre class="brush: java">
import React from "react";
export function withCat(Component, catPropName, mousePropName) {
return class extends React.Component {
state = { x: 0, y: 0 };
componentDidUpdate(prevProps) {
const mouse = this.props[mousePropName];
if (
mouse.x !== prevProps[mousePropName].x ||
mouse.y !== prevProps[mousePropName].y
) {
this.setState(mouse);
}
}
render() {
const mouse = this.props[mousePropName];
const withPropName = {
...this.props,
[catPropName]: new Date().toISOString()
};
return (
<>
<Component {...withPropName} />
<div>
style={{
position: "absolute",
left: mouse.x,
top: mouse.y,
width: "25px",
height: "25px",
backgroundColor: "red"
}}
>
cat
</div>
</>
);
}
};
}
</pre>
The above solution is moot point now, use React hooks instead.IEnableMuchhttp://www.blogger.com/profile/01393635235961303977noreply@blogger.com0