-
-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
adds live page preview on hover :) #52
Conversation
would be nice if it were possible to move mouse over popup and scroll that Iframe/etc also should block references get preview as-well? (I'm not sure about it myself) |
hmm, this seems to somehow add a small lag for when I'm typing things in the block (not trying to do hover preview or anything) |
maybe just because we need to re-render 2 instances of Roam in the same thread? |
if the reference is close to the sight side of the page the popup can go outside of the screen |
Also, currently the toggle does not work -- did not get to fixing it yet. |
Not quite sure what you mean by toggle? |
@Stvad we are hiding the iframe using opacity/height/width, set to 0; There will be no repaint/relayout happening in that case. We can easily check for this using profiling in chrome's devtools
|
Will keep a lookout for lag -- should see it if it happens; I have this enabled anyway |
ok, rendering is the wrong word, I suppose, background processing would be more precise. the following is without preview enabled: in both cases I'm just typing in the text into the block |
another thing to support here is Markdown aliases |
@Stvad could you send a dump of the log with preview enabled? It can be done by using rightclick-> save |
do you mean console log or saving the captured profile? |
captured profile @Stvad |
put it here: https://file.io/o5rQDqPn |
@Stvad getting a 404 on that URL |
@Stvad I'm, still not getting the profile you are getting.. Maybe it's the size of the page, etc. Can you please check that link? Maybe something got missed while pasting it here |
@Stvad also, I have all other features disabled; only this running. Can you try that too? |
@palashkaria hrm, another attempt: https://file.io/xW8m0Gwn |
oh, it seems those links are 1-time use |
Got it, thanks Also, are you running the other features? or just the live preview? |
Going by the minified source, I see it's triggered from a 'write' function; which seems to be triggered whenever a new block is created; post this, roam seems to do a server sync. That's why you see this randomly -- it happens when the sync is happening, and the main thread gets busy. This will need further debugging (or atleast a clean repro case). I'm running perf profile in the background to see if I get this |
just fyi - tested in incognito mode, with all the other features disabled - same story. |
yeah sync makes sense as a culprit - it's run not only when new blocks are created, but when you just add this to existing one, which is what I'm doing |
a note - my main DB is rather large (19mb unpacked JSON file), just tried this on the smaller DB - it's far less pronounced, still get small stuttering (with corresponding cpu spikes) here and there, but a lot better then with the main db |
another important thing to confirm is that we're doing the same thing while recording the profiles. As mentioned above - the issues occurs while I'm typing the text into the block (not moving mouse, not creating new blocks or links or anything like it, just continuously enter new text) - are you testing the same scenario? |
} | ||
checkSettingsAndSetupIframe() | ||
|
||
browser.runtime.onMessage.addListener(async message => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can be moved to dispatcher, as it already does these checks/etc. would need to make it be able to trigger multiple functions though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Stvad doing this causes the iframe removal to stop working, because of the same instance problem -- on toggle, it reloads this script, resulting in the instance being null
.
If used via onMessage, it works fine.
Changes I made (which caused it to break
-
in live-preview, remove this
browser.runtime.onMessage.addListener
handling. -
in
dispatcher
['settings-updated', () => {
updateShortcuts()
checkSettingsAndSetupIframeToggle()
}
- now on toggle, it does not
.destroy()
as it does not find the instance.
let iframeInstance: PreviewIframe | null = null | ||
|
||
const setupIframe = (active: boolean) => { | ||
if (!iframeInstance) { | ||
iframeInstance = new PreviewIframe() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can potentially be simplified if you are to always create the holding object, but create the actual view/iframe on activate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, that's what you do. I guess alternative consideration is the complexity of managing state if you are to re-use the object. But you seem to be doing a lot of that anyhow..
iframe.style.width = '0' | ||
iframe.style.border = '0' | ||
|
||
// styles |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: comment seems redundant
} | ||
|
||
private removeIframe() { | ||
if (this.iframe && document.body.contains(this.iframe)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when would those 2 not be true at the same time? (error scenario?) I imagine if that's the case we may still to cleanup the other part? To say it in other words - should this be 2 separate ifs
that would cleanup the corresponding thing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we try to remove an iframe which doesn't exist in body
, there will be an exception. So both of these need to be true at the same time @Stvad the condition is correct
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant having something like:
if(document.body.contains(this.iframe)) { //false for undefined/null/etc
document.body.removeChild(this.iframe)
}
if (this.iframe) this.iframe = null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Stvad typescript will throw an error there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Stvad will need to check for this.iframe
first. Updated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hrm, it's now more complicated then before 🙈.
is typescript error because the this.iframe
can still potentially be null?
private getIFrameByUrl(url: string): HTMLIFrameElement | null { | ||
return document.querySelector(`iframe[src="${url}"]`) | ||
} | ||
private getIsIFrameVisibleByUrl(url: string): boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isUrlInPreview
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Stvad this is about being 'visible', not being in preview -- visible is if opacity is set to 1
. Maybe isUrlPreviewVisible
this.isHoveredOutFromTarget(target, relatedTarget, iframe) || | ||
this.isHoveredOutFromIframe(target, relatedTarget, iframe) || | ||
!this.isHoveredElementPresentInBody() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make this a function/variable
this.hoveredElement = null | ||
this.clearPopupTimeout() | ||
this.resetIframeForNextHover(iframe) | ||
this.destroyPopper() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make it into hidePreview
function
private resetIframeForNextHover(iframe: HTMLIFrameElement) { | ||
if (iframe) { | ||
if (iframe.contentDocument) { | ||
// scroll to top when removed, so the next popup is not scrolled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be using scrollToTopHack
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a different scollToTop hack :) extracting to another function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do they have to be different. would scrolling to the top of html not be enough here too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Stvad that is during loading. When a post is loaded, you don't scroll on the html
, you scroll on a div inside .roam-center
. Need to set the scrolltop of that.
src/ts/roam/navigation.ts
Outdated
if (!uid) { | ||
return this.baseUrl().toString() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's use case for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to get the daily notes page @Stvad
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can just call baseUrl
explicitly..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that does not return a string -- don't want to leak .toString() into app code. And we might need the baseUrl as is for some other purpose
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure what do you mean by leak .toString
. you literally use this only to get baseUrl, and there is already function for that 😛
I don't find it intuitive that that this function would silently "succeed" on falsey input =\
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use this to convert eh baseUrl (which is the class URL) to a string. It's not the same thing. @Stvad Should I add a separate function for this? What should it be called?
By leak toString
, I mean exactly that. It needs to be in a function here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added a function getDailyNotesUrl
for this @Stvad
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hrm. ok 😛
|
||
class PreviewIframe { | ||
iframeId = 'roam-toolkit-iframe-preview' | ||
iframe: HTMLIFrameElement | null = null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about constructing this (and maybe calling activate in general?) as a part of class constructor, so we can can be sure that it's not null and avoid null-checking it everywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as after all, iframe should exist during the whole lifecycle of this class
if (this.iframe) { | ||
this.iframe = null | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually I don't think we need to explicitly assign this to null. as we discard the whole PreviewIframe
instance, this would just be garbage collected
|
||
private getIFrameByUrl(url: string): HTMLIFrameElement | null { | ||
return document.querySelector(`iframe[src="${url}"]`) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a nit, but can the prettier be configured to require at list 1 space between functions? 😛
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Stvad Haha, I wish 😅 It is not possible in prettier because of the way prettier works (rewriting your code). See this more more info
It turns out that empty lines are very hard to automatically generate. The approach that Prettier takes is to preserve empty lines the way they were in the original source code
https://prettier.io/docs/en/rationale.html#empty-lines
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
huh, interesting. May I suggest you configure your IDE to do so then ?😛
private getIFrameByUrl(url: string): HTMLIFrameElement | null { | ||
return document.querySelector(`iframe[src="${url}"]`) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so just occurred to me - why do we need to do this if we already have the reference to the instance as a part of the class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned, not sure. We get mouse events triggering multiple times, and these are to make sure that we don't add the iframes multiple times. Will not be able to debug and figure out immediately @Stvad
|
||
private initPreviewIframe() { | ||
const url = Navigation.getPageUrl() | ||
const existingIframe = this.getIFrameByUrl(url) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when would this happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No clear answer. Can happen, have seen mouse events getting triggered multiple times.
const visibleIframe = this.getVisibleIframeByUrl(url) | ||
if (visibleIframe) { | ||
// if visible, just return the iframe | ||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should use class iframe instance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Stvad will not work -- mouse events get called 3 times
src/ts/roam/navigation.ts
Outdated
if (!uid) { | ||
return this.baseUrl().toString() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure what do you mean by leak .toString
. you literally use this only to get baseUrl, and there is already function for that 😛
I don't find it intuitive that that this function would silently "succeed" on falsey input =\
} | ||
checkSettingsAndSetupIframe() | ||
|
||
browser.runtime.onMessage.addListener(async message => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀
use id based check
might have found the root cause for this issue; we are loading the same contentscript multiple times -- once directly, once in dispatcher, for eg. that's why when I move the code to dispatcher, it causes it to laod another time. Each content script is supposed to be isolated on it's own. See this for more details: Also attaching screenshots of fuzzy_date getting loaded two separate times, this way (see stacktrace):
|
const url = Navigation.getPageUrl() | ||
const existingIframe = this.getIFrameByUrl(url) | ||
const url = Navigation.getDailyNotesUrl() | ||
const existingIframe = this.getExisitingIframe() | ||
if (existingIframe) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hope we'd be getting rid of this now. but if we were not - I think the assignment would have been still required, to ensure that we operate on the appropriate underlying object
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this.iframe.style.width = '0' | ||
} | ||
|
||
private scrollToTopOnMouseOut() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can you place the scroll to top hacks close to each other and extract commonality between them?
# Conflicts: # src/manifest.json # src/ts/features/features.ts
move/rename scrollToTop hack
} else if (iframeInstance) { | ||
iframeInstance.destroy() | ||
iframeInstance = null | ||
if (active) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i.e. why change from previous version of this
To see it in action, see the video here:
https://twitter.com/palashkaria/status/1259589041688768512
Draft PR, WIP