-
Notifications
You must be signed in to change notification settings - Fork 15
Code intel inject #186
Code intel inject #186
Changes from all commits
8b9d0ca
8140dad
a41bfe4
b38e4ff
e9aab85
d08e525
b03cfeb
816b101
20e22f5
8e4b799
437ec6e
649244a
ebb548a
e7f81bb
d0b3013
1e5d6ce
13d0a6e
371e09a
087ed54
a4a2e30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| import { from, merge, Observable, of, Subject } from 'rxjs' | ||
| import { filter, map, mergeMap } from 'rxjs/operators' | ||
|
|
||
| import { CodeHost, ResolvedCodeView } from './code_intelligence' | ||
|
|
||
| /** | ||
| * Emits a ResolvedCodeView when it's DOM element is on or about to be on the page. | ||
| */ | ||
| const emitWhenIntersecting = (margin: number) => { | ||
| const codeViewStash = new Map<HTMLElement, ResolvedCodeView>() | ||
|
|
||
| const intersectingElements = new Subject<HTMLElement>() | ||
|
|
||
| const intersectionObserver = new IntersectionObserver( | ||
| entries => { | ||
| for (const entry of entries) { | ||
| // `entry` is an `IntersectionObserverEntry`, | ||
| // which has | ||
| // [isIntersecting](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry/isIntersecting#Browser_compatibility) | ||
| // as a prop, but TS complains that it does not | ||
| // exist. | ||
| if ((entry as any).isIntersecting) { | ||
| intersectingElements.next(entry.target as HTMLElement) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of Subjects, prefer the Observable constructor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is probably related to your comment below but I found it quite hard to implement this in another way. I only ever want to create on
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried using the observable constructor but ended up here |
||
| } | ||
| } | ||
| }, | ||
| { | ||
| rootMargin: `${margin}px`, | ||
| threshold: 0, | ||
| } | ||
| ) | ||
|
|
||
| return (codeViews: Observable<ResolvedCodeView>) => | ||
| new Observable<ResolvedCodeView>(observer => { | ||
| codeViews.subscribe(({ codeView, ...rest }) => { | ||
| intersectionObserver.observe(codeView) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems wrong to have the intersectionObserver be shared between all Observable subscriptions at this layer - if the intend is to share, the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be honest, I'm not sure how to use that in place of this subscription here. That sounds reasonable. Could you give me some guidance? |
||
| codeViewStash.set(codeView, { codeView, ...rest }) | ||
| }) | ||
|
|
||
| intersectingElements | ||
| .pipe( | ||
| map(element => codeViewStash.get(element)), | ||
| filter(codeView => !!codeView) | ||
| ) | ||
| .subscribe(observer) | ||
| }) | ||
| } | ||
|
|
||
| /** | ||
| * findCodeViews finds all the code views on a page given a CodeHost. It emits code views | ||
| * that are lazily loaded as well. | ||
| */ | ||
| export const findCodeViews = (codeHost: CodeHost, watchChildrenModifications = true) => ( | ||
| containers: Observable<Element> | ||
| ) => { | ||
| const codeViewsFromList: Observable<ResolvedCodeView> = containers.pipe( | ||
| filter(() => !!codeHost.codeViews), | ||
| mergeMap(container => | ||
| from(codeHost.codeViews!).pipe( | ||
| map(({ selector, ...info }) => ({ | ||
| info, | ||
| matches: container.querySelectorAll<HTMLElement>(selector), | ||
| })) | ||
| ) | ||
| ), | ||
| mergeMap(({ info, matches }) => | ||
| of(...matches).pipe( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| map(codeView => ({ | ||
| ...info, | ||
| codeView, | ||
| })) | ||
| ) | ||
| ) | ||
| ) | ||
|
|
||
| const codeViewsFromResolver: Observable<ResolvedCodeView> = containers.pipe( | ||
| filter(() => !!codeHost.codeViewResolver), | ||
| map(container => ({ | ||
| resolveCodeView: codeHost.codeViewResolver!.resolveCodeView, | ||
| matches: container.querySelectorAll<HTMLElement>(codeHost.codeViewResolver!.selector), | ||
| })), | ||
| mergeMap(({ resolveCodeView, matches }) => | ||
| of(...matches).pipe( | ||
| map(codeView => ({ | ||
| ...resolveCodeView(codeView), | ||
| codeView, | ||
| })) | ||
| ) | ||
| ) | ||
| ) | ||
|
|
||
| const obs = [codeViewsFromList, codeViewsFromResolver] | ||
|
|
||
| if (watchChildrenModifications) { | ||
| const possibleLazilyLoadedContainers = new Subject<HTMLElement>() | ||
|
|
||
| const mutationObserver = new MutationObserver(mutations => { | ||
| for (const mutation of mutations) { | ||
| if (mutation.type === 'childList' && mutation.target instanceof HTMLElement) { | ||
| const { target } = mutation | ||
|
|
||
| possibleLazilyLoadedContainers.next(target) | ||
| } | ||
| } | ||
| }) | ||
|
|
||
| containers.subscribe(container => | ||
| mutationObserver.observe(container, { | ||
| childList: true, | ||
| subtree: true, | ||
| }) | ||
| ) | ||
|
|
||
| const lazilyLoadedCodeViews = possibleLazilyLoadedContainers.pipe(findCodeViews(codeHost, false)) | ||
|
|
||
| obs.push(lazilyLoadedCodeViews) | ||
| } | ||
|
|
||
| return merge(...obs).pipe( | ||
| emitWhenIntersecting(250), | ||
| filter(({ codeView }) => !codeView.classList.contains('sg-mounted')) | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from './code_intelligence' |
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.
An async function that returns Subscriptions on first sight seems like a weird mix of patterns to me
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 I not make this an
asyncfunction?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.
It can be (it looks like it does only side effects)
I just thought the returned Subscription was weird because if it is to cancel the operation, the operation already happened when the Promise resolves. But I see that it actually represents the subscription for hoverify etc? In that case this makes sense. Just some documentation would help explain what the return value actually is
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.
Yea, I'll add some docs