зеркало из https://github.com/nextcloud/text.git
fix: 2020 let heading menu overflow workspace
* roll back parts of #1903 that broke it again. * Calculate top of menububble based on scrollHeight of content-wrapper. * Always display menububble below selected line. This will make it less likely to conflict with the menubar or mobile copy and paste toolbars. * Add cypress tests for the workspace. Signed-off-by: Azul <azul@riseup.net>
This commit is contained in:
Родитель
cec776f91c
Коммит
9a07509183
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2021 Azul <azul@riseup.net>
|
||||
*
|
||||
* @author Azul <azul@riseup.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
import { randHash } from '../utils/'
|
||||
const randUser = randHash()
|
||||
|
||||
describe('Workspace', function() {
|
||||
|
||||
before(function() {
|
||||
cy.nextcloudCreateUser(randUser, 'password')
|
||||
})
|
||||
|
||||
beforeEach(function() {
|
||||
cy.login(randUser, 'password')
|
||||
cy.visit('/apps/files')
|
||||
// isolate tests - each happens in it's own folder
|
||||
cy.createFolder(Cypress.currentTest.title)
|
||||
cy.openFile(Cypress.currentTest.title)
|
||||
})
|
||||
|
||||
it('adds a Readme.md', function() {
|
||||
cy.get('#fileList').should('not.contain', 'Readme.md')
|
||||
openWorkspace()
|
||||
.type('Hello')
|
||||
.should('contain', 'Hello')
|
||||
cy.get('#fileList').should('contain', 'Readme.md')
|
||||
})
|
||||
|
||||
it('formats text', function() {
|
||||
openWorkspace()
|
||||
.type('Format me')
|
||||
.type('{selectall}')
|
||||
;[['bold', 'strong'], ['italic', 'em'], ['strike', 's']]
|
||||
.forEach(([button, tag]) => {
|
||||
menuButton(button)
|
||||
.click()
|
||||
.should('have.class', 'is-active')
|
||||
cy.get(`.ProseMirror ${tag}`).should('contain', 'Format me')
|
||||
menuButton(button)
|
||||
.click()
|
||||
.should('not.have.class', 'is-active')
|
||||
})
|
||||
})
|
||||
|
||||
it('links via menububble', function() {
|
||||
openWorkspace()
|
||||
.type('Nextcloud')
|
||||
.type('{selectall}')
|
||||
menuBubbleButton('link').click()
|
||||
cy.get('.menububble input').type('https://nextcloud.com{enter}')
|
||||
cy.get('.ProseMirror a')
|
||||
.should('contain', 'Nextcloud')
|
||||
.should('be.visible')
|
||||
cy.get('.ProseMirror a').invoke('attr', 'href')
|
||||
.should('include', 'https://nextcloud.com')
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'open').as('windowOpen')
|
||||
})
|
||||
cy.get('.ProseMirror a').click()
|
||||
cy.get('@windowOpen').should('be.calledWith', 'https://nextcloud.com/')
|
||||
cy.get('.ProseMirror').type('{selectall}')
|
||||
menuBubbleButton('link').click()
|
||||
cy.get('.menububble input').type('/team{enter}')
|
||||
cy.get('.ProseMirror a').click()
|
||||
cy.get('@windowOpen').should('be.calledWith', 'https://nextcloud.com/team')
|
||||
})
|
||||
|
||||
it('creates headings via submenu', function() {
|
||||
openWorkspace()
|
||||
.type('Heading')
|
||||
.type('{selectall}')
|
||||
;['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach((heading) => {
|
||||
menuButton('h1').click()
|
||||
submenuButton(heading).click()
|
||||
menuButton(heading).should('have.class', 'is-active')
|
||||
cy.get(`.ProseMirror ${heading}`)
|
||||
.should('contain', 'Heading')
|
||||
menuButton(heading).click()
|
||||
submenuButton(heading).click()
|
||||
menuButton('h1').should('not.have.class', 'is-active')
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
const menuButton = (name) => {
|
||||
return cy.get(`#editor button.icon-${name}`)
|
||||
}
|
||||
|
||||
const submenuButton = (name) => {
|
||||
return cy.get(`#editor button .icon-${name}`)
|
||||
}
|
||||
|
||||
const menuBubbleButton = submenuButton
|
||||
|
||||
const openWorkspace = () => {
|
||||
cy.get('#rich-workspace').click()
|
||||
cy.get('#editor .content-wrapper').click()
|
||||
return cy.get('#rich-workspace .ProseMirror')
|
||||
}
|
|
@ -16,13 +16,21 @@ then
|
|||
fi
|
||||
|
||||
# start server if it's not running yet
|
||||
if $(npm bin)/wait-on -i 500 -t 1000 $CYPRESS_baseUrl
|
||||
if $(npm bin)/wait-on -i 500 -t 1000 $CYPRESS_baseUrl 2> /dev/null
|
||||
then
|
||||
echo Server is up at $CYPRESS_baseUrl
|
||||
else
|
||||
echo No server reached at $CYPRESS_baseUrl - starting containers.
|
||||
docker-compose up -d
|
||||
$(npm bin)/wait-on -i 500 -t 240000 $CYPRESS_baseUrl || ( docker-compose logs ; exit 1 )
|
||||
docker-compose exec -T nextcloud bash /var/www/html/apps/text/cypress/server.sh
|
||||
if $(npm bin)/wait-on -i 500 -t 240000 $CYPRESS_baseUrl 2> /dev/null
|
||||
then
|
||||
docker-compose exec -T nextcloud bash /var/www/html/apps/text/cypress/server.sh
|
||||
else
|
||||
echo Waiting for $CYPRESS_baseUrl timed out.
|
||||
echo Container logs:
|
||||
docker-compose logs
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
(cd .. && $(npm bin)/cypress $@)
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
</div>
|
||||
<slot name="header" />
|
||||
</MenuBar>
|
||||
<div class="content-wrapper">
|
||||
<div ref="wrapper" class="content-wrapper">
|
||||
<MenuBubble v-if="!readOnly && isRichEditor"
|
||||
:editor="tiptap"
|
||||
:file-path="relativePath" />
|
||||
|
|
|
@ -107,6 +107,21 @@ export default {
|
|||
isUsingDirectEditing: loadState('text', 'directEditingToken', null) !== null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
// Minimum left value for the bubble so that it stays inside the editor.
|
||||
// the width of the menububble changes depending on its state
|
||||
// during the bubblePosition calculation it has not been rendered yet.
|
||||
// so we have to hard code the minimum.
|
||||
minLeft() {
|
||||
if (this.linkMenuIsActive || !this.editor.isActive.link()) {
|
||||
return 150
|
||||
} else {
|
||||
return 225
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
methods: {
|
||||
showLinkMenu(attrs) {
|
||||
this.linkUrl = attrs.href
|
||||
|
@ -159,15 +174,14 @@ export default {
|
|||
command({ href: null })
|
||||
},
|
||||
bubblePosition(menu) {
|
||||
// below the first line, above all others
|
||||
const vertical = menu.top < 45
|
||||
? { top: `${menu.top}px` }
|
||||
: { bottom: `${menu.bottom}px` }
|
||||
const wrapper = this.$parent.$refs.wrapper
|
||||
const left = Math.max(this.minLeft, menu.left)
|
||||
return {
|
||||
...vertical,
|
||||
left: `${menu.left}px`,
|
||||
top: `${menu.top + wrapper.scrollTop + 5}px`,
|
||||
left: `${left}px`,
|
||||
}
|
||||
},
|
||||
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -231,7 +245,7 @@ export default {
|
|||
font: inherit;
|
||||
border: none;
|
||||
background: transparent;
|
||||
min-width: 150px;
|
||||
min-width: 250px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -213,12 +213,14 @@ export default {
|
|||
}
|
||||
|
||||
#rich-workspace::v-deep #editor {
|
||||
overflow: scroll !important;
|
||||
max-height: calc(40vh - 40px);
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
#rich-workspace::v-deep .content-wrapper {
|
||||
overflow: scroll !important;
|
||||
max-height: calc(40vh - 50px);
|
||||
padding-left: 10px;
|
||||
padding-bottom: 60px; /* ensure menububble fits below */
|
||||
}
|
||||
|
||||
#rich-workspace::v-deep #editor-wrapper .ProseMirror {
|
||||
|
|
Загрузка…
Ссылка в новой задаче