From a7d55a7d8149c1a79f939360b333907a1c4bcd58 Mon Sep 17 00:00:00 2001 From: Tom King Date: Mon, 2 Feb 2026 01:35:55 -0800 Subject: [PATCH 01/18] feat: openurlparam/unskim- check orig URL searchParam --- .cspell/dictionary-custom.txt | 2 ++ src/openurlparam.ts | 4 ++-- src/unskim.ts | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.cspell/dictionary-custom.txt b/.cspell/dictionary-custom.txt index f45e924..3dc467a 100644 --- a/.cspell/dictionary-custom.txt +++ b/.cspell/dictionary-custom.txt @@ -9,6 +9,7 @@ Listrak Matomo Octocat Omeda +openurlparam smushing Temu Textastic @@ -16,3 +17,4 @@ uglifyjs unskim utmstrip Vero +x-man diff --git a/src/openurlparam.ts b/src/openurlparam.ts index fc9f70e..6e70655 100644 --- a/src/openurlparam.ts +++ b/src/openurlparam.ts @@ -4,8 +4,8 @@ // default to getting the current address & making it a URL object to work with const origUrl: URL = new URL(document.location.href); - // exit if the current URL object searchParam is less than 11 chars ('https://x.x) - if (origUrl.search.length < 11) { + // exit if URL isn't https or if searchParam is less than 13 chars ('u=https://x.y) + if (origUrl.protocol !== 'https:' || origUrl.search.length < 13) { return; } diff --git a/src/unskim.ts b/src/unskim.ts index f41f7a0..ad97243 100644 --- a/src/unskim.ts +++ b/src/unskim.ts @@ -17,8 +17,8 @@ } } - // if the current URL object doesn't even have a searchParam, exit now - if (origUrl.search === '') { + // exit if searchParam is less than 13 chars ('u=https://x.y) + if (origUrl.search.length < 13 ) { return; } From 2b75978a8da7ff995ca6bfae0618799b61274187 Mon Sep 17 00:00:00 2001 From: Tom King Date: Mon, 2 Feb 2026 01:38:16 -0800 Subject: [PATCH 02/18] chore: openurlparam/unskim- bump version, update dist & README --- README.md | 16 ++++++++-------- bookmarklets.json | 4 ++-- dist/openurlparam.bookmarklet | 2 +- dist/unskim.bookmarklet | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3970dd3..d29aa8e 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ editor. Handy to view the source code of a web page or download a raw file. repository in Mobile Safari, show the same repo in the Working Copy iOS app (cloning the repo locally if necessary). -+ __[OpenURLParam] v1.2.0__: Work-around for blocked navigation from certain ++ __[OpenURLParam] v1.2.1__: Work-around for blocked navigation from certain ad or tracking blockers. If the current URL contains a parameter in the form of `url=...` this bookmarklet will parse the `url` parameter and navigate to that URL. @@ -88,7 +88,7 @@ to the clipboard. Finally, replaces history & reloads the page. Now works to remove host-specific trackers for common online retailers and Google Search. _NOTE:_ This bookmarklet also works with Safari and Firefox on macOS. -+ __[unskim] v2.0.1__: Bypass redirect and affiliate link wrappers by ++ __[unskim] v2.0.2__: Bypass redirect and affiliate link wrappers by detecting common URL parameters (like `url=`, `destination=`, `redirect=`, etc.) and navigating directly to the target URL. Also handles Safari DNS error pages when a redirect service is blocked, extracting the intended destination @@ -127,9 +127,9 @@ followed link into a bookmark for JavaScript bookmarklet. + __Mobile Safari setup link__ -- [Setup OpenInGoodReader] v1.5.2 + __Mobile Safari setup link__ -- [Setup OpenInTextastic] v1.1.2 + __Mobile Safari setup link__ -- [Setup OpenInWorkingCopy] v1.6.2 -+ __Mobile Safari setup link__ -- [Setup OpenURLParam] v1.2.0 ++ __Mobile Safari setup link__ -- [Setup OpenURLParam] v1.2.1 + __Mobile Safari setup link__ -- [Setup UtmStrip] v2.2.0 -+ __Mobile Safari setup link__ -- [Setup unskim] v2.0.1 ++ __Mobile Safari setup link__ -- [Setup unskim] v2.0.2 + __Mobile Safari setup link__ -- [Setup x-man] v1.3.1 ## Requirements @@ -352,9 +352,9 @@ repos I had; doesn't build yet [OpenInGoodReader]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent)%26%26%2F%5C.pdf(%24%7C%5C%3F)%2F.test(location.href))location.href='gr'%2Blocation.href%3Bvoid'1.5.2' "OpenInGoodReader" [OpenInTextastic]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=location.href.replace(%2F%5Ehttps%3F%2F%2C'textastic')%3Bvoid'1.1.2' "OpenInTextastic" [OpenInWorkingCopy]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent)%26%26('bitbucket.org'===location.host%7C%7C'github.com'===location.host))location.href=%60working-copy:%2F%2Fshow%3Fremote=%24%7BencodeURIComponent(location.href.split('%2F').slice(0%2C5).join('%2F'))%7D.git%60%3Bvoid'1.6.2' "OpenInWorkingCopy" -[OpenURLParam]: javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=new%20URL(document.location.href)%3Bif(11%3Et.search.length)return%3Bconst%20e=new%20URLSearchParams(t.search)%2Cn=%5B'url'%2C'destination'%2C'redirect'%2C'target'%2C'goto'%2C'u'%2C'dest'%2C'link'%2C'out'%5D.filter(t=%3Ee.has(t)).map(t=%3E%7Bconst%20n=e.get(t)%3Breturn%20n%3FdecodeURIComponent(n):''%7D).find(t=%3Et.match(%2F%5Ehttps:%2F))%3Bn%26%26window.location.replace(new%20URL(n))%7D)()%3Bvoid'1.2.0' "OpenURLParam" +[OpenURLParam]: javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=new%20URL(document.location.href)%3Bif('https:'!==t.protocol%7C%7C13%3Et.search.length)return%3Bconst%20e=new%20URLSearchParams(t.search)%2Cn=%5B'url'%2C'destination'%2C'redirect'%2C'target'%2C'goto'%2C'u'%2C'dest'%2C'link'%2C'out'%5D.filter(t=%3Ee.has(t)).map(t=%3E%7Bconst%20n=e.get(t)%3Breturn%20n%3FdecodeURIComponent(n):''%7D).find(t=%3Et.match(%2F%5Ehttps:%2F))%3Bn%26%26window.location.replace(new%20URL(n))%7D)()%3Bvoid'1.2.1' "OpenURLParam" [UtmStrip]: javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=location.pathname%2Ce=location.search%3Bif(3%3Ee.length%26%26!t.includes('%2Famp'))return%3Bconst%20i=location.hostname%2Cs=new%20URLSearchParams(e)%2Ca=%5B'assetType'%2C'elqTrack'%2C'mkt%5Ftok'%2C'originalReferer'%2C'referrer'%2C'terminal%5Fid'%2C'trk'%2C'trkCampaign'%2C'trkInfo'%2C'anid'%2C'assetid'%2C'campaignid'%2C'eid'%2C'gclid'%2C'recipientid'%2C'siteid'%2C'sib%5Fcuid'%2C'sib%5Fsid'%2C'%5Fbta%5Ftid'%2C'%5Fbta%5Fc'%2C'%5F%5Fs'%2C'fbclid'%2C'hrc'%2C'igsh'%2C'igshid'%2C'refsrc'%2C'%5Fgl'%2C'gclsrc'%2C'srsltid'%2C'%5Fhsenc'%2C'%5Fhsmi'%2C'%5F%5Fhsfp'%2C'%5F%5Fhssc'%2C'%5F%5Fhstc'%2C'cm%5Fmmc'%2C'cm%5Fre'%2C'cm%5Fsp'%2C'manual%5Fcm%5Fmmc'%2C'%5Fke'%2C'%5Fkx'%2C'trk%5Fcontact'%2C'trk%5Fmsg'%2C'trk%5Fmodule'%2C'trk%5Fsid'%2C'mc%5Fcid'%2C'mc%5Feid'%2C'iesrc'%2C'msclkid'%2C'dclid'%2C'twclid'%2C'ttclid'%2C'oly%5Fenc%5Fid'%2C'oly%5Fanon%5Fid'%2C'epik'%2C'vero%5Fid'%5D%2C%5F=%5B'fb%5F'%2C'action%5F'%2C'ga%5F'%2C'utm%5F'%2C'hmb%5F'%2C'hsa%5F'%2C'mtm%5F'%2C'pk%5F'%2C'oly%5F'%2C'stm%5F'%5D%3B%2F%5C.aliexpress%5C.%5Ba-z%5D%7B2%2C3%7D%24%2F.test(i)%3F(a.push('algo%5Fevid'%2C'algo%5Fpvid'%2C'btsid'%2C'spm'%2C'scm'%2C'ws%5Fab%5Ftest')%2C%5F.push('aff%5F')):%2F(%7C%5C.)amazon%5C.com%24%2F.test(i)%3Fa.push('%5Fencoding'%2C'asc%5Fcampaign'%2C'asc%5Frefurl'%2C'asc%5Fsource'%2C'ascsubtag'%2C'content-id'%2C'crid'%2C'cv%5Fct%5Fcx'%2C'dib%5Ftag'%2C'dib'%2C'ie'%2C'language'%2C'linkCode'%2C'linkId'%2C'pd%5Frd%5Fi'%2C'pd%5Frd%5Fr'%2C'pd%5Frd%5Fw'%2C'pd%5Frd%5Fwg'%2C'pf%5Frd%5Fi'%2C'pf%5Frd%5Fm'%2C'pf%5Frd%5Fp'%2C'pf%5Frd%5Fr'%2C'pf%5Frd%5Fs'%2C'pf%5Frd%5Ft'%2C'pf'%2C'psc'%2C'qid'%2C'ref%5F'%2C'sprefix'%2C'sr'%2C'tag'%2C'th'):i.endsWith('.ebay.com')%7C%7C%2F%5C.ebay%5C.co%5C.%5Ba-z%5D%7B2%7D%24%2F.test(i)%3Fa.push('mkevt'%2C'mkcid'%2C'mkrid'%2C'campid'%2C'toolid'%2C'customid'%2C'norover'%2C'itm'%2C'amdata'):%2F(%5E%7C%5C.)google%5C.(com%7C%5Ba-z%5D%7B2%7D%7Ccom%3F%5C.%5Ba-z%5D%7B2%7D)%24%2F.test(i)%26%26t.startsWith('%2Fsearch')%3Fa.push('aqs'%2C'ei'%2C'gs%5Flp'%2C'gs%5Fssp'%2C'iflsig'%2C'sca%5Fesv'%2C'ved'%2C'oq'%2C'sa'%2C'uact'%2C'rlz'%2C'sxsrf'%2C'bih'%2C'biw'%2C'client'%2C'prmd'%2C'sclient'%2C'source'%2C'sourceid'%2C'ie'%2C'oe'):i.endsWith('.linkedin.com')%3Fa.push('li%5Ffat%5Fid'%2C'licu'%2C'lipi'%2C'midSig'%2C'midToken'%2C'refId'):i.endsWith('.target.com')%3Fa.push('afid'%2C'clkid'%2C'lnm'%2C'preselect'%2C'tref'):i.endsWith('.temu.com')%3F(a.push('%5Fbg%5Ffs'%2C'%5Fp%5Fjump%5Fid'%2C'%5Fp%5Frfs'%2C'refer%5Fpage%5Fid'%2C'refer%5Fpage%5Fname'%2C'refer%5Fpage%5Fsn')%2C%5F.push('%5Fx%5F')):i.endsWith('.tiktok.com')%7C%7C'tiktok.com'===i%3Fa.push('%5Fd'%2C'%5Fr'%2C'%5Ft'%2C'is%5Ffrom%5Fwebapp'%2C'preview%5Fpb'%2C'share%5Fapp%5Fname'%2C'share%5Fitem%5Fid'%2C'tt4d%5Ft'%2C'timestamp'%2C'u%5Fcode'%2C'user%5Fid'):%2F%5C.(twitter%7Cx)%5C.com%24%2F.test(i)%7C%7C%2F%5E(twitter%7Cx)%5C.com%24%2F.test(i)%3Fa.push('cn'%2C'ref%5Fsrc'%2C'ref%5Furl'%2C's'%2C't'):i.endsWith('.walmart.com')%3Fa.push('adsredirect'%2C'affiliates%5Fad%5Fid'%2C'athcpid'%2C'athpgid'%2C'athcgid'%2C'athmtid'%2C'athstid'%2C'athznid'%2C'athiession'%2C'athancid'%2C'athposb'%2C'athena'%2C'campaign%5Fid'%2C'wmlspartner'):(%2F(m%7Cwww)%5C.youtube%5C.com%24%2F.test(i)%7C%7C'youtu.be'===i%7C%7C'www.youtube-nocookie.com'===i)%26%26a.push('ac'%2C'annotation%5Fid'%2C'app'%2C'feature'%2C'gclid'%2C'kw'%2C'src%5Fvid')%3Bconst%20c=new%20Set(a.map(t=%3Et.toLowerCase()))%2Cr=t=%3E%7Bconst%20e=t.toLowerCase()%3Breturn%20c.has(e)%7C%7C%5F.some(t=%3Ee.startsWith(t))%7C%7C%2F%5Ecm%5Fmmca%5Cd%2B%24%2Fi.test(t)%7D%3Bfor(const%20t%20of%5B...s.keys()%5D)r(t)%26%26s.delete(t)%3Blet%20d=s.toString()%3Bd=d%3F'%3F'%2Bd:''%3Bconst%20o=t.replace(%2F%5C%2Famp%5C%2F%3F%24%2F%2C'')%3Bif((e!==d%7C%7Ct!==o)%26%26confirm('Update%20history%20and%20copy%20cleaned%20URL%20to%20clipboard%3F'))%7Bconst%20t=%60%24%7Blocation.protocol%7D%2F%2F%24%7Blocation.host%7D%24%7Bo%7D%24%7Bd%7D%24%7Blocation.hash%7D%60%3Bnavigator.clipboard.writeText(t)%2Chistory.replaceState(null%2C''%2Ct)%3Bconst%20e=window.open(t%2C'%5Fself'%2C'noreferrer')%3Be%26%26(e.opener=null)%7D%7D)()%3Bvoid'2.2.0' "UtmStrip" -[unskim]: javascript:'use%20strict'%3B(()=%3E%7Blet%20e=new%20URL(document.location.href)%3Bif('safari-resource:%2FErrorPage.html'===e.href)%7Bconst%20t=document.querySelector('p.error-message')%3F.textContent%3F.match(%2FSafari%20can't%20open%20the%20page%20%22(https%3F:%5B%5E%22%5D%2B)%22%2F)%3F.%5B1%5D%3Bt%26%26(e=new%20URL(t))%7Dif(''===e.search)return%3Bconst%20t=new%20URLSearchParams(e.search)%2Cr=%5B'url'%2C'destination'%2C'redirect'%2C'target'%2C'goto'%2C'u'%2C'dest'%2C'link'%2C'out'%5D.filter(e=%3Et.has(e)).map(e=%3E%7Bconst%20r=t.get(e)%3Breturn%20r%3FdecodeURIComponent(r):''%7D).find(e=%3Ee.match(%2F%5Ehttps%3F:%2F))%3Br%26%26window.location.replace(new%20URL(r))%7D)()%3Bvoid'2.0.1' "unskim" +[unskim]: javascript:'use%20strict'%3B(()=%3E%7Blet%20e=new%20URL(document.location.href)%3Bif('safari-resource:%2FErrorPage.html'===e.href)%7Bconst%20t=document.querySelector('p.error-message')%3F.textContent%3F.match(%2FSafari%20can't%20open%20the%20page%20%22(https%3F:%5B%5E%22%5D%2B)%22%2F)%3F.%5B1%5D%3Bt%26%26(e=new%20URL(t))%7Dif(13%3Ee.search.length)return%3Bconst%20t=new%20URLSearchParams(e.search)%2Cr=%5B'url'%2C'destination'%2C'redirect'%2C'target'%2C'goto'%2C'u'%2C'dest'%2C'link'%2C'out'%5D.filter(e=%3Et.has(e)).map(e=%3E%7Bconst%20r=t.get(e)%3Breturn%20r%3FdecodeURIComponent(r):''%7D).find(e=%3Ee.match(%2F%5Ehttps%3F:%2F))%3Br%26%26window.location.replace(new%20URL(r))%7D)()%3Bvoid'2.0.2' "unskim" [x-man]: javascript:'use%20strict'%3B(()=%3E%7Bconst%20i=navigator.userAgent%2Cn=i.includes('Chrome%2F')%7C%7Ci.includes('Firefox%2F')%7C%7Ci.includes('Brave%2F')%7C%7Ci.includes('Edg%2F')%2Co=i.includes('Safari%2F')%2Ct=!n%26%26!o%2Ca=navigator.platform.startsWith('Mac')%26%26o%26%26!n%26%26!t%26%26!navigator.maxTouchPoints%26%262%3Enavigator.maxTouchPoints%2Ce=window.getSelection()%3Bif(!e)return%3Bconst%20l=e.toString().split('%5Cn')%5B0%5D.trim()%3Blet%20r='x-man-page:%2F%2F'%3Bconst%20c=l.match(%2F%5E(.%2A%3F)%5C((%5Cd)%5C)%24%2F)%3Bif(r%2B=c%3F'1'===c%5B2%5D%3Fc%5B1%5D:%60%24%7Bc%5B2%5D%7D%2F%24%7Bc%5B1%5D%7D%60:l%2Cl)%7Bif(confirm(%60Link%20for%20%22%24%7Bl%7D%22%20is:%20%24%7Br%7D%5Cn%5CnCopy%20to%20clipboard%3F%60))%7Blet%20i=''%3Bif(navigator.clipboard%3Fnavigator.clipboard.writeText(r):(i=%60Unable%20to%20copy%20%22%24%7Br%7D%22%20to%20clipboard.%60%2Cr='')%2Ca%26%26''!==r)%7Blet%20n=null%3Btry%7Bn=window.open(r)%2Cn%26%26(n.opener=null)%7Dcatch(n)%7Bi=%60Popup%20window%20blocked.%20Clipboard%20contains%20%22%24%7Br%7D%22%60%7Dfinally%7Bwindow.focus()%2Cnull!==n%26%26setTimeout(()=%3E%7Bn%26%26n.close()%7D%2C3333)%7D%7Delse''!==r%26%26(i=%60Unable%20to%20open%20new%20link%20with%20Terminal.%20Clipboard%20contains%20%22%24%7Br%7D%22%60)%3B''!==i%26%26alert(i)%7De.empty()%7D%7D)()%3Bvoid'1.3.1' "x-man" @@ -371,9 +371,9 @@ repos I had; doesn't build yet [Setup OpenInGoodReader]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent)%26%26%2F%5C.pdf(%24%7C%5C%3F)%2F.test(location.href))location.href='gr'%2Blocation.href%3Bvoid'1.5.2' "Setup OpenInGoodReader" [Setup OpenInTextastic]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=location.href.replace(%2F%5Ehttps%3F%2F%2C'textastic')%3Bvoid'1.1.2' "Setup OpenInTextastic" [Setup OpenInWorkingCopy]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent)%26%26('bitbucket.org'===location.host%7C%7C'github.com'===location.host))location.href=%60working-copy:%2F%2Fshow%3Fremote=%24%7BencodeURIComponent(location.href.split('%2F').slice(0%2C5).join('%2F'))%7D.git%60%3Bvoid'1.6.2' "Setup OpenInWorkingCopy" -[Setup OpenURLParam]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=new%20URL(document.location.href)%3Bif(11%3Et.search.length)return%3Bconst%20e=new%20URLSearchParams(t.search)%2Cn=%5B'url'%2C'destination'%2C'redirect'%2C'target'%2C'goto'%2C'u'%2C'dest'%2C'link'%2C'out'%5D.filter(t=%3Ee.has(t)).map(t=%3E%7Bconst%20n=e.get(t)%3Breturn%20n%3FdecodeURIComponent(n):''%7D).find(t=%3Et.match(%2F%5Ehttps:%2F))%3Bn%26%26window.location.replace(new%20URL(n))%7D)()%3Bvoid'1.2.0' "Setup OpenURLParam" +[Setup OpenURLParam]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=new%20URL(document.location.href)%3Bif('https:'!==t.protocol%7C%7C13%3Et.search.length)return%3Bconst%20e=new%20URLSearchParams(t.search)%2Cn=%5B'url'%2C'destination'%2C'redirect'%2C'target'%2C'goto'%2C'u'%2C'dest'%2C'link'%2C'out'%5D.filter(t=%3Ee.has(t)).map(t=%3E%7Bconst%20n=e.get(t)%3Breturn%20n%3FdecodeURIComponent(n):''%7D).find(t=%3Et.match(%2F%5Ehttps:%2F))%3Bn%26%26window.location.replace(new%20URL(n))%7D)()%3Bvoid'1.2.1' "Setup OpenURLParam" [Setup UtmStrip]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=location.pathname%2Ce=location.search%3Bif(3%3Ee.length%26%26!t.includes('%2Famp'))return%3Bconst%20i=location.hostname%2Cs=new%20URLSearchParams(e)%2Ca=%5B'assetType'%2C'elqTrack'%2C'mkt%5Ftok'%2C'originalReferer'%2C'referrer'%2C'terminal%5Fid'%2C'trk'%2C'trkCampaign'%2C'trkInfo'%2C'anid'%2C'assetid'%2C'campaignid'%2C'eid'%2C'gclid'%2C'recipientid'%2C'siteid'%2C'sib%5Fcuid'%2C'sib%5Fsid'%2C'%5Fbta%5Ftid'%2C'%5Fbta%5Fc'%2C'%5F%5Fs'%2C'fbclid'%2C'hrc'%2C'igsh'%2C'igshid'%2C'refsrc'%2C'%5Fgl'%2C'gclsrc'%2C'srsltid'%2C'%5Fhsenc'%2C'%5Fhsmi'%2C'%5F%5Fhsfp'%2C'%5F%5Fhssc'%2C'%5F%5Fhstc'%2C'cm%5Fmmc'%2C'cm%5Fre'%2C'cm%5Fsp'%2C'manual%5Fcm%5Fmmc'%2C'%5Fke'%2C'%5Fkx'%2C'trk%5Fcontact'%2C'trk%5Fmsg'%2C'trk%5Fmodule'%2C'trk%5Fsid'%2C'mc%5Fcid'%2C'mc%5Feid'%2C'iesrc'%2C'msclkid'%2C'dclid'%2C'twclid'%2C'ttclid'%2C'oly%5Fenc%5Fid'%2C'oly%5Fanon%5Fid'%2C'epik'%2C'vero%5Fid'%5D%2C%5F=%5B'fb%5F'%2C'action%5F'%2C'ga%5F'%2C'utm%5F'%2C'hmb%5F'%2C'hsa%5F'%2C'mtm%5F'%2C'pk%5F'%2C'oly%5F'%2C'stm%5F'%5D%3B%2F%5C.aliexpress%5C.%5Ba-z%5D%7B2%2C3%7D%24%2F.test(i)%3F(a.push('algo%5Fevid'%2C'algo%5Fpvid'%2C'btsid'%2C'spm'%2C'scm'%2C'ws%5Fab%5Ftest')%2C%5F.push('aff%5F')):%2F(%7C%5C.)amazon%5C.com%24%2F.test(i)%3Fa.push('%5Fencoding'%2C'asc%5Fcampaign'%2C'asc%5Frefurl'%2C'asc%5Fsource'%2C'ascsubtag'%2C'content-id'%2C'crid'%2C'cv%5Fct%5Fcx'%2C'dib%5Ftag'%2C'dib'%2C'ie'%2C'language'%2C'linkCode'%2C'linkId'%2C'pd%5Frd%5Fi'%2C'pd%5Frd%5Fr'%2C'pd%5Frd%5Fw'%2C'pd%5Frd%5Fwg'%2C'pf%5Frd%5Fi'%2C'pf%5Frd%5Fm'%2C'pf%5Frd%5Fp'%2C'pf%5Frd%5Fr'%2C'pf%5Frd%5Fs'%2C'pf%5Frd%5Ft'%2C'pf'%2C'psc'%2C'qid'%2C'ref%5F'%2C'sprefix'%2C'sr'%2C'tag'%2C'th'):i.endsWith('.ebay.com')%7C%7C%2F%5C.ebay%5C.co%5C.%5Ba-z%5D%7B2%7D%24%2F.test(i)%3Fa.push('mkevt'%2C'mkcid'%2C'mkrid'%2C'campid'%2C'toolid'%2C'customid'%2C'norover'%2C'itm'%2C'amdata'):%2F(%5E%7C%5C.)google%5C.(com%7C%5Ba-z%5D%7B2%7D%7Ccom%3F%5C.%5Ba-z%5D%7B2%7D)%24%2F.test(i)%26%26t.startsWith('%2Fsearch')%3Fa.push('aqs'%2C'ei'%2C'gs%5Flp'%2C'gs%5Fssp'%2C'iflsig'%2C'sca%5Fesv'%2C'ved'%2C'oq'%2C'sa'%2C'uact'%2C'rlz'%2C'sxsrf'%2C'bih'%2C'biw'%2C'client'%2C'prmd'%2C'sclient'%2C'source'%2C'sourceid'%2C'ie'%2C'oe'):i.endsWith('.linkedin.com')%3Fa.push('li%5Ffat%5Fid'%2C'licu'%2C'lipi'%2C'midSig'%2C'midToken'%2C'refId'):i.endsWith('.target.com')%3Fa.push('afid'%2C'clkid'%2C'lnm'%2C'preselect'%2C'tref'):i.endsWith('.temu.com')%3F(a.push('%5Fbg%5Ffs'%2C'%5Fp%5Fjump%5Fid'%2C'%5Fp%5Frfs'%2C'refer%5Fpage%5Fid'%2C'refer%5Fpage%5Fname'%2C'refer%5Fpage%5Fsn')%2C%5F.push('%5Fx%5F')):i.endsWith('.tiktok.com')%7C%7C'tiktok.com'===i%3Fa.push('%5Fd'%2C'%5Fr'%2C'%5Ft'%2C'is%5Ffrom%5Fwebapp'%2C'preview%5Fpb'%2C'share%5Fapp%5Fname'%2C'share%5Fitem%5Fid'%2C'tt4d%5Ft'%2C'timestamp'%2C'u%5Fcode'%2C'user%5Fid'):%2F%5C.(twitter%7Cx)%5C.com%24%2F.test(i)%7C%7C%2F%5E(twitter%7Cx)%5C.com%24%2F.test(i)%3Fa.push('cn'%2C'ref%5Fsrc'%2C'ref%5Furl'%2C's'%2C't'):i.endsWith('.walmart.com')%3Fa.push('adsredirect'%2C'affiliates%5Fad%5Fid'%2C'athcpid'%2C'athpgid'%2C'athcgid'%2C'athmtid'%2C'athstid'%2C'athznid'%2C'athiession'%2C'athancid'%2C'athposb'%2C'athena'%2C'campaign%5Fid'%2C'wmlspartner'):(%2F(m%7Cwww)%5C.youtube%5C.com%24%2F.test(i)%7C%7C'youtu.be'===i%7C%7C'www.youtube-nocookie.com'===i)%26%26a.push('ac'%2C'annotation%5Fid'%2C'app'%2C'feature'%2C'gclid'%2C'kw'%2C'src%5Fvid')%3Bconst%20c=new%20Set(a.map(t=%3Et.toLowerCase()))%2Cr=t=%3E%7Bconst%20e=t.toLowerCase()%3Breturn%20c.has(e)%7C%7C%5F.some(t=%3Ee.startsWith(t))%7C%7C%2F%5Ecm%5Fmmca%5Cd%2B%24%2Fi.test(t)%7D%3Bfor(const%20t%20of%5B...s.keys()%5D)r(t)%26%26s.delete(t)%3Blet%20d=s.toString()%3Bd=d%3F'%3F'%2Bd:''%3Bconst%20o=t.replace(%2F%5C%2Famp%5C%2F%3F%24%2F%2C'')%3Bif((e!==d%7C%7Ct!==o)%26%26confirm('Update%20history%20and%20copy%20cleaned%20URL%20to%20clipboard%3F'))%7Bconst%20t=%60%24%7Blocation.protocol%7D%2F%2F%24%7Blocation.host%7D%24%7Bo%7D%24%7Bd%7D%24%7Blocation.hash%7D%60%3Bnavigator.clipboard.writeText(t)%2Chistory.replaceState(null%2C''%2Ct)%3Bconst%20e=window.open(t%2C'%5Fself'%2C'noreferrer')%3Be%26%26(e.opener=null)%7D%7D)()%3Bvoid'2.2.0' "Setup UtmStrip" -[Setup unskim]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3B(()=%3E%7Blet%20e=new%20URL(document.location.href)%3Bif('safari-resource:%2FErrorPage.html'===e.href)%7Bconst%20t=document.querySelector('p.error-message')%3F.textContent%3F.match(%2FSafari%20can't%20open%20the%20page%20%22(https%3F:%5B%5E%22%5D%2B)%22%2F)%3F.%5B1%5D%3Bt%26%26(e=new%20URL(t))%7Dif(''===e.search)return%3Bconst%20t=new%20URLSearchParams(e.search)%2Cr=%5B'url'%2C'destination'%2C'redirect'%2C'target'%2C'goto'%2C'u'%2C'dest'%2C'link'%2C'out'%5D.filter(e=%3Et.has(e)).map(e=%3E%7Bconst%20r=t.get(e)%3Breturn%20r%3FdecodeURIComponent(r):''%7D).find(e=%3Ee.match(%2F%5Ehttps%3F:%2F))%3Br%26%26window.location.replace(new%20URL(r))%7D)()%3Bvoid'2.0.1' "Setup unskim" +[Setup unskim]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3B(()=%3E%7Blet%20e=new%20URL(document.location.href)%3Bif('safari-resource:%2FErrorPage.html'===e.href)%7Bconst%20t=document.querySelector('p.error-message')%3F.textContent%3F.match(%2FSafari%20can't%20open%20the%20page%20%22(https%3F:%5B%5E%22%5D%2B)%22%2F)%3F.%5B1%5D%3Bt%26%26(e=new%20URL(t))%7Dif(13%3Ee.search.length)return%3Bconst%20t=new%20URLSearchParams(e.search)%2Cr=%5B'url'%2C'destination'%2C'redirect'%2C'target'%2C'goto'%2C'u'%2C'dest'%2C'link'%2C'out'%5D.filter(e=%3Et.has(e)).map(e=%3E%7Bconst%20r=t.get(e)%3Breturn%20r%3FdecodeURIComponent(r):''%7D).find(e=%3Ee.match(%2F%5Ehttps%3F:%2F))%3Br%26%26window.location.replace(new%20URL(r))%7D)()%3Bvoid'2.0.2' "Setup unskim" [Setup x-man]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3B(()=%3E%7Bconst%20i=navigator.userAgent%2Cn=i.includes('Chrome%2F')%7C%7Ci.includes('Firefox%2F')%7C%7Ci.includes('Brave%2F')%7C%7Ci.includes('Edg%2F')%2Co=i.includes('Safari%2F')%2Ct=!n%26%26!o%2Ca=navigator.platform.startsWith('Mac')%26%26o%26%26!n%26%26!t%26%26!navigator.maxTouchPoints%26%262%3Enavigator.maxTouchPoints%2Ce=window.getSelection()%3Bif(!e)return%3Bconst%20l=e.toString().split('%5Cn')%5B0%5D.trim()%3Blet%20r='x-man-page:%2F%2F'%3Bconst%20c=l.match(%2F%5E(.%2A%3F)%5C((%5Cd)%5C)%24%2F)%3Bif(r%2B=c%3F'1'===c%5B2%5D%3Fc%5B1%5D:%60%24%7Bc%5B2%5D%7D%2F%24%7Bc%5B1%5D%7D%60:l%2Cl)%7Bif(confirm(%60Link%20for%20%22%24%7Bl%7D%22%20is:%20%24%7Br%7D%5Cn%5CnCopy%20to%20clipboard%3F%60))%7Blet%20i=''%3Bif(navigator.clipboard%3Fnavigator.clipboard.writeText(r):(i=%60Unable%20to%20copy%20%22%24%7Br%7D%22%20to%20clipboard.%60%2Cr='')%2Ca%26%26''!==r)%7Blet%20n=null%3Btry%7Bn=window.open(r)%2Cn%26%26(n.opener=null)%7Dcatch(n)%7Bi=%60Popup%20window%20blocked.%20Clipboard%20contains%20%22%24%7Br%7D%22%60%7Dfinally%7Bwindow.focus()%2Cnull!==n%26%26setTimeout(()=%3E%7Bn%26%26n.close()%7D%2C3333)%7D%7Delse''!==r%26%26(i=%60Unable%20to%20open%20new%20link%20with%20Terminal.%20Clipboard%20contains%20%22%24%7Br%7D%22%60)%3B''!==i%26%26alert(i)%7De.empty()%7D%7D)()%3Bvoid'1.3.1' "Setup x-man" diff --git a/bookmarklets.json b/bookmarklets.json index 4b06a98..2c7c9e6 100644 --- a/bookmarklets.json +++ b/bookmarklets.json @@ -58,7 +58,7 @@ { "name": "OpenURLParam", "file": "openurlparam.bookmarklet", - "version": "1.2.0" + "version": "1.2.1" }, { "name": "UtmStrip", @@ -73,7 +73,7 @@ { "name": "unskim", "file": "unskim.bookmarklet", - "version": "2.0.1" + "version": "2.0.2" }, { "name": "x-man", diff --git a/dist/openurlparam.bookmarklet b/dist/openurlparam.bookmarklet index e0b389c..23544b1 100644 --- a/dist/openurlparam.bookmarklet +++ b/dist/openurlparam.bookmarklet @@ -1 +1 @@ -javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=new%20URL(document.location.href)%3Bif(11%3Et.search.length)return%3Bconst%20e=new%20URLSearchParams(t.search)%2Cn=%5B'url'%2C'destination'%2C'redirect'%2C'target'%2C'goto'%2C'u'%2C'dest'%2C'link'%2C'out'%5D.filter(t=%3Ee.has(t)).map(t=%3E%7Bconst%20n=e.get(t)%3Breturn%20n%3FdecodeURIComponent(n):''%7D).find(t=%3Et.match(%2F%5Ehttps:%2F))%3Bn%26%26window.location.replace(new%20URL(n))%7D)()%3Bvoid'1.2.0' \ No newline at end of file +javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=new%20URL(document.location.href)%3Bif('https:'!==t.protocol%7C%7C13%3Et.search.length)return%3Bconst%20e=new%20URLSearchParams(t.search)%2Cn=%5B'url'%2C'destination'%2C'redirect'%2C'target'%2C'goto'%2C'u'%2C'dest'%2C'link'%2C'out'%5D.filter(t=%3Ee.has(t)).map(t=%3E%7Bconst%20n=e.get(t)%3Breturn%20n%3FdecodeURIComponent(n):''%7D).find(t=%3Et.match(%2F%5Ehttps:%2F))%3Bn%26%26window.location.replace(new%20URL(n))%7D)()%3Bvoid'1.2.1' \ No newline at end of file diff --git a/dist/unskim.bookmarklet b/dist/unskim.bookmarklet index 5294689..a8753a5 100644 --- a/dist/unskim.bookmarklet +++ b/dist/unskim.bookmarklet @@ -1 +1 @@ -javascript:'use%20strict'%3B(()=%3E%7Blet%20e=new%20URL(document.location.href)%3Bif('safari-resource:%2FErrorPage.html'===e.href)%7Bconst%20t=document.querySelector('p.error-message')%3F.textContent%3F.match(%2FSafari%20can't%20open%20the%20page%20%22(https%3F:%5B%5E%22%5D%2B)%22%2F)%3F.%5B1%5D%3Bt%26%26(e=new%20URL(t))%7Dif(''===e.search)return%3Bconst%20t=new%20URLSearchParams(e.search)%2Cr=%5B'url'%2C'destination'%2C'redirect'%2C'target'%2C'goto'%2C'u'%2C'dest'%2C'link'%2C'out'%5D.filter(e=%3Et.has(e)).map(e=%3E%7Bconst%20r=t.get(e)%3Breturn%20r%3FdecodeURIComponent(r):''%7D).find(e=%3Ee.match(%2F%5Ehttps%3F:%2F))%3Br%26%26window.location.replace(new%20URL(r))%7D)()%3Bvoid'2.0.1' \ No newline at end of file +javascript:'use%20strict'%3B(()=%3E%7Blet%20e=new%20URL(document.location.href)%3Bif('safari-resource:%2FErrorPage.html'===e.href)%7Bconst%20t=document.querySelector('p.error-message')%3F.textContent%3F.match(%2FSafari%20can't%20open%20the%20page%20%22(https%3F:%5B%5E%22%5D%2B)%22%2F)%3F.%5B1%5D%3Bt%26%26(e=new%20URL(t))%7Dif(13%3Ee.search.length)return%3Bconst%20t=new%20URLSearchParams(e.search)%2Cr=%5B'url'%2C'destination'%2C'redirect'%2C'target'%2C'goto'%2C'u'%2C'dest'%2C'link'%2C'out'%5D.filter(e=%3Et.has(e)).map(e=%3E%7Bconst%20r=t.get(e)%3Breturn%20r%3FdecodeURIComponent(r):''%7D).find(e=%3Ee.match(%2F%5Ehttps%3F:%2F))%3Br%26%26window.location.replace(new%20URL(r))%7D)()%3Bvoid'2.0.2' \ No newline at end of file From b64803533c6aa5453c19faf7486b695e70899a94 Mon Sep 17 00:00:00 2001 From: Tom King Date: Tue, 3 Feb 2026 19:53:18 -0800 Subject: [PATCH 03/18] chore: update dependencies --- package-lock.json | 16 ++++++++-------- package.json | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index f22cb41..ce5c289 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,12 +10,12 @@ "license": "MIT", "devDependencies": { "@eslint/js": "9.39.2", - "@types/node": "25.1.0", + "@types/node": "25.2.0", "@typescript-eslint/eslint-plugin": "8.54.0", "@typescript-eslint/parser": "8.54.0", "eslint": "9.39.2", "eslint-plugin-security": "3.0.1", - "globals": "17.2.0", + "globals": "17.3.0", "terser": "5.46.0", "typescript": "5.9.3" }, @@ -343,9 +343,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz", - "integrity": "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==", + "version": "25.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.0.tgz", + "integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==", "dev": true, "license": "MIT", "dependencies": { @@ -1120,9 +1120,9 @@ } }, "node_modules/globals": { - "version": "17.2.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.2.0.tgz", - "integrity": "sha512-tovnCz/fEq+Ripoq+p/gN1u7l6A7wwkoBT9pRCzTHzsD/LvADIzXZdjmRymh5Ztf0DYC3Rwg5cZRYjxzBmzbWg==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", + "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 1f09acb..c93ecf2 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,12 @@ "description": "Project that makes Safari bookmarklets to automagically jump to the corresponding app and more.", "devDependencies": { "@eslint/js": "9.39.2", - "@types/node": "25.1.0", + "@types/node": "25.2.0", "@typescript-eslint/eslint-plugin": "8.54.0", "@typescript-eslint/parser": "8.54.0", "eslint": "9.39.2", "eslint-plugin-security": "3.0.1", - "globals": "17.2.0", + "globals": "17.3.0", "terser": "5.46.0", "typescript": "5.9.3" }, From 18412c0f1c458759738068955dae3087146bc479 Mon Sep 17 00:00:00 2001 From: Tom King Date: Tue, 3 Feb 2026 20:11:58 -0800 Subject: [PATCH 04/18] feat(utmstrip): always offer to copy URL even if "cleaning" does no change --- bookmarklets.json | 2 +- dist/utmstrip.bookmarklet | 2 +- src/utmstrip.ts | 29 +++++++++++++++-------------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/bookmarklets.json b/bookmarklets.json index 2c7c9e6..f3ae78c 100644 --- a/bookmarklets.json +++ b/bookmarklets.json @@ -63,7 +63,7 @@ { "name": "UtmStrip", "file": "utmstrip.bookmarklet", - "version": "2.2.0" + "version": "2.3.0" }, { "name": "deLighter", diff --git a/dist/utmstrip.bookmarklet b/dist/utmstrip.bookmarklet index 02b2a94..a6ee80d 100644 --- a/dist/utmstrip.bookmarklet +++ b/dist/utmstrip.bookmarklet @@ -1 +1 @@ -javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=location.pathname%2Ce=location.search%3Bif(3%3Ee.length%26%26!t.includes('%2Famp'))return%3Bconst%20i=location.hostname%2Cs=new%20URLSearchParams(e)%2Ca=%5B'assetType'%2C'elqTrack'%2C'mkt%5Ftok'%2C'originalReferer'%2C'referrer'%2C'terminal%5Fid'%2C'trk'%2C'trkCampaign'%2C'trkInfo'%2C'anid'%2C'assetid'%2C'campaignid'%2C'eid'%2C'gclid'%2C'recipientid'%2C'siteid'%2C'sib%5Fcuid'%2C'sib%5Fsid'%2C'%5Fbta%5Ftid'%2C'%5Fbta%5Fc'%2C'%5F%5Fs'%2C'fbclid'%2C'hrc'%2C'igsh'%2C'igshid'%2C'refsrc'%2C'%5Fgl'%2C'gclsrc'%2C'srsltid'%2C'%5Fhsenc'%2C'%5Fhsmi'%2C'%5F%5Fhsfp'%2C'%5F%5Fhssc'%2C'%5F%5Fhstc'%2C'cm%5Fmmc'%2C'cm%5Fre'%2C'cm%5Fsp'%2C'manual%5Fcm%5Fmmc'%2C'%5Fke'%2C'%5Fkx'%2C'trk%5Fcontact'%2C'trk%5Fmsg'%2C'trk%5Fmodule'%2C'trk%5Fsid'%2C'mc%5Fcid'%2C'mc%5Feid'%2C'iesrc'%2C'msclkid'%2C'dclid'%2C'twclid'%2C'ttclid'%2C'oly%5Fenc%5Fid'%2C'oly%5Fanon%5Fid'%2C'epik'%2C'vero%5Fid'%5D%2C%5F=%5B'fb%5F'%2C'action%5F'%2C'ga%5F'%2C'utm%5F'%2C'hmb%5F'%2C'hsa%5F'%2C'mtm%5F'%2C'pk%5F'%2C'oly%5F'%2C'stm%5F'%5D%3B%2F%5C.aliexpress%5C.%5Ba-z%5D%7B2%2C3%7D%24%2F.test(i)%3F(a.push('algo%5Fevid'%2C'algo%5Fpvid'%2C'btsid'%2C'spm'%2C'scm'%2C'ws%5Fab%5Ftest')%2C%5F.push('aff%5F')):%2F(%7C%5C.)amazon%5C.com%24%2F.test(i)%3Fa.push('%5Fencoding'%2C'asc%5Fcampaign'%2C'asc%5Frefurl'%2C'asc%5Fsource'%2C'ascsubtag'%2C'content-id'%2C'crid'%2C'cv%5Fct%5Fcx'%2C'dib%5Ftag'%2C'dib'%2C'ie'%2C'language'%2C'linkCode'%2C'linkId'%2C'pd%5Frd%5Fi'%2C'pd%5Frd%5Fr'%2C'pd%5Frd%5Fw'%2C'pd%5Frd%5Fwg'%2C'pf%5Frd%5Fi'%2C'pf%5Frd%5Fm'%2C'pf%5Frd%5Fp'%2C'pf%5Frd%5Fr'%2C'pf%5Frd%5Fs'%2C'pf%5Frd%5Ft'%2C'pf'%2C'psc'%2C'qid'%2C'ref%5F'%2C'sprefix'%2C'sr'%2C'tag'%2C'th'):i.endsWith('.ebay.com')%7C%7C%2F%5C.ebay%5C.co%5C.%5Ba-z%5D%7B2%7D%24%2F.test(i)%3Fa.push('mkevt'%2C'mkcid'%2C'mkrid'%2C'campid'%2C'toolid'%2C'customid'%2C'norover'%2C'itm'%2C'amdata'):%2F(%5E%7C%5C.)google%5C.(com%7C%5Ba-z%5D%7B2%7D%7Ccom%3F%5C.%5Ba-z%5D%7B2%7D)%24%2F.test(i)%26%26t.startsWith('%2Fsearch')%3Fa.push('aqs'%2C'ei'%2C'gs%5Flp'%2C'gs%5Fssp'%2C'iflsig'%2C'sca%5Fesv'%2C'ved'%2C'oq'%2C'sa'%2C'uact'%2C'rlz'%2C'sxsrf'%2C'bih'%2C'biw'%2C'client'%2C'prmd'%2C'sclient'%2C'source'%2C'sourceid'%2C'ie'%2C'oe'):i.endsWith('.linkedin.com')%3Fa.push('li%5Ffat%5Fid'%2C'licu'%2C'lipi'%2C'midSig'%2C'midToken'%2C'refId'):i.endsWith('.target.com')%3Fa.push('afid'%2C'clkid'%2C'lnm'%2C'preselect'%2C'tref'):i.endsWith('.temu.com')%3F(a.push('%5Fbg%5Ffs'%2C'%5Fp%5Fjump%5Fid'%2C'%5Fp%5Frfs'%2C'refer%5Fpage%5Fid'%2C'refer%5Fpage%5Fname'%2C'refer%5Fpage%5Fsn')%2C%5F.push('%5Fx%5F')):i.endsWith('.tiktok.com')%7C%7C'tiktok.com'===i%3Fa.push('%5Fd'%2C'%5Fr'%2C'%5Ft'%2C'is%5Ffrom%5Fwebapp'%2C'preview%5Fpb'%2C'share%5Fapp%5Fname'%2C'share%5Fitem%5Fid'%2C'tt4d%5Ft'%2C'timestamp'%2C'u%5Fcode'%2C'user%5Fid'):%2F%5C.(twitter%7Cx)%5C.com%24%2F.test(i)%7C%7C%2F%5E(twitter%7Cx)%5C.com%24%2F.test(i)%3Fa.push('cn'%2C'ref%5Fsrc'%2C'ref%5Furl'%2C's'%2C't'):i.endsWith('.walmart.com')%3Fa.push('adsredirect'%2C'affiliates%5Fad%5Fid'%2C'athcpid'%2C'athpgid'%2C'athcgid'%2C'athmtid'%2C'athstid'%2C'athznid'%2C'athiession'%2C'athancid'%2C'athposb'%2C'athena'%2C'campaign%5Fid'%2C'wmlspartner'):(%2F(m%7Cwww)%5C.youtube%5C.com%24%2F.test(i)%7C%7C'youtu.be'===i%7C%7C'www.youtube-nocookie.com'===i)%26%26a.push('ac'%2C'annotation%5Fid'%2C'app'%2C'feature'%2C'gclid'%2C'kw'%2C'src%5Fvid')%3Bconst%20c=new%20Set(a.map(t=%3Et.toLowerCase()))%2Cr=t=%3E%7Bconst%20e=t.toLowerCase()%3Breturn%20c.has(e)%7C%7C%5F.some(t=%3Ee.startsWith(t))%7C%7C%2F%5Ecm%5Fmmca%5Cd%2B%24%2Fi.test(t)%7D%3Bfor(const%20t%20of%5B...s.keys()%5D)r(t)%26%26s.delete(t)%3Blet%20d=s.toString()%3Bd=d%3F'%3F'%2Bd:''%3Bconst%20o=t.replace(%2F%5C%2Famp%5C%2F%3F%24%2F%2C'')%3Bif((e!==d%7C%7Ct!==o)%26%26confirm('Update%20history%20and%20copy%20cleaned%20URL%20to%20clipboard%3F'))%7Bconst%20t=%60%24%7Blocation.protocol%7D%2F%2F%24%7Blocation.host%7D%24%7Bo%7D%24%7Bd%7D%24%7Blocation.hash%7D%60%3Bnavigator.clipboard.writeText(t)%2Chistory.replaceState(null%2C''%2Ct)%3Bconst%20e=window.open(t%2C'%5Fself'%2C'noreferrer')%3Be%26%26(e.opener=null)%7D%7D)()%3Bvoid'2.2.0' \ No newline at end of file +javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=location.pathname%2Ce=location.search%3Bif(3%3Ee.length%26%26!t.includes('%2Famp'))return%3Bconst%20i=location.hostname%2Cs=new%20URLSearchParams(e)%2Ca=%5B'assetType'%2C'elqTrack'%2C'mkt%5Ftok'%2C'originalReferer'%2C'referrer'%2C'terminal%5Fid'%2C'trk'%2C'trkCampaign'%2C'trkInfo'%2C'anid'%2C'assetid'%2C'campaignid'%2C'eid'%2C'gclid'%2C'recipientid'%2C'siteid'%2C'sib%5Fcuid'%2C'sib%5Fsid'%2C'%5Fbta%5Ftid'%2C'%5Fbta%5Fc'%2C'%5F%5Fs'%2C'fbclid'%2C'hrc'%2C'igsh'%2C'igshid'%2C'refsrc'%2C'%5Fgl'%2C'gclsrc'%2C'srsltid'%2C'%5Fhsenc'%2C'%5Fhsmi'%2C'%5F%5Fhsfp'%2C'%5F%5Fhssc'%2C'%5F%5Fhstc'%2C'cm%5Fmmc'%2C'cm%5Fre'%2C'cm%5Fsp'%2C'manual%5Fcm%5Fmmc'%2C'%5Fke'%2C'%5Fkx'%2C'trk%5Fcontact'%2C'trk%5Fmsg'%2C'trk%5Fmodule'%2C'trk%5Fsid'%2C'mc%5Fcid'%2C'mc%5Feid'%2C'iesrc'%2C'msclkid'%2C'dclid'%2C'twclid'%2C'ttclid'%2C'oly%5Fenc%5Fid'%2C'oly%5Fanon%5Fid'%2C'epik'%2C'vero%5Fid'%5D%2C%5F=%5B'fb%5F'%2C'action%5F'%2C'ga%5F'%2C'utm%5F'%2C'hmb%5F'%2C'hsa%5F'%2C'mtm%5F'%2C'pk%5F'%2C'oly%5F'%2C'stm%5F'%5D%3B%2F%5C.aliexpress%5C.%5Ba-z%5D%7B2%2C3%7D%24%2F.test(i)%3F(a.push('algo%5Fevid'%2C'algo%5Fpvid'%2C'btsid'%2C'spm'%2C'scm'%2C'ws%5Fab%5Ftest')%2C%5F.push('aff%5F')):%2F(%7C%5C.)amazon%5C.com%24%2F.test(i)%3Fa.push('%5Fencoding'%2C'asc%5Fcampaign'%2C'asc%5Frefurl'%2C'asc%5Fsource'%2C'ascsubtag'%2C'content-id'%2C'crid'%2C'cv%5Fct%5Fcx'%2C'dib%5Ftag'%2C'dib'%2C'ie'%2C'language'%2C'linkCode'%2C'linkId'%2C'pd%5Frd%5Fi'%2C'pd%5Frd%5Fr'%2C'pd%5Frd%5Fw'%2C'pd%5Frd%5Fwg'%2C'pf%5Frd%5Fi'%2C'pf%5Frd%5Fm'%2C'pf%5Frd%5Fp'%2C'pf%5Frd%5Fr'%2C'pf%5Frd%5Fs'%2C'pf%5Frd%5Ft'%2C'pf'%2C'psc'%2C'qid'%2C'ref%5F'%2C'sprefix'%2C'sr'%2C'tag'%2C'th'):i.endsWith('.ebay.com')%7C%7C%2F%5C.ebay%5C.co%5C.%5Ba-z%5D%7B2%7D%24%2F.test(i)%3Fa.push('mkevt'%2C'mkcid'%2C'mkrid'%2C'campid'%2C'toolid'%2C'customid'%2C'norover'%2C'itm'%2C'amdata'):%2F(%5E%7C%5C.)google%5C.(com%7C%5Ba-z%5D%7B2%7D%7Ccom%3F%5C.%5Ba-z%5D%7B2%7D)%24%2F.test(i)%26%26t.startsWith('%2Fsearch')%3Fa.push('aqs'%2C'ei'%2C'gs%5Flp'%2C'gs%5Fssp'%2C'iflsig'%2C'sca%5Fesv'%2C'ved'%2C'oq'%2C'sa'%2C'uact'%2C'rlz'%2C'sxsrf'%2C'bih'%2C'biw'%2C'client'%2C'prmd'%2C'sclient'%2C'source'%2C'sourceid'%2C'ie'%2C'oe'):i.endsWith('.linkedin.com')%3Fa.push('li%5Ffat%5Fid'%2C'licu'%2C'lipi'%2C'midSig'%2C'midToken'%2C'refId'):i.endsWith('.target.com')%3Fa.push('afid'%2C'clkid'%2C'lnm'%2C'preselect'%2C'tref'):i.endsWith('.temu.com')%3F(a.push('%5Fbg%5Ffs'%2C'%5Fp%5Fjump%5Fid'%2C'%5Fp%5Frfs'%2C'refer%5Fpage%5Fid'%2C'refer%5Fpage%5Fname'%2C'refer%5Fpage%5Fsn')%2C%5F.push('%5Fx%5F')):i.endsWith('.tiktok.com')%7C%7C'tiktok.com'===i%3Fa.push('%5Fd'%2C'%5Fr'%2C'%5Ft'%2C'is%5Ffrom%5Fwebapp'%2C'preview%5Fpb'%2C'share%5Fapp%5Fname'%2C'share%5Fitem%5Fid'%2C'tt4d%5Ft'%2C'timestamp'%2C'u%5Fcode'%2C'user%5Fid'):%2F%5C.(twitter%7Cx)%5C.com%24%2F.test(i)%7C%7C%2F%5E(twitter%7Cx)%5C.com%24%2F.test(i)%3Fa.push('cn'%2C'ref%5Fsrc'%2C'ref%5Furl'%2C's'%2C't'):i.endsWith('.walmart.com')%3Fa.push('adsredirect'%2C'affiliates%5Fad%5Fid'%2C'athcpid'%2C'athpgid'%2C'athcgid'%2C'athmtid'%2C'athstid'%2C'athznid'%2C'athiession'%2C'athancid'%2C'athposb'%2C'athena'%2C'campaign%5Fid'%2C'wmlspartner'):(%2F(m%7Cwww)%5C.youtube%5C.com%24%2F.test(i)%7C%7C'youtu.be'===i%7C%7C'www.youtube-nocookie.com'===i)%26%26a.push('ac'%2C'annotation%5Fid'%2C'app'%2C'feature'%2C'gclid'%2C'kw'%2C'src%5Fvid')%3Bconst%20c=new%20Set(a.map(t=%3Et.toLowerCase()))%2Cr=t=%3E%7Bconst%20e=t.toLowerCase()%3Breturn%20c.has(e)%7C%7C%5F.some(t=%3Ee.startsWith(t))%7C%7C%2F%5Ecm%5Fmmca%5Cd%2B%24%2Fi.test(t)%7D%3Bfor(const%20t%20of%5B...s.keys()%5D)r(t)%26%26s.delete(t)%3Blet%20d=s.toString()%3Bd=d%3F'%3F'%2Bd:''%3Bconst%20o=t.replace(%2F%5C%2Famp%5C%2F%3F%24%2F%2C'')%2Cn=%60%24%7Blocation.protocol%7D%2F%2F%24%7Blocation.host%7D%24%7Bo%7D%24%7Bd%7D%24%7Blocation.hash%7D%60%3Bif(confirm('Update%20history%20and%20copy%20cleaned%20URL%20to%20clipboard%3F')%26%26(navigator.clipboard.writeText(n)%2Ce!==d%7C%7Ct!==o))%7Bhistory.replaceState(null%2C''%2Cn)%3Bconst%20t=window.open(n%2C'%5Fself'%2C'noreferrer')%3Bt%26%26(t.opener=null)%7D%7D)()%3Bvoid'2.3.0' \ No newline at end of file diff --git a/src/utmstrip.ts b/src/utmstrip.ts index abad76f..aa387ae 100644 --- a/src/utmstrip.ts +++ b/src/utmstrip.ts @@ -177,19 +177,20 @@ let searchStr: string = params.toString(); searchStr = searchStr ? `?${searchStr}` : ''; - // Clean up '/amp/' in pathname - const pathStr: string = locPath.replace(/\/amp\/?$/, ''); - - // If changed, replace location with stripped version - if (locSearch !== searchStr || locPath !== pathStr) { - if (confirm('Update history and copy cleaned URL to clipboard?')) { - const newURL: string = `${location.protocol}//${location.host}${pathStr}${searchStr}${location.hash}`; - navigator.clipboard.writeText(newURL); - history.replaceState(null, '', newURL); - const newWindow: Window | null = window.open(newURL, '_self', 'noreferrer'); - if (newWindow) { - newWindow.opener = null; - } - } + // Clean up '/amp/' in pathname & assign newURL + const pathStr: string = locPath.replace(/\/amp\/?$/, ''), + newURL: string = `${location.protocol}//${location.host}${pathStr}${searchStr}${location.hash}`; + + // always _offer_ to copy the "cleaned" newURL + if (confirm('Update history and copy cleaned URL to clipboard?')) { + navigator.clipboard.writeText(newURL); + // If changed (truly cleaned), replace location with stripped version + if (locSearch !== searchStr || locPath !== pathStr) { + history.replaceState(null, '', newURL); + const newWindow: Window | null = window.open(newURL, '_self', 'noreferrer'); + if (newWindow) { + newWindow.opener = null; + } + } } })(); From edf0f81a1e4aeff4a8b7e90bad74f7f88e21d781 Mon Sep 17 00:00:00 2001 From: Tom King Date: Fri, 6 Feb 2026 15:24:47 -0800 Subject: [PATCH 05/18] chore: remove eslint-plugin-security (redundant to TypeScript + ESlint), add "no-unsanitized" --- .github/linters/eslint.config.js | 41 +++++++++++++++++++++++++------ package-lock.json | 42 ++++++-------------------------- package.json | 4 +-- 3 files changed, 43 insertions(+), 44 deletions(-) diff --git a/.github/linters/eslint.config.js b/.github/linters/eslint.config.js index 1b74f68..44196ff 100644 --- a/.github/linters/eslint.config.js +++ b/.github/linters/eslint.config.js @@ -1,6 +1,6 @@ const globals = require("globals"); const js = require("@eslint/js"); -const security = require("eslint-plugin-security"); +const noUnsanitized = require("eslint-plugin-no-unsanitized"); const tsParser = require("@typescript-eslint/parser"); const tsPlugin = require("@typescript-eslint/eslint-plugin"); @@ -101,8 +101,7 @@ const baseRules = { "symbol-description": "error", "unicode-bom": "error", "vars-on-top": "error", - ...security.configs['recommended'].rules, - "security/detect-non-literal-regexp": "off" + ...noUnsanitized.configs.recommended.rules }; module.exports = [ @@ -117,7 +116,9 @@ module.exports = [ }, sourceType: "script" }, - plugins: {security}, + plugins: { + "no-unsanitized": noUnsanitized + }, rules: { ...baseRules, "init-declarations": "error", @@ -141,7 +142,7 @@ module.exports = [ }, plugins: { "@typescript-eslint": tsPlugin, - security + "no-unsanitized": noUnsanitized }, rules: { ...baseRules, @@ -159,6 +160,29 @@ module.exports = [ "n/no-unsupported-features/node-builtins": "off" } }, + { + files: [".temp/*.js"], + ignores: [".cspell.json","*.json", "**/*{.,-}min.js", "node_modules/*", "dist/*.bookmarklet"], + languageOptions: { + ecmaVersion: 2020, + globals: {...globals.browser, ...globals.node}, + parserOptions: { + ecmaFeatures: {globalReturn: false, impliedStrict: true} + }, + sourceType: "script" + }, + plugins: { + "no-unsanitized": noUnsanitized + }, + rules: { + ...baseRules, + "init-declarations": "error", + "no-await-in-loop": "error", + "no-console": "error", + "no-inline-comments": "error", + "no-param-reassign": "error" + } + }, { files: ["scripts/*.js", ".github/linters/*.js"], ignores: [".cspell.json","*.json", "**/*{.,-}min.js", "node_modules/*", "dist/*.bookmarklet"], @@ -170,14 +194,15 @@ module.exports = [ }, sourceType: "script" }, - plugins: {security}, + plugins: { + "no-unsanitized": noUnsanitized + }, rules: { ...baseRules, "no-await-in-loop": "off", "no-console": "off", "no-inline-comments": "off", - "no-param-reassign": "off", - "security/detect-non-literal-fs-filename": "off" + "no-param-reassign": "off" } } ]; diff --git a/package-lock.json b/package-lock.json index ce5c289..0eb5cc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@typescript-eslint/eslint-plugin": "8.54.0", "@typescript-eslint/parser": "8.54.0", "eslint": "9.39.2", - "eslint-plugin-security": "3.0.1", + "eslint-plugin-no-unsanitized": "~4.1.4", "globals": "17.3.0", "terser": "5.46.0", "typescript": "5.9.3" @@ -846,20 +846,14 @@ } } }, - "node_modules/eslint-plugin-security": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-3.0.1.tgz", - "integrity": "sha512-XjVGBhtDZJfyuhIxnQ/WMm385RbX3DBu7H1J7HNNhmB2tnGxMeqVSnYv79oAj992ayvIBZghsymwkYFS6cGH4Q==", + "node_modules/eslint-plugin-no-unsanitized": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.1.4.tgz", + "integrity": "sha512-cjAoZoq3J+5KJuycYYOWrc0/OpZ7pl2Z3ypfFq4GtaAgheg+L7YGxUo2YS3avIvo/dYU5/zR2hXu3v81M9NxhQ==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "safe-regex": "^2.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "license": "MPL-2.0", + "peerDependencies": { + "eslint": "^8 || ^9" } }, "node_modules/eslint-scope": { @@ -1436,16 +1430,6 @@ "node": ">=6" } }, - "node_modules/regexp-tree": { - "version": "0.1.27", - "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", - "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", - "dev": true, - "license": "MIT", - "bin": { - "regexp-tree": "bin/regexp-tree" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -1456,16 +1440,6 @@ "node": ">=4" } }, - "node_modules/safe-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", - "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "regexp-tree": "~0.1.1" - } - }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", diff --git a/package.json b/package.json index c93ecf2..792a1a4 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "@typescript-eslint/eslint-plugin": "8.54.0", "@typescript-eslint/parser": "8.54.0", "eslint": "9.39.2", - "eslint-plugin-security": "3.0.1", + "eslint-plugin-no-unsanitized" : "~4.1.4", "globals": "17.3.0", "terser": "5.46.0", "typescript": "5.9.3" @@ -52,7 +52,7 @@ "scripts": { "build": "npm run build:compile && npm run build:minify && npm run build:bookmarklet", "build:bookmarklet": "node scripts/build-bookmarklet.js", - "build:compile": "tsc", + "build:compile": "tsc && eslint --config .github/linters/eslint.config.js ./.temp/*.js", "build:minify": "node scripts/minify.js", "build:readme": "node scripts/update-readme.js", "deploy": "npm run build && npm run build:readme", From 78eba29d5a78e8df57417ab951f8154e1a3c710f Mon Sep 17 00:00:00 2001 From: Tom King Date: Fri, 6 Feb 2026 17:08:26 -0800 Subject: [PATCH 06/18] add bookmarklets for Chrome, Edge, Opera, & Orion browsers --- README.md | 39 +++++++++++++++++++++++++++++++---- bookmarklets.json | 20 ++++++++++++++++++ dist/openinchrome.bookmarklet | 1 + dist/openinedge.bookmarklet | 1 + dist/openinopera.bookmarklet | 1 + dist/openinorion.bookmarklet | 1 + src/openinchrome.ts | 7 +++++++ src/openinedge.ts | 6 ++++++ src/openinopera.ts | 6 ++++++ src/openinorion.ts | 4 ++++ 10 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 dist/openinchrome.bookmarklet create mode 100644 dist/openinedge.bookmarklet create mode 100644 dist/openinopera.bookmarklet create mode 100644 dist/openinorion.bookmarklet create mode 100644 src/openinchrome.ts create mode 100644 src/openinedge.ts create mode 100644 src/openinopera.ts create mode 100644 src/openinorion.ts diff --git a/README.md b/README.md index d29aa8e..2a542f0 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,12 @@ iPad, and Mac, and also works with Google Chrome on desktops. + __[OpenInBrave] v1.1.2__: Open the current web page in the Brave app on iOS. ++ __[OpenInChrome] v1.0.0__: Open the current web page in the Google Chrome app +on iOS. + ++ __[OpenInEdge] v1.0.0__: Open the current web page in the Microsoft Edge +app on iOS. + + __[OpenInFirefox] v1.6.2__: Open the current web page in the Firefox app for iOS. @@ -67,6 +73,10 @@ mode of the Firefox app for iOS. + __[OpenInGoodReader] v1.5.2__: When viewing a PDF in Mobile Safari, open or download the same PDF in GoodReader. ++ __[OpenInOpera] v1.0.0__: Open the current web page in the Opera app on iOS. + ++ __[OpenInOrion] v1.0.0__: Open the current web page in the Opera app on iOS. + + __[OpenInTextastic] v1.1.2__: Open the current web page in the Textastic app on iOS. Download the server response of the current HTTP URL, save it in the root directory of the local (Textastic) file system, and then open it in the @@ -81,7 +91,7 @@ ad or tracking blockers. If the current URL contains a parameter in the form of `url=...` this bookmarklet will parse the `url` parameter and navigate to that URL. -+ __[UtmStrip] v2.2.0__: Strips off the UTM query string elements of the ++ __[UtmStrip] v2.3.0__: Strips off the UTM query string elements of the current URL to remove common "urchin" tracking information from youtube, etc. Also removes Google `/amp/` suffix from URL path. Asks to copy the new URL to the clipboard. Finally, replaces history & reloads the page. Now works to @@ -121,14 +131,18 @@ followed link into a bookmark for JavaScript bookmarklet. + __Mobile Safari setup link__ -- [Setup KillStickyHeaders] v2.1.0 + __Mobile Safari setup link__ -- [Setup Linklighter] v2.1.0 + __Mobile Safari setup link__ -- [Setup OpenInBrave] v1.1.2 ++ __Mobile Safari setup link__ -- [Setup OpenInChrome] v1.0.0 ++ __Mobile Safari setup link__ -- [Setup OpenInEdge] v1.0.0 + __Mobile Safari setup link__ -- [Setup OpenInFirefox] v1.6.2 + __Mobile Safari setup link__ -- [Setup OpenInFirefox-Focus] v1.1.2 + __Mobile Safari setup link__ -- [Setup OpenInFirefox-Private] v1.1.2 ++ __Mobile Safari setup link__ -- [Setup OpenInOpera] v1.0.0 ++ __Mobile Safari setup link__ -- [Setup OpenInOrion] v1.0.0 + __Mobile Safari setup link__ -- [Setup OpenInGoodReader] v1.5.2 + __Mobile Safari setup link__ -- [Setup OpenInTextastic] v1.1.2 + __Mobile Safari setup link__ -- [Setup OpenInWorkingCopy] v1.6.2 + __Mobile Safari setup link__ -- [Setup OpenURLParam] v1.2.1 -+ __Mobile Safari setup link__ -- [Setup UtmStrip] v2.2.0 ++ __Mobile Safari setup link__ -- [Setup UtmStrip] v2.3.0 + __Mobile Safari setup link__ -- [Setup unskim] v2.0.2 + __Mobile Safari setup link__ -- [Setup x-man] v1.3.1 @@ -206,6 +220,11 @@ using a URL protocol scheme. on Apple platforms, and Google Chrome for desktops. + __OpenInBrave__ - Uses the `brave://open-url?url=` scheme for the Brave app on iOS. ++ __OpenInChrome__ - Uses the `googlechrome://` or `googlechromes://`scheme for + the Google Chrome app on iOS. + on iOS. ++ __OpenInEdge__ - Uses the `microsoft-edge-http://` or `microsoft-edge-https://` + scheme for the Google Chrome app on iOS. + __OpenInFirefox__ _and_ __OpenInFirefox-Private__ - Uses the `firefox://open-url?url=` scheme for the Firefox app on iOS. The "Private" version appends `&private=true` after the target url for private browsing. @@ -214,6 +233,10 @@ using a URL protocol scheme. target url for private browsing. + __OpenInGoodReader__ - Uses the `grhttp://` or `grhttps://` URL protocol scheme for GoodReader. See [GoodReader URL Scheme][GoodReader URL Scheme]. ++ __OpenInOpera__ - Uses the `opera-http://` or `opera-https://` scheme for the + Opera app on iOS. ++ __OpenInOrion__ - Uses the `orion://open-url?url=` scheme for the Orion app + on iOS. + __OpenInTextastic__ - Uses the `textastic://` protocol scheme of the Textastic app on iOS. For details, see [Textastic x-callback-url API][Textastic x-callback-url API]. @@ -346,14 +369,18 @@ repos I had; doesn't build yet [KillStickyHeaders]: javascript:Array.from(document.querySelectorAll('body%20%2A')).filter(e=%3E%5B'fixed'%2C'sticky'%5D.includes(getComputedStyle(e).position)).forEach(e=%3Ee.remove())%3Bvoid'2.1.0' "KillStickyHeaders" [Linklighter]: javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=encodeURIComponent%2Ce=window.getSelection()%3Bif(!e)return%3Bconst%20n=e.toString()%2Co=n.length%2Ci=document.URL%2Cs=i.indexOf('%23')%3Blet%20r=i%2Cc=''%2Cl=''%2Ca=''%3Bif(e.rangeCount%3E0%26%26n%26%26(document.body.textContent%7C%7C'').split(n).length-1%3E1)%7Bconst%20t=e.getRangeAt(0)%2Cn=t.commonAncestorContainer.textContent%7C%7C''%2C%7BstartOffset:o%2CendOffset:i%7D=t%2Cs=Math.max(0%2Co-20)%3Bif(l=n.substring(s%2Co).trim()%2Cs%3E0%26%26'%20'!==n.charAt(s-1))%7Bconst%20t=l.indexOf('%20')%3Bt%3E0%26%26(l=l.substring(t%2B1))%7Dconst%20r=Math.min(n.length%2Ci%2B20)%3Bif(a=n.substring(i%2Cr).trim()%2Cr%3Cn.length%26%26'%20'!==n.charAt(r))%7Bconst%20t=a.lastIndexOf('%20')%3Bt%3E0%26%26(a=a.substring(0%2Ct))%7D%7Dif(e.removeAllRanges()%2Cn%26%26''!==n)%7Bs%3E-1%26%26(r=r.substring(0%2Cs))%3Blet%20e=''%3Bif(l%26%26(e=t(l)%2B'-%2C')%2C80%3Eo)c=n%2Ce%2B=t(n)%3Belse%7Blet%20i=~~(o%2F2-2)%3Bo%3E150%3Fi=48:o%3E100%26%26(i=~~(o%2F3))%3Blet%20s=n.substring(0%2Ci)%2Cr=n.slice(o-i)%3Bconst%20l=s.lastIndexOf('%20')%3Bl%3Ei%2F2%26%26(s=s.substring(0%2Cl))%3Bconst%20a=r.indexOf('%20')%3Ba%3E-1%26%26i%2F2%3Ea%26%26(r=r.substring(a%2B1))%2Cc=s%2B'%E2%80%A6'%2Ce%2B=%60%24%7Bt(s)%7D%2C%24%7Bt(r)%7D%60%7Da%26%26(e%2B='%2C-'%2Bt(a))%2Cr%2B='%23:~:text='%2Be%2Cr=r.replace(%2F(%250A%7C%250D%7C%2509%7C%2520)%2B%24%2F%2C'')%2Cr=r.replace(%2F(%2520)%7B2%2C%7D%2Fg%2C'%2520')%2Cr=r.replace(%2F%23%23%2B:~:text=%2F%2C'%23:~:text=')%7Dif(r!==i%26%26confirm(%60Open%20URL%20with%20highlight%20on%20%22%24%7Bc%7D%22%20and%20copy%20URL%20to%20clipboard%3F%60))%7Bnavigator.clipboard.writeText(r).catch(()=%3E%7Balert('Could%20not%20copy%20to%20clipboard.%20URL%20is%20in%20the%20new%20tab.')%7D)%3Bconst%20t=window.open(r%2C'%5Fblank')%3Bt%3Ft.opener=null:alert('Popup%20blocked.%20URL%20copied%20to%20clipboard.')%7D%7D)()%3Bvoid'2.1.0' "Linklighter" [OpenInBrave]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='brave:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.1.2' "OpenInBrave" +[OpenInChrome]: javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=location.protocol%3Blocation.href=%60googlechrome%24%7B'https'===o%3F's':''%7D:%2F%2F%7Blocation.href.slice(proto.length%20%2B%202)%7D%60%7Dvoid'1.0.0' "OpenInChrome" +[OpenInEdge]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='microsoft-edge-'%2Blocation.href%3Bvoid'1.0.0' "OpenInEdge" [OpenInFirefox]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='firefox:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.6.2' "OpenInFirefox" [OpenInFirefox-Focus]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60firefox-focus:%2F%2Fopen-url%3Furl=%24%7BencodeURIComponent(location.href)%7D%26private=true%60%3Bvoid'1.1.2' "OpenInFirefox-Focus" [OpenInFirefox-Private]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60firefox:%2F%2Fopen-url%3Furl=%24%7BencodeURIComponent(location.href)%7D%26private=true%60%3Bvoid'1.1.2' "OpenInFirefox-Private" +[OpenInOpera]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='opera-'%2Blocation.href%3Bvoid'1.0.0' "OpenInOpera" +[OpenInOrion]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='orion:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.0.0' "OpenInOrion" [OpenInGoodReader]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent)%26%26%2F%5C.pdf(%24%7C%5C%3F)%2F.test(location.href))location.href='gr'%2Blocation.href%3Bvoid'1.5.2' "OpenInGoodReader" [OpenInTextastic]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=location.href.replace(%2F%5Ehttps%3F%2F%2C'textastic')%3Bvoid'1.1.2' "OpenInTextastic" [OpenInWorkingCopy]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent)%26%26('bitbucket.org'===location.host%7C%7C'github.com'===location.host))location.href=%60working-copy:%2F%2Fshow%3Fremote=%24%7BencodeURIComponent(location.href.split('%2F').slice(0%2C5).join('%2F'))%7D.git%60%3Bvoid'1.6.2' "OpenInWorkingCopy" [OpenURLParam]: javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=new%20URL(document.location.href)%3Bif('https:'!==t.protocol%7C%7C13%3Et.search.length)return%3Bconst%20e=new%20URLSearchParams(t.search)%2Cn=%5B'url'%2C'destination'%2C'redirect'%2C'target'%2C'goto'%2C'u'%2C'dest'%2C'link'%2C'out'%5D.filter(t=%3Ee.has(t)).map(t=%3E%7Bconst%20n=e.get(t)%3Breturn%20n%3FdecodeURIComponent(n):''%7D).find(t=%3Et.match(%2F%5Ehttps:%2F))%3Bn%26%26window.location.replace(new%20URL(n))%7D)()%3Bvoid'1.2.1' "OpenURLParam" -[UtmStrip]: javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=location.pathname%2Ce=location.search%3Bif(3%3Ee.length%26%26!t.includes('%2Famp'))return%3Bconst%20i=location.hostname%2Cs=new%20URLSearchParams(e)%2Ca=%5B'assetType'%2C'elqTrack'%2C'mkt%5Ftok'%2C'originalReferer'%2C'referrer'%2C'terminal%5Fid'%2C'trk'%2C'trkCampaign'%2C'trkInfo'%2C'anid'%2C'assetid'%2C'campaignid'%2C'eid'%2C'gclid'%2C'recipientid'%2C'siteid'%2C'sib%5Fcuid'%2C'sib%5Fsid'%2C'%5Fbta%5Ftid'%2C'%5Fbta%5Fc'%2C'%5F%5Fs'%2C'fbclid'%2C'hrc'%2C'igsh'%2C'igshid'%2C'refsrc'%2C'%5Fgl'%2C'gclsrc'%2C'srsltid'%2C'%5Fhsenc'%2C'%5Fhsmi'%2C'%5F%5Fhsfp'%2C'%5F%5Fhssc'%2C'%5F%5Fhstc'%2C'cm%5Fmmc'%2C'cm%5Fre'%2C'cm%5Fsp'%2C'manual%5Fcm%5Fmmc'%2C'%5Fke'%2C'%5Fkx'%2C'trk%5Fcontact'%2C'trk%5Fmsg'%2C'trk%5Fmodule'%2C'trk%5Fsid'%2C'mc%5Fcid'%2C'mc%5Feid'%2C'iesrc'%2C'msclkid'%2C'dclid'%2C'twclid'%2C'ttclid'%2C'oly%5Fenc%5Fid'%2C'oly%5Fanon%5Fid'%2C'epik'%2C'vero%5Fid'%5D%2C%5F=%5B'fb%5F'%2C'action%5F'%2C'ga%5F'%2C'utm%5F'%2C'hmb%5F'%2C'hsa%5F'%2C'mtm%5F'%2C'pk%5F'%2C'oly%5F'%2C'stm%5F'%5D%3B%2F%5C.aliexpress%5C.%5Ba-z%5D%7B2%2C3%7D%24%2F.test(i)%3F(a.push('algo%5Fevid'%2C'algo%5Fpvid'%2C'btsid'%2C'spm'%2C'scm'%2C'ws%5Fab%5Ftest')%2C%5F.push('aff%5F')):%2F(%7C%5C.)amazon%5C.com%24%2F.test(i)%3Fa.push('%5Fencoding'%2C'asc%5Fcampaign'%2C'asc%5Frefurl'%2C'asc%5Fsource'%2C'ascsubtag'%2C'content-id'%2C'crid'%2C'cv%5Fct%5Fcx'%2C'dib%5Ftag'%2C'dib'%2C'ie'%2C'language'%2C'linkCode'%2C'linkId'%2C'pd%5Frd%5Fi'%2C'pd%5Frd%5Fr'%2C'pd%5Frd%5Fw'%2C'pd%5Frd%5Fwg'%2C'pf%5Frd%5Fi'%2C'pf%5Frd%5Fm'%2C'pf%5Frd%5Fp'%2C'pf%5Frd%5Fr'%2C'pf%5Frd%5Fs'%2C'pf%5Frd%5Ft'%2C'pf'%2C'psc'%2C'qid'%2C'ref%5F'%2C'sprefix'%2C'sr'%2C'tag'%2C'th'):i.endsWith('.ebay.com')%7C%7C%2F%5C.ebay%5C.co%5C.%5Ba-z%5D%7B2%7D%24%2F.test(i)%3Fa.push('mkevt'%2C'mkcid'%2C'mkrid'%2C'campid'%2C'toolid'%2C'customid'%2C'norover'%2C'itm'%2C'amdata'):%2F(%5E%7C%5C.)google%5C.(com%7C%5Ba-z%5D%7B2%7D%7Ccom%3F%5C.%5Ba-z%5D%7B2%7D)%24%2F.test(i)%26%26t.startsWith('%2Fsearch')%3Fa.push('aqs'%2C'ei'%2C'gs%5Flp'%2C'gs%5Fssp'%2C'iflsig'%2C'sca%5Fesv'%2C'ved'%2C'oq'%2C'sa'%2C'uact'%2C'rlz'%2C'sxsrf'%2C'bih'%2C'biw'%2C'client'%2C'prmd'%2C'sclient'%2C'source'%2C'sourceid'%2C'ie'%2C'oe'):i.endsWith('.linkedin.com')%3Fa.push('li%5Ffat%5Fid'%2C'licu'%2C'lipi'%2C'midSig'%2C'midToken'%2C'refId'):i.endsWith('.target.com')%3Fa.push('afid'%2C'clkid'%2C'lnm'%2C'preselect'%2C'tref'):i.endsWith('.temu.com')%3F(a.push('%5Fbg%5Ffs'%2C'%5Fp%5Fjump%5Fid'%2C'%5Fp%5Frfs'%2C'refer%5Fpage%5Fid'%2C'refer%5Fpage%5Fname'%2C'refer%5Fpage%5Fsn')%2C%5F.push('%5Fx%5F')):i.endsWith('.tiktok.com')%7C%7C'tiktok.com'===i%3Fa.push('%5Fd'%2C'%5Fr'%2C'%5Ft'%2C'is%5Ffrom%5Fwebapp'%2C'preview%5Fpb'%2C'share%5Fapp%5Fname'%2C'share%5Fitem%5Fid'%2C'tt4d%5Ft'%2C'timestamp'%2C'u%5Fcode'%2C'user%5Fid'):%2F%5C.(twitter%7Cx)%5C.com%24%2F.test(i)%7C%7C%2F%5E(twitter%7Cx)%5C.com%24%2F.test(i)%3Fa.push('cn'%2C'ref%5Fsrc'%2C'ref%5Furl'%2C's'%2C't'):i.endsWith('.walmart.com')%3Fa.push('adsredirect'%2C'affiliates%5Fad%5Fid'%2C'athcpid'%2C'athpgid'%2C'athcgid'%2C'athmtid'%2C'athstid'%2C'athznid'%2C'athiession'%2C'athancid'%2C'athposb'%2C'athena'%2C'campaign%5Fid'%2C'wmlspartner'):(%2F(m%7Cwww)%5C.youtube%5C.com%24%2F.test(i)%7C%7C'youtu.be'===i%7C%7C'www.youtube-nocookie.com'===i)%26%26a.push('ac'%2C'annotation%5Fid'%2C'app'%2C'feature'%2C'gclid'%2C'kw'%2C'src%5Fvid')%3Bconst%20c=new%20Set(a.map(t=%3Et.toLowerCase()))%2Cr=t=%3E%7Bconst%20e=t.toLowerCase()%3Breturn%20c.has(e)%7C%7C%5F.some(t=%3Ee.startsWith(t))%7C%7C%2F%5Ecm%5Fmmca%5Cd%2B%24%2Fi.test(t)%7D%3Bfor(const%20t%20of%5B...s.keys()%5D)r(t)%26%26s.delete(t)%3Blet%20d=s.toString()%3Bd=d%3F'%3F'%2Bd:''%3Bconst%20o=t.replace(%2F%5C%2Famp%5C%2F%3F%24%2F%2C'')%3Bif((e!==d%7C%7Ct!==o)%26%26confirm('Update%20history%20and%20copy%20cleaned%20URL%20to%20clipboard%3F'))%7Bconst%20t=%60%24%7Blocation.protocol%7D%2F%2F%24%7Blocation.host%7D%24%7Bo%7D%24%7Bd%7D%24%7Blocation.hash%7D%60%3Bnavigator.clipboard.writeText(t)%2Chistory.replaceState(null%2C''%2Ct)%3Bconst%20e=window.open(t%2C'%5Fself'%2C'noreferrer')%3Be%26%26(e.opener=null)%7D%7D)()%3Bvoid'2.2.0' "UtmStrip" +[UtmStrip]: javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=location.pathname%2Ce=location.search%3Bif(3%3Ee.length%26%26!t.includes('%2Famp'))return%3Bconst%20i=location.hostname%2Cs=new%20URLSearchParams(e)%2Ca=%5B'assetType'%2C'elqTrack'%2C'mkt%5Ftok'%2C'originalReferer'%2C'referrer'%2C'terminal%5Fid'%2C'trk'%2C'trkCampaign'%2C'trkInfo'%2C'anid'%2C'assetid'%2C'campaignid'%2C'eid'%2C'gclid'%2C'recipientid'%2C'siteid'%2C'sib%5Fcuid'%2C'sib%5Fsid'%2C'%5Fbta%5Ftid'%2C'%5Fbta%5Fc'%2C'%5F%5Fs'%2C'fbclid'%2C'hrc'%2C'igsh'%2C'igshid'%2C'refsrc'%2C'%5Fgl'%2C'gclsrc'%2C'srsltid'%2C'%5Fhsenc'%2C'%5Fhsmi'%2C'%5F%5Fhsfp'%2C'%5F%5Fhssc'%2C'%5F%5Fhstc'%2C'cm%5Fmmc'%2C'cm%5Fre'%2C'cm%5Fsp'%2C'manual%5Fcm%5Fmmc'%2C'%5Fke'%2C'%5Fkx'%2C'trk%5Fcontact'%2C'trk%5Fmsg'%2C'trk%5Fmodule'%2C'trk%5Fsid'%2C'mc%5Fcid'%2C'mc%5Feid'%2C'iesrc'%2C'msclkid'%2C'dclid'%2C'twclid'%2C'ttclid'%2C'oly%5Fenc%5Fid'%2C'oly%5Fanon%5Fid'%2C'epik'%2C'vero%5Fid'%5D%2C%5F=%5B'fb%5F'%2C'action%5F'%2C'ga%5F'%2C'utm%5F'%2C'hmb%5F'%2C'hsa%5F'%2C'mtm%5F'%2C'pk%5F'%2C'oly%5F'%2C'stm%5F'%5D%3B%2F%5C.aliexpress%5C.%5Ba-z%5D%7B2%2C3%7D%24%2F.test(i)%3F(a.push('algo%5Fevid'%2C'algo%5Fpvid'%2C'btsid'%2C'spm'%2C'scm'%2C'ws%5Fab%5Ftest')%2C%5F.push('aff%5F')):%2F(%7C%5C.)amazon%5C.com%24%2F.test(i)%3Fa.push('%5Fencoding'%2C'asc%5Fcampaign'%2C'asc%5Frefurl'%2C'asc%5Fsource'%2C'ascsubtag'%2C'content-id'%2C'crid'%2C'cv%5Fct%5Fcx'%2C'dib%5Ftag'%2C'dib'%2C'ie'%2C'language'%2C'linkCode'%2C'linkId'%2C'pd%5Frd%5Fi'%2C'pd%5Frd%5Fr'%2C'pd%5Frd%5Fw'%2C'pd%5Frd%5Fwg'%2C'pf%5Frd%5Fi'%2C'pf%5Frd%5Fm'%2C'pf%5Frd%5Fp'%2C'pf%5Frd%5Fr'%2C'pf%5Frd%5Fs'%2C'pf%5Frd%5Ft'%2C'pf'%2C'psc'%2C'qid'%2C'ref%5F'%2C'sprefix'%2C'sr'%2C'tag'%2C'th'):i.endsWith('.ebay.com')%7C%7C%2F%5C.ebay%5C.co%5C.%5Ba-z%5D%7B2%7D%24%2F.test(i)%3Fa.push('mkevt'%2C'mkcid'%2C'mkrid'%2C'campid'%2C'toolid'%2C'customid'%2C'norover'%2C'itm'%2C'amdata'):%2F(%5E%7C%5C.)google%5C.(com%7C%5Ba-z%5D%7B2%7D%7Ccom%3F%5C.%5Ba-z%5D%7B2%7D)%24%2F.test(i)%26%26t.startsWith('%2Fsearch')%3Fa.push('aqs'%2C'ei'%2C'gs%5Flp'%2C'gs%5Fssp'%2C'iflsig'%2C'sca%5Fesv'%2C'ved'%2C'oq'%2C'sa'%2C'uact'%2C'rlz'%2C'sxsrf'%2C'bih'%2C'biw'%2C'client'%2C'prmd'%2C'sclient'%2C'source'%2C'sourceid'%2C'ie'%2C'oe'):i.endsWith('.linkedin.com')%3Fa.push('li%5Ffat%5Fid'%2C'licu'%2C'lipi'%2C'midSig'%2C'midToken'%2C'refId'):i.endsWith('.target.com')%3Fa.push('afid'%2C'clkid'%2C'lnm'%2C'preselect'%2C'tref'):i.endsWith('.temu.com')%3F(a.push('%5Fbg%5Ffs'%2C'%5Fp%5Fjump%5Fid'%2C'%5Fp%5Frfs'%2C'refer%5Fpage%5Fid'%2C'refer%5Fpage%5Fname'%2C'refer%5Fpage%5Fsn')%2C%5F.push('%5Fx%5F')):i.endsWith('.tiktok.com')%7C%7C'tiktok.com'===i%3Fa.push('%5Fd'%2C'%5Fr'%2C'%5Ft'%2C'is%5Ffrom%5Fwebapp'%2C'preview%5Fpb'%2C'share%5Fapp%5Fname'%2C'share%5Fitem%5Fid'%2C'tt4d%5Ft'%2C'timestamp'%2C'u%5Fcode'%2C'user%5Fid'):%2F%5C.(twitter%7Cx)%5C.com%24%2F.test(i)%7C%7C%2F%5E(twitter%7Cx)%5C.com%24%2F.test(i)%3Fa.push('cn'%2C'ref%5Fsrc'%2C'ref%5Furl'%2C's'%2C't'):i.endsWith('.walmart.com')%3Fa.push('adsredirect'%2C'affiliates%5Fad%5Fid'%2C'athcpid'%2C'athpgid'%2C'athcgid'%2C'athmtid'%2C'athstid'%2C'athznid'%2C'athiession'%2C'athancid'%2C'athposb'%2C'athena'%2C'campaign%5Fid'%2C'wmlspartner'):(%2F(m%7Cwww)%5C.youtube%5C.com%24%2F.test(i)%7C%7C'youtu.be'===i%7C%7C'www.youtube-nocookie.com'===i)%26%26a.push('ac'%2C'annotation%5Fid'%2C'app'%2C'feature'%2C'gclid'%2C'kw'%2C'src%5Fvid')%3Bconst%20c=new%20Set(a.map(t=%3Et.toLowerCase()))%2Cr=t=%3E%7Bconst%20e=t.toLowerCase()%3Breturn%20c.has(e)%7C%7C%5F.some(t=%3Ee.startsWith(t))%7C%7C%2F%5Ecm%5Fmmca%5Cd%2B%24%2Fi.test(t)%7D%3Bfor(const%20t%20of%5B...s.keys()%5D)r(t)%26%26s.delete(t)%3Blet%20d=s.toString()%3Bd=d%3F'%3F'%2Bd:''%3Bconst%20o=t.replace(%2F%5C%2Famp%5C%2F%3F%24%2F%2C'')%2Cn=%60%24%7Blocation.protocol%7D%2F%2F%24%7Blocation.host%7D%24%7Bo%7D%24%7Bd%7D%24%7Blocation.hash%7D%60%3Bif(confirm('Update%20history%20and%20copy%20cleaned%20URL%20to%20clipboard%3F')%26%26(navigator.clipboard.writeText(n)%2Ce!==d%7C%7Ct!==o))%7Bhistory.replaceState(null%2C''%2Cn)%3Bconst%20t=window.open(n%2C'%5Fself'%2C'noreferrer')%3Bt%26%26(t.opener=null)%7D%7D)()%3Bvoid'2.3.0' "UtmStrip" [unskim]: javascript:'use%20strict'%3B(()=%3E%7Blet%20e=new%20URL(document.location.href)%3Bif('safari-resource:%2FErrorPage.html'===e.href)%7Bconst%20t=document.querySelector('p.error-message')%3F.textContent%3F.match(%2FSafari%20can't%20open%20the%20page%20%22(https%3F:%5B%5E%22%5D%2B)%22%2F)%3F.%5B1%5D%3Bt%26%26(e=new%20URL(t))%7Dif(13%3Ee.search.length)return%3Bconst%20t=new%20URLSearchParams(e.search)%2Cr=%5B'url'%2C'destination'%2C'redirect'%2C'target'%2C'goto'%2C'u'%2C'dest'%2C'link'%2C'out'%5D.filter(e=%3Et.has(e)).map(e=%3E%7Bconst%20r=t.get(e)%3Breturn%20r%3FdecodeURIComponent(r):''%7D).find(e=%3Ee.match(%2F%5Ehttps%3F:%2F))%3Br%26%26window.location.replace(new%20URL(r))%7D)()%3Bvoid'2.0.2' "unskim" [x-man]: javascript:'use%20strict'%3B(()=%3E%7Bconst%20i=navigator.userAgent%2Cn=i.includes('Chrome%2F')%7C%7Ci.includes('Firefox%2F')%7C%7Ci.includes('Brave%2F')%7C%7Ci.includes('Edg%2F')%2Co=i.includes('Safari%2F')%2Ct=!n%26%26!o%2Ca=navigator.platform.startsWith('Mac')%26%26o%26%26!n%26%26!t%26%26!navigator.maxTouchPoints%26%262%3Enavigator.maxTouchPoints%2Ce=window.getSelection()%3Bif(!e)return%3Bconst%20l=e.toString().split('%5Cn')%5B0%5D.trim()%3Blet%20r='x-man-page:%2F%2F'%3Bconst%20c=l.match(%2F%5E(.%2A%3F)%5C((%5Cd)%5C)%24%2F)%3Bif(r%2B=c%3F'1'===c%5B2%5D%3Fc%5B1%5D:%60%24%7Bc%5B2%5D%7D%2F%24%7Bc%5B1%5D%7D%60:l%2Cl)%7Bif(confirm(%60Link%20for%20%22%24%7Bl%7D%22%20is:%20%24%7Br%7D%5Cn%5CnCopy%20to%20clipboard%3F%60))%7Blet%20i=''%3Bif(navigator.clipboard%3Fnavigator.clipboard.writeText(r):(i=%60Unable%20to%20copy%20%22%24%7Br%7D%22%20to%20clipboard.%60%2Cr='')%2Ca%26%26''!==r)%7Blet%20n=null%3Btry%7Bn=window.open(r)%2Cn%26%26(n.opener=null)%7Dcatch(n)%7Bi=%60Popup%20window%20blocked.%20Clipboard%20contains%20%22%24%7Br%7D%22%60%7Dfinally%7Bwindow.focus()%2Cnull!==n%26%26setTimeout(()=%3E%7Bn%26%26n.close()%7D%2C3333)%7D%7Delse''!==r%26%26(i=%60Unable%20to%20open%20new%20link%20with%20Terminal.%20Clipboard%20contains%20%22%24%7Br%7D%22%60)%3B''!==i%26%26alert(i)%7De.empty()%7D%7D)()%3Bvoid'1.3.1' "x-man" @@ -365,14 +392,18 @@ repos I had; doesn't build yet [Setup KillStickyHeaders]: https://mobilemind.github.io/OpenInlets/x/#javascript:Array.from(document.querySelectorAll('body%20%2A')).filter(e=%3E%5B'fixed'%2C'sticky'%5D.includes(getComputedStyle(e).position)).forEach(e=%3Ee.remove())%3Bvoid'2.1.0' "Setup KillStickyHeaders" [Setup Linklighter]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=encodeURIComponent%2Ce=window.getSelection()%3Bif(!e)return%3Bconst%20n=e.toString()%2Co=n.length%2Ci=document.URL%2Cs=i.indexOf('%23')%3Blet%20r=i%2Cc=''%2Cl=''%2Ca=''%3Bif(e.rangeCount%3E0%26%26n%26%26(document.body.textContent%7C%7C'').split(n).length-1%3E1)%7Bconst%20t=e.getRangeAt(0)%2Cn=t.commonAncestorContainer.textContent%7C%7C''%2C%7BstartOffset:o%2CendOffset:i%7D=t%2Cs=Math.max(0%2Co-20)%3Bif(l=n.substring(s%2Co).trim()%2Cs%3E0%26%26'%20'!==n.charAt(s-1))%7Bconst%20t=l.indexOf('%20')%3Bt%3E0%26%26(l=l.substring(t%2B1))%7Dconst%20r=Math.min(n.length%2Ci%2B20)%3Bif(a=n.substring(i%2Cr).trim()%2Cr%3Cn.length%26%26'%20'!==n.charAt(r))%7Bconst%20t=a.lastIndexOf('%20')%3Bt%3E0%26%26(a=a.substring(0%2Ct))%7D%7Dif(e.removeAllRanges()%2Cn%26%26''!==n)%7Bs%3E-1%26%26(r=r.substring(0%2Cs))%3Blet%20e=''%3Bif(l%26%26(e=t(l)%2B'-%2C')%2C80%3Eo)c=n%2Ce%2B=t(n)%3Belse%7Blet%20i=~~(o%2F2-2)%3Bo%3E150%3Fi=48:o%3E100%26%26(i=~~(o%2F3))%3Blet%20s=n.substring(0%2Ci)%2Cr=n.slice(o-i)%3Bconst%20l=s.lastIndexOf('%20')%3Bl%3Ei%2F2%26%26(s=s.substring(0%2Cl))%3Bconst%20a=r.indexOf('%20')%3Ba%3E-1%26%26i%2F2%3Ea%26%26(r=r.substring(a%2B1))%2Cc=s%2B'%E2%80%A6'%2Ce%2B=%60%24%7Bt(s)%7D%2C%24%7Bt(r)%7D%60%7Da%26%26(e%2B='%2C-'%2Bt(a))%2Cr%2B='%23:~:text='%2Be%2Cr=r.replace(%2F(%250A%7C%250D%7C%2509%7C%2520)%2B%24%2F%2C'')%2Cr=r.replace(%2F(%2520)%7B2%2C%7D%2Fg%2C'%2520')%2Cr=r.replace(%2F%23%23%2B:~:text=%2F%2C'%23:~:text=')%7Dif(r!==i%26%26confirm(%60Open%20URL%20with%20highlight%20on%20%22%24%7Bc%7D%22%20and%20copy%20URL%20to%20clipboard%3F%60))%7Bnavigator.clipboard.writeText(r).catch(()=%3E%7Balert('Could%20not%20copy%20to%20clipboard.%20URL%20is%20in%20the%20new%20tab.')%7D)%3Bconst%20t=window.open(r%2C'%5Fblank')%3Bt%3Ft.opener=null:alert('Popup%20blocked.%20URL%20copied%20to%20clipboard.')%7D%7D)()%3Bvoid'2.1.0' "Setup Linklighter" [Setup OpenInBrave]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='brave:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.1.2' "Setup OpenInBrave" +[Setup OpenInChrome]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=location.protocol%3Blocation.href=%60googlechrome%24%7B'https'===o%3F's':''%7D:%2F%2F%7Blocation.href.slice(proto.length%20%2B%202)%7D%60%7Dvoid'1.0.0' "Setup OpenInChrome" +[Setup OpenInEdge]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='microsoft-edge-'%2Blocation.href%3Bvoid'1.0.0' "Setup OpenInEdge" [Setup OpenInFirefox]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='firefox:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.6.2' "Setup OpenInFirefox" [Setup OpenInFirefox-Focus]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60firefox-focus:%2F%2Fopen-url%3Furl=%24%7BencodeURIComponent(location.href)%7D%26private=true%60%3Bvoid'1.1.2' "Setup OpenInFirefox-Focus" [Setup OpenInFirefox-Private]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60firefox:%2F%2Fopen-url%3Furl=%24%7BencodeURIComponent(location.href)%7D%26private=true%60%3Bvoid'1.1.2' "Setup OpenInFirefox-Private" [Setup OpenInGoodReader]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent)%26%26%2F%5C.pdf(%24%7C%5C%3F)%2F.test(location.href))location.href='gr'%2Blocation.href%3Bvoid'1.5.2' "Setup OpenInGoodReader" +[Setup OpenInOpera]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='opera-'%2Blocation.href%3Bvoid'1.0.0' "Setup OpenInOpera" +[Setup OpenInOrion]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='orion:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.0.0' "Setup OpenInOrion" [Setup OpenInTextastic]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=location.href.replace(%2F%5Ehttps%3F%2F%2C'textastic')%3Bvoid'1.1.2' "Setup OpenInTextastic" [Setup OpenInWorkingCopy]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent)%26%26('bitbucket.org'===location.host%7C%7C'github.com'===location.host))location.href=%60working-copy:%2F%2Fshow%3Fremote=%24%7BencodeURIComponent(location.href.split('%2F').slice(0%2C5).join('%2F'))%7D.git%60%3Bvoid'1.6.2' "Setup OpenInWorkingCopy" [Setup OpenURLParam]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=new%20URL(document.location.href)%3Bif('https:'!==t.protocol%7C%7C13%3Et.search.length)return%3Bconst%20e=new%20URLSearchParams(t.search)%2Cn=%5B'url'%2C'destination'%2C'redirect'%2C'target'%2C'goto'%2C'u'%2C'dest'%2C'link'%2C'out'%5D.filter(t=%3Ee.has(t)).map(t=%3E%7Bconst%20n=e.get(t)%3Breturn%20n%3FdecodeURIComponent(n):''%7D).find(t=%3Et.match(%2F%5Ehttps:%2F))%3Bn%26%26window.location.replace(new%20URL(n))%7D)()%3Bvoid'1.2.1' "Setup OpenURLParam" -[Setup UtmStrip]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=location.pathname%2Ce=location.search%3Bif(3%3Ee.length%26%26!t.includes('%2Famp'))return%3Bconst%20i=location.hostname%2Cs=new%20URLSearchParams(e)%2Ca=%5B'assetType'%2C'elqTrack'%2C'mkt%5Ftok'%2C'originalReferer'%2C'referrer'%2C'terminal%5Fid'%2C'trk'%2C'trkCampaign'%2C'trkInfo'%2C'anid'%2C'assetid'%2C'campaignid'%2C'eid'%2C'gclid'%2C'recipientid'%2C'siteid'%2C'sib%5Fcuid'%2C'sib%5Fsid'%2C'%5Fbta%5Ftid'%2C'%5Fbta%5Fc'%2C'%5F%5Fs'%2C'fbclid'%2C'hrc'%2C'igsh'%2C'igshid'%2C'refsrc'%2C'%5Fgl'%2C'gclsrc'%2C'srsltid'%2C'%5Fhsenc'%2C'%5Fhsmi'%2C'%5F%5Fhsfp'%2C'%5F%5Fhssc'%2C'%5F%5Fhstc'%2C'cm%5Fmmc'%2C'cm%5Fre'%2C'cm%5Fsp'%2C'manual%5Fcm%5Fmmc'%2C'%5Fke'%2C'%5Fkx'%2C'trk%5Fcontact'%2C'trk%5Fmsg'%2C'trk%5Fmodule'%2C'trk%5Fsid'%2C'mc%5Fcid'%2C'mc%5Feid'%2C'iesrc'%2C'msclkid'%2C'dclid'%2C'twclid'%2C'ttclid'%2C'oly%5Fenc%5Fid'%2C'oly%5Fanon%5Fid'%2C'epik'%2C'vero%5Fid'%5D%2C%5F=%5B'fb%5F'%2C'action%5F'%2C'ga%5F'%2C'utm%5F'%2C'hmb%5F'%2C'hsa%5F'%2C'mtm%5F'%2C'pk%5F'%2C'oly%5F'%2C'stm%5F'%5D%3B%2F%5C.aliexpress%5C.%5Ba-z%5D%7B2%2C3%7D%24%2F.test(i)%3F(a.push('algo%5Fevid'%2C'algo%5Fpvid'%2C'btsid'%2C'spm'%2C'scm'%2C'ws%5Fab%5Ftest')%2C%5F.push('aff%5F')):%2F(%7C%5C.)amazon%5C.com%24%2F.test(i)%3Fa.push('%5Fencoding'%2C'asc%5Fcampaign'%2C'asc%5Frefurl'%2C'asc%5Fsource'%2C'ascsubtag'%2C'content-id'%2C'crid'%2C'cv%5Fct%5Fcx'%2C'dib%5Ftag'%2C'dib'%2C'ie'%2C'language'%2C'linkCode'%2C'linkId'%2C'pd%5Frd%5Fi'%2C'pd%5Frd%5Fr'%2C'pd%5Frd%5Fw'%2C'pd%5Frd%5Fwg'%2C'pf%5Frd%5Fi'%2C'pf%5Frd%5Fm'%2C'pf%5Frd%5Fp'%2C'pf%5Frd%5Fr'%2C'pf%5Frd%5Fs'%2C'pf%5Frd%5Ft'%2C'pf'%2C'psc'%2C'qid'%2C'ref%5F'%2C'sprefix'%2C'sr'%2C'tag'%2C'th'):i.endsWith('.ebay.com')%7C%7C%2F%5C.ebay%5C.co%5C.%5Ba-z%5D%7B2%7D%24%2F.test(i)%3Fa.push('mkevt'%2C'mkcid'%2C'mkrid'%2C'campid'%2C'toolid'%2C'customid'%2C'norover'%2C'itm'%2C'amdata'):%2F(%5E%7C%5C.)google%5C.(com%7C%5Ba-z%5D%7B2%7D%7Ccom%3F%5C.%5Ba-z%5D%7B2%7D)%24%2F.test(i)%26%26t.startsWith('%2Fsearch')%3Fa.push('aqs'%2C'ei'%2C'gs%5Flp'%2C'gs%5Fssp'%2C'iflsig'%2C'sca%5Fesv'%2C'ved'%2C'oq'%2C'sa'%2C'uact'%2C'rlz'%2C'sxsrf'%2C'bih'%2C'biw'%2C'client'%2C'prmd'%2C'sclient'%2C'source'%2C'sourceid'%2C'ie'%2C'oe'):i.endsWith('.linkedin.com')%3Fa.push('li%5Ffat%5Fid'%2C'licu'%2C'lipi'%2C'midSig'%2C'midToken'%2C'refId'):i.endsWith('.target.com')%3Fa.push('afid'%2C'clkid'%2C'lnm'%2C'preselect'%2C'tref'):i.endsWith('.temu.com')%3F(a.push('%5Fbg%5Ffs'%2C'%5Fp%5Fjump%5Fid'%2C'%5Fp%5Frfs'%2C'refer%5Fpage%5Fid'%2C'refer%5Fpage%5Fname'%2C'refer%5Fpage%5Fsn')%2C%5F.push('%5Fx%5F')):i.endsWith('.tiktok.com')%7C%7C'tiktok.com'===i%3Fa.push('%5Fd'%2C'%5Fr'%2C'%5Ft'%2C'is%5Ffrom%5Fwebapp'%2C'preview%5Fpb'%2C'share%5Fapp%5Fname'%2C'share%5Fitem%5Fid'%2C'tt4d%5Ft'%2C'timestamp'%2C'u%5Fcode'%2C'user%5Fid'):%2F%5C.(twitter%7Cx)%5C.com%24%2F.test(i)%7C%7C%2F%5E(twitter%7Cx)%5C.com%24%2F.test(i)%3Fa.push('cn'%2C'ref%5Fsrc'%2C'ref%5Furl'%2C's'%2C't'):i.endsWith('.walmart.com')%3Fa.push('adsredirect'%2C'affiliates%5Fad%5Fid'%2C'athcpid'%2C'athpgid'%2C'athcgid'%2C'athmtid'%2C'athstid'%2C'athznid'%2C'athiession'%2C'athancid'%2C'athposb'%2C'athena'%2C'campaign%5Fid'%2C'wmlspartner'):(%2F(m%7Cwww)%5C.youtube%5C.com%24%2F.test(i)%7C%7C'youtu.be'===i%7C%7C'www.youtube-nocookie.com'===i)%26%26a.push('ac'%2C'annotation%5Fid'%2C'app'%2C'feature'%2C'gclid'%2C'kw'%2C'src%5Fvid')%3Bconst%20c=new%20Set(a.map(t=%3Et.toLowerCase()))%2Cr=t=%3E%7Bconst%20e=t.toLowerCase()%3Breturn%20c.has(e)%7C%7C%5F.some(t=%3Ee.startsWith(t))%7C%7C%2F%5Ecm%5Fmmca%5Cd%2B%24%2Fi.test(t)%7D%3Bfor(const%20t%20of%5B...s.keys()%5D)r(t)%26%26s.delete(t)%3Blet%20d=s.toString()%3Bd=d%3F'%3F'%2Bd:''%3Bconst%20o=t.replace(%2F%5C%2Famp%5C%2F%3F%24%2F%2C'')%3Bif((e!==d%7C%7Ct!==o)%26%26confirm('Update%20history%20and%20copy%20cleaned%20URL%20to%20clipboard%3F'))%7Bconst%20t=%60%24%7Blocation.protocol%7D%2F%2F%24%7Blocation.host%7D%24%7Bo%7D%24%7Bd%7D%24%7Blocation.hash%7D%60%3Bnavigator.clipboard.writeText(t)%2Chistory.replaceState(null%2C''%2Ct)%3Bconst%20e=window.open(t%2C'%5Fself'%2C'noreferrer')%3Be%26%26(e.opener=null)%7D%7D)()%3Bvoid'2.2.0' "Setup UtmStrip" +[Setup UtmStrip]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=location.pathname%2Ce=location.search%3Bif(3%3Ee.length%26%26!t.includes('%2Famp'))return%3Bconst%20i=location.hostname%2Cs=new%20URLSearchParams(e)%2Ca=%5B'assetType'%2C'elqTrack'%2C'mkt%5Ftok'%2C'originalReferer'%2C'referrer'%2C'terminal%5Fid'%2C'trk'%2C'trkCampaign'%2C'trkInfo'%2C'anid'%2C'assetid'%2C'campaignid'%2C'eid'%2C'gclid'%2C'recipientid'%2C'siteid'%2C'sib%5Fcuid'%2C'sib%5Fsid'%2C'%5Fbta%5Ftid'%2C'%5Fbta%5Fc'%2C'%5F%5Fs'%2C'fbclid'%2C'hrc'%2C'igsh'%2C'igshid'%2C'refsrc'%2C'%5Fgl'%2C'gclsrc'%2C'srsltid'%2C'%5Fhsenc'%2C'%5Fhsmi'%2C'%5F%5Fhsfp'%2C'%5F%5Fhssc'%2C'%5F%5Fhstc'%2C'cm%5Fmmc'%2C'cm%5Fre'%2C'cm%5Fsp'%2C'manual%5Fcm%5Fmmc'%2C'%5Fke'%2C'%5Fkx'%2C'trk%5Fcontact'%2C'trk%5Fmsg'%2C'trk%5Fmodule'%2C'trk%5Fsid'%2C'mc%5Fcid'%2C'mc%5Feid'%2C'iesrc'%2C'msclkid'%2C'dclid'%2C'twclid'%2C'ttclid'%2C'oly%5Fenc%5Fid'%2C'oly%5Fanon%5Fid'%2C'epik'%2C'vero%5Fid'%5D%2C%5F=%5B'fb%5F'%2C'action%5F'%2C'ga%5F'%2C'utm%5F'%2C'hmb%5F'%2C'hsa%5F'%2C'mtm%5F'%2C'pk%5F'%2C'oly%5F'%2C'stm%5F'%5D%3B%2F%5C.aliexpress%5C.%5Ba-z%5D%7B2%2C3%7D%24%2F.test(i)%3F(a.push('algo%5Fevid'%2C'algo%5Fpvid'%2C'btsid'%2C'spm'%2C'scm'%2C'ws%5Fab%5Ftest')%2C%5F.push('aff%5F')):%2F(%7C%5C.)amazon%5C.com%24%2F.test(i)%3Fa.push('%5Fencoding'%2C'asc%5Fcampaign'%2C'asc%5Frefurl'%2C'asc%5Fsource'%2C'ascsubtag'%2C'content-id'%2C'crid'%2C'cv%5Fct%5Fcx'%2C'dib%5Ftag'%2C'dib'%2C'ie'%2C'language'%2C'linkCode'%2C'linkId'%2C'pd%5Frd%5Fi'%2C'pd%5Frd%5Fr'%2C'pd%5Frd%5Fw'%2C'pd%5Frd%5Fwg'%2C'pf%5Frd%5Fi'%2C'pf%5Frd%5Fm'%2C'pf%5Frd%5Fp'%2C'pf%5Frd%5Fr'%2C'pf%5Frd%5Fs'%2C'pf%5Frd%5Ft'%2C'pf'%2C'psc'%2C'qid'%2C'ref%5F'%2C'sprefix'%2C'sr'%2C'tag'%2C'th'):i.endsWith('.ebay.com')%7C%7C%2F%5C.ebay%5C.co%5C.%5Ba-z%5D%7B2%7D%24%2F.test(i)%3Fa.push('mkevt'%2C'mkcid'%2C'mkrid'%2C'campid'%2C'toolid'%2C'customid'%2C'norover'%2C'itm'%2C'amdata'):%2F(%5E%7C%5C.)google%5C.(com%7C%5Ba-z%5D%7B2%7D%7Ccom%3F%5C.%5Ba-z%5D%7B2%7D)%24%2F.test(i)%26%26t.startsWith('%2Fsearch')%3Fa.push('aqs'%2C'ei'%2C'gs%5Flp'%2C'gs%5Fssp'%2C'iflsig'%2C'sca%5Fesv'%2C'ved'%2C'oq'%2C'sa'%2C'uact'%2C'rlz'%2C'sxsrf'%2C'bih'%2C'biw'%2C'client'%2C'prmd'%2C'sclient'%2C'source'%2C'sourceid'%2C'ie'%2C'oe'):i.endsWith('.linkedin.com')%3Fa.push('li%5Ffat%5Fid'%2C'licu'%2C'lipi'%2C'midSig'%2C'midToken'%2C'refId'):i.endsWith('.target.com')%3Fa.push('afid'%2C'clkid'%2C'lnm'%2C'preselect'%2C'tref'):i.endsWith('.temu.com')%3F(a.push('%5Fbg%5Ffs'%2C'%5Fp%5Fjump%5Fid'%2C'%5Fp%5Frfs'%2C'refer%5Fpage%5Fid'%2C'refer%5Fpage%5Fname'%2C'refer%5Fpage%5Fsn')%2C%5F.push('%5Fx%5F')):i.endsWith('.tiktok.com')%7C%7C'tiktok.com'===i%3Fa.push('%5Fd'%2C'%5Fr'%2C'%5Ft'%2C'is%5Ffrom%5Fwebapp'%2C'preview%5Fpb'%2C'share%5Fapp%5Fname'%2C'share%5Fitem%5Fid'%2C'tt4d%5Ft'%2C'timestamp'%2C'u%5Fcode'%2C'user%5Fid'):%2F%5C.(twitter%7Cx)%5C.com%24%2F.test(i)%7C%7C%2F%5E(twitter%7Cx)%5C.com%24%2F.test(i)%3Fa.push('cn'%2C'ref%5Fsrc'%2C'ref%5Furl'%2C's'%2C't'):i.endsWith('.walmart.com')%3Fa.push('adsredirect'%2C'affiliates%5Fad%5Fid'%2C'athcpid'%2C'athpgid'%2C'athcgid'%2C'athmtid'%2C'athstid'%2C'athznid'%2C'athiession'%2C'athancid'%2C'athposb'%2C'athena'%2C'campaign%5Fid'%2C'wmlspartner'):(%2F(m%7Cwww)%5C.youtube%5C.com%24%2F.test(i)%7C%7C'youtu.be'===i%7C%7C'www.youtube-nocookie.com'===i)%26%26a.push('ac'%2C'annotation%5Fid'%2C'app'%2C'feature'%2C'gclid'%2C'kw'%2C'src%5Fvid')%3Bconst%20c=new%20Set(a.map(t=%3Et.toLowerCase()))%2Cr=t=%3E%7Bconst%20e=t.toLowerCase()%3Breturn%20c.has(e)%7C%7C%5F.some(t=%3Ee.startsWith(t))%7C%7C%2F%5Ecm%5Fmmca%5Cd%2B%24%2Fi.test(t)%7D%3Bfor(const%20t%20of%5B...s.keys()%5D)r(t)%26%26s.delete(t)%3Blet%20d=s.toString()%3Bd=d%3F'%3F'%2Bd:''%3Bconst%20o=t.replace(%2F%5C%2Famp%5C%2F%3F%24%2F%2C'')%2Cn=%60%24%7Blocation.protocol%7D%2F%2F%24%7Blocation.host%7D%24%7Bo%7D%24%7Bd%7D%24%7Blocation.hash%7D%60%3Bif(confirm('Update%20history%20and%20copy%20cleaned%20URL%20to%20clipboard%3F')%26%26(navigator.clipboard.writeText(n)%2Ce!==d%7C%7Ct!==o))%7Bhistory.replaceState(null%2C''%2Cn)%3Bconst%20t=window.open(n%2C'%5Fself'%2C'noreferrer')%3Bt%26%26(t.opener=null)%7D%7D)()%3Bvoid'2.3.0' "Setup UtmStrip" [Setup unskim]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3B(()=%3E%7Blet%20e=new%20URL(document.location.href)%3Bif('safari-resource:%2FErrorPage.html'===e.href)%7Bconst%20t=document.querySelector('p.error-message')%3F.textContent%3F.match(%2FSafari%20can't%20open%20the%20page%20%22(https%3F:%5B%5E%22%5D%2B)%22%2F)%3F.%5B1%5D%3Bt%26%26(e=new%20URL(t))%7Dif(13%3Ee.search.length)return%3Bconst%20t=new%20URLSearchParams(e.search)%2Cr=%5B'url'%2C'destination'%2C'redirect'%2C'target'%2C'goto'%2C'u'%2C'dest'%2C'link'%2C'out'%5D.filter(e=%3Et.has(e)).map(e=%3E%7Bconst%20r=t.get(e)%3Breturn%20r%3FdecodeURIComponent(r):''%7D).find(e=%3Ee.match(%2F%5Ehttps%3F:%2F))%3Br%26%26window.location.replace(new%20URL(r))%7D)()%3Bvoid'2.0.2' "Setup unskim" [Setup x-man]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3B(()=%3E%7Bconst%20i=navigator.userAgent%2Cn=i.includes('Chrome%2F')%7C%7Ci.includes('Firefox%2F')%7C%7Ci.includes('Brave%2F')%7C%7Ci.includes('Edg%2F')%2Co=i.includes('Safari%2F')%2Ct=!n%26%26!o%2Ca=navigator.platform.startsWith('Mac')%26%26o%26%26!n%26%26!t%26%26!navigator.maxTouchPoints%26%262%3Enavigator.maxTouchPoints%2Ce=window.getSelection()%3Bif(!e)return%3Bconst%20l=e.toString().split('%5Cn')%5B0%5D.trim()%3Blet%20r='x-man-page:%2F%2F'%3Bconst%20c=l.match(%2F%5E(.%2A%3F)%5C((%5Cd)%5C)%24%2F)%3Bif(r%2B=c%3F'1'===c%5B2%5D%3Fc%5B1%5D:%60%24%7Bc%5B2%5D%7D%2F%24%7Bc%5B1%5D%7D%60:l%2Cl)%7Bif(confirm(%60Link%20for%20%22%24%7Bl%7D%22%20is:%20%24%7Br%7D%5Cn%5CnCopy%20to%20clipboard%3F%60))%7Blet%20i=''%3Bif(navigator.clipboard%3Fnavigator.clipboard.writeText(r):(i=%60Unable%20to%20copy%20%22%24%7Br%7D%22%20to%20clipboard.%60%2Cr='')%2Ca%26%26''!==r)%7Blet%20n=null%3Btry%7Bn=window.open(r)%2Cn%26%26(n.opener=null)%7Dcatch(n)%7Bi=%60Popup%20window%20blocked.%20Clipboard%20contains%20%22%24%7Br%7D%22%60%7Dfinally%7Bwindow.focus()%2Cnull!==n%26%26setTimeout(()=%3E%7Bn%26%26n.close()%7D%2C3333)%7D%7Delse''!==r%26%26(i=%60Unable%20to%20open%20new%20link%20with%20Terminal.%20Clipboard%20contains%20%22%24%7Br%7D%22%60)%3B''!==i%26%26alert(i)%7De.empty()%7D%7D)()%3Bvoid'1.3.1' "Setup x-man" diff --git a/bookmarklets.json b/bookmarklets.json index f3ae78c..93a3469 100644 --- a/bookmarklets.json +++ b/bookmarklets.json @@ -25,6 +25,16 @@ "file": "openinbrave.bookmarklet", "version": "1.1.2" }, + { + "name": "OpenInChrome", + "file": "openinchrome.bookmarklet", + "version": "1.0.0" + }, + { + "name": "OpenInEdge", + "file": "openinedge.bookmarklet", + "version": "1.0.0" + }, { "name": "OpenInFirefox", "file": "openinfirefox.bookmarklet", @@ -45,6 +55,16 @@ "file": "openingoodreader.bookmarklet", "version": "1.5.2" }, + { + "name": "OpenInOpera", + "file": "openinopera.bookmarklet", + "version": "1.0.0" + }, + { + "name": "OpenInOrion", + "file": "openinorion.bookmarklet", + "version": "1.0.0" + }, { "name": "OpenInTextastic", "file": "openintextastic.bookmarklet", diff --git a/dist/openinchrome.bookmarklet b/dist/openinchrome.bookmarklet new file mode 100644 index 0000000..b18542c --- /dev/null +++ b/dist/openinchrome.bookmarklet @@ -0,0 +1 @@ +javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=location.protocol%3Blocation.href=%60googlechrome%24%7B'https'===o%3F's':''%7D:%2F%2F%7Blocation.href.slice(proto.length%20%2B%202)%7D%60%7Dvoid'1.0.0' \ No newline at end of file diff --git a/dist/openinedge.bookmarklet b/dist/openinedge.bookmarklet new file mode 100644 index 0000000..a0cde22 --- /dev/null +++ b/dist/openinedge.bookmarklet @@ -0,0 +1 @@ +javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='microsoft-edge-'%2Blocation.href%3Bvoid'1.0.0' \ No newline at end of file diff --git a/dist/openinopera.bookmarklet b/dist/openinopera.bookmarklet new file mode 100644 index 0000000..990fba9 --- /dev/null +++ b/dist/openinopera.bookmarklet @@ -0,0 +1 @@ +javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='opera-'%2Blocation.href%3Bvoid'1.0.0' \ No newline at end of file diff --git a/dist/openinorion.bookmarklet b/dist/openinorion.bookmarklet new file mode 100644 index 0000000..0c252a1 --- /dev/null +++ b/dist/openinorion.bookmarklet @@ -0,0 +1 @@ +javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='orion:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.0.0' \ No newline at end of file diff --git a/src/openinchrome.ts b/src/openinchrome.ts new file mode 100644 index 0000000..77bef63 --- /dev/null +++ b/src/openinchrome.ts @@ -0,0 +1,7 @@ +// verify iOS UserAgent & then prefix URL with protocol for Google Chrome iOS app +// googlechrome://URL_WITHOUT_SCHEME or googlechromes://URL_WITHOUT_SCHEME + +if (/iP(ad|hone)/.test(navigator.userAgent)) { + const proto = location.protocol; + location.href = `googlechrome${proto === 'https' ? 's' : ''}://{location.href.slice(proto.length + 2)}`; +} diff --git a/src/openinedge.ts b/src/openinedge.ts new file mode 100644 index 0000000..815421b --- /dev/null +++ b/src/openinedge.ts @@ -0,0 +1,6 @@ +// verify iOS UserAgent & then prefix URL with protocol for Microsoft Edge iOS app +// microsoft-edge-http://URL_WITHOUT_SCHEME or microsoft-edge-https://URL_WITHOUT_SCHEME + +if (/iP(ad|hone)/.test(navigator.userAgent)) { + location.href = `microsoft-edge-${location.href}`; +} diff --git a/src/openinopera.ts b/src/openinopera.ts new file mode 100644 index 0000000..929b9da --- /dev/null +++ b/src/openinopera.ts @@ -0,0 +1,6 @@ +// verify iOS UserAgent & then prefix URL with protocol for Opera iOS app +// opera-http://URL_WITHOUT_SCHEME or opera-https://URL_WITHOUT_SCHEME + +if (/iP(ad|hone)/.test(navigator.userAgent)) { + location.href = `opera-${location.href}`; +} diff --git a/src/openinorion.ts b/src/openinorion.ts new file mode 100644 index 0000000..bd1e9d2 --- /dev/null +++ b/src/openinorion.ts @@ -0,0 +1,4 @@ +// verify iOS UserAgent & then prefix URL with protocol of Orion iOS app +if (/iP(ad|hone)/.test(navigator.userAgent)) { + location.href = `orion://open-url?url=${encodeURIComponent(location.href)}`; +} From 1cc17d55d549229d0b0896ec4c07e3c0874b9909 Mon Sep 17 00:00:00 2001 From: Tom King Date: Fri, 6 Feb 2026 18:47:24 -0800 Subject: [PATCH 07/18] chore: update config files & SECURITY.md --- .npmrc | 3 --- SECURITY.md | 10 +++++----- package-lock.json | 18 +++++++++--------- package.json | 4 ++-- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/.npmrc b/.npmrc index 58b115e..0532da6 100644 --- a/.npmrc +++ b/.npmrc @@ -18,9 +18,6 @@ audit-level=low # security: block automatic lifecycle scripts (preinstall, postinstall, & prepare) ignore-scripts=true -# prefer offline - use cache when possible for faster installs -prefer-offline=true - # disable fund messages for cleaner CI logs fund=false diff --git a/SECURITY.md b/SECURITY.md index 31a5a8c..07cbebc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,7 +7,7 @@ updates: | Version | Supported | | ------- | ------------------ | -| 4.1.x | :white_check_mark: | +| 4.2.x | :white_check_mark: | ## Reporting a Vulnerability @@ -64,8 +64,8 @@ npm version patch # or minor, or major git push --follow-tags # Or manually create and push a signed tag -git tag -s 4.1.1 -m "Release version 4.1.1" -git push origin 4.1.1 +git tag -s 4.2.0 -m "Release version 4.2.0" +git push origin 4.2.0 ``` ## Release Verification @@ -76,10 +76,10 @@ All releases should be signed with GPG/SSH signatures for verification: ```bash # Verify the signature on a release tag -git verify-tag 4.1.1 +git verify-tag 4.2.0 # Show tag details with signature -git tag -v 4.1.1 +git tag -v 4.2.0 ``` ### Verifying Signed Commits diff --git a/package-lock.json b/package-lock.json index 0eb5cc2..08c1459 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "OpenInlets", - "version": "4.1.1", + "version": "4.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "OpenInlets", - "version": "4.1.1", + "version": "4.2.0", "license": "MIT", "devDependencies": { "@eslint/js": "9.39.2", - "@types/node": "25.2.0", + "@types/node": "25.2.1", "@typescript-eslint/eslint-plugin": "8.54.0", "@typescript-eslint/parser": "8.54.0", "eslint": "9.39.2", @@ -343,9 +343,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.2.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.0.tgz", - "integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==", + "version": "25.2.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.1.tgz", + "integrity": "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==", "dev": true, "license": "MIT", "dependencies": { @@ -1441,9 +1441,9 @@ } }, "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { diff --git a/package.json b/package.json index 792a1a4..af7c207 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "description": "Project that makes Safari bookmarklets to automagically jump to the corresponding app and more.", "devDependencies": { "@eslint/js": "9.39.2", - "@types/node": "25.2.0", + "@types/node": "25.2.1", "@typescript-eslint/eslint-plugin": "8.54.0", "@typescript-eslint/parser": "8.54.0", "eslint": "9.39.2", @@ -59,5 +59,5 @@ "test": "npm run build && npm run verify-build", "verify-build": "node scripts/verify-build.js" }, - "version": "4.1.1" + "version": "4.2.0" } From e223582ce1c40d38fe78d5ae07b5cb2fe7ccaea9 Mon Sep 17 00:00:00 2001 From: Tom King Date: Fri, 6 Feb 2026 19:01:40 -0800 Subject: [PATCH 08/18] docs(README): fix trailing space on a line --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2a542f0..df9ef0a 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,8 @@ iPad, and Mac, and also works with Google Chrome on desktops. + __[OpenInChrome] v1.0.0__: Open the current web page in the Google Chrome app on iOS. -+ __[OpenInEdge] v1.0.0__: Open the current web page in the Microsoft Edge -app on iOS. ++ __[OpenInEdge] v1.0.0__: Open the current web page in the Microsoft Edge app +on iOS. + __[OpenInFirefox] v1.6.2__: Open the current web page in the Firefox app for iOS. From f73c3ff5b0f13b66adadcc9f33147ee53d34216d Mon Sep 17 00:00:00 2001 From: Tom King Date: Fri, 6 Feb 2026 19:31:54 -0800 Subject: [PATCH 09/18] fix: update scripts/utils.js for new security linting --- scripts/utils.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/utils.js b/scripts/utils.js index 0def41f..2bd4259 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -38,12 +38,10 @@ function readFileOrFail(filePath) { function validateBookmarklet(bookmarklet, index) { const required = ['name', 'file', 'version']; for (const field of required) { - // eslint-disable-next-line security/detect-object-injection if (!(field in bookmarklet) || bookmarklet[field] === null) { console.error(`Invalid config: bookmarklet at index ${index} missing required field '${field}'`); process.exit(1); } - // eslint-disable-next-line security/detect-object-injection const value = bookmarklet[field]; if (typeof value === 'string' && value.trim().length === 0) { console.error(`Invalid config: bookmarklet at index ${index} has empty string for field '${field}'`); From 9d84dbd7de323f75cc88cb21fd3defce8f9942fe Mon Sep 17 00:00:00 2001 From: Tom King Date: Fri, 6 Feb 2026 21:05:23 -0800 Subject: [PATCH 10/18] fix: fix OpenInChrome & OpenInOpera --- README.md | 8 ++++---- dist/openinchrome.bookmarklet | 2 +- dist/openinopera.bookmarklet | 2 +- src/openinchrome.ts | 2 +- src/openinopera.ts | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index df9ef0a..bb58872 100644 --- a/README.md +++ b/README.md @@ -369,12 +369,12 @@ repos I had; doesn't build yet [KillStickyHeaders]: javascript:Array.from(document.querySelectorAll('body%20%2A')).filter(e=%3E%5B'fixed'%2C'sticky'%5D.includes(getComputedStyle(e).position)).forEach(e=%3Ee.remove())%3Bvoid'2.1.0' "KillStickyHeaders" [Linklighter]: javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=encodeURIComponent%2Ce=window.getSelection()%3Bif(!e)return%3Bconst%20n=e.toString()%2Co=n.length%2Ci=document.URL%2Cs=i.indexOf('%23')%3Blet%20r=i%2Cc=''%2Cl=''%2Ca=''%3Bif(e.rangeCount%3E0%26%26n%26%26(document.body.textContent%7C%7C'').split(n).length-1%3E1)%7Bconst%20t=e.getRangeAt(0)%2Cn=t.commonAncestorContainer.textContent%7C%7C''%2C%7BstartOffset:o%2CendOffset:i%7D=t%2Cs=Math.max(0%2Co-20)%3Bif(l=n.substring(s%2Co).trim()%2Cs%3E0%26%26'%20'!==n.charAt(s-1))%7Bconst%20t=l.indexOf('%20')%3Bt%3E0%26%26(l=l.substring(t%2B1))%7Dconst%20r=Math.min(n.length%2Ci%2B20)%3Bif(a=n.substring(i%2Cr).trim()%2Cr%3Cn.length%26%26'%20'!==n.charAt(r))%7Bconst%20t=a.lastIndexOf('%20')%3Bt%3E0%26%26(a=a.substring(0%2Ct))%7D%7Dif(e.removeAllRanges()%2Cn%26%26''!==n)%7Bs%3E-1%26%26(r=r.substring(0%2Cs))%3Blet%20e=''%3Bif(l%26%26(e=t(l)%2B'-%2C')%2C80%3Eo)c=n%2Ce%2B=t(n)%3Belse%7Blet%20i=~~(o%2F2-2)%3Bo%3E150%3Fi=48:o%3E100%26%26(i=~~(o%2F3))%3Blet%20s=n.substring(0%2Ci)%2Cr=n.slice(o-i)%3Bconst%20l=s.lastIndexOf('%20')%3Bl%3Ei%2F2%26%26(s=s.substring(0%2Cl))%3Bconst%20a=r.indexOf('%20')%3Ba%3E-1%26%26i%2F2%3Ea%26%26(r=r.substring(a%2B1))%2Cc=s%2B'%E2%80%A6'%2Ce%2B=%60%24%7Bt(s)%7D%2C%24%7Bt(r)%7D%60%7Da%26%26(e%2B='%2C-'%2Bt(a))%2Cr%2B='%23:~:text='%2Be%2Cr=r.replace(%2F(%250A%7C%250D%7C%2509%7C%2520)%2B%24%2F%2C'')%2Cr=r.replace(%2F(%2520)%7B2%2C%7D%2Fg%2C'%2520')%2Cr=r.replace(%2F%23%23%2B:~:text=%2F%2C'%23:~:text=')%7Dif(r!==i%26%26confirm(%60Open%20URL%20with%20highlight%20on%20%22%24%7Bc%7D%22%20and%20copy%20URL%20to%20clipboard%3F%60))%7Bnavigator.clipboard.writeText(r).catch(()=%3E%7Balert('Could%20not%20copy%20to%20clipboard.%20URL%20is%20in%20the%20new%20tab.')%7D)%3Bconst%20t=window.open(r%2C'%5Fblank')%3Bt%3Ft.opener=null:alert('Popup%20blocked.%20URL%20copied%20to%20clipboard.')%7D%7D)()%3Bvoid'2.1.0' "Linklighter" [OpenInBrave]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='brave:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.1.2' "OpenInBrave" -[OpenInChrome]: javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=location.protocol%3Blocation.href=%60googlechrome%24%7B'https'===o%3F's':''%7D:%2F%2F%7Blocation.href.slice(proto.length%20%2B%202)%7D%60%7Dvoid'1.0.0' "OpenInChrome" +[OpenInChrome]: javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=location.protocol%3Blocation.href=%60googlechrome%24%7B'https'===o%3F's':''%7D:%2F%2F%24%7Blocation.href.slice(o.length%2B2)%7D%60%7Dvoid'1.0.0' "OpenInChrome" [OpenInEdge]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='microsoft-edge-'%2Blocation.href%3Bvoid'1.0.0' "OpenInEdge" [OpenInFirefox]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='firefox:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.6.2' "OpenInFirefox" [OpenInFirefox-Focus]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60firefox-focus:%2F%2Fopen-url%3Furl=%24%7BencodeURIComponent(location.href)%7D%26private=true%60%3Bvoid'1.1.2' "OpenInFirefox-Focus" [OpenInFirefox-Private]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60firefox:%2F%2Fopen-url%3Furl=%24%7BencodeURIComponent(location.href)%7D%26private=true%60%3Bvoid'1.1.2' "OpenInFirefox-Private" -[OpenInOpera]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='opera-'%2Blocation.href%3Bvoid'1.0.0' "OpenInOpera" +[OpenInOpera]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='touch-'%2Blocation.href%3Bvoid'1.0.0' "OpenInOpera" [OpenInOrion]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='orion:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.0.0' "OpenInOrion" [OpenInGoodReader]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent)%26%26%2F%5C.pdf(%24%7C%5C%3F)%2F.test(location.href))location.href='gr'%2Blocation.href%3Bvoid'1.5.2' "OpenInGoodReader" [OpenInTextastic]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=location.href.replace(%2F%5Ehttps%3F%2F%2C'textastic')%3Bvoid'1.1.2' "OpenInTextastic" @@ -392,13 +392,13 @@ repos I had; doesn't build yet [Setup KillStickyHeaders]: https://mobilemind.github.io/OpenInlets/x/#javascript:Array.from(document.querySelectorAll('body%20%2A')).filter(e=%3E%5B'fixed'%2C'sticky'%5D.includes(getComputedStyle(e).position)).forEach(e=%3Ee.remove())%3Bvoid'2.1.0' "Setup KillStickyHeaders" [Setup Linklighter]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=encodeURIComponent%2Ce=window.getSelection()%3Bif(!e)return%3Bconst%20n=e.toString()%2Co=n.length%2Ci=document.URL%2Cs=i.indexOf('%23')%3Blet%20r=i%2Cc=''%2Cl=''%2Ca=''%3Bif(e.rangeCount%3E0%26%26n%26%26(document.body.textContent%7C%7C'').split(n).length-1%3E1)%7Bconst%20t=e.getRangeAt(0)%2Cn=t.commonAncestorContainer.textContent%7C%7C''%2C%7BstartOffset:o%2CendOffset:i%7D=t%2Cs=Math.max(0%2Co-20)%3Bif(l=n.substring(s%2Co).trim()%2Cs%3E0%26%26'%20'!==n.charAt(s-1))%7Bconst%20t=l.indexOf('%20')%3Bt%3E0%26%26(l=l.substring(t%2B1))%7Dconst%20r=Math.min(n.length%2Ci%2B20)%3Bif(a=n.substring(i%2Cr).trim()%2Cr%3Cn.length%26%26'%20'!==n.charAt(r))%7Bconst%20t=a.lastIndexOf('%20')%3Bt%3E0%26%26(a=a.substring(0%2Ct))%7D%7Dif(e.removeAllRanges()%2Cn%26%26''!==n)%7Bs%3E-1%26%26(r=r.substring(0%2Cs))%3Blet%20e=''%3Bif(l%26%26(e=t(l)%2B'-%2C')%2C80%3Eo)c=n%2Ce%2B=t(n)%3Belse%7Blet%20i=~~(o%2F2-2)%3Bo%3E150%3Fi=48:o%3E100%26%26(i=~~(o%2F3))%3Blet%20s=n.substring(0%2Ci)%2Cr=n.slice(o-i)%3Bconst%20l=s.lastIndexOf('%20')%3Bl%3Ei%2F2%26%26(s=s.substring(0%2Cl))%3Bconst%20a=r.indexOf('%20')%3Ba%3E-1%26%26i%2F2%3Ea%26%26(r=r.substring(a%2B1))%2Cc=s%2B'%E2%80%A6'%2Ce%2B=%60%24%7Bt(s)%7D%2C%24%7Bt(r)%7D%60%7Da%26%26(e%2B='%2C-'%2Bt(a))%2Cr%2B='%23:~:text='%2Be%2Cr=r.replace(%2F(%250A%7C%250D%7C%2509%7C%2520)%2B%24%2F%2C'')%2Cr=r.replace(%2F(%2520)%7B2%2C%7D%2Fg%2C'%2520')%2Cr=r.replace(%2F%23%23%2B:~:text=%2F%2C'%23:~:text=')%7Dif(r!==i%26%26confirm(%60Open%20URL%20with%20highlight%20on%20%22%24%7Bc%7D%22%20and%20copy%20URL%20to%20clipboard%3F%60))%7Bnavigator.clipboard.writeText(r).catch(()=%3E%7Balert('Could%20not%20copy%20to%20clipboard.%20URL%20is%20in%20the%20new%20tab.')%7D)%3Bconst%20t=window.open(r%2C'%5Fblank')%3Bt%3Ft.opener=null:alert('Popup%20blocked.%20URL%20copied%20to%20clipboard.')%7D%7D)()%3Bvoid'2.1.0' "Setup Linklighter" [Setup OpenInBrave]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='brave:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.1.2' "Setup OpenInBrave" -[Setup OpenInChrome]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=location.protocol%3Blocation.href=%60googlechrome%24%7B'https'===o%3F's':''%7D:%2F%2F%7Blocation.href.slice(proto.length%20%2B%202)%7D%60%7Dvoid'1.0.0' "Setup OpenInChrome" +[Setup OpenInChrome]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=location.protocol%3Blocation.href=%60googlechrome%24%7B'https'===o%3F's':''%7D:%2F%2F%24%7Blocation.href.slice(o.length%2B2)%7D%60%7Dvoid'1.0.0' "Setup OpenInChrome" [Setup OpenInEdge]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='microsoft-edge-'%2Blocation.href%3Bvoid'1.0.0' "Setup OpenInEdge" [Setup OpenInFirefox]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='firefox:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.6.2' "Setup OpenInFirefox" [Setup OpenInFirefox-Focus]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60firefox-focus:%2F%2Fopen-url%3Furl=%24%7BencodeURIComponent(location.href)%7D%26private=true%60%3Bvoid'1.1.2' "Setup OpenInFirefox-Focus" [Setup OpenInFirefox-Private]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60firefox:%2F%2Fopen-url%3Furl=%24%7BencodeURIComponent(location.href)%7D%26private=true%60%3Bvoid'1.1.2' "Setup OpenInFirefox-Private" [Setup OpenInGoodReader]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent)%26%26%2F%5C.pdf(%24%7C%5C%3F)%2F.test(location.href))location.href='gr'%2Blocation.href%3Bvoid'1.5.2' "Setup OpenInGoodReader" -[Setup OpenInOpera]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='opera-'%2Blocation.href%3Bvoid'1.0.0' "Setup OpenInOpera" +[Setup OpenInOpera]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='touch-'%2Blocation.href%3Bvoid'1.0.0' "Setup OpenInOpera" [Setup OpenInOrion]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='orion:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.0.0' "Setup OpenInOrion" [Setup OpenInTextastic]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=location.href.replace(%2F%5Ehttps%3F%2F%2C'textastic')%3Bvoid'1.1.2' "Setup OpenInTextastic" [Setup OpenInWorkingCopy]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent)%26%26('bitbucket.org'===location.host%7C%7C'github.com'===location.host))location.href=%60working-copy:%2F%2Fshow%3Fremote=%24%7BencodeURIComponent(location.href.split('%2F').slice(0%2C5).join('%2F'))%7D.git%60%3Bvoid'1.6.2' "Setup OpenInWorkingCopy" diff --git a/dist/openinchrome.bookmarklet b/dist/openinchrome.bookmarklet index b18542c..ed174a7 100644 --- a/dist/openinchrome.bookmarklet +++ b/dist/openinchrome.bookmarklet @@ -1 +1 @@ -javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=location.protocol%3Blocation.href=%60googlechrome%24%7B'https'===o%3F's':''%7D:%2F%2F%7Blocation.href.slice(proto.length%20%2B%202)%7D%60%7Dvoid'1.0.0' \ No newline at end of file +javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=location.protocol%3Blocation.href=%60googlechrome%24%7B'https'===o%3F's':''%7D:%2F%2F%24%7Blocation.href.slice(o.length%2B2)%7D%60%7Dvoid'1.0.0' \ No newline at end of file diff --git a/dist/openinopera.bookmarklet b/dist/openinopera.bookmarklet index 990fba9..c16822e 100644 --- a/dist/openinopera.bookmarklet +++ b/dist/openinopera.bookmarklet @@ -1 +1 @@ -javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='opera-'%2Blocation.href%3Bvoid'1.0.0' \ No newline at end of file +javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='touch-'%2Blocation.href%3Bvoid'1.0.0' \ No newline at end of file diff --git a/src/openinchrome.ts b/src/openinchrome.ts index 77bef63..9c0d451 100644 --- a/src/openinchrome.ts +++ b/src/openinchrome.ts @@ -3,5 +3,5 @@ if (/iP(ad|hone)/.test(navigator.userAgent)) { const proto = location.protocol; - location.href = `googlechrome${proto === 'https' ? 's' : ''}://{location.href.slice(proto.length + 2)}`; + location.href = `googlechrome${proto === 'https' ? 's' : ''}://${location.href.slice(proto.length + 2)}`; } diff --git a/src/openinopera.ts b/src/openinopera.ts index 929b9da..4349a8e 100644 --- a/src/openinopera.ts +++ b/src/openinopera.ts @@ -1,6 +1,6 @@ // verify iOS UserAgent & then prefix URL with protocol for Opera iOS app -// opera-http://URL_WITHOUT_SCHEME or opera-https://URL_WITHOUT_SCHEME +// touch-http://URL_WITHOUT_SCHEME or touch-https://URL_WITHOUT_SCHEME if (/iP(ad|hone)/.test(navigator.userAgent)) { - location.href = `opera-${location.href}`; + location.href = `touch-${location.href}`; } From cacbb38e9b40627bf63e83ff59a5c05b39cd3454 Mon Sep 17 00:00:00 2001 From: Tom King Date: Fri, 6 Feb 2026 21:10:24 -0800 Subject: [PATCH 11/18] docs: correct OpenInOpera scheme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb58872..9484364 100644 --- a/README.md +++ b/README.md @@ -233,7 +233,7 @@ using a URL protocol scheme. target url for private browsing. + __OpenInGoodReader__ - Uses the `grhttp://` or `grhttps://` URL protocol scheme for GoodReader. See [GoodReader URL Scheme][GoodReader URL Scheme]. -+ __OpenInOpera__ - Uses the `opera-http://` or `opera-https://` scheme for the ++ __OpenInOpera__ - Uses the `touch-http://` or `touch-https://` scheme for the Opera app on iOS. + __OpenInOrion__ - Uses the `orion://open-url?url=` scheme for the Orion app on iOS. From 35a39655325ee969691e951f12cb0e2e6b05decc Mon Sep 17 00:00:00 2001 From: Tom King Date: Fri, 6 Feb 2026 22:09:13 -0800 Subject: [PATCH 12/18] feat: add OpenInDDG --- .cspell/dictionary-custom.txt | 1 + README.md | 15 +++++++++++++-- bookmarklets.json | 5 +++++ dist/openinddg.bookmarklet | 1 + src/openinddg.ts | 5 +++++ 5 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 dist/openinddg.bookmarklet create mode 100644 src/openinddg.ts diff --git a/.cspell/dictionary-custom.txt b/.cspell/dictionary-custom.txt index 3dc467a..9c448ab 100644 --- a/.cspell/dictionary-custom.txt +++ b/.cspell/dictionary-custom.txt @@ -2,6 +2,7 @@ Blogsy Bronto Codacy cyclonedx +DDG delighter Ghostery Klaviyo diff --git a/README.md b/README.md index 9484364..1f580be 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,9 @@ iPad, and Mac, and also works with Google Chrome on desktops. + __[OpenInChrome] v1.0.0__: Open the current web page in the Google Chrome app on iOS. ++ __[OpenInDDG] v1.0.0__: Open the current web page in the DuckDuckGo browser +app on iOS. + + __[OpenInEdge] v1.0.0__: Open the current web page in the Microsoft Edge app on iOS. @@ -132,6 +135,7 @@ followed link into a bookmark for JavaScript bookmarklet. + __Mobile Safari setup link__ -- [Setup Linklighter] v2.1.0 + __Mobile Safari setup link__ -- [Setup OpenInBrave] v1.1.2 + __Mobile Safari setup link__ -- [Setup OpenInChrome] v1.0.0 ++ __Mobile Safari setup link__ -- [Setup OpenInDDG] v1.0.0 + __Mobile Safari setup link__ -- [Setup OpenInEdge] v1.0.0 + __Mobile Safari setup link__ -- [Setup OpenInFirefox] v1.6.2 + __Mobile Safari setup link__ -- [Setup OpenInFirefox-Focus] v1.1.2 @@ -220,9 +224,10 @@ using a URL protocol scheme. on Apple platforms, and Google Chrome for desktops. + __OpenInBrave__ - Uses the `brave://open-url?url=` scheme for the Brave app on iOS. -+ __OpenInChrome__ - Uses the `googlechrome://` or `googlechromes://`scheme for ++ __OpenInChrome__ - Uses the `googlechrome://` or `googlechromes://` scheme for the Google Chrome app on iOS. - on iOS. ++ __OpenInDDG__ - Uses the `ddgQuickLink://` scheme for the DuckDuckGo browser + app on iOS. + __OpenInEdge__ - Uses the `microsoft-edge-http://` or `microsoft-edge-https://` scheme for the Google Chrome app on iOS. + __OpenInFirefox__ _and_ __OpenInFirefox-Private__ - Uses the @@ -261,6 +266,10 @@ using a URL protocol scheme. ## Version Notes +4.2.0 Major update to browser coverage with addition of OpenInChrome, OpenInDDG, + OpenInEdge, OpenInOpera, & OpenInOrion. Improvements to unskim, UtmStrip, + & OpenURLParam. Refinements build process with updated dependencies. + 4.1.1 Major update to UtmStrip with 26 new universal exact-match static keys, 4 new universal prefixes, 57 new host-specific keys/prefixes across 7 new hosts, and host-specific tracking & decluttering for Google Search. @@ -370,6 +379,7 @@ repos I had; doesn't build yet [Linklighter]: javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=encodeURIComponent%2Ce=window.getSelection()%3Bif(!e)return%3Bconst%20n=e.toString()%2Co=n.length%2Ci=document.URL%2Cs=i.indexOf('%23')%3Blet%20r=i%2Cc=''%2Cl=''%2Ca=''%3Bif(e.rangeCount%3E0%26%26n%26%26(document.body.textContent%7C%7C'').split(n).length-1%3E1)%7Bconst%20t=e.getRangeAt(0)%2Cn=t.commonAncestorContainer.textContent%7C%7C''%2C%7BstartOffset:o%2CendOffset:i%7D=t%2Cs=Math.max(0%2Co-20)%3Bif(l=n.substring(s%2Co).trim()%2Cs%3E0%26%26'%20'!==n.charAt(s-1))%7Bconst%20t=l.indexOf('%20')%3Bt%3E0%26%26(l=l.substring(t%2B1))%7Dconst%20r=Math.min(n.length%2Ci%2B20)%3Bif(a=n.substring(i%2Cr).trim()%2Cr%3Cn.length%26%26'%20'!==n.charAt(r))%7Bconst%20t=a.lastIndexOf('%20')%3Bt%3E0%26%26(a=a.substring(0%2Ct))%7D%7Dif(e.removeAllRanges()%2Cn%26%26''!==n)%7Bs%3E-1%26%26(r=r.substring(0%2Cs))%3Blet%20e=''%3Bif(l%26%26(e=t(l)%2B'-%2C')%2C80%3Eo)c=n%2Ce%2B=t(n)%3Belse%7Blet%20i=~~(o%2F2-2)%3Bo%3E150%3Fi=48:o%3E100%26%26(i=~~(o%2F3))%3Blet%20s=n.substring(0%2Ci)%2Cr=n.slice(o-i)%3Bconst%20l=s.lastIndexOf('%20')%3Bl%3Ei%2F2%26%26(s=s.substring(0%2Cl))%3Bconst%20a=r.indexOf('%20')%3Ba%3E-1%26%26i%2F2%3Ea%26%26(r=r.substring(a%2B1))%2Cc=s%2B'%E2%80%A6'%2Ce%2B=%60%24%7Bt(s)%7D%2C%24%7Bt(r)%7D%60%7Da%26%26(e%2B='%2C-'%2Bt(a))%2Cr%2B='%23:~:text='%2Be%2Cr=r.replace(%2F(%250A%7C%250D%7C%2509%7C%2520)%2B%24%2F%2C'')%2Cr=r.replace(%2F(%2520)%7B2%2C%7D%2Fg%2C'%2520')%2Cr=r.replace(%2F%23%23%2B:~:text=%2F%2C'%23:~:text=')%7Dif(r!==i%26%26confirm(%60Open%20URL%20with%20highlight%20on%20%22%24%7Bc%7D%22%20and%20copy%20URL%20to%20clipboard%3F%60))%7Bnavigator.clipboard.writeText(r).catch(()=%3E%7Balert('Could%20not%20copy%20to%20clipboard.%20URL%20is%20in%20the%20new%20tab.')%7D)%3Bconst%20t=window.open(r%2C'%5Fblank')%3Bt%3Ft.opener=null:alert('Popup%20blocked.%20URL%20copied%20to%20clipboard.')%7D%7D)()%3Bvoid'2.1.0' "Linklighter" [OpenInBrave]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='brave:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.1.2' "OpenInBrave" [OpenInChrome]: javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=location.protocol%3Blocation.href=%60googlechrome%24%7B'https'===o%3F's':''%7D:%2F%2F%24%7Blocation.href.slice(o.length%2B2)%7D%60%7Dvoid'1.0.0' "OpenInChrome" +[OpenInDDG]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='ddgQuickLink:%2F%2F'%2Blocation.href.slice(location.protocol.length%2B2)%3Bvoid'1.0.0' "OpenInDDG" [OpenInEdge]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='microsoft-edge-'%2Blocation.href%3Bvoid'1.0.0' "OpenInEdge" [OpenInFirefox]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='firefox:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.6.2' "OpenInFirefox" [OpenInFirefox-Focus]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60firefox-focus:%2F%2Fopen-url%3Furl=%24%7BencodeURIComponent(location.href)%7D%26private=true%60%3Bvoid'1.1.2' "OpenInFirefox-Focus" @@ -393,6 +403,7 @@ repos I had; doesn't build yet [Setup Linklighter]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=encodeURIComponent%2Ce=window.getSelection()%3Bif(!e)return%3Bconst%20n=e.toString()%2Co=n.length%2Ci=document.URL%2Cs=i.indexOf('%23')%3Blet%20r=i%2Cc=''%2Cl=''%2Ca=''%3Bif(e.rangeCount%3E0%26%26n%26%26(document.body.textContent%7C%7C'').split(n).length-1%3E1)%7Bconst%20t=e.getRangeAt(0)%2Cn=t.commonAncestorContainer.textContent%7C%7C''%2C%7BstartOffset:o%2CendOffset:i%7D=t%2Cs=Math.max(0%2Co-20)%3Bif(l=n.substring(s%2Co).trim()%2Cs%3E0%26%26'%20'!==n.charAt(s-1))%7Bconst%20t=l.indexOf('%20')%3Bt%3E0%26%26(l=l.substring(t%2B1))%7Dconst%20r=Math.min(n.length%2Ci%2B20)%3Bif(a=n.substring(i%2Cr).trim()%2Cr%3Cn.length%26%26'%20'!==n.charAt(r))%7Bconst%20t=a.lastIndexOf('%20')%3Bt%3E0%26%26(a=a.substring(0%2Ct))%7D%7Dif(e.removeAllRanges()%2Cn%26%26''!==n)%7Bs%3E-1%26%26(r=r.substring(0%2Cs))%3Blet%20e=''%3Bif(l%26%26(e=t(l)%2B'-%2C')%2C80%3Eo)c=n%2Ce%2B=t(n)%3Belse%7Blet%20i=~~(o%2F2-2)%3Bo%3E150%3Fi=48:o%3E100%26%26(i=~~(o%2F3))%3Blet%20s=n.substring(0%2Ci)%2Cr=n.slice(o-i)%3Bconst%20l=s.lastIndexOf('%20')%3Bl%3Ei%2F2%26%26(s=s.substring(0%2Cl))%3Bconst%20a=r.indexOf('%20')%3Ba%3E-1%26%26i%2F2%3Ea%26%26(r=r.substring(a%2B1))%2Cc=s%2B'%E2%80%A6'%2Ce%2B=%60%24%7Bt(s)%7D%2C%24%7Bt(r)%7D%60%7Da%26%26(e%2B='%2C-'%2Bt(a))%2Cr%2B='%23:~:text='%2Be%2Cr=r.replace(%2F(%250A%7C%250D%7C%2509%7C%2520)%2B%24%2F%2C'')%2Cr=r.replace(%2F(%2520)%7B2%2C%7D%2Fg%2C'%2520')%2Cr=r.replace(%2F%23%23%2B:~:text=%2F%2C'%23:~:text=')%7Dif(r!==i%26%26confirm(%60Open%20URL%20with%20highlight%20on%20%22%24%7Bc%7D%22%20and%20copy%20URL%20to%20clipboard%3F%60))%7Bnavigator.clipboard.writeText(r).catch(()=%3E%7Balert('Could%20not%20copy%20to%20clipboard.%20URL%20is%20in%20the%20new%20tab.')%7D)%3Bconst%20t=window.open(r%2C'%5Fblank')%3Bt%3Ft.opener=null:alert('Popup%20blocked.%20URL%20copied%20to%20clipboard.')%7D%7D)()%3Bvoid'2.1.0' "Setup Linklighter" [Setup OpenInBrave]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='brave:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.1.2' "Setup OpenInBrave" [Setup OpenInChrome]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=location.protocol%3Blocation.href=%60googlechrome%24%7B'https'===o%3F's':''%7D:%2F%2F%24%7Blocation.href.slice(o.length%2B2)%7D%60%7Dvoid'1.0.0' "Setup OpenInChrome" +[Setup OpenInDDG]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='ddgQuickLink:%2F%2F'%2Blocation.href.slice(location.protocol.length%2B2)%3Bvoid'1.0.0' "Setup OpenInDDG" [Setup OpenInEdge]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='microsoft-edge-'%2Blocation.href%3Bvoid'1.0.0' "Setup OpenInEdge" [Setup OpenInFirefox]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='firefox:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.6.2' "Setup OpenInFirefox" [Setup OpenInFirefox-Focus]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60firefox-focus:%2F%2Fopen-url%3Furl=%24%7BencodeURIComponent(location.href)%7D%26private=true%60%3Bvoid'1.1.2' "Setup OpenInFirefox-Focus" diff --git a/bookmarklets.json b/bookmarklets.json index 93a3469..ee6e5e9 100644 --- a/bookmarklets.json +++ b/bookmarklets.json @@ -30,6 +30,11 @@ "file": "openinchrome.bookmarklet", "version": "1.0.0" }, + { + "name": "OpenInDDG", + "file": "openinddg.bookmarklet", + "version": "1.0.0" + }, { "name": "OpenInEdge", "file": "openinedge.bookmarklet", diff --git a/dist/openinddg.bookmarklet b/dist/openinddg.bookmarklet new file mode 100644 index 0000000..cade48f --- /dev/null +++ b/dist/openinddg.bookmarklet @@ -0,0 +1 @@ +javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='ddgQuickLink:%2F%2F'%2Blocation.href.slice(location.protocol.length%2B2)%3Bvoid'1.0.0' \ No newline at end of file diff --git a/src/openinddg.ts b/src/openinddg.ts new file mode 100644 index 0000000..334f176 --- /dev/null +++ b/src/openinddg.ts @@ -0,0 +1,5 @@ +// verify iOS UserAgent & then prefix URL with protocol for DuckDuckGo Browser + +if (/iP(ad|hone)/.test(navigator.userAgent)) { + location.href = `ddgQuickLink://${location.href.slice(location.protocol.length + 2)}`; +} From bf341d93de2f7c07261b9f79e87fb848a6e57f6f Mon Sep 17 00:00:00 2001 From: Tom King Date: Fri, 6 Feb 2026 22:19:59 -0800 Subject: [PATCH 13/18] fix: address Copilot concerns w/ OpenInChrome & OpenInDDG --- README.md | 8 ++++---- dist/openinchrome.bookmarklet | 2 +- dist/openinddg.bookmarklet | 2 +- src/openinchrome.ts | 4 ++-- src/openinddg.ts | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 1f580be..3c7480b 100644 --- a/README.md +++ b/README.md @@ -378,8 +378,8 @@ repos I had; doesn't build yet [KillStickyHeaders]: javascript:Array.from(document.querySelectorAll('body%20%2A')).filter(e=%3E%5B'fixed'%2C'sticky'%5D.includes(getComputedStyle(e).position)).forEach(e=%3Ee.remove())%3Bvoid'2.1.0' "KillStickyHeaders" [Linklighter]: javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=encodeURIComponent%2Ce=window.getSelection()%3Bif(!e)return%3Bconst%20n=e.toString()%2Co=n.length%2Ci=document.URL%2Cs=i.indexOf('%23')%3Blet%20r=i%2Cc=''%2Cl=''%2Ca=''%3Bif(e.rangeCount%3E0%26%26n%26%26(document.body.textContent%7C%7C'').split(n).length-1%3E1)%7Bconst%20t=e.getRangeAt(0)%2Cn=t.commonAncestorContainer.textContent%7C%7C''%2C%7BstartOffset:o%2CendOffset:i%7D=t%2Cs=Math.max(0%2Co-20)%3Bif(l=n.substring(s%2Co).trim()%2Cs%3E0%26%26'%20'!==n.charAt(s-1))%7Bconst%20t=l.indexOf('%20')%3Bt%3E0%26%26(l=l.substring(t%2B1))%7Dconst%20r=Math.min(n.length%2Ci%2B20)%3Bif(a=n.substring(i%2Cr).trim()%2Cr%3Cn.length%26%26'%20'!==n.charAt(r))%7Bconst%20t=a.lastIndexOf('%20')%3Bt%3E0%26%26(a=a.substring(0%2Ct))%7D%7Dif(e.removeAllRanges()%2Cn%26%26''!==n)%7Bs%3E-1%26%26(r=r.substring(0%2Cs))%3Blet%20e=''%3Bif(l%26%26(e=t(l)%2B'-%2C')%2C80%3Eo)c=n%2Ce%2B=t(n)%3Belse%7Blet%20i=~~(o%2F2-2)%3Bo%3E150%3Fi=48:o%3E100%26%26(i=~~(o%2F3))%3Blet%20s=n.substring(0%2Ci)%2Cr=n.slice(o-i)%3Bconst%20l=s.lastIndexOf('%20')%3Bl%3Ei%2F2%26%26(s=s.substring(0%2Cl))%3Bconst%20a=r.indexOf('%20')%3Ba%3E-1%26%26i%2F2%3Ea%26%26(r=r.substring(a%2B1))%2Cc=s%2B'%E2%80%A6'%2Ce%2B=%60%24%7Bt(s)%7D%2C%24%7Bt(r)%7D%60%7Da%26%26(e%2B='%2C-'%2Bt(a))%2Cr%2B='%23:~:text='%2Be%2Cr=r.replace(%2F(%250A%7C%250D%7C%2509%7C%2520)%2B%24%2F%2C'')%2Cr=r.replace(%2F(%2520)%7B2%2C%7D%2Fg%2C'%2520')%2Cr=r.replace(%2F%23%23%2B:~:text=%2F%2C'%23:~:text=')%7Dif(r!==i%26%26confirm(%60Open%20URL%20with%20highlight%20on%20%22%24%7Bc%7D%22%20and%20copy%20URL%20to%20clipboard%3F%60))%7Bnavigator.clipboard.writeText(r).catch(()=%3E%7Balert('Could%20not%20copy%20to%20clipboard.%20URL%20is%20in%20the%20new%20tab.')%7D)%3Bconst%20t=window.open(r%2C'%5Fblank')%3Bt%3Ft.opener=null:alert('Popup%20blocked.%20URL%20copied%20to%20clipboard.')%7D%7D)()%3Bvoid'2.1.0' "Linklighter" [OpenInBrave]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='brave:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.1.2' "OpenInBrave" -[OpenInChrome]: javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=location.protocol%3Blocation.href=%60googlechrome%24%7B'https'===o%3F's':''%7D:%2F%2F%24%7Blocation.href.slice(o.length%2B2)%7D%60%7Dvoid'1.0.0' "OpenInChrome" -[OpenInDDG]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='ddgQuickLink:%2F%2F'%2Blocation.href.slice(location.protocol.length%2B2)%3Bvoid'1.0.0' "OpenInDDG" +[OpenInChrome]: javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=%60googlechrome%24%7B'https:'===location.protocol%3F's':''%7D:%2F%2F%60%3Blocation.href=%60%24%7Bo%7D%24%7Blocation.host%7D%24%7Blocation.pathname%7D%24%7Blocation.search%7D%24%7Blocation.hash%7D%60%7Dvoid'1.0.0' "OpenInChrome" +[OpenInDDG]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60%24ddgQuickLink:%2F%2F%24%7Blocation.host%7D%24%7Blocation.pathname%7D%24%7Blocation.search%7D%24%7Blocation.hash%7D%60%3Bvoid'1.0.0' "OpenInDDG" [OpenInEdge]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='microsoft-edge-'%2Blocation.href%3Bvoid'1.0.0' "OpenInEdge" [OpenInFirefox]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='firefox:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.6.2' "OpenInFirefox" [OpenInFirefox-Focus]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60firefox-focus:%2F%2Fopen-url%3Furl=%24%7BencodeURIComponent(location.href)%7D%26private=true%60%3Bvoid'1.1.2' "OpenInFirefox-Focus" @@ -402,8 +402,8 @@ repos I had; doesn't build yet [Setup KillStickyHeaders]: https://mobilemind.github.io/OpenInlets/x/#javascript:Array.from(document.querySelectorAll('body%20%2A')).filter(e=%3E%5B'fixed'%2C'sticky'%5D.includes(getComputedStyle(e).position)).forEach(e=%3Ee.remove())%3Bvoid'2.1.0' "Setup KillStickyHeaders" [Setup Linklighter]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=encodeURIComponent%2Ce=window.getSelection()%3Bif(!e)return%3Bconst%20n=e.toString()%2Co=n.length%2Ci=document.URL%2Cs=i.indexOf('%23')%3Blet%20r=i%2Cc=''%2Cl=''%2Ca=''%3Bif(e.rangeCount%3E0%26%26n%26%26(document.body.textContent%7C%7C'').split(n).length-1%3E1)%7Bconst%20t=e.getRangeAt(0)%2Cn=t.commonAncestorContainer.textContent%7C%7C''%2C%7BstartOffset:o%2CendOffset:i%7D=t%2Cs=Math.max(0%2Co-20)%3Bif(l=n.substring(s%2Co).trim()%2Cs%3E0%26%26'%20'!==n.charAt(s-1))%7Bconst%20t=l.indexOf('%20')%3Bt%3E0%26%26(l=l.substring(t%2B1))%7Dconst%20r=Math.min(n.length%2Ci%2B20)%3Bif(a=n.substring(i%2Cr).trim()%2Cr%3Cn.length%26%26'%20'!==n.charAt(r))%7Bconst%20t=a.lastIndexOf('%20')%3Bt%3E0%26%26(a=a.substring(0%2Ct))%7D%7Dif(e.removeAllRanges()%2Cn%26%26''!==n)%7Bs%3E-1%26%26(r=r.substring(0%2Cs))%3Blet%20e=''%3Bif(l%26%26(e=t(l)%2B'-%2C')%2C80%3Eo)c=n%2Ce%2B=t(n)%3Belse%7Blet%20i=~~(o%2F2-2)%3Bo%3E150%3Fi=48:o%3E100%26%26(i=~~(o%2F3))%3Blet%20s=n.substring(0%2Ci)%2Cr=n.slice(o-i)%3Bconst%20l=s.lastIndexOf('%20')%3Bl%3Ei%2F2%26%26(s=s.substring(0%2Cl))%3Bconst%20a=r.indexOf('%20')%3Ba%3E-1%26%26i%2F2%3Ea%26%26(r=r.substring(a%2B1))%2Cc=s%2B'%E2%80%A6'%2Ce%2B=%60%24%7Bt(s)%7D%2C%24%7Bt(r)%7D%60%7Da%26%26(e%2B='%2C-'%2Bt(a))%2Cr%2B='%23:~:text='%2Be%2Cr=r.replace(%2F(%250A%7C%250D%7C%2509%7C%2520)%2B%24%2F%2C'')%2Cr=r.replace(%2F(%2520)%7B2%2C%7D%2Fg%2C'%2520')%2Cr=r.replace(%2F%23%23%2B:~:text=%2F%2C'%23:~:text=')%7Dif(r!==i%26%26confirm(%60Open%20URL%20with%20highlight%20on%20%22%24%7Bc%7D%22%20and%20copy%20URL%20to%20clipboard%3F%60))%7Bnavigator.clipboard.writeText(r).catch(()=%3E%7Balert('Could%20not%20copy%20to%20clipboard.%20URL%20is%20in%20the%20new%20tab.')%7D)%3Bconst%20t=window.open(r%2C'%5Fblank')%3Bt%3Ft.opener=null:alert('Popup%20blocked.%20URL%20copied%20to%20clipboard.')%7D%7D)()%3Bvoid'2.1.0' "Setup Linklighter" [Setup OpenInBrave]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='brave:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.1.2' "Setup OpenInBrave" -[Setup OpenInChrome]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=location.protocol%3Blocation.href=%60googlechrome%24%7B'https'===o%3F's':''%7D:%2F%2F%24%7Blocation.href.slice(o.length%2B2)%7D%60%7Dvoid'1.0.0' "Setup OpenInChrome" -[Setup OpenInDDG]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='ddgQuickLink:%2F%2F'%2Blocation.href.slice(location.protocol.length%2B2)%3Bvoid'1.0.0' "Setup OpenInDDG" +[Setup OpenInChrome]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=%60googlechrome%24%7B'https:'===location.protocol%3F's':''%7D:%2F%2F%60%3Blocation.href=%60%24%7Bo%7D%24%7Blocation.host%7D%24%7Blocation.pathname%7D%24%7Blocation.search%7D%24%7Blocation.hash%7D%60%7Dvoid'1.0.0' "Setup OpenInChrome" +[Setup OpenInDDG]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60%24ddgQuickLink:%2F%2F%24%7Blocation.host%7D%24%7Blocation.pathname%7D%24%7Blocation.search%7D%24%7Blocation.hash%7D%60%3Bvoid'1.0.0' "Setup OpenInDDG" [Setup OpenInEdge]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='microsoft-edge-'%2Blocation.href%3Bvoid'1.0.0' "Setup OpenInEdge" [Setup OpenInFirefox]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='firefox:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.6.2' "Setup OpenInFirefox" [Setup OpenInFirefox-Focus]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60firefox-focus:%2F%2Fopen-url%3Furl=%24%7BencodeURIComponent(location.href)%7D%26private=true%60%3Bvoid'1.1.2' "Setup OpenInFirefox-Focus" diff --git a/dist/openinchrome.bookmarklet b/dist/openinchrome.bookmarklet index ed174a7..16f4c53 100644 --- a/dist/openinchrome.bookmarklet +++ b/dist/openinchrome.bookmarklet @@ -1 +1 @@ -javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=location.protocol%3Blocation.href=%60googlechrome%24%7B'https'===o%3F's':''%7D:%2F%2F%24%7Blocation.href.slice(o.length%2B2)%7D%60%7Dvoid'1.0.0' \ No newline at end of file +javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=%60googlechrome%24%7B'https:'===location.protocol%3F's':''%7D:%2F%2F%60%3Blocation.href=%60%24%7Bo%7D%24%7Blocation.host%7D%24%7Blocation.pathname%7D%24%7Blocation.search%7D%24%7Blocation.hash%7D%60%7Dvoid'1.0.0' \ No newline at end of file diff --git a/dist/openinddg.bookmarklet b/dist/openinddg.bookmarklet index cade48f..c958172 100644 --- a/dist/openinddg.bookmarklet +++ b/dist/openinddg.bookmarklet @@ -1 +1 @@ -javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='ddgQuickLink:%2F%2F'%2Blocation.href.slice(location.protocol.length%2B2)%3Bvoid'1.0.0' \ No newline at end of file +javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60%24ddgQuickLink:%2F%2F%24%7Blocation.host%7D%24%7Blocation.pathname%7D%24%7Blocation.search%7D%24%7Blocation.hash%7D%60%3Bvoid'1.0.0' \ No newline at end of file diff --git a/src/openinchrome.ts b/src/openinchrome.ts index 9c0d451..a36d0b3 100644 --- a/src/openinchrome.ts +++ b/src/openinchrome.ts @@ -2,6 +2,6 @@ // googlechrome://URL_WITHOUT_SCHEME or googlechromes://URL_WITHOUT_SCHEME if (/iP(ad|hone)/.test(navigator.userAgent)) { - const proto = location.protocol; - location.href = `googlechrome${proto === 'https' ? 's' : ''}://${location.href.slice(proto.length + 2)}`; + const chromeScheme = `googlechrome${location.protocol === 'https:' ? 's' : ''}://`; + location.href = `${chromeScheme}${location.host}${location.pathname}${location.search}${location.hash}`; } diff --git a/src/openinddg.ts b/src/openinddg.ts index 334f176..915b573 100644 --- a/src/openinddg.ts +++ b/src/openinddg.ts @@ -1,5 +1,5 @@ // verify iOS UserAgent & then prefix URL with protocol for DuckDuckGo Browser if (/iP(ad|hone)/.test(navigator.userAgent)) { - location.href = `ddgQuickLink://${location.href.slice(location.protocol.length + 2)}`; + location.href = `$ddgQuickLink://${location.host}${location.pathname}${location.search}${location.hash}`; } From e0cc0be18938a01248e192c696070ecf960fe290 Mon Sep 17 00:00:00 2001 From: Tom King Date: Fri, 6 Feb 2026 23:02:45 -0800 Subject: [PATCH 14/18] fix(openinddg): correct typo ("$") --- README.md | 4 ++-- dist/openinddg.bookmarklet | 2 +- src/openinddg.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3c7480b..bc32ea6 100644 --- a/README.md +++ b/README.md @@ -379,7 +379,7 @@ repos I had; doesn't build yet [Linklighter]: javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=encodeURIComponent%2Ce=window.getSelection()%3Bif(!e)return%3Bconst%20n=e.toString()%2Co=n.length%2Ci=document.URL%2Cs=i.indexOf('%23')%3Blet%20r=i%2Cc=''%2Cl=''%2Ca=''%3Bif(e.rangeCount%3E0%26%26n%26%26(document.body.textContent%7C%7C'').split(n).length-1%3E1)%7Bconst%20t=e.getRangeAt(0)%2Cn=t.commonAncestorContainer.textContent%7C%7C''%2C%7BstartOffset:o%2CendOffset:i%7D=t%2Cs=Math.max(0%2Co-20)%3Bif(l=n.substring(s%2Co).trim()%2Cs%3E0%26%26'%20'!==n.charAt(s-1))%7Bconst%20t=l.indexOf('%20')%3Bt%3E0%26%26(l=l.substring(t%2B1))%7Dconst%20r=Math.min(n.length%2Ci%2B20)%3Bif(a=n.substring(i%2Cr).trim()%2Cr%3Cn.length%26%26'%20'!==n.charAt(r))%7Bconst%20t=a.lastIndexOf('%20')%3Bt%3E0%26%26(a=a.substring(0%2Ct))%7D%7Dif(e.removeAllRanges()%2Cn%26%26''!==n)%7Bs%3E-1%26%26(r=r.substring(0%2Cs))%3Blet%20e=''%3Bif(l%26%26(e=t(l)%2B'-%2C')%2C80%3Eo)c=n%2Ce%2B=t(n)%3Belse%7Blet%20i=~~(o%2F2-2)%3Bo%3E150%3Fi=48:o%3E100%26%26(i=~~(o%2F3))%3Blet%20s=n.substring(0%2Ci)%2Cr=n.slice(o-i)%3Bconst%20l=s.lastIndexOf('%20')%3Bl%3Ei%2F2%26%26(s=s.substring(0%2Cl))%3Bconst%20a=r.indexOf('%20')%3Ba%3E-1%26%26i%2F2%3Ea%26%26(r=r.substring(a%2B1))%2Cc=s%2B'%E2%80%A6'%2Ce%2B=%60%24%7Bt(s)%7D%2C%24%7Bt(r)%7D%60%7Da%26%26(e%2B='%2C-'%2Bt(a))%2Cr%2B='%23:~:text='%2Be%2Cr=r.replace(%2F(%250A%7C%250D%7C%2509%7C%2520)%2B%24%2F%2C'')%2Cr=r.replace(%2F(%2520)%7B2%2C%7D%2Fg%2C'%2520')%2Cr=r.replace(%2F%23%23%2B:~:text=%2F%2C'%23:~:text=')%7Dif(r!==i%26%26confirm(%60Open%20URL%20with%20highlight%20on%20%22%24%7Bc%7D%22%20and%20copy%20URL%20to%20clipboard%3F%60))%7Bnavigator.clipboard.writeText(r).catch(()=%3E%7Balert('Could%20not%20copy%20to%20clipboard.%20URL%20is%20in%20the%20new%20tab.')%7D)%3Bconst%20t=window.open(r%2C'%5Fblank')%3Bt%3Ft.opener=null:alert('Popup%20blocked.%20URL%20copied%20to%20clipboard.')%7D%7D)()%3Bvoid'2.1.0' "Linklighter" [OpenInBrave]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='brave:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.1.2' "OpenInBrave" [OpenInChrome]: javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=%60googlechrome%24%7B'https:'===location.protocol%3F's':''%7D:%2F%2F%60%3Blocation.href=%60%24%7Bo%7D%24%7Blocation.host%7D%24%7Blocation.pathname%7D%24%7Blocation.search%7D%24%7Blocation.hash%7D%60%7Dvoid'1.0.0' "OpenInChrome" -[OpenInDDG]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60%24ddgQuickLink:%2F%2F%24%7Blocation.host%7D%24%7Blocation.pathname%7D%24%7Blocation.search%7D%24%7Blocation.hash%7D%60%3Bvoid'1.0.0' "OpenInDDG" +[OpenInDDG]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60ddgQuickLink:%2F%2F%24%7Blocation.host%7D%24%7Blocation.pathname%7D%24%7Blocation.search%7D%24%7Blocation.hash%7D%60%3Bvoid'1.0.0' "OpenInDDG" [OpenInEdge]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='microsoft-edge-'%2Blocation.href%3Bvoid'1.0.0' "OpenInEdge" [OpenInFirefox]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='firefox:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.6.2' "OpenInFirefox" [OpenInFirefox-Focus]: javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60firefox-focus:%2F%2Fopen-url%3Furl=%24%7BencodeURIComponent(location.href)%7D%26private=true%60%3Bvoid'1.1.2' "OpenInFirefox-Focus" @@ -403,7 +403,7 @@ repos I had; doesn't build yet [Setup Linklighter]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3B(()=%3E%7Bconst%20t=encodeURIComponent%2Ce=window.getSelection()%3Bif(!e)return%3Bconst%20n=e.toString()%2Co=n.length%2Ci=document.URL%2Cs=i.indexOf('%23')%3Blet%20r=i%2Cc=''%2Cl=''%2Ca=''%3Bif(e.rangeCount%3E0%26%26n%26%26(document.body.textContent%7C%7C'').split(n).length-1%3E1)%7Bconst%20t=e.getRangeAt(0)%2Cn=t.commonAncestorContainer.textContent%7C%7C''%2C%7BstartOffset:o%2CendOffset:i%7D=t%2Cs=Math.max(0%2Co-20)%3Bif(l=n.substring(s%2Co).trim()%2Cs%3E0%26%26'%20'!==n.charAt(s-1))%7Bconst%20t=l.indexOf('%20')%3Bt%3E0%26%26(l=l.substring(t%2B1))%7Dconst%20r=Math.min(n.length%2Ci%2B20)%3Bif(a=n.substring(i%2Cr).trim()%2Cr%3Cn.length%26%26'%20'!==n.charAt(r))%7Bconst%20t=a.lastIndexOf('%20')%3Bt%3E0%26%26(a=a.substring(0%2Ct))%7D%7Dif(e.removeAllRanges()%2Cn%26%26''!==n)%7Bs%3E-1%26%26(r=r.substring(0%2Cs))%3Blet%20e=''%3Bif(l%26%26(e=t(l)%2B'-%2C')%2C80%3Eo)c=n%2Ce%2B=t(n)%3Belse%7Blet%20i=~~(o%2F2-2)%3Bo%3E150%3Fi=48:o%3E100%26%26(i=~~(o%2F3))%3Blet%20s=n.substring(0%2Ci)%2Cr=n.slice(o-i)%3Bconst%20l=s.lastIndexOf('%20')%3Bl%3Ei%2F2%26%26(s=s.substring(0%2Cl))%3Bconst%20a=r.indexOf('%20')%3Ba%3E-1%26%26i%2F2%3Ea%26%26(r=r.substring(a%2B1))%2Cc=s%2B'%E2%80%A6'%2Ce%2B=%60%24%7Bt(s)%7D%2C%24%7Bt(r)%7D%60%7Da%26%26(e%2B='%2C-'%2Bt(a))%2Cr%2B='%23:~:text='%2Be%2Cr=r.replace(%2F(%250A%7C%250D%7C%2509%7C%2520)%2B%24%2F%2C'')%2Cr=r.replace(%2F(%2520)%7B2%2C%7D%2Fg%2C'%2520')%2Cr=r.replace(%2F%23%23%2B:~:text=%2F%2C'%23:~:text=')%7Dif(r!==i%26%26confirm(%60Open%20URL%20with%20highlight%20on%20%22%24%7Bc%7D%22%20and%20copy%20URL%20to%20clipboard%3F%60))%7Bnavigator.clipboard.writeText(r).catch(()=%3E%7Balert('Could%20not%20copy%20to%20clipboard.%20URL%20is%20in%20the%20new%20tab.')%7D)%3Bconst%20t=window.open(r%2C'%5Fblank')%3Bt%3Ft.opener=null:alert('Popup%20blocked.%20URL%20copied%20to%20clipboard.')%7D%7D)()%3Bvoid'2.1.0' "Setup Linklighter" [Setup OpenInBrave]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='brave:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.1.2' "Setup OpenInBrave" [Setup OpenInChrome]: https://mobilemind.github.io/OpenInlets/x/#javascript:'use%20strict'%3Bif(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))%7Bconst%20o=%60googlechrome%24%7B'https:'===location.protocol%3F's':''%7D:%2F%2F%60%3Blocation.href=%60%24%7Bo%7D%24%7Blocation.host%7D%24%7Blocation.pathname%7D%24%7Blocation.search%7D%24%7Blocation.hash%7D%60%7Dvoid'1.0.0' "Setup OpenInChrome" -[Setup OpenInDDG]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60%24ddgQuickLink:%2F%2F%24%7Blocation.host%7D%24%7Blocation.pathname%7D%24%7Blocation.search%7D%24%7Blocation.hash%7D%60%3Bvoid'1.0.0' "Setup OpenInDDG" +[Setup OpenInDDG]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60ddgQuickLink:%2F%2F%24%7Blocation.host%7D%24%7Blocation.pathname%7D%24%7Blocation.search%7D%24%7Blocation.hash%7D%60%3Bvoid'1.0.0' "Setup OpenInDDG" [Setup OpenInEdge]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='microsoft-edge-'%2Blocation.href%3Bvoid'1.0.0' "Setup OpenInEdge" [Setup OpenInFirefox]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href='firefox:%2F%2Fopen-url%3Furl='%2BencodeURIComponent(location.href)%3Bvoid'1.6.2' "Setup OpenInFirefox" [Setup OpenInFirefox-Focus]: https://mobilemind.github.io/OpenInlets/x/#javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60firefox-focus:%2F%2Fopen-url%3Furl=%24%7BencodeURIComponent(location.href)%7D%26private=true%60%3Bvoid'1.1.2' "Setup OpenInFirefox-Focus" diff --git a/dist/openinddg.bookmarklet b/dist/openinddg.bookmarklet index c958172..eb6e6ba 100644 --- a/dist/openinddg.bookmarklet +++ b/dist/openinddg.bookmarklet @@ -1 +1 @@ -javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60%24ddgQuickLink:%2F%2F%24%7Blocation.host%7D%24%7Blocation.pathname%7D%24%7Blocation.search%7D%24%7Blocation.hash%7D%60%3Bvoid'1.0.0' \ No newline at end of file +javascript:if(%2FiP(ad%7Chone)%2F.test(navigator.userAgent))location.href=%60ddgQuickLink:%2F%2F%24%7Blocation.host%7D%24%7Blocation.pathname%7D%24%7Blocation.search%7D%24%7Blocation.hash%7D%60%3Bvoid'1.0.0' \ No newline at end of file diff --git a/src/openinddg.ts b/src/openinddg.ts index 915b573..96cb220 100644 --- a/src/openinddg.ts +++ b/src/openinddg.ts @@ -1,5 +1,5 @@ // verify iOS UserAgent & then prefix URL with protocol for DuckDuckGo Browser if (/iP(ad|hone)/.test(navigator.userAgent)) { - location.href = `$ddgQuickLink://${location.host}${location.pathname}${location.search}${location.hash}`; + location.href = `ddgQuickLink://${location.host}${location.pathname}${location.search}${location.hash}`; } From dc507c8c3e43bd5059bc1742b556fc12af0d9903 Mon Sep 17 00:00:00 2001 From: Tom King Date: Fri, 6 Feb 2026 23:04:37 -0800 Subject: [PATCH 15/18] chore(openinddg): add stronger typing in src --- src/openinchrome.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/openinchrome.ts b/src/openinchrome.ts index a36d0b3..5e651d2 100644 --- a/src/openinchrome.ts +++ b/src/openinchrome.ts @@ -2,6 +2,7 @@ // googlechrome://URL_WITHOUT_SCHEME or googlechromes://URL_WITHOUT_SCHEME if (/iP(ad|hone)/.test(navigator.userAgent)) { - const chromeScheme = `googlechrome${location.protocol === 'https:' ? 's' : ''}://`; + type ChromeScheme = 'googlechrome://' | 'googlechromes://'; + const chromeScheme: ChromeScheme = `googlechrome${location.protocol === 'https:' ? 's' : ''}://`; location.href = `${chromeScheme}${location.host}${location.pathname}${location.search}${location.hash}`; } From be22b02d591a1ce9dd648262332c325228f51669 Mon Sep 17 00:00:00 2001 From: Tom King Date: Fri, 6 Feb 2026 23:05:14 -0800 Subject: [PATCH 16/18] chore: consistent code formatting (space indent) --- src/killStickyHeaders.ts | 4 +-- src/openurlparam.ts | 42 ++++++++++++------------- src/unskim.ts | 68 ++++++++++++++++++++-------------------- src/utmstrip.ts | 30 +++++++++--------- 4 files changed, 72 insertions(+), 72 deletions(-) diff --git a/src/killStickyHeaders.ts b/src/killStickyHeaders.ts index d6ce4d0..20cfa3c 100644 --- a/src/killStickyHeaders.ts +++ b/src/killStickyHeaders.ts @@ -1,5 +1,5 @@ // find & delete all fixed/sticky position elements of body Array.from(document.querySelectorAll('body *')) - .filter(el => ['fixed', 'sticky'].includes(getComputedStyle(el).position)) - .forEach(el => el.remove()); + .filter(el => ['fixed', 'sticky'].includes(getComputedStyle(el).position)) + .forEach(el => el.remove()); diff --git a/src/openurlparam.ts b/src/openurlparam.ts index 6e70655..a242605 100644 --- a/src/openurlparam.ts +++ b/src/openurlparam.ts @@ -1,29 +1,29 @@ // grab the (target) "url" param from the current URL query string (location.search) (() => { - // default to getting the current address & making it a URL object to work with - const origUrl: URL = new URL(document.location.href); + // default to getting the current address & making it a URL object to work with + const origUrl: URL = new URL(document.location.href); - // exit if URL isn't https or if searchParam is less than 13 chars ('u=https://x.y) - if (origUrl.protocol !== 'https:' || origUrl.search.length < 13) { - return; - } + // exit if URL isn't https or if searchParam is less than 13 chars ('u=https://x.y') + if (origUrl.protocol !== 'https:' || origUrl.search.length < 13) { + return; + } - // create array of common param keys used for URLs (in priority order) - const urlKey: string[] = ['url', 'destination', 'redirect', 'target', 'goto', 'u', 'dest', 'link', 'out'], - origParams: URLSearchParams = new URLSearchParams(origUrl.search); + // create array of common param keys used for URLs (in priority order) + const urlKey: string[] = ['url', 'destination', 'redirect', 'target', 'goto', 'u', 'dest', 'link', 'out'], + origParams: URLSearchParams = new URLSearchParams(origUrl.search); - // find 1st param key match that's a valid looking URL, starting w/ 'https:' - const urlCandidate: string | undefined = urlKey - .filter((param: string) => origParams.has(param)) - .map((param: string) => { - const value: string | null = origParams.get(param); - return value ? decodeURIComponent(value) : ''; - }) - .find((url: string) => url.match(/^https:/)); + // find 1st param key match that's a valid looking URL, starting w/ 'https:' + const urlCandidate: string | undefined = urlKey + .filter((param: string) => origParams.has(param)) + .map((param: string) => { + const value: string | null = origParams.get(param); + return value ? decodeURIComponent(value) : ''; + }) + .find((url: string) => url.match(/^https:/)); - // navigate to new URL if found - if (urlCandidate) { - window.location.replace(new URL(urlCandidate)); - } + // navigate to new URL if found + if (urlCandidate) { + window.location.replace(new URL(urlCandidate)); + } })(); diff --git a/src/unskim.ts b/src/unskim.ts index ad97243..c23339e 100644 --- a/src/unskim.ts +++ b/src/unskim.ts @@ -5,39 +5,39 @@ // wrap everything in an anon function for isolation (() => { - // default to getting the current address & making it a URL object to work with - let origUrl: URL = new URL(document.location.href); - - // handle special case of Safari error page (DNS block of redirection service) - if (origUrl.href === 'safari-resource:/ErrorPage.html') { - // DO NOT "UNCURL" the apostrophe or quotes in the match regex, that formatting IS CRITICAL - const urlStr: string | undefined = document.querySelector('p.error-message')?.textContent?.match(/Safari can't open the page "(https?:[^"]+)"/)?.[1]; - if (urlStr) { - origUrl = new URL(urlStr); - } - } - - // exit if searchParam is less than 13 chars ('u=https://x.y) - if (origUrl.search.length < 13 ) { - return; - } - - // create array of common param keys used for URLs (in priority order) - const urlKey: string[] = ['url', 'destination', 'redirect', 'target', 'goto', 'u', 'dest', 'link', 'out'], - origParams: URLSearchParams = new URLSearchParams(origUrl.search); - - // find 1st param key match that's a valid looking URL - const urlCandidate: string | undefined = urlKey - .filter((param: string) => origParams.has(param)) - .map((param: string) => { - const value: string | null = origParams.get(param); - return value ? decodeURIComponent(value) : ''; - }) - .find((url: string) => url.match(/^https?:/)); - - // navigate to new URL if found - if (urlCandidate) { - window.location.replace(new URL(urlCandidate)); - } + // default to getting the current address & making it a URL object to work with + let origUrl: URL = new URL(document.location.href); + + // handle special case of Safari error page (DNS block of redirection service) + if (origUrl.href === 'safari-resource:/ErrorPage.html') { + // DO NOT "UNCURL" the apostrophe or quotes in the match regex, that formatting IS CRITICAL + const urlStr: string | undefined = document.querySelector('p.error-message')?.textContent?.match(/Safari can't open the page "(https?:[^"]+)"/)?.[1]; + if (urlStr) { + origUrl = new URL(urlStr); + } + } + + // exit if searchParam is less than 13 chars ('u=https://x.y) + if (origUrl.search.length < 13 ) { + return; + } + + // create array of common param keys used for URLs (in priority order) + const urlKey: string[] = ['url', 'destination', 'redirect', 'target', 'goto', 'u', 'dest', 'link', 'out'], + origParams: URLSearchParams = new URLSearchParams(origUrl.search); + + // find 1st param key match that's a valid looking URL + const urlCandidate: string | undefined = urlKey + .filter((param: string) => origParams.has(param)) + .map((param: string) => { + const value: string | null = origParams.get(param); + return value ? decodeURIComponent(value) : ''; + }) + .find((url: string) => url.match(/^https?:/)); + + // navigate to new URL if found + if (urlCandidate) { + window.location.replace(new URL(urlCandidate)); + } })(); diff --git a/src/utmstrip.ts b/src/utmstrip.ts index aa387ae..36ab7ce 100644 --- a/src/utmstrip.ts +++ b/src/utmstrip.ts @@ -168,8 +168,8 @@ // Iterate and delete matching params for (const key of [...params.keys()]) { - if (shouldDelete(key)) { - params.delete(key); + if (shouldDelete(key)) { + params.delete(key); } } @@ -179,18 +179,18 @@ // Clean up '/amp/' in pathname & assign newURL const pathStr: string = locPath.replace(/\/amp\/?$/, ''), - newURL: string = `${location.protocol}//${location.host}${pathStr}${searchStr}${location.hash}`; - - // always _offer_ to copy the "cleaned" newURL - if (confirm('Update history and copy cleaned URL to clipboard?')) { - navigator.clipboard.writeText(newURL); - // If changed (truly cleaned), replace location with stripped version - if (locSearch !== searchStr || locPath !== pathStr) { - history.replaceState(null, '', newURL); - const newWindow: Window | null = window.open(newURL, '_self', 'noreferrer'); - if (newWindow) { - newWindow.opener = null; - } - } + newURL: string = `${location.protocol}//${location.host}${pathStr}${searchStr}${location.hash}`; + + // always _offer_ to copy the "cleaned" newURL + if (confirm('Update history and copy cleaned URL to clipboard?')) { + navigator.clipboard.writeText(newURL); + // If changed (truly cleaned), replace location with stripped version + if (locSearch !== searchStr || locPath !== pathStr) { + history.replaceState(null, '', newURL); + const newWindow: Window | null = window.open(newURL, '_self', 'noreferrer'); + if (newWindow) { + newWindow.opener = null; + } + } } })(); From 7b7c3817d500cd388dbd623abe2e864ce3d6c3ef Mon Sep 17 00:00:00 2001 From: Tom King Date: Sun, 8 Feb 2026 21:24:32 -0800 Subject: [PATCH 17/18] chore: update to ECMAscript 2021, dependencies @types/node 25.2.2 --- .github/linters/eslint.config.js | 2 +- .npmrc | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- tsconfig.json | 2 +- tsconfig.scripts.json | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/linters/eslint.config.js b/.github/linters/eslint.config.js index 44196ff..4c7e879 100644 --- a/.github/linters/eslint.config.js +++ b/.github/linters/eslint.config.js @@ -109,7 +109,7 @@ module.exports = [ files: ["src/*.js"], ignores: [".cspell.json","*.json", "**/*{.,-}min.js", "node_modules/*", "dist/*.bookmarklet"], languageOptions: { - ecmaVersion: 2020, + ecmaVersion: 2021, globals: {...globals.browser, ...globals.node}, parserOptions: { ecmaFeatures: {globalReturn: false, impliedStrict: true} diff --git a/.npmrc b/.npmrc index 0532da6..4161fe9 100644 --- a/.npmrc +++ b/.npmrc @@ -3,7 +3,7 @@ # enforce engine compatibility - fail if Node.js/npm version doesn't match engine-strict=true -# save exact versions (no ^ or ~ ranges) for better reproducibility +# save exact versions (avoid ^ or ~ ranges) for better reproducibility save-exact=true # use public registry diff --git a/package-lock.json b/package-lock.json index 08c1459..4f5b0f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "@eslint/js": "9.39.2", - "@types/node": "25.2.1", + "@types/node": "25.2.2", "@typescript-eslint/eslint-plugin": "8.54.0", "@typescript-eslint/parser": "8.54.0", "eslint": "9.39.2", @@ -343,9 +343,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.1.tgz", - "integrity": "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==", + "version": "25.2.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.2.tgz", + "integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index af7c207..03ae0a6 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "description": "Project that makes Safari bookmarklets to automagically jump to the corresponding app and more.", "devDependencies": { "@eslint/js": "9.39.2", - "@types/node": "25.2.1", + "@types/node": "25.2.2", "@typescript-eslint/eslint-plugin": "8.54.0", "@typescript-eslint/parser": "8.54.0", "eslint": "9.39.2", diff --git a/tsconfig.json b/tsconfig.json index 2be0caa..a92adf8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "ES2020", "module": "none", - "lib": ["ES2020", "DOM"], + "lib": ["ES2021", "DOM"], "outDir": "./.temp", "strict": true, "noEmit": false, diff --git a/tsconfig.scripts.json b/tsconfig.scripts.json index d1f2e0b..9367a91 100644 --- a/tsconfig.scripts.json +++ b/tsconfig.scripts.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "module": "commonjs", - "lib": ["ES2020"], + "lib": ["ES2021"], "outDir": "./.temp-scripts", "types": ["node"] }, From c397d3efd64ef1c104388b7f57a8604c032da7ec Mon Sep 17 00:00:00 2001 From: Tom King Date: Sun, 8 Feb 2026 21:50:36 -0800 Subject: [PATCH 18/18] chore: update config files to enable ECMAscript 2023 --- .github/linters/eslint.config.js | 4 ++-- scripts/minify.js | 2 +- src/unskim.ts | 2 +- tsconfig.json | 4 ++-- tsconfig.scripts.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/linters/eslint.config.js b/.github/linters/eslint.config.js index 4c7e879..8fdcac0 100644 --- a/.github/linters/eslint.config.js +++ b/.github/linters/eslint.config.js @@ -109,7 +109,7 @@ module.exports = [ files: ["src/*.js"], ignores: [".cspell.json","*.json", "**/*{.,-}min.js", "node_modules/*", "dist/*.bookmarklet"], languageOptions: { - ecmaVersion: 2021, + ecmaVersion: 2023, globals: {...globals.browser, ...globals.node}, parserOptions: { ecmaFeatures: {globalReturn: false, impliedStrict: true} @@ -187,7 +187,7 @@ module.exports = [ files: ["scripts/*.js", ".github/linters/*.js"], ignores: [".cspell.json","*.json", "**/*{.,-}min.js", "node_modules/*", "dist/*.bookmarklet"], languageOptions: { - ecmaVersion: 2020, + ecmaVersion: 2023, globals: globals.node, parserOptions: { ecmaFeatures: {globalReturn: false, impliedStrict: true} diff --git a/scripts/minify.js b/scripts/minify.js index b4f3995..2507502 100755 --- a/scripts/minify.js +++ b/scripts/minify.js @@ -18,7 +18,7 @@ const {minify} = require('terser'); // Security note: These settings do not introduce vulnerabilities, but can // change code semantics. See SECURITY.md for build verification procedures. const terserOptions = { - ecma: 2020, // Target ES2020 - fully supported by Safari 26.1+ + ecma: 2023, // Target ES2023 - fully supported by Safari 26.2+ module: false, // Not ES modules compress: { // Aggressive unsafe optimizations (matching UglifyJS config) diff --git a/src/unskim.ts b/src/unskim.ts index c23339e..a0425e0 100644 --- a/src/unskim.ts +++ b/src/unskim.ts @@ -18,7 +18,7 @@ } // exit if searchParam is less than 13 chars ('u=https://x.y) - if (origUrl.search.length < 13 ) { + if (origUrl.search.length < 13) { return; } diff --git a/tsconfig.json b/tsconfig.json index a92adf8..6f00d59 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { - "target": "ES2020", + "target": "ES2023", "module": "none", - "lib": ["ES2021", "DOM"], + "lib": ["ES2023", "DOM"], "outDir": "./.temp", "strict": true, "noEmit": false, diff --git a/tsconfig.scripts.json b/tsconfig.scripts.json index 9367a91..1f3b961 100644 --- a/tsconfig.scripts.json +++ b/tsconfig.scripts.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.json", "compilerOptions": { "module": "commonjs", - "lib": ["ES2021"], + "lib": ["ES2023"], "outDir": "./.temp-scripts", "types": ["node"] },