Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugin-course): improve and force course editing #3543

Merged
merged 6 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion apps/web/src/components/content/entity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,28 @@ export function Entity({ data }: EntityProps) {
open={courseNavOpen}
onOverviewButtonClick={openCourseNav}
title={data.courseData.title}
pages={data.courseData.pages}
pages={data.courseData.pages.map(
({ id, title, active, noCurrentRevision, url }) => {
return {
key: id + title,
element: (
<Link
className={cn(
'text-lg leading-browser',
active &&
'font-semibold text-almost-black hover:no-underline',
noCurrentRevision && 'text-brand-300'
)}
href={active ? undefined : url}
>
{noCurrentRevision
? '(' + strings.course.noRevisionForPage + ')'
: title}
</Link>
),
}
}
)}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,16 @@ export function EditOrInvite({
const hasUnrevised =
unrevisedRevisions !== undefined && unrevisedRevisions > 0

const href = hasUnrevised ? getHistoryUrl(data.id) : getEditHref()
const isCourse =
data.type === UuidType.Course || data.type === UuidType.CoursePage

const href = isCourse
elbotho marked this conversation as resolved.
Show resolved Hide resolved
? data.unrevisedCourseRevisions && data.unrevisedCourseRevisions > 0
? getHistoryUrl(data.courseId ?? data.id)
: getEditHref()
: hasUnrevised
? getHistoryUrl(data.id)
: getEditHref()
if (!href && !isInvite) return null

const title = hasUnrevised
Expand All @@ -62,7 +71,7 @@ export function EditOrInvite({
<>
<UserToolsItem
title={title}
href={isInvite ? undefined : href}
href={href}
onClick={isInvite ? () => setInviteOpen(true) : undefined}
aboveContent={aboveContent}
icon={icon}
Expand All @@ -81,13 +90,17 @@ export function EditOrInvite({
if (!data) return undefined
const revisionId = data.revisionId
const { type, id } = data
const url = getEditUrl(id, revisionId, type.startsWith('Taxonomy'))

const url = isCourse
elbotho marked this conversation as resolved.
Show resolved Hide resolved
? getEditUrl(data.courseId ?? id, undefined, false) + '#' + data.id
: getEditUrl(id, revisionId, type.startsWith('Taxonomy'))

if (type === UuidType.Page || type === UuidRevType.Page) {
return canDo(Uuid.create(UuidRevType.Page)) ? url : undefined
}
if (type === UuidType.TaxonomyTerm)
return canDo(TaxonomyTerm.set) ? url : undefined

return url
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ export function MoreAuthorToolsCourse({

const isEmptyCourse = data.type === UuidType.Course

const hasCourseRevisions =
data.unrevisedCourseRevisions && data.unrevisedCourseRevisions > 0
const hasUnrevised =
data.unrevisedRevisions !== undefined && data.unrevisedRevisions > 0

return (
<>
{renderSettingsItem(true)}
Expand Down Expand Up @@ -70,21 +65,13 @@ export function MoreAuthorToolsCourse({
return wholeCourse
? [
Tool.Abo,
hasCourseRevisions ? Tool.UnrevisedEdit : Tool.Edit,
Tool.History,
Tool.SortCoursePages,
Tool.Curriculum,
Tool.Log,
Tool.ChangeLicense,
Tool.Trash,
]
: [
Tool.Abo,
hasUnrevised ? Tool.UnrevisedEdit : Tool.Edit,
Tool.History,
Tool.Log,
Tool.AnalyticsLink,
Tool.Trash,
]
: [Tool.Abo, Tool.History, Tool.Log]
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import { faGraduationCap } from '@fortawesome/free-solid-svg-icons'
import { Link } from '@serlo/frontend/src/components/content/link'
import { FaIcon } from '@serlo/frontend/src/components/fa-icon'
import { useInstanceData } from '@serlo/frontend/src/contexts/instance-context'
import { CoursePagesData } from '@serlo/frontend/src/data-types'
import { cn } from '@serlo/frontend/src/helper/cn'
import type { MouseEvent } from 'react'
import { type MouseEvent } from 'react'

export interface CourseNavigationProps {
title: string | JSX.Element
pages: CoursePagesData
pages: { key: string; element: JSX.Element }[]
open: boolean
onOverviewButtonClick: (e?: MouseEvent<HTMLButtonElement>) => void
}

// this is a renderer
export function CourseNavigation({
title,
pages,
Expand Down Expand Up @@ -45,25 +43,9 @@ export function CourseNavigation({
) : null}
{open ? (
<ol className="serlo-ol mb-0 mt-7">
{pages.map(({ url, active, title, noCurrentRevision }) => (
<li key={url}>
<Link
className={cn(
'text-lg leading-browser',
{
'font-semibold text-almost-black hover:no-underline':
active,
},
{ 'text-brand-300': noCurrentRevision }
)}
href={active ? undefined : url}
>
{noCurrentRevision
? '(' + strings.course.noRevisionForPage + ')'
: title}
</Link>
</li>
))}
{pages.map(({ key, element }) => {
return <li key={key}>{element}</li>
})}
</ol>
) : null}
</nav>
Expand Down
127 changes: 89 additions & 38 deletions packages/editor/src/plugins/serlo-template-plugins/course/course.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { UuidType } from '@serlo/frontend/src/data-types'
import { cn } from '@serlo/frontend/src/helper/cn'
import { ContentLoaders } from '@serlo/frontend/src/serlo-editor-integration/components/content-loaders/content-loaders'
import { RevisionHistoryLoader } from '@serlo/frontend/src/serlo-editor-integration/components/content-loaders/revision-history-loader'
import { useState } from 'react'
import { useEffect, useState } from 'react'

import { CourseNavigation } from './course-navigation'
import type { CoursePageTypePluginState } from './course-page'
Expand Down Expand Up @@ -57,15 +57,23 @@ function CourseTypeEditor(props: EditorPluginProps<CourseTypePluginState>) {
const courseStrings = editorStrings.templatePlugins.course
const [courseNavOpen, setCourseNavOpen] = useState(true)
const [showSettingsModal, setShowSettingsModal] = useState(false)
const [activePageIndex, setActivePageIndex] = useState(0)

const staticState = selectStaticDocument(store.getState(), props.id)
?.state as PrettyStaticState<CourseTypePluginState>

if (!staticState) return null
const staticPages = staticState[
'course-page'
] as PrettyStaticState<CoursePageTypePluginState>[]

useEffect(() => {
const hashId = parseInt(window.location.hash.substring(1))
if (!hashId) return
const index = staticPages.findIndex(({ id }) => id === hashId)
setActivePageIndex(Math.max(index, 0))
}, [staticPages])

if (!staticPages) return null

return (
<>
<div className="absolute right-0 -mt-10 mr-side flex">
Expand All @@ -83,40 +91,20 @@ function CourseTypeEditor(props: EditorPluginProps<CourseTypePluginState>) {
</div>
<article className="mt-20">
{renderCourseNavigation()}
{children.map((child, index) => {
const uniqueId = `page-${staticPages[index].id}`
return (
<div
key={uniqueId}
id={uniqueId}
className="mt-16 border-t-2 border-editor-primary-200 pt-2"
>
<nav className="flex justify-end">
<button
className="serlo-button-editor-secondary serlo-tooltip-trigger mr-2"
onClick={() => children.remove(index)}
>
<EditorTooltip text={courseStrings.removeCoursePage} />
<FaIcon icon={faTrashAlt} />
</button>
<ContentLoaders
id={staticPages[index].id}
currentRevision={staticPages[index].revision}
onSwitchRevision={(data) =>
child.replace(TemplatePluginType.CoursePage, data)
}
entityType={UuidType.CoursePage}
/>
</nav>
{child.render()}
</div>
)
})}
<div className="mt-24 border-t-2 border-editor-primary-200 pt-12">
<AddButton onClick={() => children.insert()}>
<div className="ml-side mt-4">
<AddButton
onClick={() => {
children.insert()
setTimeout(() => {
setActivePageIndex(staticPages.length)
window.location.hash = `#${staticPages[staticPages.length - 1].id}`
})
}}
>
{courseStrings.addCoursePage}
</AddButton>
</div>
{renderCoursePage()}
<ToolbarMain showSubscriptionOptions {...props.state} />
</article>
<ModalWithCloseButton
Expand Down Expand Up @@ -152,14 +140,77 @@ function CourseTypeEditor(props: EditorPluginProps<CourseTypePluginState>) {
onChange={(e) => title.set(e.target.value)}
/>
}
pages={staticPages.map((coursePage) => {
pages={staticPages.map(({ title, id }, index) => {
const isActive = activePageIndex === index
return {
title: coursePage.title,
url: `#page-${coursePage.id}`,
id: coursePage.id,
key: title + id + index,
element: (
<div className="group">
<button
onClick={() => {
if (isActive) return
window.location.hash = `#${staticPages[index].id}`
setActivePageIndex(index)
}}
className={cn(
'serlo-link text-lg leading-browser',
isActive &&
'font-semibold text-almost-black hover:no-underline'
)}
>
{title.trim().length ? title : '___'}
</button>{' '}
{/* This code becomes relevant when coursePages are not standalone entities any more */}
{/* {index > 0 ? (
elbotho marked this conversation as resolved.
Show resolved Hide resolved
<button
className="serlo-button-editor-secondary serlo-tooltip-trigger mr-2 opacity-0 group-focus-within:opacity-100 group-hover:opacity-100"
onClick={() => {
const newIndex = index - 1
children.move(index, newIndex)
// setActivePageIndex(() => newIndex)
}}
>
<EditorTooltip text={templateStrings.article.moveUpLabel} />
<FaIcon icon={faArrowCircleUp} />
</button>
) : null} */}
</div>
),
}
})}
/>
)
}

function renderCoursePage() {
const activePage = children.at(activePageIndex)
if (!activePage) return
const staticPage = staticPages[activePageIndex]

return (
<div
key={activePage.id}
className="mt-16 border-t-2 border-editor-primary-200 pt-2"
>
<nav className="flex justify-end">
<button
className="serlo-button-editor-secondary serlo-tooltip-trigger mr-2"
onClick={() => children.remove(activePageIndex)}
>
<EditorTooltip text={courseStrings.removeCoursePage} />
<FaIcon icon={faTrashAlt} />
</button>
<ContentLoaders
id={staticPage.id}
currentRevision={staticPage.revision}
onSwitchRevision={(data) =>
activePage.replace(TemplatePluginType.CoursePage, data)
}
entityType={UuidType.CoursePage}
/>
</nav>
{children[activePageIndex]?.render()}
</div>
)
}
}