Syncing the editor viewport scroll


(Wojciech Zawistowski) #1

I’ve checked how they do synced scrolling in Ghost and it turns out that it is extremely simple - they just keep the relative position of both panes in sync, i.e. if the source pane is scrolled to 30% of its height, preview pane is also automatically scrolled to 30% of its height.

In most typical cases both source and and preview contain almost only text, so their height is more or less proportional and this approach works quite well. This method also seems to be very performant and scalable, as it chceks only the pane height and scroll position, without parsing the text etc. so it shouldn’t be dependent on the amount of content in the pane.

However in cases when the content between panes is very disproportional (e.g. if you embed a link to a tall image, that in the source pane is just a single line of text and in the preview pane is an actual image, several hundreds of pixels high or if you put 30 newlines in the source that get squashed into a single newline in the preview) scrolling in Ghost can go out of sync.

@sam What do You think? Should we give similar simple approach a try? Or do you want to have something exactly accurate?

Discourse General Polish prior to V1
Feature Proposal: editor scroll smart sync | edit <==> preview. Making edited area visible in the preview
(Sam Saffron) #2

I would prefer something accurate, but don’t want to take any perf hit on it. Was thinking we could render a hidden div in the preview where the cursor is and then check its in the viewport, if not scroll it into the viewport with some padding.

There is also this implementation to review

(Jeff Atwood) #3

I do not agree with @sam here, we should start with the simple approach first.

(Sam Saffron) #4

Strongly disagree, we have a tiny view port (unlike ghost) and have oneboxes (unlike ghost), a naive approach is going to have some spectacular incredibly annoying edge cases.

Would prefer we do this right from get go, or not do it at all.

cc @velesin

(Jeff Atwood) #5

How is that any different? A post with images will be sized very differently on left (where the image URL is) and right (where the image is). Images are very common in blog posts.

I think it’s crazy to say “it isn’t going to work” if we don’t try the simple thing first.

(Sam Saffron) #6

Because I just tried out ghost and the feature is embarrassingly and completely broken, like 100% bust, try it out yourself with a few images and a small view port.

The whole point here is adding polish, not adding quirks. We either do it properly or not.

(Jeff Atwood) #7
  1. Most posts don’t contain images, or oneboxes

  2. Most posts are fairly short anyway

  3. It’s simple to try, we can do the science, see if we like it, see if others like it, and if we don’t like it we can take it out.

There is really no downside here. We should do the science. Unless for some reason you hate science…

(Kane York) #8

Yes, that is true. Most posts are also fairly short, and usually the whole thing fits in the left pane.

However, these posts already function perfectly - the problems with the scrolling only show up when your post has images or oneboxes, or is 10 paragraphs.

The “common case” you’re arguing about already isn’t broke, so we can’t fix it.

If Ghost solves the “long” case without solving the images or onebox cases, that’s batting .333, not .777. Ass Pull math: if ⅔ of posts work fine, ⅓ need to be fixed, and of those, ⅓ are long, ⅓ are images, and ⅓ are oneboxes, we should judge the fix against the ⅓ of the whole, not the whole. if the fix only fixes the ‘long’ case, that’s 7/9 working right, but we only improved by 1/3.

(Sam Saffron) #9

I did the science, installed ghost, shrunk the viewport, pasted a big topic from discourse, moved cursor to middle started typing. It moved me to a random spot.

So science failed here.

(Dave McClure) #10

Hey, careful there, science didn’t fail!

No reason to hate on science - science just proved the hypothesis wrong. :smile:

(Jeff Atwood) #11

In the time you guys have wasted with this discussion we could have already implemented it and tried it out.

It’s simple, that’s the attraction. If it was so godawfully bad, Ghost would have pulled it out. So why not try it and see what we think? Trying is easy when the approach is simple!

Or y’know pointlessly argue here for hours.

And by the way, I can size this editor large too. Just drag it!

(Sam Saffron) #12

Actually, we could have implemented it correctly in this amount of time.

Which is what I will end up doing, which is why it’s so frustrating for me.

(Jeff Atwood) #13

Frustration driven development!

(JohnONolan) #14

Sam is mostly right, though the “embarrassingly and completely” part was maybe a little exaggerated.

Funny story about the scrollsync, it’s almost the only piece of JavaScript in all of Ghost which was written entirely by me. It was a couple of days before we launched the Kickstarter campaign and Hannah was busy writing all of the serverside logic. There was nobody else to do the job, so I came up with the basic idea of scrolling proportionally based on a % calculation of the height. We simply haven’t gotten around to rewriting it since that day.

The limitations are well documented, you’ve recapped most of them in this thread. It works well for simple text content and quickly degrades when disparity exists between the relative height of elements between Markdown and Preview views (eg. tall images, or embedded content).

There are a lot of ways of looking at solving the basic issues - the oldest open PR on Ghost is in fact to improve the scrollsync based on proportional scrolling whenever an image is detected. It works - kind of - but not reliably.

I’ve seen many attempts at solving this problem since we launched Ghost, but the best by far that I’ve seen is the one already mentioned above: - which is fantastic on so many levels, and open source (MIT if memory serves).

But - the real question of course has little to do with which implementation is “best” and far more to do with which implementation is “right” for Discourse. In that context my humble opinion would be that you can try to perfect this feature until the proverbial cows come home - but what you actually have is 2 very simple, 200px high, textareas which are largely used for straightforward text content.

A 10 line javascript function to do basic syncing is probably all you need in the overwhelming majority of cases, and certainly far easier to test in a beta build.

One thing worth noting is that Ghost deliberately only triggers the sync function if you scroll in the left hand pane, while the right hand pane can be scrolled freely and independently. This way, if and when the scrollsync ever does do a poor job, the user is never “stuck” - but can at least sync manually as a plan B.

Good luck, whatever you decide - and my apologies for offending Sam with my hacky ways. It was a day where we simply needed to get the job done and ship :slight_smile:

(Jeff Atwood) #15

Wow, this feature is SO EASY! Just like @sam promised us! That’s why it got implemented so fast!

(Wojciech Zawistowski) #16

I’ve checked StackEdit - they indeed have a very accurate sync, but they do it in a relatively complex manner - both source and preview panes are advanced text editors that tokenize content in realtime - so then it can be accurately mapped which source tokens map to which result tokens, detect which source token is visible at the moment and adjust the preview pane to scroll to its counterpart.

In case of Discourse composer, source is just a plain text (and preview is also a plain HTML) so it’s not possible to apply similar approach on the top of existing composer - it would have to be completely redesigned.

But I wonder if some simplified version of such approach couldn’t be applied “behind the scenes” - e.g. copy source text to an invisible div, tokenize it somehow (maybe e.g. in a similar way as Ember does with metamorph script tags in templates) then render preview html based on tokenized instead of raw version and finally try to scroll the preview pane to a particular token/metamorph. It’s just a free thought right now, but I’ll take a deeper look into Discourse Markdown class to see what’s possible.

(Sam Saffron) #17

I am actually not offended at all by your hacky ways, I feel they are a totally appropriate quick solution when you have a huge viewport. however it falls really short in places where the viewport is very limited like Discourse. Love ghost, love what you are doing, you are kindred spirits to Discourse. Only thing I really wish is that you were using Discourse as your discussion/support platform :blush:

For the record, my hacky ways are often wayyyy hackier

(Sam Saffron) #18

My immediate thought here was to send the cursor position into markdown.js and have it add a special div into the preview where the cursor is.

Of course the initial concern with this approach is that you could be typing in the middle of a IMG SRC or some other non-displayable area. In which case since markdown js tokenises and parses I would just skip adding the magic div and not worry about sync there, it is an edge case.

Once you have a magic div in place positioning becomes trivial. All you need to do is ensure it is visible.

Second approach I considered was backwards matching the previous 2 words before where the cursor is, matching that in the preview html and inserting magic div there, it is a tiny bit more expensive and complicated.

I think one huge benefit here is utilising markdown js.

(JohnONolan) #19

We actually tried! That was Plan-A. The rest of is all Rails, too. I think we even have a repo somewhere with the initial implementation. Long story short: the SSO features weren’t there yet and we were having to hack out all the functionality that we didn’t want. We have our own users API with data, profiles, etc. It was a little too hard to get them to mesh together in a way that made sense. Might come back to it in the future though when we have more resources (and budget).

(Sam Saffron) #20

Sure, totally makes sense, our SSO story as of last week is pretty awesome and slowly approaching spectacular thanks to our awesome community battle testing it.

I would be more than happy to help you out, PM me if and/or when you need a hand.