Add copy link button to chapters, also generate redirect link

This commit is contained in:
Ramon Wenger 2023-02-09 17:46:57 +01:00
parent 370afd0b0c
commit a629f6a5e6
6 changed files with 119 additions and 71 deletions

View File

@ -9,8 +9,12 @@
class="hideable-element" class="hideable-element"
v-if="!titleHidden" v-if="!titleHidden"
> >
<h3 :id="'chapter-' + index"> <h3
class="chapter__title"
:id="'chapter-' + index"
>
{{ chapter.title }} {{ chapter.title }}
<copy-link :id="chapter.id" />
</h3> </h3>
</div> </div>
@ -66,6 +70,7 @@ import ContentBlock from '@/components/ContentBlock';
import AddContentButton from '@/components/AddContentButton'; import AddContentButton from '@/components/AddContentButton';
import BookmarkActions from '@/components/notes/BookmarkActions'; import BookmarkActions from '@/components/notes/BookmarkActions';
import VisibilityAction from '@/components/visibility/VisibilityAction'; import VisibilityAction from '@/components/visibility/VisibilityAction';
import CopyLink from '@/components/CopyLink.vue';
import { hidden } from '@/helpers/visibility'; import { hidden } from '@/helpers/visibility';
import { CHAPTER_DESCRIPTION_TYPE, CHAPTER_TITLE_TYPE, CONTENT_TYPE } from '@/consts/types'; import { CHAPTER_DESCRIPTION_TYPE, CHAPTER_TITLE_TYPE, CONTENT_TYPE } from '@/consts/types';
@ -98,6 +103,7 @@ export default {
VisibilityAction, VisibilityAction,
ContentBlock, ContentBlock,
AddContentButton, AddContentButton,
CopyLink,
}, },
computed: { computed: {
@ -231,6 +237,12 @@ export default {
position: relative; position: relative;
} }
&__title {
display: flex;
justify-content: space-between;
align-items: center;
}
&__description { &__description {
@include lead-paragraph; @include lead-paragraph;

View File

@ -219,10 +219,7 @@ onMounted(() => {
const rect = element.getBoundingClientRect(); const rect = element.getBoundingClientRect();
window.scrollTo({ top: rect.y, behavior: 'smooth' }); window.scrollTo({ top: rect.y, behavior: 'smooth' });
top.value = rect.y; top.value = rect.y;
console.log(rect.y);
console.log(rect);
}, 750); }, 750);
console.log(document.readyState);
} }
} }
}); });

View File

@ -11,6 +11,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { defineAsyncComponent, computed, ref } from 'vue'; import { defineAsyncComponent, computed, ref } from 'vue';
import log from 'loglevel';
const LinkIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/LinkIcon.vue')); const LinkIcon = defineAsyncComponent(() => import(/* webpackChunkName: "icons" */ '@/components/icons/LinkIcon.vue'));
@ -26,14 +27,9 @@ const show = ref(false);
const copyLink = () => { const copyLink = () => {
show.value = true; show.value = true;
navigator.clipboard.writeText(directLink.value).then( navigator.clipboard.writeText(directLink.value).then(() => {
() => { log.debug('copied link', directLink.value);
console.log('yay!', directLink.value); });
},
() => {
console.log('nay!');
}
);
setTimeout(() => { setTimeout(() => {
show.value = false; show.value = false;
}, 3000); }, 3000);

View File

@ -1,9 +1,4 @@
<template> <template><div /></template>
<div>
<h1>ContentBlockLocator {{ props.id }}</h1>
<pre>{{ result }}</pre>
</div>
</template>
<script setup lang="ts"> <script setup lang="ts">
import { useQuery } from '@vue/apollo-composable'; import { useQuery } from '@vue/apollo-composable';
@ -14,22 +9,37 @@ const props = defineProps<{
id: string; id: string;
}>(); }>();
const { result, onResult } = useQuery(gql` const decoded = window.atob(props.id);
query {
contentBlock(id: "${props.id}") { let query;
if (decoded.startsWith('ChapterNode')) {
query = gql`
query ChapterQuery($id: ID!) {
chapter(id: $id) {
path path
} }
}
`);
onResult(
({
data: {
contentBlock: { path },
},
}) => {
// console.log(queryResult);
router.push(`/${path}`);
} }
); `;
} else {
query = gql`
query ContentBlockQuery($id: ID!) {
contentBlock(id: $id) {
path
}
}
`;
}
const { onResult } = useQuery(query, () => ({ id: props.id }));
onResult(({ data }) => {
let path;
try {
path = data.chapter.path;
} catch (e) {
path = data.contentBlock.path;
}
router.push(`/${path}`);
});
</script> </script>

View File

@ -5,51 +5,67 @@ from wagtail.admin.edit_handlers import FieldPanel, TabbedInterface, ObjectList
from core.wagtail_utils import StrictHierarchyPage, get_default_settings from core.wagtail_utils import StrictHierarchyPage, get_default_settings
from users.models import SchoolClass from users.models import SchoolClass
from core.mixins import GraphqlNodeMixin
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Chapter(StrictHierarchyPage): class Chapter(StrictHierarchyPage, GraphqlNodeMixin):
class Meta: class Meta:
verbose_name = 'Kapitel' verbose_name = "Kapitel"
verbose_name_plural = 'Kapitel' verbose_name_plural = "Kapitel"
description = models.TextField(blank=True) description = models.TextField(blank=True)
content_panels = [ content_panels = [
FieldPanel('title', classname="full title"), FieldPanel("title", classname="full title"),
FieldPanel('description', classname="full description"), FieldPanel("description", classname="full description"),
] ]
edit_handler = TabbedInterface([ edit_handler = TabbedInterface(
ObjectList(content_panels, heading='Content'), [ObjectList(content_panels, heading="Content"), get_default_settings()]
get_default_settings() )
])
template = 'generic_page.html' template = "generic_page.html"
parent_page_types = ['books.Module'] parent_page_types = ["books.Module"]
subpage_types = ['books.ContentBlock'] subpage_types = ["books.ContentBlock"]
title_hidden_for = models.ManyToManyField(SchoolClass, related_name='hidden_chapter_titles') title_hidden_for = models.ManyToManyField(
description_hidden_for = models.ManyToManyField(SchoolClass, related_name='hidden_chapter_descriptions') SchoolClass, related_name="hidden_chapter_titles"
)
description_hidden_for = models.ManyToManyField(
SchoolClass, related_name="hidden_chapter_descriptions"
)
def sync_title_visibility(self, school_class_template, school_class_to_sync): def sync_title_visibility(self, school_class_template, school_class_to_sync):
if self.title_hidden_for.filter(id=school_class_template.id).exists() \ if (
and not self.title_hidden_for.filter(id=school_class_to_sync.id).exists(): self.title_hidden_for.filter(id=school_class_template.id).exists()
and not self.title_hidden_for.filter(id=school_class_to_sync.id).exists()
):
self.title_hidden_for.add(school_class_to_sync) self.title_hidden_for.add(school_class_to_sync)
if self.title_hidden_for.filter( if (
id=school_class_to_sync.id).exists() \ self.title_hidden_for.filter(id=school_class_to_sync.id).exists()
and not self.title_hidden_for.filter(id=school_class_template.id).exists(): and not self.title_hidden_for.filter(id=school_class_template.id).exists()
):
self.title_hidden_for.remove(school_class_to_sync) self.title_hidden_for.remove(school_class_to_sync)
def sync_description_visibility(self, school_class_template, school_class_to_sync): def sync_description_visibility(self, school_class_template, school_class_to_sync):
if self.description_hidden_for.filter(id=school_class_template.id).exists() \ if (
and not self.description_hidden_for.filter(id=school_class_to_sync.id).exists(): self.description_hidden_for.filter(
id=school_class_template.id).exists()
and not self.description_hidden_for.filter(
id=school_class_to_sync.id
).exists()
):
self.description_hidden_for.add(school_class_to_sync) self.description_hidden_for.add(school_class_to_sync)
if self.description_hidden_for.filter( if (
id=school_class_to_sync.id).exists() \ self.description_hidden_for.filter(
and not self.description_hidden_for.filter(id=school_class_template.id).exists(): id=school_class_to_sync.id).exists()
and not self.description_hidden_for.filter(
id=school_class_template.id
).exists()
):
self.description_hidden_for.remove(school_class_to_sync) self.description_hidden_for.remove(school_class_to_sync)

View File

@ -11,43 +11,55 @@ from notes.schema import ChapterBookmarkNode
class ChapterNode(DjangoObjectType): class ChapterNode(DjangoObjectType):
bookmark = graphene.Field(ChapterBookmarkNode) bookmark = graphene.Field(ChapterBookmarkNode)
content_blocks = graphene.List('books.schema.nodes.ContentBlockNode') content_blocks = graphene.List("books.schema.nodes.ContentBlockNode")
title_hidden_for = graphene.List('users.schema.SchoolClassNode') title_hidden_for = graphene.List("users.schema.SchoolClassNode")
description_hidden_for = graphene.List('users.schema.SchoolClassNode') description_hidden_for = graphene.List("users.schema.SchoolClassNode")
path = graphene.String()
class Meta: class Meta:
model = Chapter model = Chapter
only_fields = [ only_fields = [
'slug', 'title', 'description', 'title_hidden_for', 'description_hidden_for' "slug",
"title",
"description",
"title_hidden_for",
"description_hidden_for",
] ]
filter_fields = [ filter_fields = [
'slug', 'title', "slug",
"title",
] ]
interfaces = (relay.Node, ChapterInterface) interfaces = (relay.Node, ChapterInterface)
def resolve_content_blocks(self, info, **kwargs): def resolve_content_blocks(self, info, **kwargs):
user = info.context.user user = info.context.user
school_classes = user.school_classes.values_list('pk', flat=True) school_classes = user.school_classes.values_list("pk", flat=True)
by_parent = ContentBlock.get_by_parent(self) \ by_parent = ContentBlock.get_by_parent(self).filter(
.filter(contentblocksnapshot__isnull=True) # exclude snapshot contentblocksnapshot__isnull=True
) # exclude snapshot
# .prefetch_related('visible_for') \ # .prefetch_related('visible_for') \
# .prefetch_related('hidden_for') # .prefetch_related('hidden_for')
# don't filter the hidden blocks on the server any more, we do this on the client now, as they are not secret # don't filter the hidden blocks on the server any more, we do this on the client now, as they are not secret
default_blocks = Q(user_created=False) default_blocks = Q(user_created=False)
owned_by_user = Q(user_created=True, owner=user) owned_by_user = Q(user_created=True, owner=user)
teacher_created_and_visible = Q(Q(user_created=True) & Q(visible_for__pk__in=school_classes)) teacher_created_and_visible = Q(
Q(user_created=True) & Q(visible_for__pk__in=school_classes)
)
if user.can_manage_school_class_content: # teacher if user.can_manage_school_class_content: # teacher
return by_parent.filter(default_blocks | owned_by_user | teacher_created_and_visible).distinct() return by_parent.filter(
default_blocks | owned_by_user | teacher_created_and_visible
).distinct()
else: # student else: # student
return by_parent.filter(default_blocks | teacher_created_and_visible).distinct() return by_parent.filter(
default_blocks | teacher_created_and_visible
).distinct()
def resolve_bookmark(self, info, **kwargs): def resolve_bookmark(self, info, **kwargs):
return ChapterBookmark.objects.filter( return ChapterBookmark.objects.filter(
user=info.context.user, user=info.context.user, chapter=self
chapter=self
).first() ).first()
@staticmethod @staticmethod
@ -57,3 +69,8 @@ class ChapterNode(DjangoObjectType):
@staticmethod @staticmethod
def resolve_description_hidden_for(parent: Chapter, info, **kwargs): def resolve_description_hidden_for(parent: Chapter, info, **kwargs):
return parent.description_hidden_for.all() return parent.description_hidden_for.all()
@staticmethod
def resolve_path(root: Chapter, info, **kwargs):
module = root.get_parent()
return f"module/{module.slug}#{root.graphql_id}"