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

Rewrite custom borders to use SVGs insead of DIVs #6467

Open
20 of 33 tasks
wojciechczerniak opened this issue Nov 13, 2019 · 23 comments
Open
20 of 33 tasks

Rewrite custom borders to use SVGs insead of DIVs #6467

wojciechczerniak opened this issue Nov 13, 2019 · 23 comments

Comments

@wojciechczerniak
Copy link
Contributor

wojciechczerniak commented Nov 13, 2019

Description

Refactor DIV based border renderer to SVG one. The main reason behind this change is the performance of Handsontable instance with multiple cell borders. As a side effect, we've fixed a few long-standing issues with custom borders.

Oh, and they are now beautiful:
obraz

This was huge and the results are amazing. Multiple tests were added. Tested on all popular web browsers.

Epic. Work was done within smaller issues.

To Do

  • New documentation for SVG borders, including styling
  • Migration guide from DIV borders to SVG ones, including styling/printing
  • Document all the breaking changes for release notes and blog post

Related issues

We should check those

Checklist

  • Various widths: 1px, 2px, ..., 5px?
  • Various colours
  • Various combinations, thick with thin, vertical, horizontal
  • Is it possible to print the border?
  • Border spanned across rows / columns
  • Each cell border different
  • Put the performance at test, create as many borders as possible
  • Creating/removing borders from context menu
  • Creating/removing borders from API
  • How does it work with updateSettings?
  • Can we change the styles with CSS?
  • Do we have prformance lab test?
  • How does it look like on Firefox, Safari, Chrome, Edge and IE
  • Performance with and without autoColumnSize
  • With different setting of scroll (System Preferences/General on MacOs)
  • scrolling with the fixedBottomRows
@wojciechczerniak
Copy link
Contributor Author

wojciechczerniak commented Nov 13, 2019

It should be easier to test the feature/PR with this related issue list and checklist @aninde. If you have anything to add/remove feel free.

CC @AMBudnik @warpech

@AMBudnik
Copy link
Contributor

I would also add

  • resizing associated cells (+dbclick - quick resize)
  • undo/redo after scrolling /filtering /moving / resizing
  • in window scenarios (iframe)

and remember to check all the browsers and go locally ;)

@aninde
Copy link
Contributor

aninde commented Nov 26, 2019

@wojciechczerniak @AMBudnik thank you very much for preparing the checklist. Also, I would like to check custom borders with:

  • overlapping customBorders
  • intersection customBorders
  • fixed/frozen rows/columns
  • contextMenu
  • dropdownMenu - expanded dropdown list
  • autofill
  • various overlays - top, left and all
  • nested rows and headers
  • collapsible columns
  • IME (Internationalization)
  • on iPad & on mobile

@aninde
Copy link
Contributor

aninde commented Nov 28, 2019

Due to better readability I will report bugs related to svg branch here, not in PR.
I use during testing Mac Magic Mouse with the ability to vertical and horizontal scrolling or laptop's trackpad with the same feature
I don't click on the scrollbar, I don't want to lost focus on selection.

Case 1

Jumping selection #6157 (comment)

Case 2

Leaking fill handle in overlay

Chrome, Safari, Firefox / MacOs Pro, Regression.
Use the same setting as in demo https://jsfiddle.net/aninde/f30rjd6u/
cut fillhandle Firefox

Steps to reproduction

  1. Select fixed column B.
  2. Set viewport to be narrow to see horizontal and vertical scroll.
  3. Scroll it horizontally.

Result Notice that the blue square of fileHandle is rectangle, not square and is cutted while scrolling
Expected result Should work as in 7.2.2. Should be square and stay with border of fixed column during scrolling.

Case 3

Overlaying right border on frozen columns while scrolling.
Occurs on Chrome, Firefox, Opera, Microsoft Dev Edge / MacOs and very fast on safari, so it's hard to notice.

SVG Branch - Firefox 70, locally
left-border-ff-svg

7.2.2 Firefox, jsfiddle
selection7 2 2

Steps to reproduction

  1. Set viewport to be narrow to see horizontal and vertical scroll.
  2. Select one cell from column C, maybe C11.
  3. Scroll it horizontally with changed speed to the right and back to the left the faster when you are close to the end of the left margin of the site.

Result: Pink, the right border is overlaying fixed columns. Selection is doubled and moved.
Expected result: Fixed/frozen columns should be on top.

Case 4

Overlaying top border on frozen rows while scrolling.

It works worst in the Firefox, but even in Chrome and Safari doesn't look good.

  1. Comment fixedColumnsLeft, uncomment fixedRowsTow in settings.
  2. Scroll it vertically with a trackpad, without clicking to change focus.
  3. The result is similar to the bug connected to fixedColumnsLeft/frozen columns.

Firefox SVG Branch
fixedrowstop-ff-svg

Chrome SVG Branch
fixedrowsTop-chrome-svg

EDIT: All cases described in this comment also occurs on IE/Real Edge/Firefox on Windows 10.
This bug can be reproduced without a trackpad, only with a mouse with scroll-roundel by click and hold on scroll-roundel.
scroll-mouse-ff

@aninde
Copy link
Contributor

aninde commented Dec 2, 2019

Case 5

Border colors with spaces

customBorders are not rendering in any browser when there is space in color name https://jsfiddle.net/aninde/bfckronw/ 7.2.2 with svg borders

@aninde
Copy link
Contributor

aninde commented Dec 2, 2019

Case 6

Border color order

#6453 (comment) the color order is not preserved.

@aninde
Copy link
Contributor

aninde commented Dec 2, 2019

I'm not sure about the assumption with zooming and blurriness. On PR description was:
The borders should looks sharp in every browser at 100% zoom level. They might be blurry at other zoom levels that are not multiplies of 100. I'm not sure that we met that assumption.

I wear glasses so I need someone with healthy eyes to look at it too. @jansiegel @budnix @AMBudnik can you double-check how selection and customBorders looks on zoom 125%, 150% and 200% on Chrome, Firefox, Safari, IE and Microsoft Edge?
https://jsfiddle.net/aninde/6c2znt15/2/

Below, I paste print screens on the 150% zoom in and zoom into those print to see details.
Sadly the blurriness is worse on Windows. Here is example from Edge.
Screenshot 2019-12-02 at 15 49 27

Chrome/Mac OS 150% zoom
chrome-150-svg
Screenshot 2019-12-02 at 15 07 31

chrome-150-latest
Screenshot 2019-12-02 at 15 07 03

@jansiegel
Copy link
Member

jansiegel commented Dec 2, 2019

@aninde

The borders should looks sharp in every browser at 100% zoom level. They might be blurry at other zoom levels that are not multiplies of 100.

can you double-check how selection and customBorders looks on zoom 125%, 150% and 200% on Chrome, Firefox, Safari, IE and Microsoft Edge?

To me they look sharp at multiplies of 100, and a little blurry on other levels, just as @warpech stated in #6157, at least on Chrome, Firefox and Safari.

@aninde
Copy link
Contributor

aninde commented Dec 4, 2019

Summary of testing

Found:

Fixed issues that they need to add tests to:

warpech added a commit that referenced this issue Dec 4, 2019
@warpech
Copy link
Member

warpech commented Dec 4, 2019

@krzysztofspilka asked me for estimate how much time is needed to finish. Here are my pessimistic estimates (total: up to 9 days).

These are the occurences of the same problem: There is a race condition between border rendering and overlay positioning when main window serves as a scroll container. Additional problem, that seems to be related: in some Walkontable tests, I must call Walkontable.draw() twice to get the desired rendering. Work in progress to fix this is on the branch https://github.com/handsontable/handsontable/commits/feature/issue-6064-touch-overlays, but the further I get, the more I get entangled in quirks of overlay rendering. I might need 2-3 days for this.

This might take 1 day.

This might take 1 day.

This might take 1 day.

Fixed issues that they need to add tests to:

Can we please move this out of scope of the PR #6157. My rough estimate is that it might take 1 day of work for each test.

warpech added a commit that referenced this issue Dec 5, 2019
… spaces

fixes the problem described in #6467 (comment): customBorders are not
rendering in any browser when there is space in color name jsfiddle.net/aninde/bfckronw 7.2.2 with svg borders
@warpech
Copy link
Member

warpech commented Dec 16, 2019

As said in point 2 of #6157 OP, the feature/issue-6064 branch removes the only way to restyle borders using CSS. Meaning: it will no longer be possible to set the border colors of current selection, area selection and fill area using CSS (demo: https://jsfiddle.net/kayw7e20/1/).

I am not sure of the severity of this. Was such possibility ever advertised in the docs? Are our customers using it? If yes, then I think we need to add a proper API for selection customization and explain the migration.

@wojciechczerniak, @krzysztofspilka WDYT?

@aninde
Copy link
Contributor

aninde commented Dec 16, 2019

@warpech the demo in your comment is in the stable version 7.2.2
Screenshot 2019-12-16 at 14 43 09

This is how selection looks on your PR with changed CSS. Only square is changed. Is this in line with the assumptions?
7.2.2. v. with svg, 29.11.19 build. https://jsfiddle.net/aninde/4zkrvqgs/
Screenshot 2019-12-16 at 14 39 15

@warpech
Copy link
Member

warpech commented Dec 16, 2019

This is how selection looks on your PR with changed CSS. Only square is changed. Is this in line with the assumptions?

It is in line with my assumptions, yes. I changed the CSS API of the "borders" but did not change the CSS API of the "corners".

@wojciechczerniak
Copy link
Contributor Author

I am not sure of the severity of this. Was such possibility ever advertised in the docs? Are our customers using it? If yes, then I think we need to add a proper API for selection customization and explain the migration.

It was: https://handsontable.com/docs/1.17.0/tutorial-styling.html and was removed.

I don't have evidence for this, but it might be an important part of the customization process. Everything that is a part of the UI should be customizable in terms of color at least. In #6538 @AMBudnik proposes a feature to keep this feature. I've found one question about it #2487

warpech added a commit that referenced this issue Dec 19, 2019
…rolling

reproduces the Case 1, Case 3 and Case 4 from #6467 when the following lines are commented out:

```
wt.draw(); // TODO as it turns out, the desired rendering is only visible after the second draw. A problem that does not appear in HOT but appears in raw WOT. Why?
```
warpech added a commit that referenced this issue Dec 28, 2019
because I think that the key in solving the "Case 1", "Case 3" and "Case 4" problems in #6467 (comment) lies in the fact that adjustElementsPosition was done irrespective of redrewClone.

the next step will be to move "adjustElementsPosition" before "redrawClone"

Note that in this change, I needed to change one test (in "selectAll.spec.js"). I believe that the previous test was actually wrong and the new test fixture is the correct one, because there are less rows drawn.
@AMBudnik AMBudnik added the Custom borders Plugin label Dec 30, 2019
@warpech
Copy link
Member

warpech commented Jan 2, 2020

I don't have evidence for this, but it might be an important part of the customization process. Everything that is a part of the UI should be customizable in terms of color at least.

I made a small PR that implements this: #6593. However there is one pitfall (see in the PR's first comment).

@warpech
Copy link
Member

warpech commented Jan 2, 2020

I made a big PR that fixes "Case 1", "Case 3" and "Case 4" explained in this issue: #6594

"Case 5" and "Case 6" were fixed in early December in #6157

@aninde are you sure that "Case 2" is a regression in the SVG borders branch? I think I can reproduce the same problem with 7.2.2 and 7.3.0.

@aninde
Copy link
Contributor

aninde commented Jan 2, 2020

@warpech you are correct. "Case 2" it is not regression, it can be reproduced on 7+ version, my mistake.

@aninde
Copy link
Contributor

aninde commented Jan 8, 2020

New issue explaining a problem discovered in the PR: #6626 (Selection on printed Handsontable)

@aninde
Copy link
Contributor

aninde commented Jan 8, 2020

  1. The bottom border is doubled, it emerges from under the customBorder. This occurs when fixed rows are enabled. EDIT: This is out of Svg-borders scope, because it is not a regression. It's official bug, that I will report.
    testing 7.3.0.pdf

Screenshot 2020-01-08 at 14 21 30

without fixed rows this bug do not occur. Lack of the bottom border of colHeader is also in version 7.3.0, which is why I don't report it here.
Screenshot 2020-01-08 at 14 25 40

@wojciechczerniak
Copy link
Contributor Author

wojciechczerniak commented Jan 8, 2020

Excel does not print them. IMHO, it should not be visible on the print in Handsontable.

+1 on that. We should hide the rest of the selection and white rectangle that covers the grid.

But it's out of the scope so up to you.

Edit: not only custom border is doubled at the bottom:

obraz

warpech added a commit that referenced this issue Jan 13, 2020
* refactor: move resetFixedPosition calls to a new method in to the Overlays class

because it unnecessarily obfuscates the code in the Table class

* refactor: simplify lengthy stack traces by using a separate method to draw Walkontable clones

because previously the stack traces appeared to be recursive when Walkontable.draw (for overlay) was called inside Walkontable.draw (for master).

from now on, Walkontable.drawClone (for overlay) is called inside Walkontable.draw (for master).

this allows me to do refactor further in the following commits

* refactor: split the Walkontable class (core.js) into a real Core class and child classes Master, Clone

because it allows to further simplify the code of Walkontable.Table

* refactor: remove properties `drawn` and `drawInterrupted` from Clone class

because it seems that they only have use in Master

* refactor: limit the amount of information passed from Overlay to Clone

because most of it was not used

* refactor: getOverlayName is only needed in Clone, not in Master

because in Master it only returns one value deterministically

* refactor: the `hasOversizedColumnHeadersMarked` object is only ever populated in case of Master

as it was revealed by the previous commit. Hence, it makes sense to flatten the object into a boolean

* code style: use the name "overlayRoot" consistently

because the same variable is called "overlayRoot" in many other contexts

* refactor: merge the methods "adjustRootElementSize" and "adjustRootChildrenSize"

because having them separately only obfuscates the code in my opinion, making it hard for me to debug other problems

this commit also improves the JSDoc for two functions

* change: have just a single instance of "rowUtils" and "columnUtils" for master and all clones

because so far every clone had it's own instances of "rowUtils" and "columnUtils", which was bad for memory usage and debugging

* refactor: move "rowUtils" and "columnUtils" from Table to Master class

because it is more used from other classes (and externally) than from within the Table class

* test fix: change the expected path depending on the scrollbar width

because the coordinates on Windows are different due to to the different scrollbar width

* test: check for the draw position of borders in overlays when fast scrolling

reproduces the Case 1, Case 3 and Case 4 from #6467 when the following lines are commented out:

```
wt.draw(); // TODO as it turns out, the desired rendering is only visible after the second draw. A problem that does not appear in HOT but appears in raw WOT. Why?
```

* refactor: rearrange some if(isMaster) conditions

because they could be simpler and/or more readable

* refactor: use unpacked values of wtViewport and wtOverlays

since they already exist in this function, use them consistently

* refactor: don't use own instance through an external reference

because that's just a waste of CPU cycles

* step1

* step 2 - move refreshSelections as the last thing in draw

* step 3 - calling syncScrollWithMaster at the end of draw() seems redundant

at least no tests fail

* refactor: remove "this.instance" aliases of "this.wot"

because they are marked as legacy and deprecated since long time

* refactor: rename "wtOverlay.wot" to "wtOverlays.master"

because it is not obvious what instance does "wot" refer to. There already is one instance referred to as "clone", so it makes sense to refer to the original counterpart as "master"

* refactor: rename "clone.cloneSource" to "clone.overlay.master"

for the most consistent addressing of "master" WOT instance I can think of

* refactor: previous refactor exposed quite redundant way of finding the "master" instance

since we are already in an "overlay" class, we know the "master" class is available always as "this.master"

* refactor: more consistent naming of a variable

because it is already called "master" in many other places

* refactor: split out the draw() function used by the master table

because it worked way too different compared to overlay tables. Also, in OOP it is a anti-pattern for the
parent class to know about the child classes.

* refactor: remove the property "tableOffset"

because it was not used anywhere

* refactor: use an else clause to call "setHeaderContentRenderers" only one per draw

because one additional call in case of bottom/bottomLeftCorner clones was redundant

* refactor: do not return a value from table draw()

because the result was not used anywhere

* refactor: do not return a value from overlay.updateStateOfRendering() and overlays.prepareOverlays()

because the results were not used anywhere

* refactor: move "this.holderOffset" definition to master table constructor

because this property is only used in the master table

* refactor: reduce the number of variables used

* refactor: remove table.isTableVisible

because it was not used anywhere

* refactor: use table.wtRootElement consistently

because in many places an unnecessary DOM traversal was used

* refactor: remove redundant checks for "this.clone"

because it is always defined after the constructor is finished

* change: move the calls of "wtOverlays" methods closer to each other

because it will allow me to combine these methods.

The method "resetFixedPositions", in case of a full render, is now only
called if the rendering was not skipped by the "beforeDraw" hook

* change: move the call of "prepareOverlays" down

to make it closer to calls of other methods of "wtOverlays". This allows
me to bulk these methods into one in the next commits

* change: don't prepare overlays in fast draw

because they are always already prepared

* refactor: move "prepareOverlays", "applyToDom", "resetFixedPositions" into "refresh"

because as single API entry point ("wtOverlays.refresh") it allows to better understand the context in which it is used

* change: don't call "refreshSelections" in full render if rendering is skipped

don't call "refreshSelections" in full render if rendering is skipped in the "beforeDraw" hook. I don't have a particular problem with it, but I am changing it because it seems redundant and I want to clean up the code from unnecessary calls.

* refactor: reduce the ambiguity in overlay method names

overlay.refresh -> redrawClone
overlay.reset -> resetElementsSize (analogous to overlay.adjustElementsSize)
overlay.resetFixedPosition -> adjustElementsPosition (analogous to overlay.adjustElementsSize)
overlay.applyToDOM -> workaroundsForPositionAndSize (really, this function needs like it needs to be removed)
overlay.adjustRootElementSize -> _adjustElementsSize

overlays.refresh -> refreshClones
overlays.refreshAll -> refreshMasterAndClones
overlays.resetFixedPositions -> adjustElementsPositions
overlays.adjustElementsSize -> adjustElementsSizes (plural, analogous to adjustElementsPositions)
overlays.applyToDOM -> workaroundsForPositionsAndSizes (plural, analogous to workaroundsForPositionAndSize)

* refactor: remove the method "syncOverlayOffset"

because it wasn't clear why this particular code is not inlined inside "workaroundsForPositionAndSize" just like everything else

* change: merge methods "syncScrollPositions", "syncScrollWithMaster" into one "propagateMasterScrollPositionsToClones"

because these two methods were almost identical

* refactor: remove the master element's properties in the overlay classes

because it was very confusing that they refer to the master instance. Better to not have such properties at all,
to avoid ambiguity

Two were not used at all: TABLE, wtRootElement. Three were used: hider, holder, spreader

* change: don't check if the master table is visible

because at the point overlays.workaroundsForPositionsAndSizes is called, it should always
be visible

* refactor: don't use destructuring assignment on master instance in _adjustElementsSize

because it is less confusing if I can see which lines of code use the master and which use the clone

* change: don't call adjustElementsSize in workaroundsForPositionAndSize

because there is no clear reason why this is called. All tests pass without it

* refactor: use less variables

because the logic was confusing

* change: don't overwrite the flag "needFullRender" in "redrawClone"

because it was already done in "updateStateOfRendering" and I don't understand why it is needed again. No tests fail without it.

* refactor: change the way the destructuring assignment is used

to prevent confusion when we are using the master and when the clone instance

this commit also changes the constant names to be consistent:
masterParent -> masterRootElement
overlayRoot -> overlayRootElement
overlayRootStyle -> overlayRootElementStyle

* refactor: DRY code to set trimmingContainer

because it was using unneccessary repetitions

* change: remove some calls to "adjustElementsSize" and "adjustElementsSizes"

because they seem redundant.

It was impossible to remove the line in "table.js" without removing the lines in "top.js", "left.js" and "bottom.js", because otherwise tests failed

* refactor: flatten the method "adjustElementsSize"

and inline the implementation method "_adjustElementsSize" inside "adjustElementsSize" to make reasoning about the code simpler

* change: cluster lines that call "adjustElementsSizes" closer together

as a step in unifying as much code as possible

* refactor: simplify if/else clause

because the nested ifs were redundant

* change: remove calls to "adjustElementsSizes" that seem redundant

because no tests fail when they are skipped

* refactor: rename "workaroundsForPosition(s)AndSize(s)" to "workaroundsForPosition(s)"

because it no longer contains code related to sizing

* change: do not run "adjustElementsPositions" in fastDraw

I took a hint from "workaroundsForPositions"... if "workaroundsForPositions" are run only in fastDraw, why should it be otherwise for "adjustElementsPositions"?

* change: incorporate "workaroundsForPositons" into "adjustElementsPositions"

because these methods are only called together, there is a big chance they can be combined with a slightly different order of function calls

* refactor: merge methods "workaroundsForPosition", "repositionOverlay" into "adjustElementsPosition"

because these methods were always called together and dealt with a similar topic

* refactor: remove the constant that holds the value of "getScrollbarWidth"

because that constant was only used in one place. Better to call "getScrollbarWidth" there directly

* refactor: use "overlayRootElementStyle" consistently

since it was already defined in this method, use it in all relevant lines

* change: inline "adjustElementsPositions" in "redrawClones"

because I think that the key in solving the "Case 1", "Case 3" and "Case 4" problems in #6467 (comment) lies in the fact that adjustElementsPosition was done irrespective of redrewClone.

the next step will be to move "adjustElementsPosition" before "redrawClone"

Note that in this change, I needed to change one test (in "selectAll.spec.js"). I believe that the previous test was actually wrong and the new test fixture is the correct one, because there are less rows drawn.

* change: calculate trimmingContainer once for all overlays

because calculating it for each and every overlay was redundant, since it always has the same result

* change: determine the master table's trimming container once per draw

because calculating it for each and every overlay was redundant, since it always has the same result

* refactor: remove the need to set "trimmingContainer" property on an overlay

because the master table's "trimmingContainer" property can be used directly, since it is always equal

* refactor: move element resizing of topLeft and bottomLeft overlays to a separate method

because that is analogous to top, bottom and left overlays. This allows to update all overlays in a consistent way.

* refactor: move workaround that fixes CSS created in "clone.draw" directly after "clone.draw"

because the method, where this code was previously, failed if it was called before "clone.draw"

* change: call `adjustElementsPosition` before `redrawClone`

this allows to remove double `wt.draw();` workarounds used in `border.spec.js` and `selection.spec.js`

* change: remove dead code about debug overlay

Debug overlay was removed in 9e8b832. Some leftover CSS rules were cleaned up in e2ee03d. This commit removes the remaining code related to the debug overlay.

* change: call "adjustElementsPosition" unconditionally (reverts d2363d2)

because overlays must be repositioned also in the case of fast draw. Such use is not tested, but becomes apparent immediately in manual tests

Putting this method inside "redrawClone" in more DRY

* refactor: cleanup temporary comments

because they were only used while work was in progress

* refactor: move code related only to "master" overlay

to the constructor of that overlay

* change: calculate hypothetical position of the neighboring cell instead of measuring TD in another overlay

because measuring in another overlay will not always is possible if the table is scrolled far from the overlay edge. The new solution calculated the hypothetical position of the neighboring cell by assuming the position and the dimensions of the last cell rendered in the current overlay.

This changes the fix for the problem "the overlays overlaps custom borders". The original problem was explained in #6157 (review). The fix was introduced in d62d215 and later modified in d831e90 and 1c6fc14.

* code cleanup: remove redundant comments

because one of them was relevant to the behavior changed in f8c226b. The other comment was added unintentionally in that commit

* fix tests in Windows

verified in Firefox and Chrome. Using the scrollbar size in the equation was redundant after change f8c226b
@wojciechczerniak wojciechczerniak mentioned this issue Jan 14, 2020
204 tasks
@warpech
Copy link
Member

warpech commented Jan 15, 2020

Keep in mind that the problem still exists that we cannot set custom styles for area selection, until the area selection was used. It is explained in more details in #6593 (comment) (OP and the first comment by @aninde).

Edit: There's a new issue for that: #6656

Edit 2: #6656 is done.

@warpech warpech changed the title Refactor custom borders to use SVGs insead of DIVs Rewrite custom borders to use SVGs insead of DIVs Mar 11, 2020
@warpech
Copy link
Member

warpech commented Jan 13, 2022

I closed the SVG borders PR #6157, becuase it got largely outdated. At some point, we will reimplement this on a fresh branch.

@adrianszymanski89
Copy link
Contributor

Inform ZD

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants