Skip to content
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

Migrate to bootstrap-vue popovers #1033

Merged
merged 12 commits into from
Mar 3, 2020
Merged

Conversation

openorclose
Copy link
Contributor

@openorclose openorclose commented Feb 8, 2020

What is the purpose of this pull request? (put "X" next to an item, remove the rest)

• [ ] Documentation update
• [ ] Bug fix
• [ ] New feature
• [x] Enhancement to an existing feature
• [ ] Other, please explain:

Fixes #873
Fixes #814

Updated PR, modals, tooltips, and popovers are all working

Some breaking changes:

Contextmenu (right click) no longer a trigger

bootstrap-vue modals don't support the width attribute
But their modals have a whole lot of other options, see them here:
https://bootstrap-vue.js.org/docs/components/modal/

Popovers now don't have the zoom effect. I tried to reimplement the zoom effect, but was only successful for modals. Will try to add back in later.

I have updated the docs for these.


What is the rationale for this request?
We are still reliant on vue-strap for popovers, modals, and tooltips. This aims to complete the migration to bootstrap-vue, by using their versions of tooltips, popovers, and modals.

What changes did you make? (Give an overview)
Updated our bootstrap-vue js and css to the latest version. This gives us better versions of popovers and tooltips.

bootstrap-vue's trigger mechanism is very different from ours:

Each popover must target a single element on page load, so our "many triggers for one popover" implementation does not work.

So, we take all <trigger> elements and turn it into a with a v-b-popover directive, and pass in functions that allow us to return a HTML string of the desired popover.
Provide some example code that this change will affect:
Taken from the docs:

More about <trigger for="pop:trigger_id">trigger</trigger>.
<popover id="pop:trigger_id" content="This popover is triggered by a trigger"></popover>
<br>
This is the same <trigger for="pop:trigger_id">trigger</trigger> as last one.

Old:
image

New:
image

Is there anything you'd like reviewers to focus on?

Functionality of the popovers.

Note that modals and tooltips are broken for now, but the solution for them should be similar
Testing instructions:

Proposed commit message: (wrap lines at 72 characters)

@openorclose
Copy link
Contributor Author

https://deploy-preview-1033--markbind-master.netlify.com/userguide/usingcomponents#popovers

All popovers trigger only on click for now for better testing (can open multiple at once, inspect element more easily).

@ang-zeyu
Copy link
Contributor

ang-zeyu commented Feb 8, 2020

Whether duplicating the popovers is an acceptable solution.

Since the typical use case for a popover is to include really small tidbits of info, I don't think the cost for this is significant


I like the idea of moving the processing work to /markbind, but why a plugin though?
This feels like something that should be inherent to markbind.

Could we try integrating this into componentParser instead?
It would be in line with where we do custom processing for specific components, and reduce a round of parsing the entire content with cheerio.

The tricky part would be that componentParser does not have access to the entire dom, which is needed for $([target='${popoverId}']). Perhaps during _preprocess you could whitelist popovers and add their content to a parser instance variable map for access later.

That said, that's just my two cents, but at the very least this should be a stage in the page.generate() chain, not a plugin I think.


Another possible issue is the way popoverId is done. There will likely be duplicate popover ids between a page and its external dependencies. This will not cause issues when the page is initially loaded, but may after the dynamic dependency has loaded. ( assuming #1032 is implemented ) A simple fix may be to prepend something unique to the page ( file path hash? ) before the id though

@openorclose
Copy link
Contributor Author

Thanks for the review!

Whether duplicating the popovers is an acceptable solution.

Since the typical use case for a popover is to include really small tidbits of info, I don't think the cost for this is significant

I like the idea of moving the processing work to /markbind, but why a plugin though?
This feels like something that should be inherent to markbind.

Could we try integrating this into componentParser instead?
It would be in line with where we do custom processing for specific components, and reduce a round of parsing the entire content with cheerio.

The tricky part would be that componentParser does not have access to the entire dom, which is needed for $([target='${popoverId}']). Perhaps during _preprocess you could whitelist popovers and add their content to a parser instance variable map for access later.

This also wouldn't work. Consider a trigger, followed by the actual popover element. When the trigger component is parsed, the popover is not yet parsed. Later on, there's no way to "go back" and edit it back in. If we were to go that route then we would need two maps, one storing all the triggers, and another storing all the popovers, and each time we parse either we need to check if they have been referenced by another popover/trigger. Maybe I'll try this.

That said, that's just my two cents, but at the very least this should be a stage in the page.generate() chain, not a plugin I think.

Yes, the main reason for it being a plugin is so that it has access to the entire DOM.

Yeah and noted regarding the dependent files too, could probably have a counter counting the number of included files too.

@ang-zeyu
Copy link
Contributor

ang-zeyu commented Feb 8, 2020

This also wouldn't work. Consider a trigger, followed by the actual popover element. When the trigger component is parsed, the popover is not yet parsed. Later on, there's no way to "go back" and edit it back in. If we were to go that route then we would need two maps, one storing all the triggers, and another storing all the popovers, and each time we parse either we need to check if they have been referenced by another popover/trigger. Maybe I'll try this.

Hmm, there shouldn't be a need to 'go back'.
To clarify what I meant :

Pseudo code:

// preprocess
if (node.name === 'popover')
  this.popoverContentMap.set(popover.id, popover's content and title attribute)

// componentParser
case 'trigger':
  search popoverContentMap for popover indicated by our for attribute
  rename element as popover
  set content and title attribute to equal the content and title retrieved

// front end popover component
use the v-b-popover directive, and set the content and title attribute accordingly

@openorclose
Copy link
Contributor Author

This also wouldn't work. Consider a trigger, followed by the actual popover element. When the trigger component is parsed, the popover is not yet parsed. Later on, there's no way to "go back" and edit it back in. If we were to go that route then we would need two maps, one storing all the triggers, and another storing all the popovers, and each time we parse either we need to check if they have been referenced by another popover/trigger. Maybe I'll try this.

Hmm, there shouldn't be a need to 'go back'.
To clarify what I meant :

Pseudo code:

// preprocess
if (node.name === 'popover')
  this.popoverContentMap.set(popover.id, popover's content and title attribute)

// componentParser
case 'trigger':
  search popoverContentMap for popover indicated by our for attribute
  rename element as popover
  set content and title attribute to equal the content and title retrieved

// front end popover component
use the v-b-popover directive, and set the content and title attribute accordingly

Yeah I got that, but the problem is in case trigger. The popoverContentMap might not contain our needed element yet, because a trigger can appear after a popover, but a trigger can also appear before a popover.

Like

<trigger for=pop> click me</trigger>
<popover id=pop content = "i will appear">/popover>

or

<trigger for=pop> click me</trigger>
<popover id=pop content = "i will appear">/popover>

I believe it still can be a solution if we use two maps, a triggerMap and a popoverContentMap. Not sure if that will be too messy.

@ang-zeyu
Copy link
Contributor

ang-zeyu commented Feb 8, 2020

Yeah I got that, but the problem is in case trigger. The popoverContentMap might not contain our needed element yet,

Hmm, isn't _preprocess ( includeFile ) quite a few stages before _parse ( renderFile )?
This shouldn't occur

@openorclose
Copy link
Contributor Author

Yeah I got that, but the problem is in case trigger. The popoverContentMap might not contain our needed element yet,

Hmm, isn't _preprocess ( includeFile ) quite a few stages before _parse ( renderFile )?
This shouldn't occur

Ah yes, you're right. Thanks!

@openorclose
Copy link
Contributor Author

// preprocess
if (node.name === 'popover')
  this.popoverContentMap.set(popover.id, popover's content and title attribute)

// componentParser
case 'trigger':
  search popoverContentMap for popover indicated by our for attribute
  rename element as popover
  set content and title attribute to equal the content and title retrieved

// front end popover component
use the v-b-popover directive, and set the content and title attribute accordingly

I realised this doesn't work well also:

The popover's content and title might contain other stuff that need to be processed if slots are used,

like

<popover>
<div slot="content><include src="file.md"/></div> Hover me </popover>

but then the map wouldn't have access to the processed popover.

I can add the not-yet-parsed popover into a map during the preprocess stage no problem, but that will not give access to the actual parsed popover.

@ang-zeyu
Copy link
Contributor

ang-zeyu commented Feb 8, 2020

I can add the not-yet-parsed popover into a map during the preprocess stage no problem, but that will not give access to the actual parsed popover.

True. In that case, I can't think of a better implementation than this at the moment.

An alternative would be to do this on the front end, by creating b-popovers as many times as there are triggers. But this is likely to be more heavy on the client, which we should try to avoid.

@alyip98
Copy link
Contributor

alyip98 commented Feb 9, 2020

Plugin
I like the plugin implementation, as it consolidates all popover related functionality rather than sprinkling them all over the then train. Another benefit of doing everything in postRender is we avoid tricky situations with includes or dynamic panels.

Duplication
I think that html duplication for popovers and tooltips isn't an issue as they are meant for light content. Modals are often heavier, but even then a typical page would not have too many trigger-modal pairs.

@openorclose
Copy link
Contributor Author

Plugin
I like the plugin implementation, as it consolidates all popover related functionality rather than sprinkling them all over the then train. Another benefit of doing everything in postRender is we avoid tricky situations with includes or dynamic panels.

The plugin works, but we'd run into issues along the way: the footnotes plugin uses popovers, so we'd want the plugins to render footnotes into popovers, and then transform all popovers into bootstrap-vue components. So we need to control the order in which plugins are executed. Is there a way to do this now?

@ang-zeyu
Copy link
Contributor

ang-zeyu commented Feb 9, 2020

The plugin works, but we'd run into issues along the way: the footnotes plugin uses popovers, so we'd want the plugins to render footnotes into popovers, and then transform all popovers into bootstrap-vue components. So we need to control the order in which plugins are executed. Is there a way to do this now?

but at the very least this should be a stage in the page.generate() chain, not a plugin I think.

We could insert this a seperate stage before postRender, something like processBvComponents, without much changes needed to this implementation, if any.
This consolidates all functionality as well, and prevents the user from misconfiguring the plugin config to turn this off, which we likely don't want since this is a core feature.

We could also use this for same-page id conflict resolution as well, e.g. a bvPopoversIdMap page variable, which avoids having to append something unique to the page before the id, reducing clutter in the output. ( and avoid passing bvPopoversIdMap into a plugin )

@ang-zeyu
Copy link
Contributor

ang-zeyu commented Feb 9, 2020

On another note though, was using popper.js ( the library bootstrap-vue uses for popover positioning ) considered?

It seems like bootstrap-vue popovers & tooltips aren't suited to our use case ( multiple triggers for one popover ).
I've looked into popper.js briefly, and it seems to support creating multiple 'popper instances' with just one content element.
It's also pretty lightweight ( ~3kb ), if we were to include it as a standalone library over bootstrap-vue. ( ~200kb )

That said if the long term goal is to exploit the large amount of features bootstrap-vue has, and not just for popovers / tooltips, then I don't think there's an issue here.

@alyip98
Copy link
Contributor

alyip98 commented Feb 9, 2020

So we need to control the order in which plugins are executed. Is there a way to do this now?

I'm not sure, does the current implementation respect load order? If not we could make a separate issue and PR to improve plugins, possibly also adding more hooks throughout the generate() chain.

@openorclose
Copy link
Contributor Author

openorclose commented Feb 13, 2020

Okay after some days I realised a better solution: v-b-popover directives allow us to pass in an object, so that we can provide functions that return strings for content and title:
https://bootstrap-vue.js.org/docs/directives/popover#directive-syntax-and-usage

image

This allows us to have the best of both words: no more duplication of any popovers 🎉 and the subjectively better positioning offered by bootstrap-vue.

One downside is that we won't have support for the contextmenu trigger (right click) for now, will that be a problem @damithc?

View the popovers here:
https://deploy-preview-1033--markbind-master.netlify.com/userguide/usingcomponents#popovers

This solution is a lot simpler than my previous solution, and does not require the iffy generation of ids for popovers at all.

@damithc
Copy link
Contributor

damithc commented Feb 13, 2020

One downside is that we won't have support for the contextmenu trigger (right click) for now, will that be a problem @damithc?

I think it's an acceptable compromise.

@openorclose
Copy link
Contributor Author

@openorclose
Copy link
Contributor Author

Ready for review!

Some breaking changes:

Contextmenu (right click) no longer a trigger

bootstrap-vue modals don't support the width attribute
But their modals have a whole lot of other options, see them here:
https://bootstrap-vue.js.org/docs/components/modal/

Popovers now don't have the zoom effect. I tried to reimplement the zoom effect, but was only successful for modals. Will try to add back in later.

@openorclose openorclose changed the title [RFC] Migrate to bootstrap-vue popovers Migrate to bootstrap-vue popovers Feb 21, 2020
# Conflicts:
#	asset/css/markbind.css
#	test/functional/test_site/expected/markbind/css/markbind.css
#	test/functional/test_site/expected/testTooltipSpacing.html
#	test/functional/test_site_algolia_plugin/expected/markbind/css/markbind.css
#	test/functional/test_site_convert/expected/markbind/css/markbind.css
#	test/functional/test_site_expressive_layout/expected/markbind/css/markbind.css
#	test/functional/test_site_templates/test_default/expected/markbind/css/markbind.css
#	test/functional/test_site_templates/test_minimal/expected/markbind/css/markbind.css
@ang-zeyu
Copy link
Contributor

Took a brief look at the code, looks good 👍.

Functionality wise, noticed some sort of flickering when the placement is top/bottom, where the 'arrow' isn't completely flush with the popover / tooltip's content. Could the flickering and positioning be fixed?

flickering

Is it possible to use an empty modal-footer slot to override the ok and cancel buttons, while preserving the user's ability to use a modal-footer slot as well? ( i'm fine with it being there though, although I think its use case is more for forms contained in modals which markbind dosen't have )

modal

@openorclose
Copy link
Contributor Author

I've removed the cancel button (just added the ok-only attribute to b-modal).

Seems like bootstrap-vue has fixed the positioning thing!

I updated bootstrap-vue.js and bootstrap-vue.css to the latest versions and they work better. They even have support for the hovering over of popovers, so that you can select text inside them.

Copy link
Contributor

@marvinchin marvinchin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach seems fine, but I think it would be great if we can leave more context for people who might come across this in the future and wonder why we are doing all of these transformations. Just a couple of questions about variables!

Also, are the issues you mentioned in the PR regarding modals and tooltips resolved? I gave it a try on the netlify site and it seems to work.

/*!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be preferable to update the bootstrap-vue assets in a separate PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok! I'll revert the change.

*/
function _transformSlottedComponents(element) {
element.children.forEach((c) => {
const child = c;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be missing something here, but could we just rename the c parameter to child directly instead? 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, It's because of our eslint rule no-param-reassign. We do seem to be assigning properties a lot though, I'll raise an issue on this.

*/

function _parseTrigger(element) {
const el = element;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for el and element

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, in the above we seem to be favouring explicitness (child instead of c), but here we seem to be favouring terseness. Is there a reason for this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll change the child function then, to keep it more consistent.

@openorclose
Copy link
Contributor Author

The approach seems fine, but I think it would be great if we can leave more context for people who might come across this in the future and wonder why we are doing all of these transformations. Just a couple of questions about variables!

Also, are the issues you mentioned in the PR regarding modals and tooltips resolved? I gave it a try on the netlify site and it seems to work.

Thanks for the review! Yeah modals and tooltips all work now, updated the PR.

I'll add in more documentation on the transformations do too

@marvinchin
Copy link
Contributor

marvinchin commented Feb 28, 2020

@openorclose there seems to be an issue with the changes that causes code blocks to line wrap when mousing over it:

code-line-wrap

The markbind.org version seems fine, so I suspect it might be related to some changes here?

I think this is a bug on master. Other netlify builds that have been rebased on top of master has the same issue.

@openorclose
Copy link
Contributor Author

@openorclose there seems to be an issue with the changes that causes code blocks to line wrap when mousing over it:

code-line-wrap

The markbind.org version seems fine, so I suspect it might be related to some changes here?

I think this is a bug on master. Other netlify builds that have been rebased on top of master has the same issue.

It's a new feature in #991, when the user hovers over too-long code it will automatically wrap

@ang-zeyu
Copy link
Contributor

ang-zeyu commented Mar 2, 2020

It's a new feature in #991, when the user hovers over too-long code it will automatically wrap

Hmm is this suppose to occur? ( its not tied to this PR though, just using your netlify preview for convenience )
https://deploy-preview-1033--markbind-master.netlify.com/userguide/makingthesitesearchable#search-bars
( Hover over the first code block and observe the width of the main container )

I should probably open an issue ( #1077) 😅

Copy link
Contributor

@marvinchin marvinchin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@marvinchin marvinchin added this to the v2.11.1 milestone Mar 2, 2020
@yamgent yamgent merged commit f215eee into MarkBind:master Mar 3, 2020
@yamgent yamgent added the pr.Enhancement 📈 Enhancement to an existing feature label Mar 3, 2020
Tejas2805 added a commit to Tejas2805/markbind that referenced this pull request Mar 7, 2020
…nvert-to-code-block

* 'master' of https://github.com/MarkBind/markbind:
  Allow changing parameter properties (MarkBind#1075)
  Custom timezone for built-in timestamp (MarkBind#1073)
  Fix reload inconsistency when updating frontmatter (MarkBind#1068)
  Implement an api to ignore content in certain tags (MarkBind#1047)
  Enable AppVeyor CI (MarkBind#1040)
  Add heading and line highlighting to code blocks (MarkBind#1034)
  Add dividers and fix bug in siteNav (MarkBind#1063)
  Fixed navbar no longer covers modals (MarkBind#1070)
  Add copy code-block plugin (MarkBind#1043)
  Render plugins on dynamic resources (MarkBind#1051)
  Documentation for Implement no-* attributes for <box>  (MarkBind#1042)
  Migrate to bootstrap-vue popovers (MarkBind#1033)
  Refactor preprocess and url processing functions (MarkBind#1026)
  Add pageNav to Using Plugins Page (MarkBind#1062)

# Conflicts:
#	docs/userGuide/syntax/siteNavigationMenus.mbdf
Tejas2805 added a commit to Tejas2805/markbind that referenced this pull request Mar 9, 2020
* 'master' of https://github.com/MarkBind/markbind:
  2.12.0
  Update outdated test files
  Update vue-strap version to v2.0.1-markbind.37
  Fix refactor to processDynamicResources (MarkBind#1092)
  Implement lazy page building for markbind serve (MarkBind#1038)
  Add warnings for conflicting/deprecated component attribs (MarkBind#1057)
  Allow changing parameter properties (MarkBind#1075)
  Custom timezone for built-in timestamp (MarkBind#1073)
  Fix reload inconsistency when updating frontmatter (MarkBind#1068)
  Implement an api to ignore content in certain tags (MarkBind#1047)
  Enable AppVeyor CI (MarkBind#1040)
  Add heading and line highlighting to code blocks (MarkBind#1034)
  Add dividers and fix bug in siteNav (MarkBind#1063)
  Fixed navbar no longer covers modals (MarkBind#1070)
  Add copy code-block plugin (MarkBind#1043)
  Render plugins on dynamic resources (MarkBind#1051)
  Documentation for Implement no-* attributes for <box>  (MarkBind#1042)
  Migrate to bootstrap-vue popovers (MarkBind#1033)
  Refactor preprocess and url processing functions (MarkBind#1026)
  Add pageNav to Using Plugins Page (MarkBind#1062)
openorclose added a commit that referenced this pull request Mar 9, 2020
A regression from #1033

Let's add back the two styles of triggers:
- click: show mouse pointer to signal that it is clickable
  and use underline dashed
- not click: no special cursor, and use underline dotted
openorclose added a commit to openorclose/markbind that referenced this pull request Mar 9, 2020
A regression from MarkBind#1033

Let's add back the two styles of triggers:
- click: show mouse pointer to signal that it is clickable
  and use underline dashed
- not click: no special cursor, and use underline dotted
marvinchin pushed a commit that referenced this pull request Mar 28, 2020
Fix a regression from #1033.

Let's add back the two styles of triggers:
- click: show mouse pointer to signal that it is clickable
  and use underline dashed
- not click: no special cursor, and use underline dotted
marvinchin pushed a commit that referenced this pull request Apr 10, 2020
marvinchin pushed a commit that referenced this pull request Apr 10, 2020
Fix a regression from #1033.

Let's add back the two styles of triggers:
- click: show mouse pointer to signal that it is clickable
  and use underline dashed
- not click: no special cursor, and use underline dotted
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr.Enhancement 📈 Enhancement to an existing feature
Projects
None yet
6 participants