-
Notifications
You must be signed in to change notification settings - Fork 25
feat: Insert markdown tables #816
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
Merged
Merged
Changes from 1 commit
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
ab2f5d9
integrate text editor
silverkszlo 04c8535
feat: Add table insertion and markdown rendering utilities
silverkszlo ba5c2d3
feat: Add table editor UI
silverkszlo 2a7e284
remove html2canvas
silverkszlo d536afc
add hint about all content being rendered as table elements
silverkszlo fb2e0cc
allow empty cells
silverkszlo 2afd869
use createTable to only use table markdown from Text
silverkszlo 009cb87
add 'insert table' icon to toolbar
silverkszlo 171eb4e
feat: add collaborative table editing lock mechanism
silverkszlo bb3c5c0
test the locking mechanism
silverkszlo dd40f13
remove border and some padding in tableeditor
silverkszlo 02ff6c2
adjust table styling and remove obsolete code
silverkszlo 9638756
simplify to HTML-only architecture and remove markdown storage
silverkszlo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
feat: Add table editor UI
Integrate table functionality into the whiteboard interface: - Add "Insert table" menu item to Excalidraw menu with mdiTable icon - Create TableEditorDialog using Nextcloud Text editor component Requires Text app to be installed and enabled for table editing. Signed-off-by: silver <[email protected]>
- Loading branch information
commit ba5c2d3497efd8c82fbe9e9ffa646a99cacd0868
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,222 @@ | ||
| <!-- | ||
| - SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors | ||
| - SPDX-License-Identifier: AGPL-3.0-or-later | ||
| --> | ||
| <script> | ||
| import { NcButton, NcModal, NcNoteCard } from '@nextcloud/vue' | ||
| import { defineComponent } from 'vue' | ||
| import { t } from '@nextcloud/l10n' | ||
|
|
||
| export default defineComponent({ | ||
| name: 'TableEditorDialog', | ||
| components: { | ||
| NcButton, | ||
| NcModal, | ||
| NcNoteCard, | ||
| }, | ||
| props: { | ||
| initialMarkdown: { | ||
| type: String, | ||
| default: '', | ||
| }, | ||
| }, | ||
| emits: ['cancel', 'submit'], | ||
| data() { | ||
| return { | ||
| show: true, | ||
| editor: null, | ||
| isLoading: true, | ||
| error: null, | ||
| currentMarkdown: this.initialMarkdown || this.getDefaultTable(), | ||
| } | ||
| }, | ||
| computed: { | ||
| isEditing() { | ||
| return Boolean(this.initialMarkdown) | ||
| }, | ||
| }, | ||
| async mounted() { | ||
| await this.$nextTick() | ||
| await this.initializeEditor() | ||
| }, | ||
| beforeUnmount() { | ||
| this.destroyEditor() | ||
| }, | ||
| methods: { | ||
| t, | ||
| async initializeEditor() { | ||
| try { | ||
| // Check if Text app is available | ||
| if (!window.OCA?.Text) { | ||
| this.error = t('whiteboard', 'Nextcloud Text app is not available. Please install and enable it.') | ||
| this.isLoading = false | ||
| return | ||
| } | ||
|
|
||
| const editorContainer = this.$refs.editorContainer | ||
| if (!editorContainer) { | ||
| this.error = t('whiteboard', 'Editor container not found') | ||
| this.isLoading = false | ||
| return | ||
| } | ||
|
|
||
| // Create the Text editor instance with callbacks | ||
| this.editor = await window.OCA.Text.createEditor({ | ||
| el: editorContainer, | ||
| content: this.currentMarkdown, | ||
| // Track content changes | ||
| onUpdate: ({ markdown }) => { | ||
| this.currentMarkdown = markdown | ||
| }, | ||
| onCreate: ({ markdown }) => { | ||
| this.currentMarkdown = markdown | ||
| }, | ||
| }) | ||
|
|
||
| this.isLoading = false | ||
|
|
||
| // Focus the editor after a short delay | ||
| setTimeout(() => { | ||
| if (this.editor) { | ||
| this.editor.focus?.() | ||
| } | ||
| }, 100) | ||
| } catch (error) { | ||
| console.error('Failed to initialize Text editor:', error) | ||
| this.error = t('whiteboard', 'Failed to load the editor: {error}', { error: error.message }) | ||
| this.isLoading = false | ||
| } | ||
| }, | ||
|
|
||
| getDefaultTable() { | ||
| return `| Column 1 | Column 2 | Column 3 | | ||
| | -------- | -------- | -------- | | ||
| | Cell 1 | Cell 2 | Cell 3 | | ||
| | Cell 4 | Cell 5 | Cell 6 |` | ||
silverkszlo marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }, | ||
|
|
||
| onCancel() { | ||
| this.show = false | ||
| this.$emit('cancel') | ||
| }, | ||
|
|
||
| async onInsert() { | ||
| if (!this.editor) { | ||
| this.error = t('whiteboard', 'Editor not initialized') | ||
| return | ||
| } | ||
|
|
||
| try { | ||
| // Use the tracked markdown content | ||
| const markdown = this.currentMarkdown | ||
|
|
||
| if (!markdown || !markdown.trim()) { | ||
| this.error = t('whiteboard', 'Please enter some table content') | ||
| return | ||
| } | ||
|
|
||
| this.$emit('submit', { | ||
| markdown: markdown.trim(), | ||
| }) | ||
|
|
||
| this.show = false | ||
| } catch (error) { | ||
| console.error('Failed to get editor content:', error) | ||
| this.error = t('whiteboard', 'Failed to get content: {error}', { error: error.message }) | ||
| } | ||
| }, | ||
|
|
||
| destroyEditor() { | ||
| if (this.editor) { | ||
| try { | ||
| this.editor.destroy() | ||
| } catch (error) { | ||
| console.error('Error destroying editor:', error) | ||
| } | ||
| this.editor = null | ||
| } | ||
| }, | ||
| }, | ||
| }) | ||
| </script> | ||
|
|
||
| <template> | ||
| <NcModal v-if="show" | ||
| :can-close="true" | ||
| size="large" | ||
| @close="onCancel"> | ||
| <div class="table-editor-dialog"> | ||
| <div class="editor-header"> | ||
| <h2> | ||
| {{ isEditing ? t('whiteboard', 'Edit Table') : t('whiteboard', 'Insert Table') }} | ||
| </h2> | ||
| <p>{{ t('whiteboard', 'Use the table feature in the editor to create or edit your table') }}</p> | ||
| </div> | ||
|
|
||
| <NcNoteCard v-if="error" type="error"> | ||
| {{ error }} | ||
| </NcNoteCard> | ||
|
|
||
| <div v-if="isLoading" class="loading-message"> | ||
| {{ t('whiteboard', 'Loading editor…') }} | ||
| </div> | ||
|
|
||
| <div ref="editorContainer" class="editor-container" /> | ||
|
|
||
| <div class="dialog-buttons"> | ||
| <NcButton @click="onCancel"> | ||
| {{ t('whiteboard', 'Cancel') }} | ||
| </NcButton> | ||
| <NcButton type="primary" :disabled="isLoading || error" @click="onInsert"> | ||
| {{ isEditing ? t('whiteboard', 'Update') : t('whiteboard', 'Insert') }} | ||
| </NcButton> | ||
| </div> | ||
| </div> | ||
| </NcModal> | ||
| </template> | ||
|
|
||
| <style scoped lang="scss"> | ||
| .table-editor-dialog { | ||
| padding: 20px; | ||
| display: flex; | ||
| flex-direction: column; | ||
| min-height: 500px; | ||
| } | ||
|
|
||
| .editor-header { | ||
| margin-bottom: 16px; | ||
|
|
||
| h2 { | ||
| margin: 0 0 8px 0; | ||
| } | ||
|
|
||
| p { | ||
| margin: 0; | ||
| color: var(--color-text-maxcontrast); | ||
| font-size: 14px; | ||
| } | ||
| } | ||
|
|
||
| .loading-message { | ||
| padding: 40px; | ||
| text-align: center; | ||
| color: var(--color-text-maxcontrast); | ||
| } | ||
|
|
||
| .editor-container { | ||
| flex: 1; | ||
| min-height: 400px; | ||
| border: 1px solid var(--color-border); | ||
| border-radius: var(--border-radius); | ||
| overflow: hidden; | ||
| } | ||
|
|
||
| .dialog-buttons { | ||
| display: flex; | ||
| justify-content: flex-end; | ||
| gap: 8px; | ||
| margin-top: 16px; | ||
| padding-top: 16px; | ||
| border-top: 1px solid var(--color-border); | ||
| } | ||
| </style> | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.