Full Width [alt+shift+f] FOCUS MODE Shortcuts [alt+shift+k]
Sign Up [alt+shift+s] Log In [alt+shift+l]
99
When I copy a browser tab URL, I often want to also keep the title. Sometimes I want to use the link as rich text (e.g., when pasting the link into OneNote or Jira). Sometimes I prefer a Markdown link. There are browser extensions to achieve this task, but I don't want to introduce potential security issues. Instead, I've written a bookmarklet based on this example extension. To use it, drag the following link onto your browser bookmarks bar: Copy Tab When you click the bookmark(let), the current page including its title will be copied into your clipboard. You don't even have to choose the output format: the link is copied both as rich text and plain text (Markdown). This works because it's possible to write multiple values into the clipboard with different content types. Here's the source code: function escapeHTML(str) { return String(str) .replace(/&/g, "&amp;") .replace(/"/g, "&quot;") .replace(/'/g, "&#39;") .replace(/</g, "&lt;") .replace(/>/g,...
8 months ago

Comments

Improve your reading experience

Logged in users get linked directly to articles resulting in a better reading experience. Please login for free, it takes less than 1 minute.

More from Darek Kay

Open Graph images: Format compatibility across platforms

While redesigning my photography website, I've looked into the Open Graph (OG) images, which are displayed when sharing a link on social media or messaging apps. Here's an example from WhatsApp: For each photo that I publish, I create a WebP thumbnail for the gallery. I wanted to use those as OG images, but the WebP support was lacking, so I've been creating an additional JPG variant just for Open Graph. I was interested in seeing how things have changed in the last 2.5 years. I've tested the following platforms: WhatsApp, Telegram, Signal, Discord, Slack, Teams, Facebook, LinkedIn, Xing, Bluesky, Threads and Phanpy (Mastodon). Here are the results: All providers support JPEG and PNG. All providers except Teams and Xing support WebP. No provider except Facebook supports AVIF. WhatsApp displays the AVIF image, but the colors are broken. "X, formerly Twitter" didn't display OG images for my test pages at all. I don't care about that platform, so I didn't bother to further investigate. Those results confirmed that I could now use WebP Open Graph images without creating an additional JPG file.

9 months ago 90 votes
A guide to bookmarklets

I'm a frequent user of bookmarklets. As I'm sharing some of them on my blog, I wrote this post to explain what bookmarklets are and how to use them. In short, a bookmarklet is a browser bookmark containing JavaScript code. Clicking the bookmark executes the script in the context of the current web page, allowing users to perform tasks such as modifying the appearance of a webpage or extracting information. Bookmarklets are a simpler, more lightweight alternative to browser extensions, Chrome snippets, and userscripts. How to add a bookmarklet? Here's an example to display a browser dialog with the title of the current web page: Display page title You can click the link to see what it does. To run this script on other websites, we have to save it as a bookmarklet. My preferred way is to drag the link onto the bookmarks toolbar: A link on a web page is dragged and dropped onto a browser bookmark bar. A bookmark creation dialog appears. The prompt is confirmed and closed. The created bookmarklet is clicked. The current web page title is displayed in a browser dialog. Another way is to right-click the link to open its context menu: In Firefox, you can then select "Bookmark Link…". Other browsers make it a little more difficult: select "Copy Link (Address)", manually create a new bookmark, and then paste the copied URL as the link target. Once created, you can click the bookmark(let) on any web page to display its title. Scroll further down to see more useful use cases. How to write a bookmarklet? Let's start with the code for the previous bookmarklet example: window.alert(document.title) To turn that script into a bookmarklet, we have to put javascript: in front of it: javascript:window.alert(document.title) To keep our code self-contained, we should wrap it with an IIFE (immediately invoked function expression): javascript:(() => { window.alert(document.title) })() Finally, you might have to URL-encode your bookmarklet if you get issues with special characters: javascript:%28%28%29%20%3D%3E%20%7B%0A%20%20window.alert%28document.title%29%0A%7D%29%28%29 Useful bookmarklets Here are some bookmarklets I've created: Debugger — Starts the browser DevTools debugger after 3 seconds, useful for debugging dynamic content changes. Log Focus Changes — Logs DOM elements when the focus changes. Design Mode — Makes the web page content-editable (toggle).

10 months ago 91 votes
Prevent data loss on page refresh

It can be frustrating to fill out a web form, only to accidentally refresh the page (or click "back") and lose all the hard work. In this blog post, I present a method to retain form data when the page is reloaded, which improves the user experience. Browser behavior Most browsers provide an autofill feature. In the example form below, enter anything into the input field. Then, try out the following: Click the "Example link" and use the "back" functionality of your browser. Reload the page. Query Example link Depending on your browser, the input value might be restored: Browser Reload Back Firefox 130 Yes Yes Chrome 129 No Yes Safari 18 No Yes How does it work? I was surprised to learn that this autofill behavior is controlled via the autocomplete, that is mostly used for value autocompletion from past web forms. However, if we disable the autocompletion, the autofill feature will be disabled as well: <input autocomplete="false" /> To learn more about the behavior, read the full spec on persisted history entry state. Preserving application state Even with autofill, no browser will restore dynamic changes previously triggered by the user. In the following example, the user has to always press the "Search" button to view the results: This is an interactive example. Please enable JavaScript to use it. Query Search ... const inputElementNative = document.querySelector("#example-search-input"); const outputElementNative = document.querySelector("#example-search-output"); const performSearch_exampleSearch = (outputElement) => { outputElement.innerText = ""; const introText = document.createTextNode("Open the result for "); outputElement.appendChild(introText); const link = document.createElement("a"); link.href = "https://example.com"; link.innerText = inputElementNative.value || "no text"; outputElement.appendChild(link); }; document.querySelector("#example-search-form").addEventListener("submit", (event) => { event.preventDefault(); performSearch_exampleSearch(outputElementNative); }, ); If the web page changes its content after user interaction, it might be a good idea to restore the UI state after the page has been refreshed. For example, it's useful to restore previous search results for an on-site search. Note that Chrome will fire a change event on inputs, but this is considered a bug as the respective spec has been updated. Storing form values As the form value might be lost on reload, we need to store it temporarily. Some common places to store data include local storage, session storage, cookies, query parameters or hash. They all come with drawbacks for our use case, though. Instead, I suggest using the browser history state, which has several advantages: We get data separation between multiple browser tabs with no additional effort. The data is automatically cleaned up when the browser tab is closed. We don't pollute the URL and prevent page reloads. Let's store the search input value as query: document.querySelector("form").addEventListener("submit", (event) => { event.preventDefault(); const inputElement = document.querySelector("input"); history.replaceState({ query: inputElement.value }, ""); performSearch(); }); This example uses the submit event to store the data, which fits our "search" use case. In a regular form, using the input change event might be a better trigger to store form values. Using replaceState over pushState will ensure that no unnecessary history entry is created. Uncaught TypeError: Failed to execute 'replaceState' on 'History': 2 arguments required, but only 1 present. Restoring form values My first approach to restore form values was to listen to the pageshow event. Once it's fired, we can access the page load type from window.performance: window.addEventListener("pageshow", () => { const type = window.performance.getEntriesByType("navigation")[0].type; const query = history.state?.query; if (query && (type === "back_forward" || type === "reload")) { document.querySelector("#my-input").value = query; performSearch(); } }); I will keep the solution here in case someone needs it, but usually it is unnecessary to check the page load type. Because the history state is only set after the search form has been submitted, we can check the state directly: const query = history.state?.query; if (query) { document.querySelector("#my-input").value = query; performSearch(); } Demo Here's an example combining both techniques to store and restore the input value: This is an interactive example. Please enable JavaScript to use it. Query Search ... const inputElementCustom = document.querySelector("#example-preserve-input"); const outputElementCustom = document.querySelector("#example-preserve-output"); const performSearch_examplePreserve = (outputElement) => { outputElement.innerText = ""; const introText = document.createTextNode("Open the result for "); outputElement.appendChild(introText); const link = document.createElement("a"); link.href = "https://example.com"; link.innerText = inputElementCustom.value || "no text"; outputElement.appendChild(link); }; document.querySelector("#example-preserve-form").addEventListener("submit", (event) => { event.preventDefault(); performSearch_examplePreserve(outputElementCustom); history.replaceState({ query: inputElementCustom.value }, ""); }); const historyQuery = history.state?.query; if (historyQuery) { document.querySelector("#example-preserve-input").value = historyQuery; performSearch_examplePreserve(outputElementCustom); } Conclusion Preserving form data on page refresh is a small but impactful way to improve user satisfaction. The default browser autofill feature handles only basic use cases, so ideally we should maintain the form state ourselves. In this blog post, I've explained how to use the browser history state to temporarily store and retrieve form values.

11 months ago 97 votes
Web push notifications: issues and limitations

In this post, I will summarize some problems and constraints that I've encountered with the Notifications and Push web APIs. Notification settings on macOS Someone who's definitely not me wasted half an hour wondering why triggered notifications would not appear. On macOS, make sure to enable system notifications for your browsers. Open "System Settings" → "Notifications". For each browser, select "Allow notifications" and set the appearance to "Alerts": Onchange listener not called Web APIs offer a way to subscribe to change events. This is especially useful in React: navigator.permissions .query({ name: "push", userVisibleOnly: true }) .then((status) => { status.onchange = function () { // synchronize permission status with local state setNotificationPermission(this.state); }; }); Whenever the notification permission changes (either through our application logic or via browser controls), we can synchronize the UI in real-time according to the current permission value (prompt, denied or granted). However, due to a Firefox bug, the event listener callback is never called. This means that we can't react to permission changes via browser controls in Firefox. That's especially unfortunate when combined with push messages, where we want to subscribe the user once they grant the notification permission. One workaround is to check at page load if the notification permission is granted with no valid subscription and resubscribe the user. Notification image not supported Browser notifications support an optional image property. This property is marked as "experimental", so it's not surprising that some browsers (Firefox, Safari) don't support it. There is an open feature request to add support in Firefox, but it has been open since 2019. VAPID contact information required When sending a push message, we have to provide VAPID parameters (e.g. the public and private key). According to the specification, the sub property (contact email or link) is optional: If the application server wishes to provide, the JWT MAY include a "sub" (Subject) claim. Despite this specification, the Mozilla push message server will return an error if the subject is missing: 401 Unauthorized for (...) and subscription https://updates.push.services.mozilla.com/wpush/v2/… You might not encounter this issue when using the popular web-push npm package, as its API encourages you to provide the subject as the first parameter: webpush.setVapidDetails("hello@example.com", publicKey, privateKey); However, in the webpush-java library, you need to set the subject explicitly: builder.subject("hello@example.com"); There is an open issue with more information about this problem. Microsoft Edge pitfalls Microsoft introduced adaptive notification requests in the Edge browser. It is a crowdsourced score system, which may auto-accept or auto-reject notification requests. The behavior can be changed in the Edge notification settings. Additionally, on a business or school device, those settings might be fixed, displaying the following tooltip: This setting is managed by your organization.

a year ago 92 votes

More in programming

Bear is now source-available

Updates to the Bear license

10 hours ago 4 votes
Exploring Interlisp-10 and TWENEX

<![CDATA[I'm exploring another corner of the Interlisp ecosystem and history: the Interlisp-10 implementation for DEC PDP-10 mainframes, a 1970s character based environment that predated the graphical Interlisp-D system. I approached this corner when I set out to learn and experiment with a tool I initially checked out only superficially, the TTY editor. This command line structure editor for Lisp code and expressions was the only one of Interlisp-10. The oldest of the Interlisp editors, it came before graphical interfaces and SEdit. On Medley Interlisp the TTY editor is still useful for specialized tasks. For example, its extensive set of commands with macro support is effectively a little language for batch editing and list structure manipulation. Think Unix sed for s-exps. The language even provides the variable EDITMACROS (wink wink). Evaluating (PRINTDEF EDITMACROS) gives a flavor for the language. For an experience closer to 1970s Interlisp I'm using the editor in its original environment, Interlisp-10 on TWENEX. SDF provides a publicly accessible TWENEX system running on a PDP-10 setup. With the product name TOPS-20, TWENEX was a DEC operating system for DECSYSTEM-20/PDP-10 mainframes derived from TENEX originally developed by BBN. SDF's TWENEX system comes with Interlisp-10 and other languages. This is Interlisp-10 in a TWENEX session accessed from my Linux box: A screenshot of a Linux terminal showing Interlisp-10 running under TWENEX in a SSH session. Creating a TWENEX account is straightforward but I didn't receive the initial password via email as expected. After reporting this to the twenex-l mailing list I was soon emailed the password which I changed with the TWENEX command CHANGE DIRECTORY PASSWORD. Interacting with TWENEX is less alien or arcane than I thought. I recognize the influence of TENEX and TWENEX on Interlisp terminology and notation. For example, the Interlisp REPL is called Exec after the Exec command processor of the TENEX operating system. And, like TENEX, Interlisp uses angle brackets as part of directory names. It's clear the influence of these operating systems also on the design of CP/M and hence MS-DOS, for example the commands DIR and TYPE. SDF's TWENEX system provides a complete Interlisp-10 implementation with only one notable omission: HELPSYS, the interactive facility for consulting the online documentation of Interlisp. The SDF wiki describes the basics of using Interlisp-10 and editing Lisp code with the TTY editor. After a couple of years of experience with Medley Interlisp the Interlisp-10 environment feels familiar. Most of the same functions and commands control the development tools and facilities. My first impression of the TTY editor is it's reasonably efficient and intuitive to edit Lisp code, at least using the basic commands. One thing that's not immediately apparent is that EDITF, the entry point for editing a function, works only with existing functions and can't create new ones. The workaround is to define a stub from the Exec like this: (DEFINEQ (NEW.FUNCTION () T)) and then call (EDITF NEW.FUNCTION) to flesh it out. Transferring files between TWENEX and the external world, such as my Linux box, involves two steps because the TWENEX system is not accessible outside of SDF. First, I log into Unix on sdf.org with my SDF account and from there ftp to kankan.twenex.org (172.16.36.36) with my TWENEX account. Once the TWENEX files are on Unix I access them from Linux with scp or sftp to sdf.org. This may require the ARPA tier of SDF membership. Everything is ready for a small Interlisp-10 programming project. #Interlisp #Lisp a href="https://remark.as/p/journal.paoloamoroso.com/exploring-interlisp-10-and-twenex"Discuss.../a Email | Reply @amoroso@oldbytes.space !--emailsub--]]>

14 hours ago 4 votes
you can never go back

Total disassociation, fully out your mind That Funny Feeling I was thinking today about a disc jockey. Like one in the 80s, where you actually had to put the records on the turntables to get the music. You move the information. You were the file system. I like the Retro Game Mechanics channel on YouTube. What was possible was limited by the hardware, and in a weird way it forced games to be good. Skill was apparent by a quick viewing, and different skill is usually highly correlated. Good graphics meant good story – not true today. I was thinking about all the noobs showing up to comma. If you can put a technical barrier up to stop them, like it used to be. But you can’t. These barriers can’t be fake, because a fake barrier isn’t like a real barrier. A fake barrier is one small patch away from being gone. What if the Internet was a mistake? I feel like it’s breaking my brain. It was this mind expanding world in my childhood, but now it’s a set of narrow loops that are harder and harder to get out of. And you can’t escape it. Once you have Starlink to your phone, not having the Internet with you will be a choice, not a real barrier. There’s nowhere to hide. Chris McCandless wanted to be an explorer, but being born in 1968 meant that the world was already all explored. His clever solution, throw away the map. But that didn’t make him an explorer, it made him an idiot who died 5 miles from a bridge that would have saved his life. And I’ll tell you something else that you ain’t dying enough to know Big Casino Sure, you can still spin real records, code for the NES, and SSH into your comma device. But you don’t have to. And that makes the people who do it come from a different distribution from the people who used to. They are not explorers in the same way Chris McCandless wasn’t. When I found out about the singularity at 15, I was sure it was going to happen. It was depressing for a while, realizing that machines would be able to do everything a lot better than I could. But then I realized that it wasn’t like that yet and I could still work on this problem. And here I am, working in AI 20 years later. I thought I came to grips with obsolescence. But it’s not obsolescence, the reality is looking to be so much sadder than I imagined. It won’t be humans accepting the rise of the machines, it won’t be humans fighting the rise of the machines, it will be human shaped zoo animals oddly pacing back and forth in a corner of the cage while the world keeps turning around them. It’s easy to see the appeal of conspiracy theories. Even if they hate you, it’s more comforting to believe that they exist. That at least somebody is driving. But that’s not true. It’s just going. There are no longer Western institutions capable of making sense of the world. (maybe the Chinese ones can? it’s hard to tell) We are shoved up brutally against evolution, just of the memetic variety. The TikTok brainrot kids will be nothing compared to the ChatGPT brainrot kids. And I’m not talking like an old curmudgeon about the new forms of media being bad and the youth being bad like Socrates said. Because you can never go back. It will be whatever it is. To every fool preaching the end of history, evolution spits in your face. To every fool preaching the world government AI singleton, evolution spits in your face. I knew these things intellectually, but viscerally it’s just hard to live through. The world feels so small and I feel like I’m being stared at by the Eye of Sauron.

yesterday 4 votes
Why Amateur Radio

I always had a diffuse idea of why people are spending so much time and money on amateur radio. Once I got my license and started to amass radios myself, it became more clear.

3 days ago 9 votes
strongly typed?

What does it mean when someone writes that a programming language is “strongly typed”? I’ve known for many years that “strongly typed” is a poorly-defined term. Recently I was prompted on Lobsters to explain why it’s hard to understand what someone means when they use the phrase. I came up with more than five meanings! how strong? The various meanings of “strongly typed” are not clearly yes-or-no. Some developers like to argue that these kinds of integrity checks must be completely perfect or else they are entirely worthless. Charitably (it took me a while to think of a polite way to phrase this), that betrays a lack of engineering maturity. Software engineers, like any engineers, have to create working systems from imperfect materials. To do so, we must understand what guarantees we can rely on, where our mistakes can be caught early, where we need to establish processes to catch mistakes, how we can control the consequences of our mistakes, and how to remediate when somethng breaks because of a mistake that wasn’t caught. strong how? So, what are the ways that a programming language can be strongly or weakly typed? In what ways are real programming languages “mid”? Statically typed as opposed to dynamically typed? Many languages have a mixture of the two, such as run time polymorphism in OO languages (e.g. Java), or gradual type systems for dynamic languages (e.g. TypeScript). Sound static type system? It’s common for static type systems to be deliberately unsound, such as covariant subtyping in arrays or functions (Java, again). Gradual type systems migh have gaping holes for usability reasons (TypeScript, again). And some type systems might be unsound due to bugs. (There are a few of these in Rust.) Unsoundness isn’t a disaster, if a programmer won’t cause it without being aware of the risk. For example: in Lean you can write “sorry” as a kind of “to do” annotation that deliberately breaks soundness; and Idris 2 has type-in-type so it accepts Girard’s paradox. Type safe at run time? Most languages have facilities for deliberately bypassing type safety, with an “unsafe” library module or “unsafe” language features, or things that are harder to spot. It can be more or less difficult to break type safety in ways that the programmer or language designer did not intend. JavaScript and Lua are very safe, treating type safety failures as security vulnerabilities. Java and Rust have controlled unsafety. In C everything is unsafe. Fewer weird implicit coercions? There isn’t a total order here: for instance, C has implicit bool/int coercions, Rust does not; Rust has implicit deref, C does not. There’s a huge range in how much coercions are a convenience or a source of bugs. For example, the PHP and JavaScript == operators are made entirely of WAT, but at least you can use === instead. How fancy is the type system? To what degree can you model properties of your program as types? Is it convenient to parse, not validate? Is the Curry-Howard correspondance something you can put into practice? Or is it only capable of describing the physical layout of data? There are probably other meanings, e.g. I have seen “strongly typed” used to mean that runtime representations are abstract (you can’t see the underlying bytes); or in the past it sometimes meant a language with a heavy type annotation burden (as a mischaracterization of static type checking). how to type So, when you write (with your keyboard) the phrase “strongly typed”, delete it, and come up with a more precise description of what you really mean. The desiderata above are partly overlapping, sometimes partly orthogonal. Some of them you might care about, some of them not. But please try to communicate where you draw the line and how fuzzy your line is.

4 days ago 15 votes