Skip to content
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
8 changes: 7 additions & 1 deletion packages/tdev/page-index/README.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ export const Comp = observer(() => {

## Für Plugin-Autoren

:::tip[`@tdev/page-read-check` als Beispiel]
Das Plugin `@tdev/page-read-check` implementiert das `iTaskableDocument`-Interface und wird daher automatisch im Seitenindex erfasst.
Es dient als Beispiel und Referenz, wie ein *Statusdokument* implementiert werden kann, damit es im Seitenindex erfasst wird.
:::

Wenn ein Plugin ein weiteres *Statusdokument* implementiert, das im Seitenindex erfasst werden soll, müssen folgende Schritte durchgeführt werden:
1. In der `siteConfig` die neue Komponente für das `remark`-Plugin registrieren:
```ts title="siteConfig.ts"
Expand All @@ -84,7 +89,7 @@ Wenn ein Plugin ein weiteres *Statusdokument* implementiert, das im Seitenindex
export interface TaskableDocumentMapping {
['my_new_taskable_document']: MyNewTaskableData;
}
export interface TypeModelMapping {
export interface TaskableTypeModelMapping {
['my_new_taskable_document']: MyNewTaskableDocument;
}
}
Expand All @@ -104,6 +109,7 @@ Wenn ein Plugin ein weiteres *Statusdokument* implementiert, das im Seitenindex
und den neuen Dokumenttyp im `ComponentStore` registrieren:
```ts title="packages/<scope>/<new-plugin>/register.ts"
const register = () => {
rootStore.documentStore.registerFactory('my_new_taskable_document', createModel);
rootStore.componentStore.registerTaskableDocumentType('my_new_taskable_document');
};
```
Expand Down
105 changes: 105 additions & 0 deletions packages/tdev/page-read-check/PageReadCheck/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import { useStore } from '@tdev-hooks/useStore';
import { MetaInit, ModelMeta } from '../model/ModelMeta';
import { useFirstMainDocument } from '@tdev-hooks/useFirstMainDocument';
import SlideButton from '@tdev-components/shared/SlideButton';
import Badge from '@tdev-components/shared/Badge';
import styles from './styles.module.scss';
import clsx from 'clsx';
import { mdiFlashTriangle } from '@mdi/js';
import Icon from '@mdi/react';
import PageReadChecker from '../model';

interface Props extends MetaInit {
id: string;
text?: (unlocked: boolean, doc: PageReadChecker) => string;
disabledReason?: (doc: PageReadChecker) => string;
hideTime?: boolean;
hideWarning?: boolean;
continueAfterUnlock?: boolean;
}

const defaultText = (unlocked: boolean, doc: PageReadChecker) =>
unlocked ? `Gelesen ${doc.fReadTime}` : doc.fReadTime;
const defaultDisabledReason = (doc: PageReadChecker) =>
`Mindestens ${doc.meta.fMinReadTime} lesen, um zu entsperren`;

const PageReadCheck = observer((props: Props) => {
const { text = defaultText, disabledReason = defaultDisabledReason } = props;
const [meta] = React.useState(new ModelMeta(props));
const ref = React.useRef<HTMLDivElement>(null);

const viewStore = useStore('viewStore');
const doc = useFirstMainDocument(props.id, meta);
const [animate, setAnimate] = React.useState(false);

React.useEffect(() => {
if (!viewStore.isPageVisible || !doc) {
return;
}
if (doc.read && !props.continueAfterUnlock) {
return;
}
const id = setInterval(() => {
doc.incrementReadTime(1);
}, 1000);
return () => {
clearInterval(id);
};
}, [doc, doc?.read, viewStore.isPageVisible, props.continueAfterUnlock]);

React.useEffect(() => {
if (ref.current && doc?.scrollTo) {
ref.current.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'start' });
doc.setScrollTo(false);
setAnimate(true);
}
}, [ref, doc?.scrollTo]);

React.useEffect(() => {
if (animate) {
const timeout = setTimeout(() => {
setAnimate(false);
}, 2000);
return () => {
clearTimeout(timeout);
};
}
}, [animate]);

if (!doc) {
return null;
}

return (
<div className={clsx(styles.pageReadCheck, animate && styles.animate)} ref={ref}>
<SlideButton
text={(unlocked) => text(unlocked, doc)}
onUnlock={() => doc.setReadState(true)}
onReset={() => doc.setReadState(false)}
isUnlocked={doc.read}
disabled={!doc.canUnlock}
sliderWidth={320}
disabledReason={disabledReason(doc)}
/>
<div className={clsx(styles.status)}>
{!doc.canUnlock && (
<Badge title={disabledReason(doc)} className={clsx(styles.minReadTime)}>
{doc.meta.fMinReadTime}
</Badge>
)}
{doc.isDummy && !props.hideWarning && (
<Icon
path={mdiFlashTriangle}
size={0.7}
color="var(--ifm-color-warning)"
title="Wird nicht gespeichert."
/>
)}
</div>
</div>
);
});

export default PageReadCheck;
53 changes: 53 additions & 0 deletions packages/tdev/page-read-check/PageReadCheck/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
.pageReadCheck {
display: flex;
flex-direction: column;
align-items: center;
border-radius: var(--ifm-global-radius);
.status {
margin-left: 240px;
min-width: 4em;
display: flex;
gap: 8px;
align-items: center;
justify-content: flex-end;
.minReadTime {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}
@keyframes flash {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}

@keyframes shake {
0%,
100% {
transform: translateX(0);
}
10%,
30%,
50%,
70%,
90% {
transform: translateX(-5px);
}
20%,
40%,
60%,
80% {
transform: translateX(5px);
}
}
&.animate {
animation:
flash 1s normal,
shake 1.5s normal;
transform-origin: center;
}
}
125 changes: 125 additions & 0 deletions packages/tdev/page-read-check/README.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
page_id: 34714568-d5e3-4965-8947-fdcf46da810a
sidebar_custom_props:
taskable_state: 'show'
---

import PageReadCheck from '@tdev/page-read-check/PageReadCheck';
import BrowserWindow from '@tdev-components/BrowserWindow';
import { action } from 'mobx';

# page-read-check

Mit der Komponente `PageReadCheck` kann der Bearbeitungs- und Lesezustand einer Seite als "gelesen" oder "ungelesen" markiert werden.

```tsx
import PageReadCheck from '@tdev/page-read-check/PageReadCheck';

<PageReadCheck id="2cf38080-4b13-4b0f-a589-94e78cce585c" />
```


<BrowserWindow>
<PageReadCheck id="2cf38080-4b13-4b0f-a589-94e78cce585c" />
</BrowserWindow>

## Mit Mindestlesezeit

Über die `minReadTime` kann eine Mindestlesezeit vorgegeben werden, bevor die Seite als "gelesen" markiert werden kann. Der Timer startet und stoppt automatisch, wenn die Seite sichtbar bzw. unsichtbar wird. Der Standardwert beträgt 10 Sekunden.

```tsx
<PageReadCheck minReadTime={15} />
```

<BrowserWindow>
<PageReadCheck minReadTime={15}/>
</BrowserWindow>

## Lesezeit nach Freischaltung fortsetzen

Standardmässig wird die Lesezeit nicht weiter gezählt, wenn die Seite freigeschaltet wurde. Mit der Option `continueAfterUnlock` kann dieses Verhalten geändert werden, sodass die Lesezeit auch nach Freischaltung weiter gezählt wird.

```tsx
<PageReadCheck continueAfterUnlock id="4a84e4a2-dee6-4575-bcd3-9682af03a17d" />
```

<BrowserWindow>
<PageReadCheck continueAfterUnlock id="4a84e4a2-dee6-4575-bcd3-9682af03a17d" />
</BrowserWindow>

## Anpassung der Texte

Die angezeigten Texte und Tooltipps können angepasst werden:

```tsx
<PageReadCheck
text={(unlocked, doc) => {
if (unlocked || doc.canUnlock) {
return `Gelesen ${doc.fReadTime}`;
}
return `Noch ${doc.meta.minReadTime - doc.readTime}s übrig`;
}}
disabledReason={(doc) => `Erst ${doc.fReadTime} bearbeitet, das ist zu wenig!`}
/>
```

<BrowserWindow>
<PageReadCheck
text={(unlocked, doc) => {
if (unlocked || doc.canUnlock) {
return `Gelesen ${doc.fReadTime}`;
}
return `Noch ${doc.meta.minReadTime - doc.readTime}s übrig`;
}}
disabledReason={(doc) => `Erst ${doc.fReadTime} bearbeitet, das ist zu wenig!`}
/>
</BrowserWindow>

## Installation

:::info[`packages/tdev/page-read-check`]
Kopiere des `packages/tdev/page-read-check`-Verzeichnis in das `tdev-website/website/packages`-Verzeichnis oder über `updateTdev.config.yaml` hinzufügen.
:::

Hinzufügen des `page-read-check`-Package zu den `apiDocumentProviders` im `siteConfig.ts`:

```ts title="siteConfig.ts"
const getSiteConfig: SiteConfigProvider = () => {
return {
apiDocumentProviders: [
require.resolve('@tdev/page-read-check/register'),
]
};
};
```

Falls **nicht** die standardoptionen des `PageIndexPluginDefaultOptions` verwendet werden, muss zusätzlich die `remark`-Plugin-Konfiguration angepasst werden, damit die neuen Dokumente im Seitenindex erfasst werden:

```ts title="siteConfig.ts"
import pageIndexPlugin from './packages/tdev/page-index/plugin';

const getSiteConfig: SiteConfigProvider = () => ({
remarkPlugins: [
[
pageIndexPlugin,
{
// ...
components: [
// highlight-start
{
name: 'PageReadCheck',
docTypeExtractor: () => 'page_read_check'
}
// highlight-end
]
}
]
]
});
```

Danach muss erneut installiert werden:

```bash
yarn install
```
19 changes: 19 additions & 0 deletions packages/tdev/page-read-check/helpers/time.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const fSeconds = (time: number) => {
const hours = Math.floor(time / 3600);
const minutes = Math.floor((time % 3600) / 60);
const seconds = time % 60;
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
};

export const fSecondsLong = (time: number) => {
const hours = Math.floor(time / 3600);
const minutes = Math.floor((time % 3600) / 60);
const seconds = time % 60;
if (hours > 0) {
return `${hours} Stunden ${minutes.toString().padStart(2, '0')} Minuten ${seconds.toString().padStart(2, '0')} Sekunden`;
}
return `${minutes} Minuten ${seconds.toString().padStart(2, '0')} Sekunden`;
};
14 changes: 14 additions & 0 deletions packages/tdev/page-read-check/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import PageReadCheck from './model';
export interface PageReadCheckData {
readTime: number;
read: boolean;
}

declare module '@tdev-api/document' {
export interface TaskableDocumentMapping {
['page_read_check']: PageReadCheckData;
}
export interface TaskableTypeModelMapping {
['page_read_check']: PageReadCheck;
}
}
29 changes: 29 additions & 0 deletions packages/tdev/page-read-check/model/ModelMeta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { TypeDataMapping, Access } from '@tdev-api/document';
import { TypeMeta } from '@tdev-models/DocumentRoot';
import { fSeconds } from '../helpers/time';

export interface MetaInit {
readonly?: boolean;
minReadTime?: number;
}

export class ModelMeta extends TypeMeta<'page_read_check'> {
readonly type = 'page_read_check';
readonly minReadTime: number;

constructor(props: Partial<MetaInit>) {
super('page_read_check', props.readonly ? Access.RO_User : undefined);
this.minReadTime = props.minReadTime || 10;
}

get defaultData(): TypeDataMapping['page_read_check'] {
return {
readTime: 0,
read: false
};
}

get fMinReadTime() {
return fSeconds(this.minReadTime);
}
}
Loading