Compare commits
No commits in common. "385699abc1eb3db84cc433af066a8fd040e7f254" and "4f8dce9c5e3d331176288eed616c65ecd54748ab" have entirely different histories.
385699abc1
...
4f8dce9c5e
8 changed files with 37 additions and 171 deletions
|
|
@ -27,7 +27,6 @@ $app->post('/api/auth/register', [AuthController::class, 'register']);
|
||||||
|
|
||||||
// Authenticated routes (any logged-in user)
|
// Authenticated routes (any logged-in user)
|
||||||
$app->group('', function (RouteCollectorProxy $group) {
|
$app->group('', function (RouteCollectorProxy $group) {
|
||||||
$group->get('/', [ViewController::class, 'home']);
|
|
||||||
$group->get('/home', [ViewController::class, 'home']);
|
$group->get('/home', [ViewController::class, 'home']);
|
||||||
$group->get('/today', [ViewController::class, 'today']);
|
$group->get('/today', [ViewController::class, 'today']);
|
||||||
$group->get('/texts', [ViewController::class, 'userTexts']);
|
$group->get('/texts', [ViewController::class, 'userTexts']);
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,7 @@ describe('The admin text detail page', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('clicking "Add child" reveals an inline form', () => {
|
it('clicking "Add child" reveals an inline form', () => {
|
||||||
cy.get('#text-detail li').first().activateNode()
|
cy.get('#text-detail li').first().children('button.add-child').click()
|
||||||
.children('button.add-child').click()
|
|
||||||
cy.get('#text-detail li').first().children('input.child-title').should('be.visible')
|
cy.get('#text-detail li').first().children('input.child-title').should('be.visible')
|
||||||
cy.get('#text-detail li').first().children('button.save-child').should('be.visible')
|
cy.get('#text-detail li').first().children('button.save-child').should('be.visible')
|
||||||
})
|
})
|
||||||
|
|
@ -42,8 +41,7 @@ describe('The admin text detail page', () => {
|
||||||
cy.intercept('POST', '/api/nodes').as('createNode')
|
cy.intercept('POST', '/api/nodes').as('createNode')
|
||||||
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
||||||
|
|
||||||
cy.get('#text-detail > ul > li').first().activateNode()
|
cy.get('#text-detail > ul > li').first().children('button.add-child').click()
|
||||||
.children('button.add-child').click()
|
|
||||||
cy.get('#text-detail > ul > li').first().children('input.child-title').type('New Child Node')
|
cy.get('#text-detail > ul > li').first().children('input.child-title').type('New Child Node')
|
||||||
cy.get('#text-detail > ul > li').first().children('button.save-child').click()
|
cy.get('#text-detail > ul > li').first().children('button.save-child').click()
|
||||||
|
|
||||||
|
|
@ -57,8 +55,7 @@ describe('The admin text detail page', () => {
|
||||||
cy.intercept('POST', '/api/nodes').as('createNode')
|
cy.intercept('POST', '/api/nodes').as('createNode')
|
||||||
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
||||||
|
|
||||||
cy.get('#text-detail > ul > li > ul > li').first().activateNode()
|
cy.get('#text-detail > ul > li > ul > li').first().children('button.add-child').click()
|
||||||
.children('button.add-child').click()
|
|
||||||
cy.get('#text-detail > ul > li > ul > li').first().children('input.child-title').type('Nested Child Node')
|
cy.get('#text-detail > ul > li > ul > li').first().children('input.child-title').type('Nested Child Node')
|
||||||
cy.get('#text-detail > ul > li > ul > li').first().children('button.save-child').click()
|
cy.get('#text-detail > ul > li > ul > li').first().children('button.save-child').click()
|
||||||
|
|
||||||
|
|
@ -72,9 +69,6 @@ describe('The admin text detail page', () => {
|
||||||
cy.intercept('POST', '/api/nodes').as('createNode')
|
cy.intercept('POST', '/api/nodes').as('createNode')
|
||||||
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
||||||
|
|
||||||
cy.get('#text-detail > ul > li > ul > li')
|
|
||||||
.first()
|
|
||||||
.activateNode()
|
|
||||||
cy.get('#text-detail > ul > li > ul > li')
|
cy.get('#text-detail > ul > li > ul > li')
|
||||||
.first()
|
.first()
|
||||||
.find('button.toggle-children')
|
.find('button.toggle-children')
|
||||||
|
|
@ -113,7 +107,6 @@ describe('The admin text detail page', () => {
|
||||||
|
|
||||||
cy.get('#text-detail > ul > li')
|
cy.get('#text-detail > ul > li')
|
||||||
.first()
|
.first()
|
||||||
.activateNode()
|
|
||||||
.children('button.add-child')
|
.children('button.add-child')
|
||||||
.click()
|
.click()
|
||||||
cy.get('#text-detail > ul > li')
|
cy.get('#text-detail > ul > li')
|
||||||
|
|
@ -130,7 +123,6 @@ describe('The admin text detail page', () => {
|
||||||
it('opening add-child on another node closes the first one', () => {
|
it('opening add-child on another node closes the first one', () => {
|
||||||
cy.get('#text-detail > ul > li')
|
cy.get('#text-detail > ul > li')
|
||||||
.first()
|
.first()
|
||||||
.activateNode()
|
|
||||||
.children('button.add-child')
|
.children('button.add-child')
|
||||||
.click()
|
.click()
|
||||||
cy.get('#text-detail > ul > li')
|
cy.get('#text-detail > ul > li')
|
||||||
|
|
@ -140,7 +132,6 @@ describe('The admin text detail page', () => {
|
||||||
|
|
||||||
cy.get('#text-detail > ul > li > ul > li')
|
cy.get('#text-detail > ul > li > ul > li')
|
||||||
.first()
|
.first()
|
||||||
.activateNode()
|
|
||||||
.children('button.add-child')
|
.children('button.add-child')
|
||||||
.click()
|
.click()
|
||||||
|
|
||||||
|
|
@ -161,7 +152,6 @@ describe('The admin text detail page', () => {
|
||||||
it('opening bulk-add closes an open add-child form', () => {
|
it('opening bulk-add closes an open add-child form', () => {
|
||||||
cy.get('#text-detail > ul > li')
|
cy.get('#text-detail > ul > li')
|
||||||
.first()
|
.first()
|
||||||
.activateNode()
|
|
||||||
.children('button.add-child')
|
.children('button.add-child')
|
||||||
.click()
|
.click()
|
||||||
cy.get('#text-detail > ul > li')
|
cy.get('#text-detail > ul > li')
|
||||||
|
|
@ -171,7 +161,6 @@ describe('The admin text detail page', () => {
|
||||||
|
|
||||||
cy.get('#text-detail > ul > li > ul > li')
|
cy.get('#text-detail > ul > li > ul > li')
|
||||||
.first()
|
.first()
|
||||||
.activateNode()
|
|
||||||
.children('button.bulk-add-children')
|
.children('button.bulk-add-children')
|
||||||
.click()
|
.click()
|
||||||
|
|
||||||
|
|
@ -189,8 +178,7 @@ describe('The admin text detail page', () => {
|
||||||
cy.intercept('POST', '/api/nodes').as('createNode')
|
cy.intercept('POST', '/api/nodes').as('createNode')
|
||||||
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
||||||
|
|
||||||
cy.get('#text-detail > ul > li').first().activateNode()
|
cy.get('#text-detail > ul > li').first().children('button.add-child').click()
|
||||||
.children('button.add-child').click()
|
|
||||||
cy.get('#text-detail > ul > li').first().children('input.child-title').type('Persistent Child')
|
cy.get('#text-detail > ul > li').first().children('input.child-title').type('Persistent Child')
|
||||||
cy.get('#text-detail > ul > li').first().children('button.save-child').click()
|
cy.get('#text-detail > ul > li').first().children('button.save-child').click()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,16 +20,14 @@ describe('Bulk add children on the admin text detail page', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('clicking "Bulk add children" reveals inline form inputs', () => {
|
it('clicking "Bulk add children" reveals inline form inputs', () => {
|
||||||
cy.get('#text-detail > ul > li').first().activateNode()
|
cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click()
|
||||||
.children('button.bulk-add-children').click()
|
|
||||||
cy.get('#text-detail > ul > li').first().children('input.bulk-title').should('be.visible')
|
cy.get('#text-detail > ul > li').first().children('input.bulk-title').should('be.visible')
|
||||||
cy.get('#text-detail > ul > li').first().children('input.bulk-count').should('be.visible')
|
cy.get('#text-detail > ul > li').first().children('input.bulk-count').should('be.visible')
|
||||||
cy.get('#text-detail > ul > li').first().children('button.save-bulk').should('be.visible')
|
cy.get('#text-detail > ul > li').first().children('button.save-bulk').should('be.visible')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('clicking "Bulk add children" again hides the form', () => {
|
it('clicking "Bulk add children" again hides the form', () => {
|
||||||
cy.get('#text-detail > ul > li').first().activateNode()
|
cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click()
|
||||||
.children('button.bulk-add-children').click()
|
|
||||||
cy.get('#text-detail > ul > li').first().children('input.bulk-title').should('be.visible')
|
cy.get('#text-detail > ul > li').first().children('input.bulk-title').should('be.visible')
|
||||||
cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click()
|
cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click()
|
||||||
cy.get('#text-detail > ul > li').first().children('input.bulk-title').should('not.exist')
|
cy.get('#text-detail > ul > li').first().children('input.bulk-title').should('not.exist')
|
||||||
|
|
@ -40,8 +38,7 @@ describe('Bulk add children on the admin text detail page', () => {
|
||||||
it('can bulk add children to the root node', () => {
|
it('can bulk add children to the root node', () => {
|
||||||
cy.intercept('POST', '/api/nodes/bulk').as('bulkCreate')
|
cy.intercept('POST', '/api/nodes/bulk').as('bulkCreate')
|
||||||
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
||||||
cy.get('#text-detail > ul > li').first().activateNode()
|
cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click()
|
||||||
.children('button.bulk-add-children').click()
|
|
||||||
cy.get('#text-detail > ul > li').first().children('input.bulk-title').type('Page')
|
cy.get('#text-detail > ul > li').first().children('input.bulk-title').type('Page')
|
||||||
cy.get('#text-detail > ul > li').first().children('input.bulk-count').type('3')
|
cy.get('#text-detail > ul > li').first().children('input.bulk-count').type('3')
|
||||||
cy.get('#text-detail > ul > li').first().children('button.save-bulk').click()
|
cy.get('#text-detail > ul > li').first().children('button.save-bulk').click()
|
||||||
|
|
@ -54,8 +51,7 @@ describe('Bulk add children on the admin text detail page', () => {
|
||||||
|
|
||||||
it('does not submit if title prefix is empty', () => {
|
it('does not submit if title prefix is empty', () => {
|
||||||
cy.intercept('POST', '/api/nodes/bulk').as('bulkCreate')
|
cy.intercept('POST', '/api/nodes/bulk').as('bulkCreate')
|
||||||
cy.get('#text-detail > ul > li').first().activateNode()
|
cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click()
|
||||||
.children('button.bulk-add-children').click()
|
|
||||||
cy.get('#text-detail > ul > li').first().children('input.bulk-count').type('3')
|
cy.get('#text-detail > ul > li').first().children('input.bulk-count').type('3')
|
||||||
cy.get('#text-detail > ul > li').first().children('button.save-bulk').click()
|
cy.get('#text-detail > ul > li').first().children('button.save-bulk').click()
|
||||||
cy.get('@bulkCreate.all').should('have.length', 0)
|
cy.get('@bulkCreate.all').should('have.length', 0)
|
||||||
|
|
@ -63,8 +59,7 @@ describe('Bulk add children on the admin text detail page', () => {
|
||||||
|
|
||||||
it('does not submit if count is empty', () => {
|
it('does not submit if count is empty', () => {
|
||||||
cy.intercept('POST', '/api/nodes/bulk').as('bulkCreate')
|
cy.intercept('POST', '/api/nodes/bulk').as('bulkCreate')
|
||||||
cy.get('#text-detail > ul > li').first().activateNode()
|
cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click()
|
||||||
.children('button.bulk-add-children').click()
|
|
||||||
cy.get('#text-detail > ul > li').first().children('input.bulk-title').type('Page')
|
cy.get('#text-detail > ul > li').first().children('input.bulk-title').type('Page')
|
||||||
cy.get('#text-detail > ul > li').first().children('button.save-bulk').click()
|
cy.get('#text-detail > ul > li').first().children('button.save-bulk').click()
|
||||||
cy.get('@bulkCreate.all').should('have.length', 0)
|
cy.get('@bulkCreate.all').should('have.length', 0)
|
||||||
|
|
@ -73,8 +68,7 @@ describe('Bulk add children on the admin text detail page', () => {
|
||||||
it('pressing Enter in the bulk-count input submits', () => {
|
it('pressing Enter in the bulk-count input submits', () => {
|
||||||
cy.intercept('POST', '/api/nodes/bulk').as('bulkCreate')
|
cy.intercept('POST', '/api/nodes/bulk').as('bulkCreate')
|
||||||
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
||||||
cy.get('#text-detail > ul > li').first().activateNode()
|
cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click()
|
||||||
.children('button.bulk-add-children').click()
|
|
||||||
cy.get('#text-detail > ul > li').first().children('input.bulk-title').type('Enter')
|
cy.get('#text-detail > ul > li').first().children('input.bulk-title').type('Enter')
|
||||||
cy.get('#text-detail > ul > li').first().children('input.bulk-count').type('2{enter}')
|
cy.get('#text-detail > ul > li').first().children('input.bulk-count').type('2{enter}')
|
||||||
cy.wait('@bulkCreate').its('response.statusCode').should('eq', 201)
|
cy.wait('@bulkCreate').its('response.statusCode').should('eq', 201)
|
||||||
|
|
@ -86,8 +80,7 @@ describe('Bulk add children on the admin text detail page', () => {
|
||||||
it('pressing Enter in the bulk-title input submits', () => {
|
it('pressing Enter in the bulk-title input submits', () => {
|
||||||
cy.intercept('POST', '/api/nodes/bulk').as('bulkCreate')
|
cy.intercept('POST', '/api/nodes/bulk').as('bulkCreate')
|
||||||
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
||||||
cy.get('#text-detail > ul > li').first().activateNode()
|
cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click()
|
||||||
.children('button.bulk-add-children').click()
|
|
||||||
cy.get('#text-detail > ul > li').first().children('input.bulk-count').type('2')
|
cy.get('#text-detail > ul > li').first().children('input.bulk-count').type('2')
|
||||||
cy.get('#text-detail > ul > li').first().children('input.bulk-title').type('Title{enter}')
|
cy.get('#text-detail > ul > li').first().children('input.bulk-title').type('Title{enter}')
|
||||||
cy.wait('@bulkCreate').its('response.statusCode').should('eq', 201)
|
cy.wait('@bulkCreate').its('response.statusCode').should('eq', 201)
|
||||||
|
|
@ -99,8 +92,7 @@ describe('Bulk add children on the admin text detail page', () => {
|
||||||
it('bulk added nodes persist after page reload', () => {
|
it('bulk added nodes persist after page reload', () => {
|
||||||
cy.intercept('POST', '/api/nodes/bulk').as('bulkCreate')
|
cy.intercept('POST', '/api/nodes/bulk').as('bulkCreate')
|
||||||
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
||||||
cy.get('#text-detail > ul > li').first().activateNode()
|
cy.get('#text-detail > ul > li').first().children('button.bulk-add-children').click()
|
||||||
.children('button.bulk-add-children').click()
|
|
||||||
cy.get('#text-detail > ul > li').first().children('input.bulk-title').type('Page')
|
cy.get('#text-detail > ul > li').first().children('input.bulk-title').type('Page')
|
||||||
cy.get('#text-detail > ul > li').first().children('input.bulk-count').type('3')
|
cy.get('#text-detail > ul > li').first().children('input.bulk-count').type('3')
|
||||||
cy.get('#text-detail > ul > li').first().children('button.save-bulk').click()
|
cy.get('#text-detail > ul > li').first().children('button.save-bulk').click()
|
||||||
|
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
describe('The admin text detail page horizontal layout', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
cy.exec('npm run db:seed')
|
|
||||||
cy.loginAsAdmin()
|
|
||||||
cy.intercept('GET', '/api/texts/0').as('getText')
|
|
||||||
cy.intercept('GET', '/api/nodes/0').as('getNodes')
|
|
||||||
cy.visit('/admin/texts/0')
|
|
||||||
cy.wait('@getText')
|
|
||||||
cy.wait('@getNodes')
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
cy.exec('npm run db:wipe')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renders child list to the right of the parent title', () => {
|
|
||||||
cy.get('#text-detail > ul > li').first().as('rootLi')
|
|
||||||
cy.get('@rootLi').children('span').first().as('rootTitle')
|
|
||||||
cy.get('@rootLi').children('ul').first().as('childList')
|
|
||||||
|
|
||||||
cy.get('@childList').should('be.visible')
|
|
||||||
|
|
||||||
cy.get('@rootTitle').then(($title) => {
|
|
||||||
const titleRect = $title[0].getBoundingClientRect()
|
|
||||||
cy.get('@childList').then(($list) => {
|
|
||||||
const listRect = $list[0].getBoundingClientRect()
|
|
||||||
expect(listRect.left).to.be.greaterThan(titleRect.right)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('stacks sibling child nodes vertically within the right column', () => {
|
|
||||||
cy.intercept('POST', '/api/nodes').as('createNode')
|
|
||||||
cy.intercept('GET', '/api/nodes/0').as('getNodesRefresh')
|
|
||||||
|
|
||||||
cy.get('#text-detail > ul > li')
|
|
||||||
.first()
|
|
||||||
.activateNode()
|
|
||||||
.children('button.add-child')
|
|
||||||
.click()
|
|
||||||
cy.get('#text-detail > ul > li')
|
|
||||||
.first()
|
|
||||||
.children('input.child-title')
|
|
||||||
.type('Sibling One{enter}')
|
|
||||||
cy.wait('@createNode')
|
|
||||||
cy.wait('@getNodesRefresh')
|
|
||||||
|
|
||||||
cy.get('#text-detail > ul > li > ul > li').should(
|
|
||||||
'have.length.greaterThan',
|
|
||||||
1
|
|
||||||
)
|
|
||||||
|
|
||||||
cy.get('#text-detail > ul > li > ul > li').then(($siblings) => {
|
|
||||||
const firstRect = $siblings[0].getBoundingClientRect()
|
|
||||||
const lastRect = $siblings[$siblings.length - 1]
|
|
||||||
.getBoundingClientRect()
|
|
||||||
expect(lastRect.top).to.be.greaterThan(firstRect.top)
|
|
||||||
expect(Math.abs(lastRect.left - firstRect.left)).to.be.lessThan(2)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -30,7 +30,7 @@ describe('The user text detail page', () => {
|
||||||
cy.visit('/texts/0')
|
cy.visit('/texts/0')
|
||||||
cy.wait('@getNodes')
|
cy.wait('@getNodes')
|
||||||
|
|
||||||
cy.get('#text-detail > ul > li').first().activateNode()
|
cy.get('#text-detail > ul > li').first()
|
||||||
.children('button.add-child').click()
|
.children('button.add-child').click()
|
||||||
cy.get('#text-detail > ul > li').first()
|
cy.get('#text-detail > ul > li').first()
|
||||||
.children('input.child-title').type('My new child')
|
.children('input.child-title').type('My new child')
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,3 @@ Cypress.Commands.add('loginAsUser', () => {
|
||||||
Cypress.Commands.add('loginAsSecondUser', () => {
|
Cypress.Commands.add('loginAsSecondUser', () => {
|
||||||
cy.login('user2@example.com', 'password2')
|
cy.login('user2@example.com', 'password2')
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add('activateNode', { prevSubject: 'element' }, ($li) => {
|
|
||||||
cy.wrap($li).children('span').first().click()
|
|
||||||
return cy.wrap($li)
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -444,12 +444,6 @@ form {
|
||||||
elements added.
|
elements added.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.node-tree ul {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: var(--space-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-tree > ul {
|
.node-tree > ul {
|
||||||
border-left: 2px solid var(--color-border);
|
border-left: 2px solid var(--color-border);
|
||||||
padding-left: var(--space-4);
|
padding-left: var(--space-4);
|
||||||
|
|
@ -457,41 +451,26 @@ form {
|
||||||
|
|
||||||
.node-tree ul ul {
|
.node-tree ul ul {
|
||||||
border-left: 1px solid var(--color-border);
|
border-left: 1px solid var(--color-border);
|
||||||
margin-left: var(--space-3);
|
margin-top: var(--space-2);
|
||||||
|
margin-left: var(--space-2);
|
||||||
padding-left: var(--space-4);
|
padding-left: var(--space-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-tree li {
|
.node-tree li {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: wrap;
|
||||||
align-items: flex-start;
|
align-items: center;
|
||||||
gap: var(--space-2);
|
gap: var(--space-2);
|
||||||
padding: 0;
|
padding: var(--space-2) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-tree li > ul {
|
.node-tree li > ul {
|
||||||
flex-shrink: 0;
|
flex-basis: 100%;
|
||||||
|
margin-top: var(--space-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-tree li > span {
|
.node-tree li > span {
|
||||||
flex-shrink: 0;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-tree li > span:hover,
|
|
||||||
.node-tree li.is-active > span {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-tree li > button.add-child,
|
|
||||||
.node-tree li > button.bulk-add-children {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-tree li.is-active > button.add-child,
|
|
||||||
.node-tree li.is-active > button.bulk-add-children {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-tree li > button {
|
.node-tree li > button {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
const expandedChildByParent = new Map();
|
const expandedNodeIds = new Set();
|
||||||
let activeNodeId = null;
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const textId = window.location.pathname.split('/').pop();
|
const textId = window.location.pathname.split('/').pop();
|
||||||
|
|
@ -76,36 +75,12 @@ function renderTree(nodes, textId, depth = 0) {
|
||||||
|
|
||||||
const titleSpan = document.createElement('span');
|
const titleSpan = document.createElement('span');
|
||||||
titleSpan.textContent = node.title;
|
titleSpan.textContent = node.title;
|
||||||
titleSpan.addEventListener('click', () => {
|
|
||||||
if (activeNodeId === node.id) {
|
|
||||||
activeNodeId = null;
|
|
||||||
li.classList.remove('is-active');
|
|
||||||
closeAllAddForms();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const previousActive = document.querySelector(
|
|
||||||
'#text-detail li.is-active'
|
|
||||||
);
|
|
||||||
if (previousActive) {
|
|
||||||
previousActive.classList.remove('is-active');
|
|
||||||
}
|
|
||||||
closeAllAddForms();
|
|
||||||
li.classList.add('is-active');
|
|
||||||
activeNodeId = node.id;
|
|
||||||
});
|
|
||||||
li.appendChild(titleSpan);
|
li.appendChild(titleSpan);
|
||||||
|
|
||||||
if (node.id === activeNodeId) {
|
|
||||||
li.classList.add('is-active');
|
|
||||||
}
|
|
||||||
|
|
||||||
const addBtn = document.createElement('button');
|
const addBtn = document.createElement('button');
|
||||||
addBtn.textContent = 'Add child';
|
addBtn.textContent = 'Add child';
|
||||||
addBtn.className = 'add-child';
|
addBtn.className = 'add-child';
|
||||||
addBtn.addEventListener(
|
addBtn.addEventListener('click', () => toggleAddForm(li, node.id, textId));
|
||||||
'click',
|
|
||||||
() => toggleAddForm(li, node.id, node.parentNodeId, textId)
|
|
||||||
);
|
|
||||||
li.appendChild(addBtn);
|
li.appendChild(addBtn);
|
||||||
|
|
||||||
const bulkBtn = document.createElement('button');
|
const bulkBtn = document.createElement('button');
|
||||||
|
|
@ -113,32 +88,31 @@ function renderTree(nodes, textId, depth = 0) {
|
||||||
bulkBtn.className = 'bulk-add-children';
|
bulkBtn.className = 'bulk-add-children';
|
||||||
bulkBtn.addEventListener(
|
bulkBtn.addEventListener(
|
||||||
'click',
|
'click',
|
||||||
() => toggleBulkAddForm(li, node.id, node.parentNodeId, textId)
|
() => toggleBulkAddForm(li, node.id, textId)
|
||||||
);
|
);
|
||||||
li.appendChild(bulkBtn);
|
li.appendChild(bulkBtn);
|
||||||
|
|
||||||
if (node.children.length > 0) {
|
if (node.children.length > 0) {
|
||||||
const childUl = renderTree(node.children, textId, depth + 1);
|
const childUl = renderTree(node.children, textId, depth + 1);
|
||||||
const childrenVisible =
|
const childrenVisible =
|
||||||
depth === 0 ||
|
expandedNodeIds.has(node.id) || depth === 0;
|
||||||
expandedChildByParent.get(node.parentNodeId) === node.id;
|
if (childrenVisible) {
|
||||||
|
expandedNodeIds.add(node.id);
|
||||||
|
}
|
||||||
childUl.style.display = childrenVisible ? '' : 'none';
|
childUl.style.display = childrenVisible ? '' : 'none';
|
||||||
|
|
||||||
const toggleBtn = document.createElement('button');
|
const toggleBtn = document.createElement('button');
|
||||||
toggleBtn.className = 'toggle-children';
|
toggleBtn.className = 'toggle-children';
|
||||||
toggleBtn.textContent = childrenVisible ? '▼' : '▶';
|
toggleBtn.textContent = childrenVisible ? '▼' : '▶';
|
||||||
toggleBtn.addEventListener('click', () => {
|
toggleBtn.addEventListener('click', () => {
|
||||||
if (depth === 0) {
|
const isHidden = childUl.style.display === 'none';
|
||||||
return;
|
childUl.style.display = isHidden ? '' : 'none';
|
||||||
}
|
toggleBtn.textContent = isHidden ? '▼' : '▶';
|
||||||
const expandedSibling =
|
if (isHidden) {
|
||||||
expandedChildByParent.get(node.parentNodeId);
|
expandedNodeIds.add(node.id);
|
||||||
if (expandedSibling === node.id) {
|
|
||||||
expandedChildByParent.delete(node.parentNodeId);
|
|
||||||
} else {
|
} else {
|
||||||
expandedChildByParent.set(node.parentNodeId, node.id);
|
expandedNodeIds.delete(node.id);
|
||||||
}
|
}
|
||||||
fetchAndRenderNodes(textId);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
li.appendChild(toggleBtn);
|
li.appendChild(toggleBtn);
|
||||||
|
|
@ -165,7 +139,7 @@ function closeAllAddForms() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleAddForm(li, parentNodeId, grandparentId, textId) {
|
function toggleAddForm(li, parentNodeId, textId) {
|
||||||
const existing = li.querySelector('input.child-title');
|
const existing = li.querySelector('input.child-title');
|
||||||
if (existing) {
|
if (existing) {
|
||||||
existing.remove();
|
existing.remove();
|
||||||
|
|
@ -188,7 +162,7 @@ function toggleAddForm(li, parentNodeId, grandparentId, textId) {
|
||||||
const title = input.value.trim();
|
const title = input.value.trim();
|
||||||
if (!title) return;
|
if (!title) return;
|
||||||
|
|
||||||
expandedChildByParent.set(grandparentId, parentNodeId);
|
expandedNodeIds.add(parentNodeId);
|
||||||
fetch('/api/nodes', {
|
fetch('/api/nodes', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
|
@ -215,7 +189,7 @@ function toggleAddForm(li, parentNodeId, grandparentId, textId) {
|
||||||
input.focus();
|
input.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleBulkAddForm(li, parentNodeId, grandparentId, textId) {
|
function toggleBulkAddForm(li, parentNodeId, textId) {
|
||||||
const existing = li.querySelector('input.bulk-title');
|
const existing = li.querySelector('input.bulk-title');
|
||||||
if (existing) {
|
if (existing) {
|
||||||
existing.remove();
|
existing.remove();
|
||||||
|
|
@ -246,7 +220,7 @@ function toggleBulkAddForm(li, parentNodeId, grandparentId, textId) {
|
||||||
const count = parseInt(countInput.value);
|
const count = parseInt(countInput.value);
|
||||||
if (!titlePrefix || !count || count < 1) return;
|
if (!titlePrefix || !count || count < 1) return;
|
||||||
|
|
||||||
expandedChildByParent.set(grandparentId, parentNodeId);
|
expandedNodeIds.add(parentNodeId);
|
||||||
fetch('/api/nodes/bulk', {
|
fetch('/api/nodes/bulk', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue