It's not actually a straightforward idea. It's just good design. No project manager will ask for it (just as no one asked for multiple cursors until sublime came along and won lots of hearts with it). There's plenty of space for such ideas which will all feel stunning. But, surprising they were not made before, neah...
I think this is a great example of where an individual can really innovate on their own, but they do need to have a good mix of experience with developing on a particular platform as well as a good sense for UX.
Working in a larger company, you tend to have each of those tasks (implementing vs designing) split out into multiple roles, making it very difficult to know what's possible to innovate, plus experience levels often differ, so newer designers may not know the limitations on a platform, so may over-design, and developers may not have experience to implement certain things, so they may limit the end result as well.
Please see footnote in linked article. Like all ideas it's probably been invented many times before. But also like all ideas you need to invent, make good implementation, and then hopefully luck into people finding out a about it.
Of course, but it doesn't seem that any currently available text editor has this. Even the one mentioned in the Stack Overflow post is now gone, it seems.
Still, this looks amazing - thank you for implementing it. Would love to see this formatting UI used in more apps going forward.
That sounds an awful lot like what emacs is doing in org mode (if you configure it too). I have to say, it's often nice, but sometime still gets in the way of editing in my experience. So I'm often conflicted if I want this or pure editing of the org without the rendering.
I was just going to comment about how bringing features like this to org would be great. Do you know if there's a way to have caret affinity displayed in org, or equivalents for the other features mentioned? Sorry if they're obvious features, I'm a bit rusty on the latest developments.
Text affinity and the command pallet is innovative. Could probably bundle this up as a Objective-C library that acts as a drop in replacement for UITextView/NSTextView. Perhaps a JS/WebComponent library for <textarea> as well. I’m sure you could extract some revenue with licensing those libraries or tech support.
Writing the text editor has certainly been painful and taken me a long time. This particular feature (typing affinity) isn't actually to hard and I think could be bolted on to most existing edit editors. Really you just need to take one extra bit of state... and then can calculate cursor drawing + moment based on that state and all the other state the editor normally tracks.
Very nice. I love these little innovations, it’s exactly the type of design ideas that makes me want to try an app.
I wonder if it’s an intentional easter egg that the keyboard shortcuts in the popover spell out “bischl”… which is more than a little reminiscent of “bicycle”. (Could improve it by replacing “h” for “highlight” with “y” for “yellow”: “bisycl”.)
No easter eggs in Bike yet... though I should add something someday. At the moment just trying to get features in to make it a good place to work and think.
Yeah a lot of love has gone into that. I like the subtle animations as the caret slides rightwards whilst typing or the "_I_" appears above it when he sets it to italic.
My solution, when I coded my own word processor, was to use a single, invisible control character, '¶' ¹.
So, for example, bold would be enclosed by '¶b' and '¶B', and a first-level heading would be a line starting with '¶1'.
So, there exist characters in the text that are invisible but you know are there: you can insert the couple and delete it just like any other character.
¹(part of the extended character set - '¶' is easily accessible on some systems as [AltGr]+[R])
Two chars - fixed amount. One control, one command. Just cleaner, simple and functional. It's something you type, and it is hidden, you want it to be short and clear.
«How is that different», more literally: it is markup, like in htMl, but it is a special markup for that other special purpose of direct rich text tagging in word processors, not for hypertext (HTml). I called it differently, but if you so fancy, it is DRTTIWPML.
The video, demonstrating the features of the rich text editor, is well done. Now I want to try this Bike app (unfortunately I have no mac). I hope more developers will copy this whenever they are implementing a rich text editor.
For me, it was too long and verbose: instead of showing with no prior knowledge, it starts telling me how intuitive stuff will be. Also the sound started activated and by clicking, as instructed by the blinking text, I disabled it, which was confusing.
The "affinity cursor" is a really lovely innovation. Most HTML/DOM/contenteditable based rich text editors end up adding support for virtual cursors that enable you to have the insertion cursor somewhere that the native browser cursor hasn't got support for (next to a floating image, end of a table row). Effectively this is what this is doing.
Internally the inline formatting will be marked as tags in a rage, exactly like html. Essentially there is a possible cursor position both inside and outside a formatting tag, but both appear as the same visual location on the screen. This is such a lovely way to indicate the difference.
So with this code:
Something <b>bold</b>
There are two cursor positions that are visually the same place:
Something |<b>|bold</b>
^ ^
Most editors "jump" over one of those positions to hide the implementation detail from users. This idea exposes it with a little tail on the cursor.
I really wish browsers would support this for `contenteditable` (even without the tail, just having some way to put the cursor in either position, maybe based on from what direction the cursor is coming from).
Slack's message editor is `contenteditable`-based, and it means there is absolutely no way to put the cursor inside at the beginning of e.g. an inline code snippet.
Despite the code snippet formatting having an outline border and some padding, meaning it should be possible to even click inside or outside the snippet unambiguously just with your mouse, Chrome will always place it in the same spot and you have to do the awkward trick of replacing the first character.
This single behavior makes `contenteditable` so terrible
It is possible via JS to place the cursor in that position. The issue is the a native browser cursor navigation skips over it. It would be quite possible to implement this on a contenteditable editor.
That particular bug with slack would be possible to fix with JS. The particular difference about that style is there is a little bit of padding at the start of the style. Bowsers have no understanding of clicking on that padding to select inside the the beginning of the inline style.
The behavior for inserting text varies by browser and/or OS, unfortunately. I made [sandbox] to test this. Click the button to set the selection to the first position inside the first <strong> tag in the contenteditable div.
On my Mac, Firefox 104.0.2 inserts new characters inside the <strong>, so they're bold.
In Chrome, the browser API allows positioning the selection there, but during the input event, the selection ends up normalized to just before the strong tag, so the characters aren't inserted into the <strong> tag.
In Safari, the browser API accepts the arguments to set the selection to the first position inside the strong tag, but normalizes during the API call, and the selection always ends up right before the strong tag.
The variance of contentEditable is a huge impediment to using the API raw. If you want reliable, relatively low-level control of rich text input on the web, use ProseMirror.
(I learned a lot about this stuff while working on Notion's rich-text editor, which is all custom.)
Thank you for the example. I do mostly the same thing and as a Safari user there is no way to place the caret in "the right spot". What I found working (but I still have to test on other browsers and make my mind if it is really worth the hack) is to place a dummy marker with [contenteditable="false"] at the beginning and the end of the inline elements (the one on the end has to be skipped to avoid getting stuck in a limbo inside the parent element). This way the result is similar to the one in Bike, with the caret that stops on both sides of the element. Let's see if this thing goes somewhere...
Of of the top of my head, you'll need to watch out for:
- The default behavior that triple-click to selects a whole paragraph/line will stop at the first contenteditable=false element.
- Android may dismiss the keyboard & selection if the selection ever touches one of your contenteditable=false marker elements.
- Firefox won't let you place the caret in between two contenteditable=false elements, or at the start or end of a contenteditable=true element if the first/last child is contenteditable=false.
I am sure that there are plenty more! Working on top of customeditable is hell, but it is the price to pay if you want to implement a custom editing experience. Of the issues you listed, the triple-click is a good one, and easy to solve with a custom handler (even if there is no tripleclick event, the detail property of the event object holds the number of consecutive clicks). I am pretty sure that ProseMirror does something similar, because it uses [contenteditable="false"] decorations without interfering with the triple-click selection. For mobile devices and Firefox I would surrender, and gracefully degrade the editing experience.
Why not use ProseMirror? It doesn't fix this issue, does it? At least in the demos on their website you cannot insert text at the beginning of a styled element. You cannot do it even in a native application (at least on macOS) without a custom handling of the selection.
Thanks for the sandbox. I'm working on a company-wide blog post on a good richtext editing strategy / library and wanted to demostrate how unpredictable it is with contenteditable. This sample is perfect and I'm thinking of hyperlinking the sandbox :)
Do you happen to have any more samples to demonstrate how difficult it is to work with contenteditable APIs?
Oh, I made that sandbox for my HN comment. I actually have a similar internal blog post & several more complex examples I built for that, but can't share any of it publicly.
I just started experimenting with contenteditable yesterday so there might be an easier way, but I think you do this by manipulating the Range object in window.getSelection().
I think natively if you don’t jump into a tag and you don’t jump out of a tag. So if your cursor is in the tag and you move to the boundary, you stay in it. Similarly if you are outside of it, and move to the boundary you stay outside of it.
So I basically would listen to the the relevant input events, see if I’m at the boundary and the orientation of the cursor (which I save in memory) and if I’m only moving across a boundary (i.e. not to the next character) I call event.preventDefault(), manipulate the range accordingly, and update the orientation state.
I’m not sure how to style the caret it self (other then the color) but I’m sure it can be achieved with some absolute position hacking.
You'll need to track your own selection state and take over actually inserting the characters by calling preventDefault() during beforeinput in Chrome and Safari. Both Chrome and Safari will fight you on this one. To say nothing of Android...
I guess it won't work for rich editors that create tons of elements for every style applied, like `<b><span style=font:...><span style=...>...<i>...` it requires some sanity of the editor.
Older content editable editors would do that. But modern ones have moved to not nesting inline styles, and separating internal data structure from the exported html. Full html isn't really that great for the internal data structure of a rich text editor.
Think of them as having only a none nestable <span> tag with a style attribute.
I don't think it would matter, text affinity happens at texts "run" boundaries... so only where formatting changes. Doesn't matter how many attributes each run has. I think that is proper way to design the feature, but it does means it's maybe less flexible then you are thinking. You can pick left or right run... you can pick individual attributes within a run with text affinity.
It works on text "runs", so there will only ever be two choices. In the case you describe there's no way to choose only bold, or only link... you would need to revert to standard rich text dancing for that.
Microsoft Word never had problems with inserting any formatting at any position in the text. It also has some visual style indicators via caret shape and size.
MSWord doesn't have formatting codes, at least not for simple text styles. Instead, each individual character has formatting attributes. So the word italics is not a single italicized region, it's 7 separately italicized characters, that just happen to be next to one another. It's a radically different model from HTML/markdown/WordPerfect markup.
Microsoft Word may never have problems inserting any formatting, but I have; when it comes to paragraph structure, indenting and such, I find it very hard to predict. For example, I'll look at sections of a document with basically the same structure, but in one place the spacing between a table and the paragraph that follows is much larger, and I have no idea why or how to make them the same.
In https://tigyog.app I'm solving the same problem in a different way. Like Bike, the TigYog editor treats those two cursor locations as district. But TigYog shows which position you're at in a different way: when the cursor is inside a link, the link gets a "focus" outline.
When I came up with this feature in Bike I was pretty proud of the idea and it seemed original. Then a few weeks later I came across your post on StackExchange UX... everything already been invented I guess :) I'm curious how you came with idea? Also have you come across other editors that implement it?
For me I new that something bothered me about rich text editing... always seemed more painful then required. I was also aware of the idea of split cursor from Humane Interface book. And also aware of affinity idea from text cursor position in wrapped text editor lines. Bike's typing affinity came from combining those ideas.
Anyway would love to hear any related thoughts or ideas.
Internally a lot of editors don't nest inline styles like that. The formatting is more like a style attribute on a <span> tag, listing all style properties. So you still only have two positions, previous formatting to the left, new formatting to the right.
If I were implementing this, I would ignore the middle one. There's virtually nothing visible to the user that there is a middle one OR that <b> is actually placed before <i>. I feel handling this edgecase causes more UX problems than not.
The approach I took with my own hacky editor component was to highlight the styles that currently apply at the caret position via toggle buttons for B, I, etc. This is something word processors have been doing for decades, although I recognise it's far from a perfect solution.
Brilliant idea. I am actually just researching how to build my own structured document editor for the web, based on my parsing engine for Pyramid grammars, and I'll certainly examine how to apply this idea in my context.
This is genius. Very clear explanation of what's wrong with rich text editing too. Especially frustrating in Slack. On desktop they support "Mrkdwn" (a bastardisation of Markdown) but there's no way to do custom link text with it at all! The only way is if you switch to their shitty WYSIWYG editor.
In their app I think you can only use WYSIWYG, but it's full of insane flaws, like if you open a code span (backticks) and then close it but don't immediately enter some more text then there is literally no way to escape it into normal text again. You have to delete the whole code span and start again.
If anyone from Slack is reading this, fix your shit!
This looks awesome. I do all my writing (creative writing, outlining, todos) in Bear and really love it. But this app seems like it would make outlining more fun and intuitive. Def gonna try it!
Great to see fresh, innovate apps like this that prioritize fast native experience.
Are there any examples out there what kind of documents people are writing with this?
What's the right way to write more than one line per row? Return always just adds a new row. For instance, with Workflowy, you can hit Return+shift to add a note to row which can be as long as you want, and only the first line of it will be shown if not focused.
I personally use it for a thinking tool... as opposed to document creating at this point. So I store notes and ideas. Process ideas. I don't author final documents.
Currently there is no way to add multiple lines to a row. I "think" I would rather solve this problem by adding multiple row types (maybe heading vrs notes) and then use some sort of filtering. That's still on todo list.
These are pretty nice ideas fixing existing problems for desktop computers.
How would these work with mobile devices ? It doesn't seam compatible.
Also, these are user interaction suggestions. What about the encoding ? There are plenty limitations with Markdown (the pseudo standard) on this aspect.
This is the second time in 8 days that something I submitted first made it to the front page from someone else's repost less than 24 hours later. What am I doing wrong?
https://news.ycombinator.com/item?id=33480410
I think a lot of getting to the front page depends on your random luck of the first 10 people that see the post. If enough of those 10 like your thing, it gets seen by thousands more and may make it to the front page. If none of those 10 do, it's fades away. It's 10 people out of millions that visit HN, so there's a lot of variability. You could post the exact same thing 15 minutes later then the first time and get a totally different "first 10" and that post could perform completely differently.
Yeah, all good points. I guess I'm partially just confused about the dupe detector. E.g., when I post something that someone else has already submitted recently, it just redirects me to the existing post.
Re the luck factor, part of the appeal of Hacker News is there seems to be a lot of work done to minimize the role of luck. Which generally felt like it worked well for me historical, just not recently.
Not a big deal in any event, but if there's something I should be doing differently, I'd like to do it.
Yeah, unfortunately I think a huge amount still comes down to luck.
HN is better then any other platform (which is incredible considering how many engineers work on it) at detecting fraud (e.g. "upvote rings"). But I think a ton still comes down to the first few people that see the post.
Pretty sure :) I submitted this story a month ago when Bike first released this feature and only got 3-4 upvotes. Very pleased and surprised to see it generating interest today.
139 comments
[ 3.0 ms ] story [ 223 ms ] threadI think this is a great example of where an individual can really innovate on their own, but they do need to have a good mix of experience with developing on a particular platform as well as a good sense for UX.
Working in a larger company, you tend to have each of those tasks (implementing vs designing) split out into multiple roles, making it very difficult to know what's possible to innovate, plus experience levels often differ, so newer designers may not know the limitations on a platform, so may over-design, and developers may not have experience to implement certain things, so they may limit the end result as well.
Still, this looks amazing - thank you for implementing it. Would love to see this formatting UI used in more apps going forward.
Thanks!
- https://codemirror.net - https://prosemirror.net - https://xi-editor.io
\s
I wonder if it’s an intentional easter egg that the keyboard shortcuts in the popover spell out “bischl”… which is more than a little reminiscent of “bicycle”. (Could improve it by replacing “h” for “highlight” with “y” for “yellow”: “bisycl”.)
https://www.versionmuseum.com/history-of/microsoft-word
My solution, when I coded my own word processor, was to use a single, invisible control character, '¶' ¹.
So, for example, bold would be enclosed by '¶b' and '¶B', and a first-level heading would be a line starting with '¶1'.
So, there exist characters in the text that are invisible but you know are there: you can insert the couple and delete it just like any other character.
¹(part of the extended character set - '¶' is easily accessible on some systems as [AltGr]+[R])
Two chars - fixed amount. One control, one command. Just cleaner, simple and functional. It's something you type, and it is hidden, you want it to be short and clear.
«How is that different», more literally: it is markup, like in htMl, but it is a special markup for that other special purpose of direct rich text tagging in word processors, not for hypertext (HTml). I called it differently, but if you so fancy, it is DRTTIWPML.
Internally the inline formatting will be marked as tags in a rage, exactly like html. Essentially there is a possible cursor position both inside and outside a formatting tag, but both appear as the same visual location on the screen. This is such a lovely way to indicate the difference.
So with this code:
There are two cursor positions that are visually the same place: Most editors "jump" over one of those positions to hide the implementation detail from users. This idea exposes it with a little tail on the cursor.That particular bug with slack would be possible to fix with JS. The particular difference about that style is there is a little bit of padding at the start of the style. Bowsers have no understanding of clicking on that padding to select inside the the beginning of the inline style.
How? No matter what I try, the insertion point is always before the bold element, and not inside it.
[sandbox]: https://codesandbox.io/s/gracious-dew-rx3xhz?file=/index.htm...
On my Mac, Firefox 104.0.2 inserts new characters inside the <strong>, so they're bold.
In Chrome, the browser API allows positioning the selection there, but during the input event, the selection ends up normalized to just before the strong tag, so the characters aren't inserted into the <strong> tag.
In Safari, the browser API accepts the arguments to set the selection to the first position inside the strong tag, but normalizes during the API call, and the selection always ends up right before the strong tag.
The variance of contentEditable is a huge impediment to using the API raw. If you want reliable, relatively low-level control of rich text input on the web, use ProseMirror.
(I learned a lot about this stuff while working on Notion's rich-text editor, which is all custom.)
- The default behavior that triple-click to selects a whole paragraph/line will stop at the first contenteditable=false element.
- Android may dismiss the keyboard & selection if the selection ever touches one of your contenteditable=false marker elements.
- Firefox won't let you place the caret in between two contenteditable=false elements, or at the start or end of a contenteditable=true element if the first/last child is contenteditable=false.
Why not use ProseMirror?
Why not use ProseMirror? It doesn't fix this issue, does it? At least in the demos on their website you cannot insert text at the beginning of a styled element. You cannot do it even in a native application (at least on macOS) without a custom handling of the selection.
Do you happen to have any more samples to demonstrate how difficult it is to work with contenteditable APIs?
I think natively if you don’t jump into a tag and you don’t jump out of a tag. So if your cursor is in the tag and you move to the boundary, you stay in it. Similarly if you are outside of it, and move to the boundary you stay outside of it.
So I basically would listen to the the relevant input events, see if I’m at the boundary and the orientation of the cursor (which I save in memory) and if I’m only moving across a boundary (i.e. not to the next character) I call event.preventDefault(), manipulate the range accordingly, and update the orientation state.
I’m not sure how to style the caret it self (other then the color) but I’m sure it can be achieved with some absolute position hacking.
Think of them as having only a none nestable <span> tag with a style attribute.
The innovation is the visual indicator, the cursor tail. The rest is how WordPerfect worked 30 years ago.
It is horrifying to think how much the Microsoft Word approach has hobbled rich text editing for all those years, and continues to do so.
Microsoft Word may never have problems inserting any formatting, but I have; when it comes to paragraph structure, indenting and such, I find it very hard to predict. For example, I'll look at sections of a document with basically the same structure, but in one place the spacing between a table and the paragraph that follows is much larger, and I have no idea why or how to make them the same.
The feature there is named "directional caret"...
When I came up with this feature in Bike I was pretty proud of the idea and it seemed original. Then a few weeks later I came across your post on StackExchange UX... everything already been invented I guess :) I'm curious how you came with idea? Also have you come across other editors that implement it?
For me I new that something bothered me about rich text editing... always seemed more painful then required. I was also aware of the idea of split cursor from Humane Interface book. And also aware of affinity idea from text cursor position in wrapped text editor lines. Bike's typing affinity came from combining those ideas.
Anyway would love to hear any related thoughts or ideas.
For example, “<b><i>text” has three insertion points shown with the pipe character: “|<b>|<i>|text”
I suspect that is how Bike is doing it.
In their app I think you can only use WYSIWYG, but it's full of insane flaws, like if you open a code span (backticks) and then close it but don't immediately enter some more text then there is literally no way to escape it into normal text again. You have to delete the whole code span and start again.
If anyone from Slack is reading this, fix your shit!
Great to see fresh, innovate apps like this that prioritize fast native experience.
Being a meta-shortcut I guess the more complex/options it gets, the worst.
What's the right way to write more than one line per row? Return always just adds a new row. For instance, with Workflowy, you can hit Return+shift to add a note to row which can be as long as you want, and only the first line of it will be shown if not focused.
Currently there is no way to add multiple lines to a row. I "think" I would rather solve this problem by adding multiple row types (maybe heading vrs notes) and then use some sort of filtering. That's still on todo list.
How would these work with mobile devices ? It doesn't seam compatible.
Also, these are user interaction suggestions. What about the encoding ? There are plenty limitations with Markdown (the pseudo standard) on this aspect.
Few years ago, I was writing a tiny note taking app, and the only viable alternative was Trix.
The most popular is Quill, and then there are block style like EditorJS, and those meant to be very extensible like Lexical.
And many more- try searching for "Quill alternatives"
Re the luck factor, part of the appeal of Hacker News is there seems to be a lot of work done to minimize the role of luck. Which generally felt like it worked well for me historical, just not recently.
Not a big deal in any event, but if there's something I should be doing differently, I'd like to do it.
HN is better then any other platform (which is incredible considering how many engineers work on it) at detecting fraud (e.g. "upvote rings"). But I think a ton still comes down to the first few people that see the post.