2020-02-25 11:43:31 +02:00
|
|
|
import * as React from 'react';
|
|
|
|
import { useState, useEffect } from 'react';
|
2020-11-07 17:59:37 +02:00
|
|
|
import { _ } from '@joplin/lib/locale';
|
|
|
|
const { themeStyle } = require('@joplin/lib/theme');
|
2020-02-25 11:43:31 +02:00
|
|
|
const DialogButtonRow = require('./DialogButtonRow.min');
|
2020-05-15 12:52:59 +02:00
|
|
|
const Countable = require('countable');
|
2020-11-07 17:59:37 +02:00
|
|
|
const markupLanguageUtils = require('@joplin/lib/markupLanguageUtils').default;
|
2020-02-25 11:43:31 +02:00
|
|
|
|
|
|
|
interface NoteContentPropertiesDialogProps {
|
2020-09-15 15:01:07 +02:00
|
|
|
themeId: number,
|
2020-02-25 11:43:31 +02:00
|
|
|
text: string,
|
2020-07-15 00:27:12 +02:00
|
|
|
markupLanguage: number,
|
2020-02-25 11:43:31 +02:00
|
|
|
onClose: Function,
|
|
|
|
}
|
|
|
|
|
|
|
|
interface TextPropertiesMap {
|
|
|
|
[key: string]: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface KeyToLabelMap {
|
|
|
|
[key: string]: string;
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
let markupToHtml_: any = null;
|
2020-07-15 00:27:12 +02:00
|
|
|
function markupToHtml() {
|
|
|
|
if (markupToHtml_) return markupToHtml_;
|
|
|
|
markupToHtml_ = markupLanguageUtils.newMarkupToHtml();
|
|
|
|
return markupToHtml_;
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
function countElements(text: string, wordSetter: Function, characterSetter: Function, characterNoSpaceSetter: Function, lineSetter: Function) {
|
|
|
|
Countable.count(text, (counter: any) => {
|
2020-05-15 12:52:59 +02:00
|
|
|
wordSetter(counter.words);
|
|
|
|
characterSetter(counter.all);
|
|
|
|
characterNoSpaceSetter(counter.characters);
|
|
|
|
});
|
|
|
|
text === '' ? lineSetter(0) : lineSetter(text.split('\n').length);
|
|
|
|
}
|
|
|
|
|
2020-07-07 01:39:36 +02:00
|
|
|
function formatReadTime(readTimeMinutes: number) {
|
|
|
|
if (readTimeMinutes < 1) {
|
|
|
|
return '< 1';
|
|
|
|
}
|
|
|
|
|
|
|
|
return Math.ceil(readTimeMinutes).toString();
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
export default function NoteContentPropertiesDialog(props: NoteContentPropertiesDialogProps) {
|
2020-09-15 15:01:07 +02:00
|
|
|
const theme = themeStyle(props.themeId);
|
2020-11-05 18:58:23 +02:00
|
|
|
const tableBodyComps: any[] = [];
|
2020-05-15 12:28:31 +02:00
|
|
|
// For the source Markdown
|
2020-02-25 11:43:31 +02:00
|
|
|
const [lines, setLines] = useState<number>(0);
|
|
|
|
const [words, setWords] = useState<number>(0);
|
|
|
|
const [characters, setCharacters] = useState<number>(0);
|
|
|
|
const [charactersNoSpace, setCharactersNoSpace] = useState<number>(0);
|
2020-05-15 12:28:31 +02:00
|
|
|
// For source with Markdown syntax stripped out
|
|
|
|
const [strippedLines, setStrippedLines] = useState<number>(0);
|
|
|
|
const [strippedWords, setStrippedWords] = useState<number>(0);
|
|
|
|
const [strippedCharacters, setStrippedCharacters] = useState<number>(0);
|
|
|
|
const [strippedCharactersNoSpace, setStrippedCharactersNoSpace] = useState<number>(0);
|
2020-07-07 01:39:36 +02:00
|
|
|
const [strippedReadTime, setStrippedReadTime] = useState<number>(0);
|
|
|
|
// This amount based on the following paper:
|
|
|
|
// https://www.researchgate.net/publication/332380784_How_many_words_do_we_read_per_minute_A_review_and_meta-analysis_of_reading_rate
|
|
|
|
const wordsPerMinute = 250;
|
2020-02-25 11:43:31 +02:00
|
|
|
|
|
|
|
useEffect(() => {
|
2020-05-15 12:28:31 +02:00
|
|
|
countElements(props.text, setWords, setCharacters, setCharactersNoSpace, setLines);
|
|
|
|
}, [props.text]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
2020-07-15 00:27:12 +02:00
|
|
|
const strippedText: string = markupToHtml().stripMarkup(props.markupLanguage, props.text);
|
2020-05-15 12:28:31 +02:00
|
|
|
countElements(strippedText, setStrippedWords, setStrippedCharacters, setStrippedCharactersNoSpace, setStrippedLines);
|
2020-02-27 02:20:55 +02:00
|
|
|
}, [props.text]);
|
2020-02-25 11:43:31 +02:00
|
|
|
|
2020-07-07 01:39:36 +02:00
|
|
|
useEffect(() => {
|
|
|
|
const readTimeMinutes: number = strippedWords / wordsPerMinute;
|
|
|
|
setStrippedReadTime(readTimeMinutes);
|
|
|
|
}, [strippedWords]);
|
|
|
|
|
2020-02-25 11:43:31 +02:00
|
|
|
const textProperties: TextPropertiesMap = {
|
|
|
|
lines: lines,
|
|
|
|
words: words,
|
|
|
|
characters: characters,
|
|
|
|
charactersNoSpace: charactersNoSpace,
|
|
|
|
};
|
|
|
|
|
2020-05-15 12:28:31 +02:00
|
|
|
const strippedTextProperties: TextPropertiesMap = {
|
2020-07-15 00:27:12 +02:00
|
|
|
// The function stripMarkup() currently removes all new lines so we can't use the
|
|
|
|
// strippedLines property. Instead we simply use the lines property which should
|
|
|
|
// be a good approximation anyway.
|
|
|
|
// Also dummy check to silence TypeScript warning
|
|
|
|
lines: strippedLines === -5000 ? strippedLines : lines,
|
2020-05-15 12:28:31 +02:00
|
|
|
words: strippedWords,
|
|
|
|
characters: strippedCharacters,
|
|
|
|
charactersNoSpace: strippedCharactersNoSpace,
|
|
|
|
};
|
|
|
|
|
2020-02-25 11:43:31 +02:00
|
|
|
const keyToLabel: KeyToLabelMap = {
|
|
|
|
words: _('Words'),
|
|
|
|
characters: _('Characters'),
|
|
|
|
charactersNoSpace: _('Characters excluding spaces'),
|
|
|
|
lines: _('Lines'),
|
|
|
|
};
|
|
|
|
|
|
|
|
const buttonRow_click = () => {
|
|
|
|
props.onClose();
|
|
|
|
};
|
|
|
|
|
2020-05-15 12:28:31 +02:00
|
|
|
const labelCompStyle = {
|
|
|
|
...theme.textStyle,
|
|
|
|
fontWeight: 'bold',
|
|
|
|
width: '10em',
|
|
|
|
};
|
|
|
|
|
|
|
|
const controlCompStyle = {
|
|
|
|
...theme.textStyle,
|
|
|
|
textAlign: 'center',
|
|
|
|
};
|
|
|
|
|
|
|
|
const createTableBodyRow = (key: string, value: number, strippedValue: number) => {
|
|
|
|
const labelComp = <td style={labelCompStyle}>{keyToLabel[key]}</td>;
|
|
|
|
const controlComp = <td style={controlCompStyle}>{value}</td>;
|
|
|
|
const strippedControlComp = <td style={controlCompStyle}>{strippedValue}</td>;
|
2020-02-25 11:43:31 +02:00
|
|
|
|
|
|
|
return (
|
2020-05-15 12:28:31 +02:00
|
|
|
<tr key={key}>{labelComp}{controlComp}{strippedControlComp}</tr>
|
2020-02-25 11:43:31 +02:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2020-05-15 12:28:31 +02:00
|
|
|
const tableHeaderStyle = {
|
|
|
|
...theme.textStyle,
|
|
|
|
textAlign: 'center',
|
|
|
|
};
|
|
|
|
|
|
|
|
const tableHeader = (
|
|
|
|
<tr>
|
|
|
|
<th style={tableHeaderStyle}></th>
|
|
|
|
<th style={tableHeaderStyle}>{_('Editor')}</th>
|
|
|
|
<th style={tableHeaderStyle}>{_('Viewer')}</th>
|
|
|
|
</tr>
|
|
|
|
);
|
|
|
|
|
|
|
|
for (const key in textProperties) {
|
|
|
|
const comp = createTableBodyRow(key, textProperties[key], strippedTextProperties[key]);
|
|
|
|
tableBodyComps.push(comp);
|
2020-02-25 11:43:31 +02:00
|
|
|
}
|
|
|
|
|
2020-05-15 12:28:31 +02:00
|
|
|
const dialogBoxHeadingStyle = {
|
|
|
|
...theme.dialogTitle,
|
|
|
|
textAlign: 'center',
|
|
|
|
};
|
|
|
|
|
2020-09-21 17:16:28 +02:00
|
|
|
const readTimeLabel = _('Read time: %s min', formatReadTime(strippedReadTime));
|
|
|
|
|
2020-02-25 11:43:31 +02:00
|
|
|
return (
|
|
|
|
<div style={theme.dialogModalLayer}>
|
|
|
|
<div style={theme.dialogBox}>
|
2020-06-02 20:36:22 +02:00
|
|
|
<div style={dialogBoxHeadingStyle}>{_('Statistics')}</div>
|
2020-05-15 12:28:31 +02:00
|
|
|
<table>
|
|
|
|
<thead>
|
|
|
|
{tableHeader}
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
|
|
|
{tableBodyComps}
|
|
|
|
</tbody>
|
|
|
|
</table>
|
2020-09-21 17:16:28 +02:00
|
|
|
<div style={{ ...labelCompStyle, marginTop: 10 }}>
|
|
|
|
{readTimeLabel}
|
2020-07-07 01:39:36 +02:00
|
|
|
</div>
|
2020-09-15 15:01:07 +02:00
|
|
|
<DialogButtonRow themeId={props.themeId} onClick={buttonRow_click} okButtonShow={false} cancelButtonLabel={_('Close')}/>
|
2020-02-25 11:43:31 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|