1
0

Add easymde & markdown preview for editing descriptions and comments (#183)

Make sure no text from previous mounts is left in the editor text field

Make preview not the default when rendering descrition settings

Add option to show editor by default while still having the option to show preview

Add option to show editor by default while still having the option to show preview

Use editor component for edit labels

Use editor component for edit team

Use editor component for edit namespace

Use editor component for edit list

Use editor component for edit task

Make sure we find all checkboxes

Fix checking wrong checkbox

Make finding and replacing checkboxes in a function actually work

Add upading text with checked checkboxes

Lazy load editor

Remove preview since we have a better one

Make easymde smaller by default

Add image upload from comments

Rename easymde component to editor

Only show preview button if editing is currently active

Make editor tabs look better when commenting

Make comments meta look better

Don't try to update if the value was initially set

Use editor to render and edit comments

Make preview optional

Make tabs look better

Don't switch to preview after editing

Centralize attachment state

Render markdown by default

Fix title being "null"

Fix loading attachment images

Add standalone preview

Fix callback url

Add onsuccess callback

Add file upload

Fix date parsing once and for all

Add more props for upload and such

Fix editor border color

Fix changing text after mounting

Add link to guide

Fix sizing of icons

Add timeout for changes

Add all easymde icons

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/183
This commit is contained in:
konrad
2020-07-14 19:26:05 +00:00
parent b07bbe4474
commit 3874355953
20 changed files with 661 additions and 179 deletions

View File

@ -3,17 +3,28 @@
<div class="field">
<label class="label" for="tasktext">Task Text</label>
<div class="control">
<input v-focus :class="{ 'disabled': taskService.loading}" :disabled="taskService.loading" class="input"
type="text" id="tasktext" placeholder="The task text is here..." v-model="taskEditTask.title" @change="editTaskSubmit()">
<input
v-focus
:class="{ 'disabled': taskService.loading}"
:disabled="taskService.loading"
class="input"
type="text"
id="tasktext"
placeholder="The task text is here..."
v-model="taskEditTask.title"
@change="editTaskSubmit()"/>
</div>
</div>
<div class="field">
<label class="label" for="taskdescription">Description</label>
<div class="control">
<textarea :class="{ 'disabled': taskService.loading}" :disabled="taskService.loading" class="textarea"
placeholder="The tasks description goes here..." id="taskdescription"
v-model="taskEditTask.description" @change="editTaskSubmit()">
</textarea>
<editor
placeholder="The tasks description goes here..."
id="taskdescription"
v-model="taskEditTask.description"
:preview-is-default="false"
v-if="editorActive"
/>
</div>
</div>
@ -106,7 +117,10 @@
<div class="field has-addons">
<div class="control is-expanded">
<edit-assignees :task-id="taskEditTask.id" :list-id="taskEditTask.listId" :initial-assignees="taskEditTask.assignees"/>
<edit-assignees
:task-id="taskEditTask.id"
:list-id="taskEditTask.listId"
:initial-assignees="taskEditTask.assignees"/>
</div>
</div>
@ -118,10 +132,10 @@
</div>
<related-tasks
class="is-narrow"
:task-id="task.id"
:list-id="task.listId"
:initial-related-tasks="task.relatedTasks"
class="is-narrow"
:task-id="task.id"
:list-id="task.listId"
:initial-related-tasks="task.relatedTasks"
/>
<button type="submit" class="button is-success is-fullwidth" :class="{ 'is-loading': taskService.loading}">
@ -158,6 +172,7 @@
priorities: priorities,
list: {},
editorActive: false,
newTask: TaskModel,
isTaskEdit: false,
taskEditTask: TaskModel,
@ -181,6 +196,7 @@
PercentDoneSelect,
PrioritySelect,
flatPickr,
editor: () => import(/* webpackPrefetch: true */ '../../components/input/editor'),
},
props: {
task: {
@ -206,6 +222,13 @@
this.taskEditTask.dueDate = +new Date(this.task.dueDate) === 0 ? null : this.task.dueDate
this.taskEditTask.startDate = +new Date(this.task.startDate) === 0 ? null : this.task.startDate
this.taskEditTask.endDate = +new Date(this.task.endDate) === 0 ? null : this.task.endDate
// This makes the editor trigger its mounted function again which makes it forget every input
// it currently has in its textarea. This is a counter-hack to a hack inside of vue-easymde
// which made it impossible to detect change from the outside. Therefore the component would
// not update if new content from the outside was made available.
// See https://github.com/NikulinIlya/vue-easymde/issues/3
this.editorActive = false
this.$nextTick(() => this.editorActive = true)
},
editTaskSubmit() {
this.taskService.update(this.taskEditTask)

View File

@ -0,0 +1,32 @@
import AttachmentModel from '../../../models/attachment'
import AttachmentService from '../../../services/attachment'
export default {
methods: {
attachmentUpload(file, onSuccess) {
const files = [file]
const attachmentService = new AttachmentService()
const attachmentModel = new AttachmentModel({taskId: this.taskId})
attachmentService.create(attachmentModel, files)
.then(r => {
if (r.success !== null) {
r.success.forEach(a => {
this.$store.commit('attachments/add', a)
this.$store.dispatch('tasks/addTaskAttachment', {taskId: this.taskId, attachment: a})
onSuccess(`${window.API_URL}/tasks/${this.taskId}/attachments/${a.id}`)
})
}
if (r.errors !== null) {
r.errors.forEach(m => {
this.error(m)
})
}
})
.catch(e => {
this.error(e, this)
})
},
}
}

View File

@ -79,6 +79,7 @@
import AttachmentService from '../../../services/attachment'
import AttachmentModel from '../../../models/attachment'
import User from '../../misc/user'
import {mapState} from 'vuex'
export default {
name: 'attachments',
@ -87,7 +88,6 @@
},
data() {
return {
attachments: [],
attachmentService: AttachmentService,
showDropzone: false,
@ -106,8 +106,10 @@
},
created() {
this.attachmentService = new AttachmentService()
this.attachments = this.initialAttachments
},
computed: mapState({
attachments: state => state.attachments.attachments
}),
mounted() {
document.addEventListener('dragenter', e => {
e.stopPropagation()
@ -136,11 +138,6 @@
this.showDropzone = false
})
},
watch: {
initialAttachments(newVal) {
this.attachments = newVal
},
},
methods: {
downloadAttachment(attachment) {
this.attachmentService.download(attachment)
@ -158,7 +155,7 @@
.then(r => {
if(r.success !== null) {
r.success.forEach(a => {
this.attachments.push(a)
this.$store.commit('attachments/add', a)
this.$store.dispatch('tasks/addTaskAttachment', {taskId: this.taskId, attachment: a})
})
}
@ -171,17 +168,11 @@
.catch(e => {
this.error(e, this)
})
},
deleteAttachment() {
this.attachmentService.delete(this.attachmentToDelete)
.then(r => {
// Remove the file from the list
for (const a in this.attachments) {
if (this.attachments[a].id === this.attachmentToDelete.id) {
this.attachments.splice(a, 1)
}
}
this.$store.commit('attachments/removeById', this.attachmentToDelete.id)
this.success(r, this)
})
.catch(e => {

View File

@ -7,33 +7,29 @@
Comments
</h1>
<div class="comments">
<progress class="progress is-small is-info" max="100" v-if="taskCommentService.loading">Loading comments...</progress>
<progress class="progress is-small is-info" max="100" v-if="taskCommentService.loading">Loading
comments...
</progress>
<div class="media comment" v-for="c in comments" :key="c.id">
<figure class="media-left">
<img class="image is-avatar" :src="c.author.getAvatarUrl(48)" alt="" width="48" height="48"/>
</figure>
<div class="media-content">
<div class="form" v-if="isCommentEdit && commentEdit.id === c.id">
<div class="field">
<textarea class="textarea" :class="{'is-loading': taskCommentService.loading}" placeholder="Add your comment..." v-model="commentEdit.comment" @keyup.ctrl.enter="editComment()"></textarea>
</div>
<div class="field">
<button class="button is-primary" :class="{'is-loading': taskCommentService.loading}" @click="editComment()" :disabled="commentEdit.comment === ''">Comment</button>
<a @click="() => isCommentEdit = false">Cancel</a>
</div>
</div>
<div class="content" v-else>
<div class="comment-info">
<strong>{{ c.author.username }}</strong>&nbsp;
<small v-tooltip="formatDate(c.created)">{{ formatDateSince(c.created) }}</small>
<small v-if="+new Date(c.created) !== +new Date(c.updated)" v-tooltip="formatDate(c.updated)"> · edited {{ formatDateSince(c.updated) }}</small>
<br/>
<p>
{{c.comment}}
</p>
<div class="comment-actions">
<a @click="toggleEdit(c)">Edit</a>&nbsp;·&nbsp;
<a @click="toggleDelete(c.id)">Remove</a>
</div>
<small v-if="+new Date(c.created) !== +new Date(c.updated)" v-tooltip="formatDate(c.updated)"> ·
edited {{ formatDateSince(c.updated) }}</small>
</div>
<editor
v-model="c.comment"
:has-preview="true"
@change="() => {toggleEdit(c);editComment()}"
:upload-enabled="true"
:upload-callback="attachmentUpload"
/>
<div class="comment-actions">
<a @click="toggleDelete(c.id)">Remove</a>
</div>
</div>
</div>
@ -44,10 +40,21 @@
<div class="media-content">
<div class="form">
<div class="field">
<textarea class="textarea" :class="{'is-loading': taskCommentService.loading && !isCommentEdit}" placeholder="Add your comment..." v-model="newComment.comment" @keyup.ctrl.enter="addComment()"></textarea>
<editor
placeholder="Add your comment..."
:class="{'is-loading': taskCommentService.loading && !isCommentEdit}"
v-model="newComment.comment"
:has-preview="false"
:upload-enabled="true"
:upload-callback="attachmentUpload"
v-if="editorActive"
/>
</div>
<div class="field">
<button class="button is-primary" :class="{'is-loading': taskCommentService.loading && !isCommentEdit}" @click="addComment()" :disabled="newComment.comment === ''">Comment</button>
<button class="button is-primary"
:class="{'is-loading': taskCommentService.loading && !isCommentEdit}"
@click="addComment()" :disabled="newComment.comment === ''">Comment
</button>
</div>
</div>
</div>
@ -67,9 +74,16 @@
<script>
import TaskCommentService from '../../../services/taskComment'
import TaskCommentModel from '../../../models/taskComment'
import attachmentUpload from '../mixins/attachmentUpload'
export default {
name: 'comments',
components: {
editor: () => import(/* webpackPrefetch: true */ '../../input/editor'),
},
mixins: [
attachmentUpload,
],
props: {
taskId: {
type: Number,
@ -88,6 +102,7 @@
taskCommentService: TaskCommentService,
newComment: TaskCommentModel,
editorActive: true,
}
},
created() {
@ -124,6 +139,15 @@
if (this.newComment.comment === '') {
return
}
// This makes the editor trigger its mounted function again which makes it forget every input
// it currently has in its textarea. This is a counter-hack to a hack inside of vue-easymde
// which made it impossible to detect change from the outside. Therefore the component would
// not update if new content from the outside was made available.
// See https://github.com/NikulinIlya/vue-easymde/issues/3
this.editorActive = false
this.$nextTick(() => this.editorActive = true)
this.taskCommentService.create(this.newComment)
.then(r => {
this.comments.push(r)