Merged in feature/rooms-updated-edit-fields-MS-486-MS-487 (pull request #111)
Feature/rooms updated edit fields MS-486 MS 487 Approved-by: Lorenz Padberg
This commit is contained in:
commit
ff7e5ad1f6
|
|
@ -3,11 +3,22 @@ import {getMinimalMe} from '../../../support/helpers';
|
||||||
describe('Article page', () => {
|
describe('Article page', () => {
|
||||||
const slug = 'this-article-has-a-slug';
|
const slug = 'this-article-has-a-slug';
|
||||||
const roomEntry = {
|
const roomEntry = {
|
||||||
slug,
|
slug,
|
||||||
id: 'room-entry-id',
|
id: 'room-entry-id',
|
||||||
title: 'Some Room Entry, yay!',
|
title: 'Some Room Entry, yay!',
|
||||||
comments: [],
|
comments: [],
|
||||||
};
|
contents: [{
|
||||||
|
type: 'text_block',
|
||||||
|
value: {
|
||||||
|
text: 'Ein Text',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
type: 'subtitle',
|
||||||
|
value: {
|
||||||
|
text: 'Ein Untertitel'
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
const operations = {
|
const operations = {
|
||||||
MeQuery: getMinimalMe({}),
|
MeQuery: getMinimalMe({}),
|
||||||
|
|
@ -23,18 +34,28 @@ describe('Article page', () => {
|
||||||
roomEntry: roomEntry,
|
roomEntry: roomEntry,
|
||||||
owner: {
|
owner: {
|
||||||
firstName: 'Matt',
|
firstName: 'Matt',
|
||||||
lastName: 'Damon'
|
lastName: 'Damon',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.setup();
|
cy.setup();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows the article with contents', () => {
|
||||||
|
cy.mockGraphqlOps({
|
||||||
|
operations,
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.visit(`/article/${slug}`);
|
||||||
|
cy.getByDataCy('text-block').should('contain.text', 'Ein Text');
|
||||||
|
cy.getByDataCy('subtitle-block').should('contain.text', 'Ein Untertitel');
|
||||||
|
});
|
||||||
|
|
||||||
it('goes to article and leaves a comment', () => {
|
it('goes to article and leaves a comment', () => {
|
||||||
cy.mockGraphqlOps({
|
cy.mockGraphqlOps({
|
||||||
operations,
|
operations,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import {getMinimalMe} from '../../../support/helpers';
|
import {getMinimalMe} from '../../../support/helpers';
|
||||||
|
|
||||||
describe('The Room Page', () => {
|
describe('The Room Page (Teacher)', () => {
|
||||||
const MeQuery = getMinimalMe();
|
const MeQuery = getMinimalMe();
|
||||||
const selectedClass = MeQuery.me.selectedClass;
|
const selectedClass = MeQuery.me.selectedClass;
|
||||||
const entryText = 'something should be here';
|
const entryText = 'something should be here';
|
||||||
|
|
@ -61,32 +61,17 @@ describe('The Room Page', () => {
|
||||||
});
|
});
|
||||||
cy.visit(`/room/${slug}`);
|
cy.visit(`/room/${slug}`);
|
||||||
|
|
||||||
cy.get('[data-cy=add-room-entry-button]').click();
|
cy.getByDataCy('add-room-entry-button').click();
|
||||||
cy.get('.add-content-element:first-of-type').click();
|
cy.getByDataCy('add-content-link').first().click();
|
||||||
cy.get('[data-cy=choose-text-widget]').click();
|
cy.getByDataCy('choose-text-widget').click();
|
||||||
cy.get('[data-cy=modal-title-input] > .modal-input__inputfield').type(entryTitle);
|
cy.getByDataCy('input-with-label-input').type(entryTitle);
|
||||||
|
|
||||||
cy.get('[data-cy=text-form-input]').type(entryText);
|
cy.get('.tip-tap__editor').type(entryText);
|
||||||
cy.get('[data-cy=modal-save-button]').click();
|
cy.getByDataCy('save-button').click();
|
||||||
|
|
||||||
cy.get('.room-entry__content:first').should('contain', entryText).should('contain', 'Rachel Green');
|
cy.get('.room-entry__content:first').should('contain', entryText).should('contain', 'Rachel Green');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('room actions should not exist for student', () => {
|
|
||||||
const operations = {
|
|
||||||
MeQuery: getMinimalMe({isTeacher: false}),
|
|
||||||
RoomEntriesQuery,
|
|
||||||
};
|
|
||||||
|
|
||||||
cy.mockGraphqlOps({
|
|
||||||
operations,
|
|
||||||
});
|
|
||||||
cy.visit(`/room/${slug}`);
|
|
||||||
|
|
||||||
cy.getByDataCy('room-title').should('exist');
|
|
||||||
cy.getByDataCy('room-actions').should('not.exist');
|
|
||||||
});
|
|
||||||
|
|
||||||
// todo: re-enable once cypress can do it correctly
|
// todo: re-enable once cypress can do it correctly
|
||||||
it.skip('changes visibility of a room', () => {
|
it.skip('changes visibility of a room', () => {
|
||||||
const MeQuery = getMinimalMe({
|
const MeQuery = getMinimalMe({
|
||||||
|
|
@ -149,6 +134,7 @@ describe('The Room Page', () => {
|
||||||
};
|
};
|
||||||
const otherRoom = {
|
const otherRoom = {
|
||||||
id: btoa('RoomNode:otherRoom'),
|
id: btoa('RoomNode:otherRoom'),
|
||||||
|
slug: 'other-slug',
|
||||||
schoolClass,
|
schoolClass,
|
||||||
};
|
};
|
||||||
let rooms = [roomToDelete, otherRoom];
|
let rooms = [roomToDelete, otherRoom];
|
||||||
|
|
@ -156,7 +142,7 @@ describe('The Room Page', () => {
|
||||||
MeQuery,
|
MeQuery,
|
||||||
RoomsQuery() {
|
RoomsQuery() {
|
||||||
return {
|
return {
|
||||||
rooms
|
rooms,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
RoomEntriesQuery: {
|
RoomEntriesQuery: {
|
||||||
|
|
@ -176,47 +162,126 @@ describe('The Room Page', () => {
|
||||||
cy.getByDataCy('room-widget').should('have.length', 2);
|
cy.getByDataCy('room-widget').should('have.length', 2);
|
||||||
cy.getByDataCy('room-widget').first().click();
|
cy.getByDataCy('room-widget').first().click();
|
||||||
cy.getByDataCy('toggle-more-actions-menu').click();
|
cy.getByDataCy('toggle-more-actions-menu').click();
|
||||||
cy.getByDataCy('delete-room').click();
|
cy.getByDataCy('delete-room').within(() => {
|
||||||
|
cy.get('a').click();
|
||||||
|
});
|
||||||
cy.url().should('include', 'rooms');
|
cy.url().should('include', 'rooms');
|
||||||
cy.getByDataCy('room-widget').should('have.length', 1);
|
cy.getByDataCy('room-widget').should('have.length', 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('edits own room entry', () => {
|
it('changes class while on room page', () => {
|
||||||
const MeQuery = getMinimalMe({isTeacher: false});
|
|
||||||
const {me} = MeQuery;
|
const {me} = MeQuery;
|
||||||
const id = atob(me.id).split(':')[1];
|
const otherClass = {
|
||||||
const authorId = btoa(`PublicUserNode:${id}`);
|
id: btoa('SchoolClassNode:34'),
|
||||||
const room = {
|
name: 'Other Class',
|
||||||
id: 'some-room',
|
readOnly: false,
|
||||||
roomEntries: {
|
|
||||||
edges: [
|
|
||||||
{
|
|
||||||
node: {
|
|
||||||
id: '',
|
|
||||||
slug: '',
|
|
||||||
contents: [],
|
|
||||||
author: {
|
|
||||||
...me,
|
|
||||||
id: authorId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
let selectedClass = me.selectedClass;
|
||||||
const operations = {
|
const operations = {
|
||||||
MeQuery: MeQuery,
|
MeQuery: () => {
|
||||||
RoomEntriesQuery: {
|
return {
|
||||||
room,
|
me: {
|
||||||
|
...me,
|
||||||
|
schoolClasses: [...me.schoolClasses, otherClass],
|
||||||
|
selectedClass,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
RoomEntriesQuery,
|
||||||
|
UpdateSettings() {
|
||||||
|
selectedClass = otherClass;
|
||||||
|
return {
|
||||||
|
updateSettings: {
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
ModuleDetailsQuery: {},
|
||||||
|
MySchoolClassQuery: () => {
|
||||||
|
return {
|
||||||
|
me: {
|
||||||
|
selectedClass,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
RoomsQuery: {
|
||||||
|
rooms: [],
|
||||||
},
|
},
|
||||||
RoomEntryQuery: {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
cy.mockGraphqlOps({
|
cy.mockGraphqlOps({
|
||||||
operations,
|
operations,
|
||||||
});
|
});
|
||||||
cy.visit(`/room/${slug}`);
|
cy.visit(`/room/${slug}`);
|
||||||
cy.getByDataCy('room-entry-actions').click();
|
cy.getByDataCy('room-title').should('contain', 'A Room');
|
||||||
cy.getByDataCy('edit-room-entry').click();
|
cy.selectClass('Other Class');
|
||||||
|
cy.url().should('include', 'rooms');
|
||||||
|
cy.getByDataCy('current-class-name').should('contain', 'Other Class');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('The Room Page (student)', () => {
|
||||||
|
const slug = 'ein-historisches-festival';
|
||||||
|
const MeQuery = getMinimalMe({isTeacher: false});
|
||||||
|
const {me} = MeQuery;
|
||||||
|
const id = atob(me.id).split(':')[1];
|
||||||
|
const authorId = btoa(`PublicUserNode:${id}`);
|
||||||
|
const entrySlug = 'entry-slug';
|
||||||
|
const {selectedClass} = me;
|
||||||
|
const roomEntry = {
|
||||||
|
id: 'entry-id',
|
||||||
|
slug: entrySlug,
|
||||||
|
title: 'My Entry',
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
type: 'text_block',
|
||||||
|
value: {
|
||||||
|
text: 'some text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
comments: [{}, {}],
|
||||||
|
author: {
|
||||||
|
...me,
|
||||||
|
id: authorId,
|
||||||
|
firstName: 'Hans',
|
||||||
|
lastName: 'Was Heiri',
|
||||||
|
avatarUrl: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const room = {
|
||||||
|
id,
|
||||||
|
slug,
|
||||||
|
schoolClass: selectedClass,
|
||||||
|
restricted: false,
|
||||||
|
roomEntries: {
|
||||||
|
edges: [{
|
||||||
|
node: roomEntry,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const RoomEntriesQuery = {
|
||||||
|
room,
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.setup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('room actions should not exist for student', () => {
|
||||||
|
const operations = {
|
||||||
|
MeQuery: getMinimalMe({isTeacher: false}),
|
||||||
|
RoomEntriesQuery,
|
||||||
|
};
|
||||||
|
|
||||||
|
cy.mockGraphqlOps({
|
||||||
|
operations,
|
||||||
|
});
|
||||||
|
cy.visit(`/room/${slug}`);
|
||||||
|
|
||||||
|
cy.getByDataCy('room-title').should('exist');
|
||||||
|
cy.getByDataCy('room-actions').should('not.exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates a room entry', () => {
|
it('creates a room entry', () => {
|
||||||
|
|
@ -240,56 +305,83 @@ describe('The Room Page', () => {
|
||||||
|
|
||||||
cy.visit(`/room/${slug}`);
|
cy.visit(`/room/${slug}`);
|
||||||
cy.getByDataCy('add-room-entry-button').click();
|
cy.getByDataCy('add-room-entry-button').click();
|
||||||
cy.getByDataCy('add-room-entry-modal').should('exist');
|
|
||||||
|
cy.getByDataCy('content-form-section-title').should('have.text', 'Titel (Pflichtfeld)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it.only('changes class while on room page', () => {
|
it('edits own room entry', () => {
|
||||||
const {me} = MeQuery;
|
const room = {
|
||||||
const otherClass = {
|
id: 'some-room',
|
||||||
id: btoa('SchoolClassNode:34'),
|
slug,
|
||||||
name: 'Other Class',
|
// schoolClass: me.selectedClass,
|
||||||
readOnly: false
|
roomEntries: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: roomEntry,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
let selectedClass = me.selectedClass;
|
|
||||||
const operations = {
|
const operations = {
|
||||||
MeQuery: () => {
|
MeQuery: MeQuery,
|
||||||
return {
|
RoomEntriesQuery: {
|
||||||
me: {
|
room,
|
||||||
...me,
|
|
||||||
schoolClasses: [...me.schoolClasses, otherClass],
|
|
||||||
selectedClass
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
RoomEntryQuery: {
|
||||||
|
roomEntry,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
cy.mockGraphqlOps({
|
||||||
|
operations,
|
||||||
|
});
|
||||||
|
cy.visit(`/room/${slug}`);
|
||||||
|
cy.getByDataCy('room-entry-actions').click();
|
||||||
|
cy.getByDataCy('edit-room-entry').click();
|
||||||
|
cy.location('pathname').should('include', entrySlug);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('deletes room entry', () => {
|
||||||
|
const DeleteRoomEntry = {
|
||||||
|
deleteRoomEntry: {
|
||||||
|
success: true,
|
||||||
|
errors: null,
|
||||||
|
roomSlug: slug,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const operations = {
|
||||||
|
MeQuery,
|
||||||
RoomEntriesQuery,
|
RoomEntriesQuery,
|
||||||
UpdateSettings() {
|
DeleteRoomEntry,
|
||||||
selectedClass = otherClass;
|
|
||||||
return {
|
|
||||||
updateSettings: {
|
|
||||||
success: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
ModuleDetailsQuery: {},
|
|
||||||
MySchoolClassQuery: () => {
|
|
||||||
return {
|
|
||||||
me: {
|
|
||||||
selectedClass
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
RoomsQuery: {
|
|
||||||
rooms: []
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
cy.mockGraphqlOps({
|
cy.mockGraphqlOps({
|
||||||
operations,
|
operations,
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.visit(`/room/${slug}`);
|
cy.visit(`/room/${slug}`);
|
||||||
cy.getByDataCy('room-title').should('contain', 'A Room');
|
cy.getByDataCy('room-entry').should('have.length', 1);
|
||||||
cy.selectClass('Other Class');
|
cy.getByDataCy('room-entry-actions').click();
|
||||||
cy.url().should('include', 'rooms');
|
cy.getByDataCy('delete-room-entry').click();
|
||||||
cy.getByDataCy('current-class-name').should('contain', 'Other Class');
|
cy.getByDataCy('delete-room-entry').should('not.exist');
|
||||||
|
cy.getByDataCy('modal-save-button').click();
|
||||||
|
cy.getByDataCy('room-entry').should('have.length', 0);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows room entries with comment count', () => {
|
||||||
|
const operations = {
|
||||||
|
MeQuery,
|
||||||
|
RoomEntriesQuery,
|
||||||
|
};
|
||||||
|
cy.mockGraphqlOps({
|
||||||
|
operations,
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.visit(`/room/${slug}`);
|
||||||
|
cy.getByDataCy('room-entry').should('have.length', 1).within(() => {
|
||||||
|
cy.getByDataCy('entry-count').should('contain.text', '2');
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@
|
||||||
|
|
||||||
const NewContentBlockWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/content-block-form/NewContentBlockWizard');
|
const NewContentBlockWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/content-block-form/NewContentBlockWizard');
|
||||||
const EditContentBlockWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/content-block-form/EditContentBlockWizard');
|
const EditContentBlockWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/content-block-form/EditContentBlockWizard');
|
||||||
const NewRoomEntryWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/rooms/room-entries/NewRoomEntryWizard');
|
|
||||||
const EditRoomEntryWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/rooms/room-entries/EditRoomEntryWizard');
|
const EditRoomEntryWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/rooms/room-entries/EditRoomEntryWizard');
|
||||||
const NewProjectEntryWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/portfolio/NewProjectEntryWizard');
|
const NewProjectEntryWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/portfolio/NewProjectEntryWizard');
|
||||||
const EditProjectEntryWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/portfolio/EditProjectEntryWizard');
|
const EditProjectEntryWizard = () => import(/* webpackChunkName: "content-forms" */'@/components/portfolio/EditProjectEntryWizard');
|
||||||
|
|
@ -58,7 +57,6 @@
|
||||||
SplitLayout,
|
SplitLayout,
|
||||||
NewContentBlockWizard,
|
NewContentBlockWizard,
|
||||||
EditContentBlockWizard,
|
EditContentBlockWizard,
|
||||||
NewRoomEntryWizard,
|
|
||||||
EditRoomEntryWizard,
|
EditRoomEntryWizard,
|
||||||
NewProjectEntryWizard,
|
NewProjectEntryWizard,
|
||||||
EditProjectEntryWizard,
|
EditProjectEntryWizard,
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,15 @@
|
||||||
:checked="localContentBlock.isAssignment"
|
:checked="localContentBlock.isAssignment"
|
||||||
class="content-block-form__task-toggle"
|
class="content-block-form__task-toggle"
|
||||||
label="Inhaltsblock als Auftrag formatieren"
|
label="Inhaltsblock als Auftrag formatieren"
|
||||||
|
v-if="hasDefaultFeatures"
|
||||||
@input="localContentBlock.isAssignment=$event"
|
@input="localContentBlock.isAssignment=$event"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Form for title of content block -->
|
<!-- Form for title of content block -->
|
||||||
<content-form-section title="Titel (Pflichtfeld)">
|
<content-form-section
|
||||||
|
data-cy="content-form-title-section"
|
||||||
|
title="Titel (Pflichtfeld)"
|
||||||
|
>
|
||||||
<input-with-label
|
<input-with-label
|
||||||
:value="localContentBlock.title"
|
:value="localContentBlock.title"
|
||||||
data-cy="content-block-title"
|
data-cy="content-block-title"
|
||||||
|
|
@ -139,8 +143,12 @@
|
||||||
import {CHOOSER, transformInnerContents} from '@/components/content-block-form/helpers.js';
|
import {CHOOSER, transformInnerContents} from '@/components/content-block-form/helpers.js';
|
||||||
import ContentElementActions from '@/components/content-block-form/ContentElementActions.vue';
|
import ContentElementActions from '@/components/content-block-form/ContentElementActions.vue';
|
||||||
import {ContentBlock, numberOrUndefined} from "@/@types";
|
import {ContentBlock, numberOrUndefined} from "@/@types";
|
||||||
|
import {DEFAULT_FEATURE_SET} from "@/consts/features.consts";
|
||||||
|
|
||||||
// TODO: refactor this file, it's huuuuuge!
|
// TODO: refactor this file, it's huuuuuge!
|
||||||
|
interface ContentBlockFormData {
|
||||||
|
localContentBlock: any;
|
||||||
|
}
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -152,6 +160,15 @@
|
||||||
type: Object as PropType<ContentBlock>,
|
type: Object as PropType<ContentBlock>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
features: {
|
||||||
|
type: String,
|
||||||
|
default: DEFAULT_FEATURE_SET
|
||||||
|
}
|
||||||
|
},
|
||||||
|
provide(): object {
|
||||||
|
return {
|
||||||
|
features: this.features
|
||||||
|
};
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
ContentElementActions,
|
ContentElementActions,
|
||||||
|
|
@ -161,7 +178,7 @@
|
||||||
ContentFormSection,
|
ContentFormSection,
|
||||||
Toggle,
|
Toggle,
|
||||||
},
|
},
|
||||||
data() {
|
data(): ContentBlockFormData {
|
||||||
return {
|
return {
|
||||||
localContentBlock: Object.assign({}, {
|
localContentBlock: Object.assign({}, {
|
||||||
title: this.contentBlock.title,
|
title: this.contentBlock.title,
|
||||||
|
|
@ -176,6 +193,9 @@
|
||||||
isValid(): boolean {
|
isValid(): boolean {
|
||||||
return this.localContentBlock.title > '';
|
return this.localContentBlock.title > '';
|
||||||
},
|
},
|
||||||
|
hasDefaultFeatures(): boolean {
|
||||||
|
return this.features === DEFAULT_FEATURE_SET;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
update(index: number, element: any, parent?: number) {
|
update(index: number, element: any, parent?: number) {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@
|
||||||
<component
|
<component
|
||||||
class="content-form-section__icon"
|
class="content-form-section__icon"
|
||||||
:is="icon"
|
:is="icon"
|
||||||
/> <span class="content-form-section__title">{{ title }}</span>
|
/> <span
|
||||||
|
class="content-form-section__title"
|
||||||
|
data-cy="content-form-section-title"
|
||||||
|
>{{ title }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<content-element-actions
|
<content-element-actions
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
<h5
|
<h5
|
||||||
class="subtitle"
|
class="subtitle"
|
||||||
|
data-cy="subtitle-block"
|
||||||
v-html="sanitizedText"
|
v-html="sanitizedText"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
<div
|
<div
|
||||||
class="text-block"
|
class="text-block"
|
||||||
|
data-cy="text-block"
|
||||||
v-html="sanitizedText"
|
v-html="sanitizedText"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="['chooser-element', subclass]"
|
||||||
|
:data-cy="cy"
|
||||||
|
@click="$emit('select')"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
class="chooser-element__icon"
|
||||||
|
:is="icon"
|
||||||
|
/>
|
||||||
|
<div class="chooser-element__title">
|
||||||
|
{{ title }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import formElementIcons from '@/components/ui/form-element-icons';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default() {
|
||||||
|
return `${this.type}-icon`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default() {
|
||||||
|
return this.type.replace(/^\w/, c => c.toUpperCase());
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
...formElementIcons,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
subclass: `chooser-element--${this.type}`,
|
||||||
|
cy: `choose-${this.type}-widget`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~styles/helpers';
|
||||||
|
|
||||||
|
.chooser-element {
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid $color-silver;
|
||||||
|
border-radius: 4px;
|
||||||
|
height: 105px;
|
||||||
|
width: 105px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr 45px;
|
||||||
|
justify-content: center;
|
||||||
|
justify-items: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
align-self: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
Neuer Inhalt
|
Neuer Inhalt
|
||||||
</h3>
|
</h3>
|
||||||
<template
|
<template
|
||||||
v-if="includeListOption"
|
v-if="includeListOption && hasDefaultFeatures"
|
||||||
>
|
>
|
||||||
<checkbox
|
<checkbox
|
||||||
class="content-block-element-chooser-widget__list-toggle"
|
class="content-block-element-chooser-widget__list-toggle"
|
||||||
|
|
@ -28,77 +28,14 @@
|
||||||
:class="{'content-block-element-chooser-widget--no-assignment': hideAssignment}"
|
:class="{'content-block-element-chooser-widget--no-assignment': hideAssignment}"
|
||||||
class="content-block-element-chooser-widget"
|
class="content-block-element-chooser-widget"
|
||||||
>
|
>
|
||||||
<div
|
<chooser-element
|
||||||
class="content-block-element-chooser-widget__link content-block-element-chooser-widget__link--subtitle"
|
:title="type.title"
|
||||||
data-cy="choose-subtitle-widget"
|
:type="type.type"
|
||||||
@click="changeType('subtitle')"
|
:icon="type.icon"
|
||||||
>
|
v-for="(type, idx) in filteredChooserTypes"
|
||||||
<title-icon class="content-block-element-chooser-widget__link-icon" />
|
:key="idx"
|
||||||
<div class="content-block-element-chooser-widget__link-title">
|
@select="changeType(type.block)"
|
||||||
Untertitel
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="content-block-element-chooser-widget__link content-block-element-chooser-widget__link--link"
|
|
||||||
data-cy="choose-link-widget"
|
|
||||||
@click="changeType('link_block')"
|
|
||||||
>
|
|
||||||
<link-icon class="content-block-element-chooser-widget__link-icon" />
|
|
||||||
<div class="content-block-element-chooser-widget__link-title">
|
|
||||||
Link
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="content-block-element-chooser-widget__link content-block-element-chooser-widget__link--video"
|
|
||||||
data-cy="choose-video-widget"
|
|
||||||
@click="changeType('video_block')"
|
|
||||||
>
|
|
||||||
<video-icon class="content-block-element-chooser-widget__link-icon" />
|
|
||||||
<div class="content-block-element-chooser-widget__link-title">
|
|
||||||
Video
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="content-block-element-chooser-widget__link content-block-element-chooser-widget__link--image"
|
|
||||||
data-cy="choose-image-widget"
|
|
||||||
@click="changeType('image_url_block')"
|
|
||||||
>
|
|
||||||
<image-icon class="content-block-element-chooser-widget__link-icon" />
|
|
||||||
<div class="content-block-element-chooser-widget__link-title">
|
|
||||||
Bild
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="content-block-element-chooser-widget__link content-block-element-chooser-widget__link--text"
|
|
||||||
data-cy="choose-text-widget"
|
|
||||||
@click="changeType('text_block')"
|
|
||||||
>
|
|
||||||
<text-icon class="content-block-element-chooser-widget__link-icon" />
|
|
||||||
<div class="content-block-element-chooser-widget__link-title">
|
|
||||||
Text
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="content-block-element-chooser-widget__link content-block-element-chooser-widget__link--assignment"
|
|
||||||
data-cy="choose-assignment-widget"
|
|
||||||
v-if="!hideAssignment"
|
|
||||||
@click="changeType('assignment')"
|
|
||||||
>
|
|
||||||
<speech-bubble-icon class="content-block-element-chooser-widget__link-icon" />
|
|
||||||
<div class="content-block-element-chooser-widget__link-title">
|
|
||||||
Aufgabe & Ergebnis
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="content-block-element-chooser-widget__link content-block-element-chooser-widget__link--document"
|
|
||||||
data-cy="choose-document-widget"
|
|
||||||
@click="changeType('document_block')"
|
|
||||||
>
|
|
||||||
<document-icon class="content-block-element-chooser-widget__link-icon" />
|
|
||||||
<div class="content-block-element-chooser-widget__link-title">
|
|
||||||
Dokument
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -107,8 +44,10 @@
|
||||||
import Checkbox from '@/components/ui/Checkbox';
|
import Checkbox from '@/components/ui/Checkbox';
|
||||||
|
|
||||||
import formElementIcons from '@/components/ui/form-element-icons';
|
import formElementIcons from '@/components/ui/form-element-icons';
|
||||||
import TitleIcon from '@/components/icons/TitleIcon';
|
|
||||||
import CrossIcon from '@/components/icons/CrossIcon';
|
import CrossIcon from '@/components/icons/CrossIcon';
|
||||||
|
import ChooserElement from '@/components/content-forms/ChooserElement';
|
||||||
|
import {DEFAULT_FEATURE_SET} from '@/consts/features.consts';
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -124,16 +63,72 @@
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
inject: ['features'],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
|
ChooserElement,
|
||||||
CrossIcon,
|
CrossIcon,
|
||||||
TitleIcon,
|
|
||||||
Checkbox,
|
Checkbox,
|
||||||
...formElementIcons,
|
...formElementIcons,
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data() {
|
||||||
convertToList: false,
|
const hasDefaultFeatures = this.features === DEFAULT_FEATURE_SET;
|
||||||
}),
|
return {
|
||||||
|
convertToList: false,
|
||||||
|
chooserTypes: [
|
||||||
|
{
|
||||||
|
type: 'subtitle',
|
||||||
|
block: 'subtitle',
|
||||||
|
title: 'Untertitel',
|
||||||
|
icon: 'title-icon',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'link',
|
||||||
|
block: 'link_block',
|
||||||
|
title: 'Link',
|
||||||
|
icon: 'link-icon',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'video',
|
||||||
|
block: 'video_block',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'image',
|
||||||
|
block: 'image_url_block',
|
||||||
|
title: 'Bild',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
block: 'text_block',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'assignment',
|
||||||
|
block: 'assignment',
|
||||||
|
icon: 'speech-bubble-icon',
|
||||||
|
title: 'Aufgabe & Ergebnis',
|
||||||
|
show: !this.hideAssignment && hasDefaultFeatures
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'document',
|
||||||
|
block: 'document_block',
|
||||||
|
title: 'Dokument',
|
||||||
|
show: hasDefaultFeatures
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
filteredChooserTypes() {
|
||||||
|
return this.chooserTypes.filter(type => !("show" in type) || type.show ); // display element if `show` is not set or if `show` evaluates to true
|
||||||
|
},
|
||||||
|
hasDefaultFeatures() {
|
||||||
|
return this.features === DEFAULT_FEATURE_SET;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
changeType(type) {
|
changeType(type) {
|
||||||
|
|
@ -144,7 +139,7 @@
|
||||||
},
|
},
|
||||||
remove() {
|
remove() {
|
||||||
this.$emit('remove');
|
this.$emit('remove');
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -222,29 +217,6 @@
|
||||||
grid-row: 1;
|
grid-row: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__link {
|
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid $color-silver;
|
|
||||||
border-radius: 4px;
|
|
||||||
height: 105px;
|
|
||||||
width: 105px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: 1fr 45px;
|
|
||||||
justify-content: center;
|
|
||||||
justify-items: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__link-icon {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
align-self: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__link-text {
|
|
||||||
font-size: toRem(13px);
|
|
||||||
align-self: start;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 100 100"
|
viewBox="0 0 100 100"
|
||||||
id="shape"
|
id="shape"
|
||||||
><title>cards</title>
|
>
|
||||||
<path
|
<path
|
||||||
d="M30.89,21v9.67H17.44v9.67H4V79H69.11V69.34H82.56V59.67H96V21Zm3.77,3.77H92.23V55.89H82.56V30.65H34.67ZM21.22,34.43H78.78V65.57H69.11V40.33H21.22ZM7.77,44.1H65.34V75.24H7.77Z"
|
d="M30.89,21v9.67H17.44v9.67H4V79H69.11V69.34H82.56V59.67H96V21Zm3.77,3.77H92.23V55.89H82.56V30.65H34.67ZM21.22,34.43H78.78V65.57H69.11V40.33H21.22ZM7.77,44.1H65.34V75.24H7.77Z"
|
||||||
id="Fill-1"
|
id="Fill-1"
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,32 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<router-link
|
||||||
class="add-room-entry-button"
|
class="add-room-entry-button"
|
||||||
data-cy="add-room-entry-button"
|
data-cy="add-room-entry-button"
|
||||||
@click="addRoomEntry"
|
:to="addRoomEntryRoute"
|
||||||
>
|
>
|
||||||
<plus-icon class="add-room-entry-button__icon" />
|
<plus-icon class="add-room-entry-button__icon" />
|
||||||
<span class="add-room-entry-button__text">Beitrag erfassen</span>
|
<span class="add-room-entry-button__text">Beitrag erfassen</span>
|
||||||
</div>
|
</router-link>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { ADD_ROOM_ENTRY_PAGE } from '@/router/room.names';
|
||||||
const PlusIcon = () => import(/* webpackChunkName: "icons" */'@/components/icons/PlusIcon');
|
const PlusIcon = () => import(/* webpackChunkName: "icons" */'@/components/icons/PlusIcon');
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['parent'],
|
props: ['parent'],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
PlusIcon
|
PlusIcon,
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
data() {
|
||||||
addRoomEntry() {
|
return {
|
||||||
this.$store.dispatch('addRoomEntry', this.parent);
|
addRoomEntryRoute: {
|
||||||
}
|
name: ADD_ROOM_ENTRY_PAGE,
|
||||||
}
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="entry-count-widget">
|
<div class="entry-count-widget">
|
||||||
<cards />
|
<component :is="icon" />
|
||||||
<span data-cy="entry-count">{{ entryCount }} <template v-if="verbose">{{ entryCount === 1 ? 'Beitrag' : 'Beiträge' }}</template></span>
|
<span data-cy="entry-count">{{ entryCount }} <template v-if="verbose">{{ entryCount === 1 ? 'Beitrag' : 'Beiträge' }}</template></span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import SpeechBubbleIcon from '@/components/icons/SpeechBubbleIcon';
|
||||||
const Cards = () => import(/* webpackChunkName: "icons" */'@/components/icons/Cards.vue');
|
const Cards = () => import(/* webpackChunkName: "icons" */'@/components/icons/Cards.vue');
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -17,9 +18,15 @@
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: 'cards'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
|
'speech-bubble': SpeechBubbleIcon,
|
||||||
|
SpeechBubbleIcon,
|
||||||
Cards,
|
Cards,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
v-if="showMenu"
|
v-if="showMenu"
|
||||||
@hide-me="showMenu = false"
|
@hide-me="showMenu = false"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot :toggle="toggleMenu" />
|
||||||
</widget-popover>
|
</widget-popover>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -59,9 +59,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&__toggle {
|
&__toggle {
|
||||||
background: white;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|
||||||
|
&--background {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import DELETE_ROOM_MUTATION from 'gql/mutations/rooms/deleteRoom.gql';
|
import DELETE_ROOM_MUTATION from 'gql/mutations/rooms/deleteRoom.gql';
|
||||||
import UPDATE_ROOM_VISIBILITY_MUTATION from 'gql/mutations/rooms/updateRoomVisibility.gql';
|
import UPDATE_ROOM_VISIBILITY_MUTATION from 'gql/mutations/rooms/updateRoomVisibility.gql';
|
||||||
|
|
||||||
|
|
@ -45,7 +44,6 @@
|
||||||
components: {
|
components: {
|
||||||
MoreActions,
|
MoreActions,
|
||||||
PopoverLink,
|
PopoverLink,
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="room-entry">
|
<div
|
||||||
|
class="room-entry"
|
||||||
|
data-cy="room-entry"
|
||||||
|
>
|
||||||
<router-link
|
<router-link
|
||||||
:to="{name: 'article', params: { slug: slug }}"
|
:to="{name: 'article', params: { slug: slug }}"
|
||||||
class="room-entry__router-link"
|
class="room-entry__router-link"
|
||||||
|
|
@ -24,18 +27,25 @@
|
||||||
class="room-entry__teaser"
|
class="room-entry__teaser"
|
||||||
v-html="teaser"
|
v-html="teaser"
|
||||||
/>
|
/>
|
||||||
<user-meta-widget
|
<div class="room-entry__footer">
|
||||||
v-bind="author"
|
<user-meta-widget
|
||||||
class="room-entry__author"
|
v-bind="author"
|
||||||
/>
|
class="room-entry__author"
|
||||||
|
/>
|
||||||
|
<entry-count-widget
|
||||||
|
:entry-count="comments"
|
||||||
|
icon="speech-bubble"
|
||||||
|
:verbose="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<room-entry-actions
|
<room-entry-actions
|
||||||
data-cy="room-entry-actions"
|
data-cy="room-entry-actions"
|
||||||
class="room-entry__more"
|
class="room-entry__more"
|
||||||
|
:slug="slug"
|
||||||
v-if="myEntry"
|
v-if="myEntry"
|
||||||
:id="id"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -46,11 +56,13 @@
|
||||||
import UserMetaWidget from '@/components/UserMetaWidget';
|
import UserMetaWidget from '@/components/UserMetaWidget';
|
||||||
import teaser from '@/helpers/teaser';
|
import teaser from '@/helpers/teaser';
|
||||||
import RoomEntryActions from '@/components/rooms/RoomEntryActions';
|
import RoomEntryActions from '@/components/rooms/RoomEntryActions';
|
||||||
|
import EntryCountWidget from '@/components/rooms/EntryCountWidget';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['title', 'author', 'contents', 'slug', 'id'],
|
props: ['title', 'author', 'contents', 'slug', 'id', 'comments'],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
|
EntryCountWidget,
|
||||||
RoomEntryActions,
|
RoomEntryActions,
|
||||||
UserMetaWidget,
|
UserMetaWidget,
|
||||||
},
|
},
|
||||||
|
|
@ -122,5 +134,12 @@
|
||||||
right: 10px;
|
right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<more-actions data-cy="room-entry-actions">
|
<more-actions
|
||||||
|
data-cy="room-entry-actions"
|
||||||
|
v-slot="{toggle}"
|
||||||
|
>
|
||||||
<popover-link
|
<popover-link
|
||||||
icon="trash-icon"
|
icon="trash-icon"
|
||||||
text="Eintrag löschen"
|
text="Eintrag löschen"
|
||||||
@link-action="deleteRoomEntry(id)"
|
data-cy="delete-room-entry"
|
||||||
|
@link-action="deleteRoomEntry(slug, toggle)"
|
||||||
/>
|
/>
|
||||||
<popover-link
|
<popover-link
|
||||||
icon="pen-icon"
|
icon="pen-icon"
|
||||||
data-cy="edit-room-entry"
|
data-cy="edit-room-entry"
|
||||||
text="Eintrag bearbeiten"
|
text="Eintrag bearbeiten"
|
||||||
@link-action="editRoomEntry(id)"
|
@link-action="editRoomEntry(slug)"
|
||||||
/>
|
/>
|
||||||
</more-actions>
|
</more-actions>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -19,10 +23,12 @@
|
||||||
import MoreActions from '@/components/rooms/MoreActions';
|
import MoreActions from '@/components/rooms/MoreActions';
|
||||||
import DELETE_ROOM_ENTRY_MUTATION from 'gql/mutations/rooms/deleteRoomEntry';
|
import DELETE_ROOM_ENTRY_MUTATION from 'gql/mutations/rooms/deleteRoomEntry';
|
||||||
import ROOM_ENTRIES_QUERY from 'gql/queries/roomEntriesQuery';
|
import ROOM_ENTRIES_QUERY from 'gql/queries/roomEntriesQuery';
|
||||||
|
import {UPDATE_ROOM_ENTRY_PAGE} from '@/router/room.names';
|
||||||
|
import {removeAtIndex} from '@/graphql/immutable-operations';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
id: {
|
slug: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
|
@ -40,30 +46,51 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
deleteRoomEntry(id) {
|
deleteRoomEntry(slug, toggle) {
|
||||||
this.$apollo.mutate({
|
toggle();
|
||||||
mutation: DELETE_ROOM_ENTRY_MUTATION,
|
this.$modal.open('confirm')
|
||||||
variables: {
|
.then(() => {
|
||||||
input: {
|
this.$apollo.mutate({
|
||||||
id,
|
mutation: DELETE_ROOM_ENTRY_MUTATION,
|
||||||
},
|
variables: {
|
||||||
},
|
input: {
|
||||||
update(store, {data: {deleteRoomEntry: {success, roomSlug}}}) {
|
slug,
|
||||||
if (success) {
|
},
|
||||||
const query = ROOM_ENTRIES_QUERY;
|
},
|
||||||
const variables = {slug: roomSlug};
|
update(store, {data: {deleteRoomEntry: {success, roomSlug}}}) {
|
||||||
const data = store.readQuery({query, variables});
|
if (success) {
|
||||||
if (data) {
|
const query = ROOM_ENTRIES_QUERY;
|
||||||
data.room.roomEntries.edges.splice(data.room.roomEntries.edges.findIndex(edge => edge.node.id === id), 1);
|
const variables = {slug: roomSlug};
|
||||||
store.writeQuery({query, data, variables});
|
const {room} = store.readQuery({query, variables});
|
||||||
}
|
if (room) {
|
||||||
}
|
const index = room.roomEntries.edges.findIndex(edge => edge.node.slug === slug);
|
||||||
|
const edges = removeAtIndex(room.roomEntries.edges, index);
|
||||||
|
const data = {
|
||||||
|
room: {
|
||||||
|
...room,
|
||||||
|
roomEntries: {
|
||||||
|
edges,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
store.writeQuery({query, data, variables});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
});
|
||||||
|
},
|
||||||
|
editRoomEntry(slug) {
|
||||||
|
this.$router.push({
|
||||||
|
name: UPDATE_ROOM_ENTRY_PAGE,
|
||||||
|
params: {
|
||||||
|
slug: this.$route.params.slug,
|
||||||
|
entrySlug: slug,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
editRoomEntry(id) {
|
|
||||||
this.$store.dispatch('editRoomEntry', id);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<li
|
<li
|
||||||
class="popover-links__link"
|
class="popover-links__link"
|
||||||
@click="$emit('link-action')"
|
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="popover-link"
|
class="popover-link"
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
border-bottom-left-radius: $default-border-radius;
|
border-bottom-left-radius: $default-border-radius;
|
||||||
border-bottom-right-radius: $default-border-radius;
|
border-bottom-right-radius: $default-border-radius;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
padding-inline: $small-spacing;
|
||||||
|
|
||||||
@include desktop {
|
@include desktop {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export const DEFAULT_FEATURE_SET = 'default';
|
||||||
|
export const ROOMS_FEATURE_SET = 'rooms';
|
||||||
|
|
@ -22,6 +22,9 @@ const typePolicies = {
|
||||||
InstrumentNode: {
|
InstrumentNode: {
|
||||||
keyFields: ['slug']
|
keyFields: ['slug']
|
||||||
},
|
},
|
||||||
|
RoomNode: {
|
||||||
|
keyFields: ['slug']
|
||||||
|
},
|
||||||
ModuleNode: {
|
ModuleNode: {
|
||||||
fields: {
|
fields: {
|
||||||
inEditMode: {
|
inEditMode: {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ fragment RoomEntryParts on RoomEntryNode {
|
||||||
slug
|
slug
|
||||||
title
|
title
|
||||||
contents
|
contents
|
||||||
|
comments {
|
||||||
|
id
|
||||||
|
}
|
||||||
author {
|
author {
|
||||||
id
|
id
|
||||||
firstName
|
firstName
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ function networkErrorCallback(statusCode) {
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
Vue.$log.debug('navigation guard called', to, from);
|
Vue.$log.debug('navigation guard called', to, from);
|
||||||
if (to.path === '/logout') {
|
if (to.path === '/logout') {
|
||||||
|
Vue.$log.debug('logout', to);
|
||||||
publicApolloClient.resetStore();
|
publicApolloClient.resetStore();
|
||||||
if (process.env.LOGOUT_REDIRECT_URL) {
|
if (process.env.LOGOUT_REDIRECT_URL) {
|
||||||
location.replace(`https://sso.hep-verlag.ch/logout?return_to=${process.env.LOGOUT_REDIRECT_URL}`);
|
location.replace(`https://sso.hep-verlag.ch/logout?return_to=${process.env.LOGOUT_REDIRECT_URL}`);
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@
|
||||||
const VideoBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/VideoBlock');
|
const VideoBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/VideoBlock');
|
||||||
const LinkBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/LinkBlock');
|
const LinkBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/LinkBlock');
|
||||||
const DocumentBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/DocumentBlock');
|
const DocumentBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/DocumentBlock');
|
||||||
|
const SubtitleBlock = () => import(/* webpackChunkName: "content-components" */'@/components/content-blocks/SubtitleBlock');
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -51,6 +52,7 @@
|
||||||
'video_block': VideoBlock,
|
'video_block': VideoBlock,
|
||||||
'link_block': LinkBlock,
|
'link_block': LinkBlock,
|
||||||
'document_block': DocumentBlock,
|
'document_block': DocumentBlock,
|
||||||
|
'subtitle': SubtitleBlock,
|
||||||
UserMetaWidget,
|
UserMetaWidget,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
// todo: refactor this, we don't need 2 components, remove editRoom or EditRoom component
|
// todo: refactor this, we don't need 2 components, remove editRoom or EditRoom component
|
||||||
import EditRoom from '@/components/rooms/EditRoom';
|
import EditRoom from '@/components/rooms/EditRoom';
|
||||||
|
|
||||||
import ROOM_QUERY from '@/graphql/gql/queries/roomQuery.gql';
|
import ROOM_QUERY from 'gql/queries/roomQuery.gql';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['id'],
|
props: ['id'],
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
<template>
|
||||||
|
<content-block-form
|
||||||
|
:content-block="roomEntry"
|
||||||
|
:features="features"
|
||||||
|
v-if="roomEntry.id"
|
||||||
|
@save="save"
|
||||||
|
@back="goBack"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
import ROOM_ENTRY_QUERY from 'gql/queries/roomEntryQuery.gql';
|
||||||
|
import ROOM_ENTRY_FRAGMENT from 'gql/fragments/roomEntryParts.gql';
|
||||||
|
import UPDATE_ROOM_ENTRY_MUTATION from 'gql/mutations/rooms/updateRoomEntry.gql';
|
||||||
|
|
||||||
|
import ContentBlockForm from '@/components/content-block-form/ContentBlockForm';
|
||||||
|
import {ROOMS_FEATURE_SET} from '@/consts/features.consts';
|
||||||
|
import {ROOM_PAGE} from '@/router/room.names';
|
||||||
|
|
||||||
|
export default Vue.extend( {
|
||||||
|
props: {
|
||||||
|
slug: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
entrySlug: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
components: {
|
||||||
|
ContentBlockForm,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
features: ROOMS_FEATURE_SET,
|
||||||
|
roomEntry: {
|
||||||
|
title: '',
|
||||||
|
contents: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
apollo: {
|
||||||
|
roomEntry: {
|
||||||
|
query: ROOM_ENTRY_QUERY,
|
||||||
|
variables() {
|
||||||
|
return {
|
||||||
|
slug: this.entrySlug
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
goBack() {
|
||||||
|
this.$router.push({
|
||||||
|
name: ROOM_PAGE,
|
||||||
|
params: {
|
||||||
|
slug: this.slug
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
save({title, contents}) {
|
||||||
|
const entry = {
|
||||||
|
slug: this.roomEntry.slug,
|
||||||
|
title,
|
||||||
|
contents,
|
||||||
|
};
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: UPDATE_ROOM_ENTRY_MUTATION,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
roomEntry: entry,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update: (store, {data: {updateRoomEntry: {roomEntry}}}) => {
|
||||||
|
try {
|
||||||
|
const fragment = ROOM_ENTRY_FRAGMENT;
|
||||||
|
const id = store.identify(roomEntry);
|
||||||
|
const cachedEntry = store.readQuery({fragment, id});
|
||||||
|
const data = Object.assign({}, cachedEntry, roomEntry);
|
||||||
|
store.writeFragment({
|
||||||
|
id,
|
||||||
|
fragment,
|
||||||
|
data
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Query did not exist in the cache, and apollo throws a generic Error. Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
this.goBack();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
} );
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~styles/helpers';
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
import RoomForm from '@/components/rooms/RoomForm';
|
import RoomForm from '@/components/rooms/RoomForm';
|
||||||
|
|
||||||
import ADD_ROOM_MUTATION from 'gql/mutations/rooms/addRoom.gql';
|
import ADD_ROOM_MUTATION from 'gql/mutations/rooms/addRoom.gql';
|
||||||
import ROOMS_QUERY from '@/graphql/gql/queries/roomsQuery.gql';
|
import ROOMS_QUERY from 'gql/queries/roomsQuery.gql';
|
||||||
|
|
||||||
const defaultAppearance = 'blue';
|
const defaultAppearance = 'blue';
|
||||||
|
|
||||||
|
|
@ -1,58 +1,70 @@
|
||||||
<template>
|
<template>
|
||||||
<contents-form
|
<content-block-form
|
||||||
:content-block="entry"
|
:content-block="roomEntry"
|
||||||
:show-task-selection="false"
|
:features="features"
|
||||||
:disable-save="saving"
|
@save="save"
|
||||||
data-cy="add-room-entry-modal"
|
@back="goBack"
|
||||||
block-type="RoomEntry"
|
|
||||||
@save="saveEntry"
|
|
||||||
@hide="hideModal"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
import NEW_ROOM_ENTRY_MUTATION from 'gql/mutations/rooms/addRoomEntry.gql';
|
import NEW_ROOM_ENTRY_MUTATION from 'gql/mutations/rooms/addRoomEntry.gql';
|
||||||
import ROOM_ENTRIES_QUERY from '@/graphql/gql/queries/roomEntriesQuery.gql';
|
import ROOM_ENTRIES_QUERY from '@/graphql/gql/queries/roomEntriesQuery.gql';
|
||||||
|
|
||||||
import ContentsForm from '@/components/content-block-form/ContentsForm';
|
import ContentBlockForm from '@/components/content-block-form/ContentBlockForm';
|
||||||
|
import {ROOMS_FEATURE_SET} from '@/consts/features.consts';
|
||||||
|
import {ROOM_PAGE} from '@/router/room.names';
|
||||||
|
|
||||||
|
export default Vue.extend( {
|
||||||
|
props: {
|
||||||
|
slug: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
components: {
|
||||||
ContentsForm
|
ContentBlockForm,
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
entry: {
|
features: ROOMS_FEATURE_SET,
|
||||||
|
roomEntry: {
|
||||||
title: '',
|
title: '',
|
||||||
contents: []
|
contents: [],
|
||||||
},
|
},
|
||||||
saving: false
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
room() {
|
|
||||||
return this.$store.state.parentRoom;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
saveEntry(entry) {
|
goBack() {
|
||||||
this.saving = true;
|
this.$router.push({
|
||||||
|
name: ROOM_PAGE,
|
||||||
|
params: {
|
||||||
|
slug: this.slug
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
save({title, contents}) {
|
||||||
|
const entry = {
|
||||||
|
title,
|
||||||
|
contents,
|
||||||
|
roomSlug: this.slug
|
||||||
|
};
|
||||||
this.$apollo.mutate({
|
this.$apollo.mutate({
|
||||||
mutation: NEW_ROOM_ENTRY_MUTATION,
|
mutation: NEW_ROOM_ENTRY_MUTATION,
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
roomEntry: Object.assign({}, entry, {
|
roomEntry: entry,
|
||||||
room: this.room.id
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
update: (store, {data: {addRoomEntry: {roomEntry}}}) => {
|
update: (store, {data: {addRoomEntry: {roomEntry}}}) => {
|
||||||
try {
|
try {
|
||||||
const query = ROOM_ENTRIES_QUERY;
|
const query = ROOM_ENTRIES_QUERY;
|
||||||
const variables = {slug: this.room.slug};
|
const variables = {slug: this.slug};
|
||||||
const {room} = store.readQuery({query, variables});
|
const {room} = store.readQuery({query, variables});
|
||||||
if (room && room.roomEntries) {
|
if (room && room.roomEntries) {
|
||||||
const newEdge ={
|
const newEdge ={
|
||||||
|
|
@ -79,13 +91,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
this.saving = false;
|
this.goBack();
|
||||||
this.hideModal();
|
|
||||||
});
|
});
|
||||||
},
|
|
||||||
hideModal() {
|
}
|
||||||
this.$store.dispatch('hideModal');
|
},
|
||||||
},
|
} );
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '~styles/helpers';
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -44,8 +44,9 @@
|
||||||
-->
|
-->
|
||||||
</add-room-entry-button>
|
</add-room-entry-button>
|
||||||
<room-entry
|
<room-entry
|
||||||
v-for="entry in room.roomEntries"
|
|
||||||
v-bind="entry"
|
v-bind="entry"
|
||||||
|
:comments="entry.comments.length"
|
||||||
|
v-for="entry in room.roomEntries"
|
||||||
:key="entry.id"
|
:key="entry.id"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -53,7 +54,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ROOM_ENTRIES_QUERY from '@/graphql/gql/queries/roomEntriesQuery.gql';
|
import ROOM_ENTRIES_QUERY from 'gql/queries/roomEntriesQuery.gql';
|
||||||
import room from '@/mixins/room';
|
import room from '@/mixins/room';
|
||||||
import me from '@/mixins/me';
|
import me from '@/mixins/me';
|
||||||
import BackLink from '@/components/BackLink';
|
import BackLink from '@/components/BackLink';
|
||||||
|
|
@ -22,8 +22,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ROOMS_QUERY from '@/graphql/gql/queries/roomsQuery.gql';
|
import ROOMS_QUERY from 'gql/queries/roomsQuery.gql';
|
||||||
import ME_QUERY from '@/graphql/gql/queries/meQuery.gql';
|
import ME_QUERY from 'gql/queries/meQuery.gql';
|
||||||
|
|
||||||
import RoomWidget from '@/components/rooms/RoomWidget.vue';
|
import RoomWidget from '@/components/rooms/RoomWidget.vue';
|
||||||
import AddRoom from '@/components/rooms/AddRoom.vue';
|
import AddRoom from '@/components/rooms/AddRoom.vue';
|
||||||
|
|
@ -1,2 +1,5 @@
|
||||||
export const NEW_ROOM_PAGE = 'new-room';
|
export const NEW_ROOM_PAGE = 'new-room';
|
||||||
export const ROOMS_PAGE = 'rooms';
|
export const ROOMS_PAGE = 'rooms';
|
||||||
|
export const ROOM_PAGE = 'room';
|
||||||
|
export const ADD_ROOM_ENTRY_PAGE = 'add-room-entry';
|
||||||
|
export const UPDATE_ROOM_ENTRY_PAGE = 'update-room-entry';
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,20 @@
|
||||||
import {NEW_ROOM_PAGE, ROOMS_PAGE} from '@/router/room.names';
|
import {NEW_ROOM_PAGE, ROOMS_PAGE, ADD_ROOM_ENTRY_PAGE, ROOM_PAGE, UPDATE_ROOM_ENTRY_PAGE} from '@/router/room.names';
|
||||||
|
|
||||||
const rooms = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms');
|
const rooms = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/rooms');
|
||||||
const newRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/newRoom');
|
const newRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/newRoom');
|
||||||
const editRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/editRoom');
|
const editRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/editRoom');
|
||||||
const room = () => import(/* webpackChunkName: "rooms" */'@/pages/room');
|
const room = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/room');
|
||||||
|
const newRoomEntry = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/newRoomEntry');
|
||||||
|
const editRoomEntry = () => import(/* webpackChunkName: "rooms" */'@/pages/rooms/editRoomEntry');
|
||||||
const moduleRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/module/moduleRoom');
|
const moduleRoom = () => import(/* webpackChunkName: "rooms" */'@/pages/module/moduleRoom');
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{path: '/rooms', name: ROOMS_PAGE, component: rooms, meta: {filter: true, hideFooter: true}},
|
{path: '/rooms', name: ROOMS_PAGE, component: rooms, meta: {filter: true, hideFooter: true}},
|
||||||
{path: '/new-room/', name: NEW_ROOM_PAGE, component: newRoom},
|
{path: '/new-room/', name: NEW_ROOM_PAGE, component: newRoom},
|
||||||
{path: '/edit-room/:id', name: 'edit-room', component: editRoom, props: true},
|
{path: '/edit-room/:id', name: 'edit-room', component: editRoom, props: true},
|
||||||
{path: '/room/:slug', name: 'room', component: room, props: true},
|
{path: '/room/:slug', name: ROOM_PAGE, component: room, props: true},
|
||||||
|
{path: '/room/:slug/add', name: ADD_ROOM_ENTRY_PAGE, component: newRoomEntry, props: true},
|
||||||
|
{path: '/room/:slug/edit/:entrySlug', name: UPDATE_ROOM_ENTRY_PAGE, component: editRoomEntry, props: true},
|
||||||
{
|
{
|
||||||
path: '/module-room/:slug',
|
path: '/module-room/:slug',
|
||||||
name: 'moduleRoom',
|
name: 'moduleRoom',
|
||||||
|
|
|
||||||
|
|
@ -105,10 +105,6 @@ export default new Vuex.Store({
|
||||||
commit('setContentBlockPosition', payload);
|
commit('setContentBlockPosition', payload);
|
||||||
dispatch('showModal', 'new-content-block-wizard');
|
dispatch('showModal', 'new-content-block-wizard');
|
||||||
},
|
},
|
||||||
addRoomEntry({commit, dispatch}, payload) {
|
|
||||||
commit('setParentRoom', payload);
|
|
||||||
dispatch('showModal', 'new-room-entry-wizard');
|
|
||||||
},
|
|
||||||
editRoomEntry({commit, dispatch}, payload) {
|
editRoomEntry({commit, dispatch}, payload) {
|
||||||
commit('setCurrentRoomEntry', payload);
|
commit('setCurrentRoomEntry', payload);
|
||||||
dispatch('showModal', 'edit-room-entry-wizard');
|
dispatch('showModal', 'edit-room-entry-wizard');
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ class RoomEntryArgument(InputObjectType):
|
||||||
|
|
||||||
|
|
||||||
class AddRoomEntryArgument(RoomEntryArgument):
|
class AddRoomEntryArgument(RoomEntryArgument):
|
||||||
room = graphene.ID(required=True)
|
room_slug = graphene.String(required=True)
|
||||||
|
|
||||||
|
|
||||||
class UpdateRoomEntryArgument(RoomEntryArgument):
|
class UpdateRoomEntryArgument(RoomEntryArgument):
|
||||||
id = graphene.ID(required=True)
|
slug = graphene.String(required=True)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from django.db import models
|
||||||
from django_extensions.db.models import TitleSlugDescriptionModel
|
from django_extensions.db.models import TitleSlugDescriptionModel
|
||||||
from wagtail.core.fields import StreamField
|
from wagtail.core.fields import StreamField
|
||||||
|
|
||||||
from books.blocks import DocumentBlock, ImageUrlBlock, LinkBlock, VideoBlock
|
from books.blocks import DocumentBlock, ImageUrlBlock, LinkBlock, SubtitleBlock, VideoBlock
|
||||||
from books.models import TextBlock
|
from books.models import TextBlock
|
||||||
from core.mixins import GraphqlNodeMixin
|
from core.mixins import GraphqlNodeMixin
|
||||||
from users.models import SchoolClass
|
from users.models import SchoolClass
|
||||||
|
|
@ -37,6 +37,7 @@ class RoomEntry(TitleSlugDescriptionModel):
|
||||||
('image_url_block', ImageUrlBlock()),
|
('image_url_block', ImageUrlBlock()),
|
||||||
('link_block', LinkBlock()),
|
('link_block', LinkBlock()),
|
||||||
('document_block', DocumentBlock()),
|
('document_block', DocumentBlock()),
|
||||||
|
('subtitle', SubtitleBlock()),
|
||||||
('video_block', VideoBlock())
|
('video_block', VideoBlock())
|
||||||
], null=True, blank=True)
|
], null=True, blank=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,12 +86,13 @@ class MutateRoomEntry(relay.ClientIDMutation):
|
||||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
room_entry_data = kwargs.get('room_entry')
|
room_entry_data = kwargs.get('room_entry')
|
||||||
room = None
|
room = None
|
||||||
|
room_slug = room_entry_data.get('room_slug')
|
||||||
|
|
||||||
if room_entry_data.get('room') is not None:
|
if room_slug is not None:
|
||||||
room = get_object(Room, room_entry_data.get('room'))
|
room = Room.objects.get(slug=room_slug)
|
||||||
room_entry_data['room'] = room.id
|
room_entry_data['room'] = room.id
|
||||||
|
|
||||||
if room_entry_data.get('id') is not None:
|
if room_entry_data.get('slug') is not None:
|
||||||
serializer = cls.update_room_entry(info, room_entry_data)
|
serializer = cls.update_room_entry(info, room_entry_data)
|
||||||
else:
|
else:
|
||||||
serializer = cls.add_room_entry(info, room_entry_data, room)
|
serializer = cls.add_room_entry(info, room_entry_data, room)
|
||||||
|
|
@ -105,19 +106,18 @@ class MutateRoomEntry(relay.ClientIDMutation):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_room_entry(cls, info, room_entry_data):
|
def update_room_entry(cls, info, room_entry_data):
|
||||||
instance = get_object(RoomEntry, room_entry_data.get('id'))
|
instance = RoomEntry.objects.get(slug=room_entry_data.get('slug'))
|
||||||
|
|
||||||
if not instance.room.school_class.is_user_in_schoolclass(info.context.user):
|
if not instance.room.school_class.is_user_in_schoolclass(info.context.user):
|
||||||
raise Exception('You are in the wrong class')
|
raise Exception('You are in the wrong class')
|
||||||
|
|
||||||
if instance.author.pk != info.context.user.pk:
|
if instance.author.pk != info.context.user.pk:
|
||||||
raise Exception('You are not the author')
|
raise Exception('You are not the author')
|
||||||
|
|
||||||
return RoomEntrySerializer(instance, data=room_entry_data, partial=True)
|
return RoomEntrySerializer(instance, data=room_entry_data, partial=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_room_entry(cls, info, room_entry_data, room):
|
def add_room_entry(cls, info, room_entry_data, room):
|
||||||
|
|
||||||
if not room or not room.school_class.is_user_in_schoolclass(info.context.user):
|
if not room or not room.school_class.is_user_in_schoolclass(info.context.user):
|
||||||
raise PermissionDenied('You are in the wrong class')
|
raise PermissionDenied('You are in the wrong class')
|
||||||
|
|
||||||
|
|
@ -137,7 +137,7 @@ class UpdateRoomEntry(MutateRoomEntry):
|
||||||
|
|
||||||
class DeleteRoomEntry(relay.ClientIDMutation):
|
class DeleteRoomEntry(relay.ClientIDMutation):
|
||||||
class Input:
|
class Input:
|
||||||
id = graphene.ID(required=True)
|
slug = graphene.String(required=True)
|
||||||
|
|
||||||
success = graphene.Boolean()
|
success = graphene.Boolean()
|
||||||
room_slug = graphene.String()
|
room_slug = graphene.String()
|
||||||
|
|
@ -146,8 +146,8 @@ class DeleteRoomEntry(relay.ClientIDMutation):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
id = kwargs.get('id')
|
slug = kwargs.get('slug')
|
||||||
room_entry = get_object(RoomEntry, id)
|
room_entry = RoomEntry.objects.get(slug=slug)
|
||||||
if room_entry.author.pk != info.context.user.pk:
|
if room_entry.author.pk != info.context.user.pk:
|
||||||
raise Exception('You are not the owner of this room entry')
|
raise Exception('You are not the owner of this room entry')
|
||||||
room_id = to_global_id('RoomNode', room_entry.room.pk)
|
room_id = to_global_id('RoomNode', room_entry.room.pk)
|
||||||
|
|
@ -170,7 +170,8 @@ class UpdateRoomVisibility(relay.ClientIDMutation):
|
||||||
restricted = kwargs.get('restricted')
|
restricted = kwargs.get('restricted')
|
||||||
user = info.context.user
|
user = info.context.user
|
||||||
room = get_object(Room, id)
|
room = get_object(Room, id)
|
||||||
if not user.is_teacher() or not SchoolClassMember.objects.filter(active=True,user=user,school_class=room.school_class).exists():
|
if not user.is_teacher() or not SchoolClassMember.objects.filter(active=True, user=user,
|
||||||
|
school_class=room.school_class).exists():
|
||||||
raise Exception('You are not permitted to do this')
|
raise Exception('You are not permitted to do this')
|
||||||
room.restricted = restricted
|
room.restricted = restricted
|
||||||
room.save()
|
room.save()
|
||||||
|
|
@ -197,7 +198,6 @@ class AddComment(relay.ClientIDMutation):
|
||||||
return cls(success=True, comment=comment)
|
return cls(success=True, comment=comment)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RoomMutations:
|
class RoomMutations:
|
||||||
update_room = UpdateRoom.Field()
|
update_room = UpdateRoom.Field()
|
||||||
add_room = AddRoom.Field()
|
add_room = AddRoom.Field()
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,32 @@ from rooms.factories import RoomEntryFactory, RoomFactory
|
||||||
from rooms.models import RoomEntry
|
from rooms.models import RoomEntry
|
||||||
from users.factories import SchoolClassFactory
|
from users.factories import SchoolClassFactory
|
||||||
|
|
||||||
|
ADD_ROOM_ENTRY_MUTATION = """
|
||||||
|
fragment RoomEntryParts on RoomEntryNode {
|
||||||
|
id
|
||||||
|
slug
|
||||||
|
title
|
||||||
|
contents
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
firstName
|
||||||
|
lastName
|
||||||
|
avatarUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mutation AddRoomEntry($input: AddRoomEntryInput!){
|
||||||
|
addRoomEntry(input: $input) {
|
||||||
|
roomEntry {
|
||||||
|
...RoomEntryParts
|
||||||
|
}
|
||||||
|
errors
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class RoomEntryMutationsTestCase(SkillboxTestCase):
|
class RoomEntryMutationsTestCase(SkillboxTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
@ -39,7 +65,7 @@ class RoomEntryMutationsTestCase(SkillboxTestCase):
|
||||||
|
|
||||||
result = self.client.execute(mutation, variables={
|
result = self.client.execute(mutation, variables={
|
||||||
'input': {
|
'input': {
|
||||||
'id': to_global_id('RoomEntryNode', self.room_entry.pk)
|
'slug': self.room_entry.slug
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
self.assertIsNone(result.get('errors'))
|
self.assertIsNone(result.get('errors'))
|
||||||
|
|
@ -141,41 +167,16 @@ class RoomEntryMutationsTestCase(SkillboxTestCase):
|
||||||
|
|
||||||
def test_add_room_entry_not_owner_from_other_class(self):
|
def test_add_room_entry_not_owner_from_other_class(self):
|
||||||
self.assertEqual(RoomEntry.objects.count(), 1)
|
self.assertEqual(RoomEntry.objects.count(), 1)
|
||||||
mutation = """
|
|
||||||
fragment RoomEntryParts on RoomEntryNode {
|
|
||||||
id
|
|
||||||
slug
|
|
||||||
title
|
|
||||||
contents
|
|
||||||
author {
|
|
||||||
id
|
|
||||||
firstName
|
|
||||||
lastName
|
|
||||||
avatarUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
mutation AddRoomEntry($input: AddRoomEntryInput!){
|
|
||||||
addRoomEntry(input: $input) {
|
|
||||||
roomEntry {
|
|
||||||
...RoomEntryParts
|
|
||||||
}
|
|
||||||
errors
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
# input:
|
# input:
|
||||||
# title = graphene.String(required=True)
|
# title = graphene.String(required=True)
|
||||||
# contents = graphene.List(ContentElementInput)
|
# contents = graphene.List(ContentElementInput)
|
||||||
# room = graphene.ID(required=True)
|
# room = graphene.ID(required=True)
|
||||||
room_entry = {
|
room_entry = {
|
||||||
'title': 'Bad Actor!',
|
'title': 'Bad Actor!',
|
||||||
'room': self.room.graphql_id
|
'roomSlug': self.room.slug
|
||||||
}
|
}
|
||||||
|
|
||||||
result = self.get_client(self.yet_another_user).execute(mutation, variables={
|
result = self.get_client(self.yet_another_user).execute(ADD_ROOM_ENTRY_MUTATION, variables={
|
||||||
'input': {
|
'input': {
|
||||||
'roomEntry': room_entry
|
'roomEntry': room_entry
|
||||||
}
|
}
|
||||||
|
|
@ -183,3 +184,26 @@ mutation AddRoomEntry($input: AddRoomEntryInput!){
|
||||||
self.assertIsNotNone(result.errors)
|
self.assertIsNotNone(result.errors)
|
||||||
self.assertTrue('message' in result.errors[0])
|
self.assertTrue('message' in result.errors[0])
|
||||||
self.assertEqual(result.errors[0]['message'], 'You are in the wrong class')
|
self.assertEqual(result.errors[0]['message'], 'You are in the wrong class')
|
||||||
|
|
||||||
|
def test_add_room_entry(self):
|
||||||
|
self.assertEqual(RoomEntry.objects.count(), 1)
|
||||||
|
text_block = {"type": "text_block", "value": {"text": "<p>some text</p>"}}
|
||||||
|
subtitle_block = {"type": "subtitle", "value": {"text": "A subtitle"}}
|
||||||
|
room_entry = {
|
||||||
|
'title': 'A room entry',
|
||||||
|
'roomSlug': self.room.slug,
|
||||||
|
'contents': [text_block, subtitle_block]
|
||||||
|
}
|
||||||
|
result = self.get_client(self.user).execute(ADD_ROOM_ENTRY_MUTATION, variables={
|
||||||
|
'input': {
|
||||||
|
'roomEntry': room_entry
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.assertIsNone(result.errors)
|
||||||
|
room_entry_data = result.data.get('addRoomEntry').get('roomEntry')
|
||||||
|
contents = room_entry_data.get('contents')
|
||||||
|
self.assertEqual(len(contents), 2)
|
||||||
|
text, subtitle = contents
|
||||||
|
self.assertEqual(text.get('type'), 'text_block')
|
||||||
|
self.assertEqual(subtitle.get('type'), 'subtitle')
|
||||||
|
self.assertEqual(RoomEntry.objects.count(), 2)
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ input AddRoomArgument {
|
||||||
input AddRoomEntryArgument {
|
input AddRoomEntryArgument {
|
||||||
title: String!
|
title: String!
|
||||||
contents: [ContentElementInput]
|
contents: [ContentElementInput]
|
||||||
room: ID!
|
roomSlug: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
input AddRoomEntryInput {
|
input AddRoomEntryInput {
|
||||||
|
|
@ -421,7 +421,7 @@ type DeleteProjectPayload {
|
||||||
}
|
}
|
||||||
|
|
||||||
input DeleteRoomEntryInput {
|
input DeleteRoomEntryInput {
|
||||||
id: ID!
|
slug: String!
|
||||||
clientMutationId: String
|
clientMutationId: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1401,7 +1401,7 @@ input UpdateRoomArgument {
|
||||||
input UpdateRoomEntryArgument {
|
input UpdateRoomEntryArgument {
|
||||||
title: String!
|
title: String!
|
||||||
contents: [ContentElementInput]
|
contents: [ContentElementInput]
|
||||||
id: ID!
|
slug: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
input UpdateRoomEntryInput {
|
input UpdateRoomEntryInput {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue