Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions cypress/e2e/nodes/Details.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { initUserAndFiles, randUser } from '../../utils/index.js'

const user = randUser()
const fileName = 'empty.md'

describe('Details plugin', () => {
before(() => {
initUserAndFiles(user)
})

beforeEach(() => {
cy.login(user)

cy.isolateTest({
sourceFile: fileName,
})

return cy.openFile(fileName, { force: true })
})

it('inserts and removes details', () => {
cy.getContent()
.type('content{selectAll}')

cy.getMenuEntry('details').click()

cy.getContent()
.find('div[data-text-el="details"]')
.should('exist')

cy.getContent()
.type('summary')

cy.getContent()
.find('div[data-text-el="details"]')
.find('summary')
.should('contain', 'summary')

cy.getContent()
.find('div[data-text-el="details"]')
.find('.details-content')
.should('contain', 'content')

cy.getMenuEntry('details').click()

cy.getContent()
.find('div[data-text-el="details"]')
.should('not.exist')

cy.getContent()
.should('contain', 'content')
})
})
45 changes: 45 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
"@nextcloud/eslint-config": "^8.4.1",
"@nextcloud/stylelint-config": "^3.0.1",
"@nextcloud/vite-config": "^1.4.2",
"@types/markdown-it": "^13.0.2",
"@vitejs/plugin-vue2": "^2.3.1",
"@vue/test-utils": "^1.3.0 <2",
"@vue/tsconfig": "^0.5.1",
Expand Down
5 changes: 5 additions & 0 deletions src/components/Editor/PreviewOptions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,9 @@ div[data-text-preview-options] {
left: -44px;
}

// Inside details, button needs to be shifted further
.details-content div[data-text-preview-options] {
left: calc(-44px - 24px);
}

</style>
11 changes: 11 additions & 0 deletions src/components/Menu/entries.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
Paperclip,
Positive,
Table,
UnfoldMoreHorizontal,
Warn,
} from '../icons.js'
import EmojiPickerAction from './EmojiPickerAction.vue'
Expand Down Expand Up @@ -322,6 +323,16 @@ export default [
},
priority: 17,
},
{
Copy link
Member

Choose a reason for hiding this comment

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

@jancborchardt Keeping #2836 into consideration should we take action for this one right now and instead of adding this as yet another menu bar entry try to group them in the callout menu as listed in the issue?

Blockquote and code block go into the callouts menu

In the end the details is just another "block style"

Copy link
Member

Choose a reason for hiding this comment

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

Though one argument against would be that you could still have a callout nested in a details block which is then not very obvious from the active indication in the menu bar

Copy link
Member

Choose a reason for hiding this comment

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

In the end the details is just another "block style"

Yeah, good point, makes sense to directly group it properly.

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree, but then we probably should move block quote and code blocks into this submenu as well straight away, right? What icon should be used for the "block style" submenu?

key: 'details',
label: t('text', 'Details'),
isActive: 'details',
icon: UnfoldMoreHorizontal,
action: (command) => {
return command.toggleDetails()
},
priority: 18,
},
{
key: 'emoji-picker',
label: t('text', 'Insert emoji'),
Expand Down
2 changes: 2 additions & 0 deletions src/components/icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import MDI_TableAddRowBefore from 'vue-material-design-icons/TableRowPlusBefore.
import MDI_TableSettings from 'vue-material-design-icons/TableCog.vue'
import MDI_TrashCan from 'vue-material-design-icons/TrashCan.vue'
import MDI_Undo from 'vue-material-design-icons/ArrowULeftTop.vue'
import MDI_UnfoldMoreHorizontal from 'vue-material-design-icons/UnfoldMoreHorizontal.vue'
import MDI_Upload from 'vue-material-design-icons/Upload.vue'
import MDI_Warn from 'vue-material-design-icons/Alert.vue'
import MDI_Web from 'vue-material-design-icons/Web.vue'
Expand Down Expand Up @@ -131,6 +132,7 @@ export const TableSettings = makeIcon(MDI_TableSettings)
export const TrashCan = makeIcon(MDI_TrashCan)
export const TranslateVariant = makeIcon(MDI_TranslateVariant)
export const Undo = makeIcon(MDI_Undo)
export const UnfoldMoreHorizontal = makeIcon(MDI_UnfoldMoreHorizontal)
export const Upload = makeIcon(MDI_Upload)
export const Warn = makeIcon(MDI_Warn)
export const Web = makeIcon(MDI_Web)
2 changes: 2 additions & 0 deletions src/extensions/RichText.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Callouts from './../nodes/Callouts.js'
import CharacterCount from '@tiptap/extension-character-count'
import Code from '@tiptap/extension-code'
import CodeBlock from './../nodes/CodeBlock.js'
import Details from './../nodes/Details.js'
import Document from '@tiptap/extension-document'
import Dropcursor from '@tiptap/extension-dropcursor'
import EditableTable from './../nodes/EditableTable.js'
Expand Down Expand Up @@ -79,6 +80,7 @@ export default Extension.create({
lowlight,
defaultLanguage: 'plaintext',
}),
Details,
BulletList,
HorizontalRule,
OrderedList,
Expand Down
122 changes: 122 additions & 0 deletions src/markdownit/details.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type MarkdownIt from 'markdown-it'
import type StateBlock from 'markdown-it/lib/rules_block/state_block'
import type Token from 'markdown-it/lib/token'

const DETAILS_START_REGEX = /^<details>\s*$/
const DETAILS_END_REGEX = /^<\/details>\s*$/
const SUMMARY_REGEX = /(?<=^<summary>).*(?=<\/summary>\s*$)/

function parseDetails(state: StateBlock, startLine: number, endLine: number, silent: boolean) {
// let autoClosedBlock = false
let start = state.bMarks[startLine] + state.tShift[startLine]
let max = state.eMarks[startLine]

// Details block start
if (!state.src.slice(start, max).match(DETAILS_START_REGEX)) {
return false
}

// Since start is found, we can report success here in validation mode
if (silent) {
return true
}

let detailsFound = false
let detailsSummary = null
let nestedCount = 0
let nextLine = startLine
for (;;) {
nextLine++
if (nextLine >= endLine) {
break
}

start = state.bMarks[nextLine] + state.tShift[nextLine]
max = state.eMarks[nextLine]

// Details summary
const m = state.src.slice(start, max).match(SUMMARY_REGEX)
if (m && detailsSummary === null) {
// Only set `detailsSummary` the first time
// Ignore future summary tags (in nested/broken details)
detailsSummary = m[0].trim()
continue
}

// Nested details
if (state.src.slice(start, max).match(DETAILS_START_REGEX)) {
nestedCount++
}

// Details block end
if (!state.src.slice(start, max).match(DETAILS_END_REGEX)) {
continue
}

// Regard nested details blocks
if (nestedCount > 0) {
nestedCount--
} else {
detailsFound = true
break
}
}

if (!detailsFound || detailsSummary === null) {
return false
}

const oldParent = state.parentType
const oldLineMax = state.lineMax
state.parentType = 'reference'

// This will prevent lazy continuations from ever going past our end marker
state.lineMax = nextLine;

// Push tokens to the state

let token = state.push('details_open', 'details', 1)
token.block = true
token.info = detailsSummary
token.map = [ startLine, nextLine ]

token = state.push('details_summary', 'summary', 1)
token.block = false

// Parse and push summary to preserve markup
let tokens: Token[] = []
state.md.inline.parse(detailsSummary, state.md, state.env, tokens)
for (const t of tokens) {
token = state.push(t.type, t.tag, t.nesting)
token.block = t.block
token.markup = t.markup
token.content = t.content
}

token = state.push('details_summary', 'summary', -1)

state.md.block.tokenize(state, startLine + 2, nextLine);

token = state.push('details_close', 'details', -1)
token.block = true

state.parentType = oldParent
state.lineMax = oldLineMax
state.line = nextLine + 1

return true
}

/**
* @param {object} md Markdown object
*/
export default function details(md: MarkdownIt) {
md.block.ruler.before('fence', 'details', parseDetails, {
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ],
})
}
2 changes: 2 additions & 0 deletions src/markdownit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import markdownitMentions from '@quartzy/markdown-it-mentions'
import underline from './underline.js'
import splitMixedLists from './splitMixedLists.js'
import callouts from './callouts.js'
import details from './details.ts'
import preview from './preview.js'
import hardbreak from './hardbreak.js'
import keepSyntax from './keepSyntax.js'
Expand All @@ -25,6 +26,7 @@ const markdownit = MarkdownIt('commonmark', { html: false, breaks: false })
.use(underline)
.use(hardbreak)
.use(callouts)
.use(details)
.use(preview)
.use(keepSyntax)
.use(markdownitMentions)
Expand Down
1 change: 1 addition & 0 deletions src/nodes/CodeBlockView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ export default {
<style lang="scss" scoped>
.code-block {
background-color: var(--color-background-dark);
position: relative;
}

.code-block-header {
Expand Down
Loading