diff --git a/client/src/App.vue b/client/src/App.vue index 0407ed93..e3bd6c25 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -23,6 +23,7 @@ import EditProjectEntryWizard from '@/components/portfolio/EditProjectEntryWizard'; import NewObjectiveWizard from '@/components/objective-groups/NewObjectiveWizard'; import NewNoteWizard from '@/components/notes/NewNoteWizard'; + import EditNoteWizard from '@/components/notes/EditNoteWizard'; import FullscreenImage from '@/components/FullscreenImage'; import FullscreenInfographic from '@/components/FullscreenInfographic'; import FullscreenVideo from '@/components/FullscreenVideo'; @@ -50,6 +51,7 @@ EditProjectEntryWizard, NewObjectiveWizard, NewNoteWizard, + EditNoteWizard, FullscreenImage, FullscreenInfographic, FullscreenVideo diff --git a/client/src/components/ContentBlock.vue b/client/src/components/ContentBlock.vue index d3c2d700..d440ffd7 100644 --- a/client/src/components/ContentBlock.vue +++ b/client/src/components/ContentBlock.vue @@ -22,7 +22,9 @@ :key="component.id" :component="component" :parent="contentBlock.id" - :bookmarks="contentBlock.bookmarks"> + :bookmarks="contentBlock.bookmarks" + :notes="contentBlock.notes" + > diff --git a/client/src/components/content-blocks/ContentComponent.vue b/client/src/components/content-blocks/ContentComponent.vue index c1a9555a..1e329f02 100644 --- a/client/src/components/content-blocks/ContentComponent.vue +++ b/client/src/components/content-blocks/ContentComponent.vue @@ -2,8 +2,10 @@
+ :bookmarked="bookmarked" + :note="note"> @@ -33,7 +35,7 @@ import MODULE_DETAILS_QUERY from '@/graphql/gql/moduleDetailsQuery.gql'; export default { - props: ['component', 'parent', 'bookmarks'], + props: ['component', 'parent', 'bookmarks', 'notes'], components: { 'text_block': TextBlock, @@ -58,6 +60,10 @@ computed: { bookmarked() { return !!this.bookmarks.find(bookmark => bookmark.uuid === this.component.id); + }, + note() { + const bookmark = this.bookmarks.find(bookmark => bookmark.uuid === this.component.id); + return bookmark && bookmark.note; } }, @@ -68,6 +74,9 @@ contentBlock: this.parent }); }, + editNote() { + this.$store.dispatch('editNote', this.note); + }, bookmarkContent(uuid, bookmarked) { this.$apollo.mutate({ mutation: UPDATE_CONTENT_BOOKMARK, diff --git a/client/src/components/notes/BookmarkActions.vue b/client/src/components/notes/BookmarkActions.vue index ea176496..816d4d9e 100644 --- a/client/src/components/notes/BookmarkActions.vue +++ b/client/src/components/notes/BookmarkActions.vue @@ -4,9 +4,11 @@ :class="{'bookmark-actions__action--bookmarked': bookmarked}"> - + - + + +
@@ -17,7 +19,7 @@ import NoteIcon from '@/components/icons/NoteIcon'; export default { - props: ['bookmarked'], + props: ['bookmarked', 'note'], components: { BookmarkIcon, AddNoteIcon, @@ -43,8 +45,9 @@ &__action { opacity: 0; transition: opacity 0.3s; + cursor: pointer; - &--bookmarked { + &--bookmarked, &--noted { opacity: 1; } } diff --git a/client/src/components/notes/EditNoteWizard.vue b/client/src/components/notes/EditNoteWizard.vue new file mode 100644 index 00000000..ce94c1c6 --- /dev/null +++ b/client/src/components/notes/EditNoteWizard.vue @@ -0,0 +1,46 @@ + + + diff --git a/client/src/components/notes/NewNoteWizard.vue b/client/src/components/notes/NewNoteWizard.vue index 510f0e5e..0a5e455f 100644 --- a/client/src/components/notes/NewNoteWizard.vue +++ b/client/src/components/notes/NewNoteWizard.vue @@ -1,35 +1,23 @@ diff --git a/client/src/graphql/gql/mutations/updateNote.gql b/client/src/graphql/gql/mutations/updateNote.gql new file mode 100644 index 00000000..f5ef5981 --- /dev/null +++ b/client/src/graphql/gql/mutations/updateNote.gql @@ -0,0 +1,8 @@ +mutation UpdateNote($input: UpdateNoteInput!) { + updateNote(input: $input) { + note { + id + text + } + } +} diff --git a/client/src/store/index.js b/client/src/store/index.js index 3353a5bb..6bc4b044 100644 --- a/client/src/store/index.js +++ b/client/src/store/index.js @@ -20,6 +20,7 @@ export default new Vuex.Store({ objectiveGroupType: '', currentObjectiveGroup: '', parentProject: null, + currentNote: null, currentProjectEntry: null, imageUrl: '', infographic: { @@ -43,7 +44,8 @@ export default new Vuex.Store({ editModule: state => state.editModule, currentObjectiveGroup: state => state.currentObjectiveGroup, currentContent: state => state.currentContent, - currentContentBlock: state => state.currentContentBlock + currentContentBlock: state => state.currentContentBlock, + currentNote: state => state.currentNote, }, actions: { @@ -73,6 +75,7 @@ export default new Vuex.Store({ type: '' }); commit('setVimeoId', null); + commit('setCurrentNote', null); }, resetContentBlockPosition({commit}) { commit('setContentBlockPosition', {}); @@ -127,6 +130,10 @@ export default new Vuex.Store({ commit('setCurrentContent', payload.content); dispatch('showModal', 'new-note-wizard'); }, + editNote({commit, dispatch}, payload) { + commit('setCurrentNote', payload); + dispatch('showModal', 'edit-note-wizard'); + }, showFullscreenImage({commit, dispatch}, payload) { commit('setImageUrl', payload); dispatch('showModal', 'fullscreen-image'); @@ -152,7 +159,6 @@ export default new Vuex.Store({ if (payload && !state.scrollingToAssignment) { commit('setScrollingToAssignment', true); } - ; if (!payload && state.scrollingToAssignment) { commit('setScrollingToAssignment', false); @@ -189,6 +195,9 @@ export default new Vuex.Store({ setParentRoom(state, payload) { state.parentRoom = payload; }, + setCurrentNote(state, payload) { + state.currentNote = payload; + }, setCurrentRoomEntry(state, payload) { state.currentRoomEntry = payload; }, diff --git a/server/books/migrations/0015_contentblock_bookmarks.py b/server/books/migrations/0015_contentblock_bookmarks.py new file mode 100644 index 00000000..1c08b9df --- /dev/null +++ b/server/books/migrations/0015_contentblock_bookmarks.py @@ -0,0 +1,21 @@ +# Generated by Django 2.0.6 on 2019-10-10 09:52 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('books', '0014_auto_20190912_1228'), + ] + + operations = [ + migrations.AddField( + model_name='contentblock', + name='bookmarks', + field=models.ManyToManyField(related_name='bookmarked_content_blocks', through='notes.ContentBlockBookmark', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/server/notes/inputs.py b/server/notes/inputs.py index 16f39c5d..0080269d 100644 --- a/server/notes/inputs.py +++ b/server/notes/inputs.py @@ -7,3 +7,7 @@ class AddNoteArgument(InputObjectType): content_block = graphene.ID(required=True) text = graphene.String(required=True) + +class UpdateNoteArgument(InputObjectType): + id = graphene.ID(required=True) + text = graphene.String(required=True) diff --git a/server/notes/migrations/0001_initial.py b/server/notes/migrations/0001_initial.py new file mode 100644 index 00000000..e49e890a --- /dev/null +++ b/server/notes/migrations/0001_initial.py @@ -0,0 +1,46 @@ +# Generated by Django 2.0.6 on 2019-10-10 09:52 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('books', '0014_auto_20190912_1228'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='ContentBlockBookmark', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(unique=True)), + ('content_block', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='books.ContentBlock')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='Note', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('text', models.TextField()), + ], + ), + migrations.AddField( + model_name='contentblockbookmark', + name='note', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='notes.Note'), + ), + migrations.AddField( + model_name='contentblockbookmark', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/server/notes/mutations.py b/server/notes/mutations.py index 6e7963c6..8c1587e0 100644 --- a/server/notes/mutations.py +++ b/server/notes/mutations.py @@ -1,16 +1,19 @@ +from builtins import PermissionError + import graphene import json from graphene import relay from api.utils import get_object from books.models import ContentBlock -from notes.inputs import AddNoteArgument +from notes.inputs import AddNoteArgument, UpdateNoteArgument from notes.models import ContentBlockBookmark, Note from notes.schema import NoteNode + class UpdateContentBookmark(relay.ClientIDMutation): class Input: - id = graphene.String(required=True) + uuid = graphene.UUID(required=True) content_block = graphene.ID(required=True) bookmarked = graphene.Boolean(required=True) @@ -19,7 +22,7 @@ class UpdateContentBookmark(relay.ClientIDMutation): @classmethod def mutate_and_get_payload(cls, root, info, **kwargs): - id = kwargs.get('id') + uuid = kwargs.get('uuid') user = info.context.user content_block_id = kwargs.get('content_block') bookmarked = kwargs.get('bookmarked') @@ -29,20 +32,19 @@ class UpdateContentBookmark(relay.ClientIDMutation): if bookmarked: ContentBlockBookmark.objects.create( content_block=content_block, - id=id, + uuid=uuid, user=user ) else: ContentBlockBookmark.objects.get( content_block=content_block, - id=id, + uuid=uuid, user=user ).delete() return cls(success=True) - class AddNote(relay.ClientIDMutation): class Input: note = graphene.Argument(AddNoteArgument) @@ -61,7 +63,7 @@ class AddNote(relay.ClientIDMutation): bookmark = ContentBlockBookmark.objects.get( content_block=content_block, - id=content_uuid, + uuid=content_uuid, user=user ) @@ -71,6 +73,30 @@ class AddNote(relay.ClientIDMutation): return cls(note=bookmark.note) +class UpdateNote(relay.ClientIDMutation): + class Input: + note = graphene.Argument(UpdateNoteArgument) + + note = graphene.Field(NoteNode) + + @classmethod + def mutate_and_get_payload(cls, root, info, **kwargs): + user = info.context.user + + note = kwargs.get('note') + id = note.get('id') + text = note.get('text') + note = get_object(Note, id) + + if note.contentblockbookmark.user != user: + raise PermissionError + + note.text = text + note.save() + return cls(note=note) + + class NoteMutations: add_note = AddNote.Field() + update_note = UpdateNote.Field() update_content_bookmark = UpdateContentBookmark.Field()