Skip to content

Commit 0e23ea5

Browse files
authored
Desktop: Resolves #160: Add word counter feature to notes (#2444)
* Add word counter logic Fix errant whitespace changes * update to using react hooks Use React hooks remove extra theme set Update styling function * correct linting and package lock issues * WIP: update button functionality * Add line count and update styling from feedback * corrected file location to fit new build
1 parent f9143ad commit 0e23ea5

File tree

12 files changed

+242
-48
lines changed

12 files changed

+242
-48
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ ReactNativeClient/lib/joplin-renderer/assets/
5353
ReactNativeClient/lib/rnInjectedJs/
5454

5555
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
56+
ElectronClient/gui/NoteContentPropertiesDialog.js
5657
ElectronClient/gui/ResourceScreen.js
5758
ElectronClient/gui/ShareNoteDialog.js
5859
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Tools/commit_hook.txt
4747
*.map
4848

4949
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
50+
ElectronClient/gui/NoteContentPropertiesDialog.js
5051
ElectronClient/gui/ResourceScreen.js
5152
ElectronClient/gui/ShareNoteDialog.js
5253
ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/mermaid.js

CliClient/package-lock.json

Lines changed: 10 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ElectronClient/gui/MainScreen.jsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const { SideBar } = require('./SideBar.min.js');
55
const { NoteList } = require('./NoteList.min.js');
66
const { NoteText } = require('./NoteText.min.js');
77
const { PromptDialog } = require('./PromptDialog.min.js');
8+
const NoteContentPropertiesDialog = require('./NoteContentPropertiesDialog.js').default;
89
const NotePropertiesDialog = require('./NotePropertiesDialog.min.js');
910
const ShareNoteDialog = require('./ShareNoteDialog.js').default;
1011
const Setting = require('lib/models/Setting.js');
@@ -25,6 +26,7 @@ class MainScreenComponent extends React.Component {
2526
super();
2627

2728
this.notePropertiesDialog_close = this.notePropertiesDialog_close.bind(this);
29+
this.noteContentPropertiesDialog_close = this.noteContentPropertiesDialog_close.bind(this);
2830
this.shareNoteDialog_close = this.shareNoteDialog_close.bind(this);
2931
this.sidebar_onDrag = this.sidebar_onDrag.bind(this);
3032
this.noteList_onDrag = this.noteList_onDrag.bind(this);
@@ -42,6 +44,10 @@ class MainScreenComponent extends React.Component {
4244
this.setState({ notePropertiesDialogOptions: {} });
4345
}
4446

47+
noteContentPropertiesDialog_close() {
48+
this.setState({ noteContentPropertiesDialogOptions: {} });
49+
}
50+
4551
shareNoteDialog_close() {
4652
this.setState({ shareNoteDialogOptions: {} });
4753
}
@@ -54,6 +60,7 @@ class MainScreenComponent extends React.Component {
5460
message: '',
5561
},
5662
notePropertiesDialogOptions: {},
63+
noteContentPropertiesDialogOptions: {},
5764
shareNoteDialogOptions: {},
5865
});
5966
}
@@ -274,6 +281,14 @@ class MainScreenComponent extends React.Component {
274281
onRevisionLinkClick: command.onRevisionLinkClick,
275282
},
276283
});
284+
} else if (command.name === 'commandContentProperties') {
285+
this.setState({
286+
noteContentPropertiesDialogOptions: {
287+
visible: true,
288+
text: command.text,
289+
lines: command.lines,
290+
},
291+
});
277292
} else if (command.name === 'commandShareNoteDialog') {
278293
this.setState({
279294
shareNoteDialogOptions: {
@@ -609,13 +624,15 @@ class MainScreenComponent extends React.Component {
609624
const modalLayerStyle = Object.assign({}, styles.modalLayer, { display: this.state.modalLayer.visible ? 'block' : 'none' });
610625

611626
const notePropertiesDialogOptions = this.state.notePropertiesDialogOptions;
627+
const noteContentPropertiesDialogOptions = this.state.noteContentPropertiesDialogOptions;
612628
const shareNoteDialogOptions = this.state.shareNoteDialogOptions;
613629
const keyboardMode = Setting.value('editor.keyboardMode');
614630

615631
return (
616632
<div style={style}>
617633
<div style={modalLayerStyle}>{this.state.modalLayer.message}</div>
618634

635+
{noteContentPropertiesDialogOptions.visible && <NoteContentPropertiesDialog theme={this.props.theme} onClose={this.noteContentPropertiesDialog_close} text={noteContentPropertiesDialogOptions.text} lines={noteContentPropertiesDialogOptions.lines}/>}
619636
{notePropertiesDialogOptions.visible && <NotePropertiesDialog theme={this.props.theme} noteId={notePropertiesDialogOptions.noteId} onClose={this.notePropertiesDialog_close} onRevisionLinkClick={notePropertiesDialogOptions.onRevisionLinkClick} />}
620637
{shareNoteDialogOptions.visible && <ShareNoteDialog theme={this.props.theme} noteIds={shareNoteDialogOptions.noteIds} onClose={this.shareNoteDialog_close} />}
621638

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import * as React from 'react';
2+
import { useState, useEffect } from 'react';
3+
const { _ } = require('lib/locale.js');
4+
const { themeStyle } = require('../theme.js');
5+
const DialogButtonRow = require('./DialogButtonRow.min');
6+
const Countable = require('countable');
7+
8+
interface NoteContentPropertiesDialogProps {
9+
theme: number,
10+
text: string,
11+
onClose: Function,
12+
}
13+
14+
interface TextPropertiesMap {
15+
[key: string]: number;
16+
}
17+
18+
interface KeyToLabelMap {
19+
[key: string]: string;
20+
}
21+
22+
export default function NoteContentPropertiesDialog(props:NoteContentPropertiesDialogProps) {
23+
const theme = themeStyle(props.theme);
24+
const textComps: JSX.Element[] = [];
25+
const [lines, setLines] = useState<number>(0);
26+
const [words, setWords] = useState<number>(0);
27+
const [characters, setCharacters] = useState<number>(0);
28+
const [charactersNoSpace, setCharactersNoSpace] = useState<number>(0);
29+
30+
useEffect(() => {
31+
Countable.count(props.text, (counter: { words: number; all: number; characters: number; }) => {
32+
setWords(counter.words);
33+
setCharacters(counter.all);
34+
setCharactersNoSpace(counter.characters);
35+
});
36+
setLines(props.text.split('\n').length);
37+
}, []);
38+
39+
const textProperties: TextPropertiesMap = {
40+
lines: lines,
41+
words: words,
42+
characters: characters,
43+
charactersNoSpace: charactersNoSpace,
44+
};
45+
46+
const keyToLabel: KeyToLabelMap = {
47+
words: _('Words'),
48+
characters: _('Characters'),
49+
charactersNoSpace: _('Characters excluding spaces'),
50+
lines: _('Lines'),
51+
};
52+
53+
const buttonRow_click = () => {
54+
props.onClose();
55+
};
56+
57+
const createItemField = (key: string, value: number) => {
58+
const labelComp = <label style={Object.assign({}, theme.textStyle, theme.controlBoxLabel)}>{keyToLabel[key]}</label>;
59+
const controlComp = <div style={Object.assign({}, theme.textStyle, theme.controlBoxValue)}>{value}</div>;
60+
61+
return (
62+
<div key={key} style={theme.controlBox} className="note-text-property-box">{labelComp}{controlComp}</div>
63+
);
64+
};
65+
66+
if (textProperties) {
67+
for (let key in textProperties) {
68+
if (!textProperties.hasOwnProperty(key)) continue;
69+
const comp = createItemField(key, textProperties[key]);
70+
textComps.push(comp);
71+
}
72+
}
73+
74+
return (
75+
<div style={theme.dialogModalLayer}>
76+
<div style={theme.dialogBox}>
77+
<div style={theme.dialogTitle}>{_('Content properties')}</div>
78+
<div>{textComps}</div>
79+
<DialogButtonRow theme={props.theme} onClick={buttonRow_click} okButtonShow={false} cancelButtonLabel={_('Close')}/>
80+
</div>
81+
</div>
82+
);
83+
}

ElectronClient/gui/NotePropertiesDialog.jsx

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,6 @@ class NotePropertiesDialog extends React.Component {
114114
this.styles_ = {};
115115
this.styleKey_ = styleKey;
116116

117-
this.styles_.controlBox = {
118-
marginBottom: '1em',
119-
color: 'black', // This will apply for the calendar
120-
display: 'flex',
121-
flexDirection: 'row',
122-
alignItems: 'center',
123-
};
124-
125117
this.styles_.button = {
126118
minWidth: theme.buttonMinWidth,
127119
minHeight: theme.buttonMinHeight,
@@ -234,7 +226,7 @@ class NotePropertiesDialog extends React.Component {
234226
createNoteField(key, value) {
235227
const styles = this.styles(this.props.theme);
236228
const theme = themeStyle(this.props.theme);
237-
const labelComp = <label style={Object.assign({}, theme.textStyle, { marginRight: '1em', width: '6em', display: 'inline-block', fontWeight: 'bold' })}>{this.formatLabel(key)}</label>;
229+
const labelComp = <label style={Object.assign({}, theme.textStyle, theme.controlBoxLabel)}>{this.formatLabel(key)}</label>;
238230
let controlComp = null;
239231
let editComp = null;
240232
let editCompHandler = null;
@@ -315,7 +307,7 @@ class NotePropertiesDialog extends React.Component {
315307
</a>
316308
);
317309
} else {
318-
controlComp = <div style={Object.assign({}, theme.textStyle, { display: 'inline-block' })}>{displayedValue}</div>;
310+
controlComp = <div style={Object.assign({}, theme.textStyle, theme.controlBoxValue)}>{displayedValue}</div>;
319311
}
320312

321313
if (['id', 'revisionsLink', 'markup_language'].indexOf(key) < 0) {
@@ -335,7 +327,7 @@ class NotePropertiesDialog extends React.Component {
335327
}
336328

337329
return (
338-
<div key={key} style={this.styles_.controlBox} className="note-property-box">
330+
<div key={key} style={theme.controlBox} className="note-property-box">
339331
{labelComp}
340332
{controlComp}
341333
{editComp}

ElectronClient/gui/NoteText.jsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1817,6 +1817,18 @@ class NoteTextComponent extends React.Component {
18171817
},
18181818
});
18191819

1820+
toolbarItems.push({
1821+
tooltip: _('Content Properties'),
1822+
iconName: 'fa-sticky-note',
1823+
onClick: () => {
1824+
this.props.dispatch({
1825+
type: 'WINDOW_COMMAND',
1826+
name: 'commandContentProperties',
1827+
text: this.state.note.body,
1828+
});
1829+
},
1830+
});
1831+
18201832
return toolbarItems;
18211833
}
18221834

0 commit comments

Comments
 (0)