mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-15 06:33:55 +01:00
Update the word count modal (#27068)
GitOrigin-RevId: c4d11bda020e435bcf8b6daec253cedb37df0252
This commit is contained in:
@@ -248,6 +248,7 @@
|
|||||||
"change_your_email": "",
|
"change_your_email": "",
|
||||||
"changing_the_position_of_your_figure": "",
|
"changing_the_position_of_your_figure": "",
|
||||||
"changing_the_position_of_your_table": "",
|
"changing_the_position_of_your_table": "",
|
||||||
|
"characters": "",
|
||||||
"chat": "",
|
"chat": "",
|
||||||
"chat_error": "",
|
"chat_error": "",
|
||||||
"check_logs": "",
|
"check_logs": "",
|
||||||
@@ -997,6 +998,7 @@
|
|||||||
"main_bibliography_file_for_this_project": "",
|
"main_bibliography_file_for_this_project": "",
|
||||||
"main_document": "",
|
"main_document": "",
|
||||||
"main_file_not_found": "",
|
"main_file_not_found": "",
|
||||||
|
"main_text": "",
|
||||||
"make_a_copy": "",
|
"make_a_copy": "",
|
||||||
"make_email_primary_description": "",
|
"make_email_primary_description": "",
|
||||||
"make_owner": "",
|
"make_owner": "",
|
||||||
@@ -1767,7 +1769,6 @@
|
|||||||
"test_configuration_successful": "",
|
"test_configuration_successful": "",
|
||||||
"tex_live_version": "",
|
"tex_live_version": "",
|
||||||
"texgpt": "",
|
"texgpt": "",
|
||||||
"text": "",
|
|
||||||
"thank_you": "",
|
"thank_you": "",
|
||||||
"thank_you_exclamation": "",
|
"thank_you_exclamation": "",
|
||||||
"thank_you_for_your_feedback": "",
|
"thank_you_for_your_feedback": "",
|
||||||
@@ -1910,7 +1911,6 @@
|
|||||||
"tooltip_show_filetree": "",
|
"tooltip_show_filetree": "",
|
||||||
"tooltip_show_panel": "",
|
"tooltip_show_panel": "",
|
||||||
"tooltip_show_pdf": "",
|
"tooltip_show_pdf": "",
|
||||||
"total": "",
|
|
||||||
"total_due_in_x_days": "",
|
"total_due_in_x_days": "",
|
||||||
"total_due_today": "",
|
"total_due_today": "",
|
||||||
"total_per_month": "",
|
"total_per_month": "",
|
||||||
@@ -2114,6 +2114,7 @@
|
|||||||
"with_premium_subscription_you_also_get": "",
|
"with_premium_subscription_you_also_get": "",
|
||||||
"word_count": "",
|
"word_count": "",
|
||||||
"word_count_lower": "",
|
"word_count_lower": "",
|
||||||
|
"words": "",
|
||||||
"work_in_vim_or_emacs_emulation_mode": "",
|
"work_in_vim_or_emacs_emulation_mode": "",
|
||||||
"work_offline": "",
|
"work_offline": "",
|
||||||
"work_offline_pull_to_overleaf": "",
|
"work_offline_pull_to_overleaf": "",
|
||||||
|
|||||||
+8
-1
@@ -9,6 +9,7 @@ import OLButton from '@/shared/components/ol/ol-button'
|
|||||||
import { WordCountServer } from './word-count-server'
|
import { WordCountServer } from './word-count-server'
|
||||||
import { WordCountClient } from './word-count-client'
|
import { WordCountClient } from './word-count-client'
|
||||||
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
|
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
|
||||||
|
import SplitTestBadge from '@/shared/components/split-test-badge'
|
||||||
|
|
||||||
// NOTE: this component is only mounted when the modal is open
|
// NOTE: this component is only mounted when the modal is open
|
||||||
export default function WordCountModalContent({
|
export default function WordCountModalContent({
|
||||||
@@ -21,7 +22,13 @@ export default function WordCountModalContent({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<OLModalHeader closeButton>
|
<OLModalHeader closeButton>
|
||||||
<OLModalTitle>{t('word_count')}</OLModalTitle>
|
<OLModalTitle>
|
||||||
|
{t('word_count')}{' '}
|
||||||
|
<SplitTestBadge
|
||||||
|
splitTestName="word-count-client"
|
||||||
|
displayOnVariants={['enabled']}
|
||||||
|
/>
|
||||||
|
</OLModalTitle>
|
||||||
</OLModalHeader>
|
</OLModalHeader>
|
||||||
|
|
||||||
<OLModalBody>
|
<OLModalBody>
|
||||||
|
|||||||
+69
-46
@@ -5,6 +5,8 @@ import { Container, Row, Col, Form } from 'react-bootstrap'
|
|||||||
import OLNotification from '@/shared/components/ol/ol-notification'
|
import OLNotification from '@/shared/components/ol/ol-notification'
|
||||||
import usePersistedState from '@/shared/hooks/use-persisted-state'
|
import usePersistedState from '@/shared/hooks/use-persisted-state'
|
||||||
|
|
||||||
|
const numberFormat = new Intl.NumberFormat()
|
||||||
|
|
||||||
export const WordCountsClient: FC<{ data: WordCountData }> = ({ data }) => {
|
export const WordCountsClient: FC<{ data: WordCountData }> = ({ data }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@@ -17,7 +19,7 @@ export const WordCountsClient: FC<{ data: WordCountData }> = ({ data }) => {
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
key: 'text',
|
key: 'text',
|
||||||
label: t('text'),
|
label: t('main_text'),
|
||||||
words: data.textWords,
|
words: data.textWords,
|
||||||
chars: data.textCharacters,
|
chars: data.textCharacters,
|
||||||
},
|
},
|
||||||
@@ -85,52 +87,73 @@ export const WordCountsClient: FC<{ data: WordCountData }> = ({ data }) => {
|
|||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{items.map(item => (
|
<Row className="mb-4">
|
||||||
<Row
|
<table style={{ width: 'auto' }}>
|
||||||
key={item.key}
|
<thead>
|
||||||
style={{
|
<tr>
|
||||||
borderBottom: '1px solid #eee',
|
<th />
|
||||||
padding: 5,
|
<th className="visually-hidden">{t('words')}</th>
|
||||||
marginBottom: 5,
|
<th className="visually-hidden">{t('characters')}</th>
|
||||||
}}
|
</tr>
|
||||||
>
|
</thead>
|
||||||
<Col
|
<tbody>
|
||||||
style={{
|
<tr>
|
||||||
display: 'flex',
|
<th style={{ width: 100 }}>{t('total_words')}:</th>
|
||||||
alignItems: 'top',
|
<td style={{ width: 100, textAlign: 'right' }}>
|
||||||
justifyContent: 'space-between',
|
{numberFormat.format(totals.words)}
|
||||||
}}
|
</td>
|
||||||
>
|
<td style={{ width: 250, textAlign: 'right' }}>
|
||||||
<Form.Check
|
<b style={{ marginRight: 10 }} aria-hidden="true">
|
||||||
type="checkbox"
|
{t('characters')}:
|
||||||
id={`word-count-${item.key}`}
|
</b>{' '}
|
||||||
label={item.label}
|
{numberFormat.format(totals.chars)}
|
||||||
checked={included.includes(item.key)}
|
</td>
|
||||||
onChange={event =>
|
</tr>
|
||||||
setIncluded(prevValue => {
|
{items.map(item => (
|
||||||
return event.target.checked
|
<tr key={item.key}>
|
||||||
? prevValue.concat(item.key)
|
<th style={{ fontWeight: 'normal', paddingLeft: 20 }}>
|
||||||
: prevValue.filter(key => key !== item.key)
|
<Form.Check
|
||||||
})
|
type="checkbox"
|
||||||
}
|
id={`word-count-${item.key}`}
|
||||||
aria-label={`Include ${item.label} in total`}
|
label={`${item.label}:`}
|
||||||
/>
|
checked={included.includes(item.key)}
|
||||||
</Col>
|
onChange={event =>
|
||||||
<Col>
|
setIncluded(prevValue => {
|
||||||
{item.words} words
|
return event.target.checked
|
||||||
<br />
|
? prevValue.concat(item.key)
|
||||||
{item.chars} chars
|
: prevValue.filter(key => key !== item.key)
|
||||||
</Col>
|
})
|
||||||
</Row>
|
}
|
||||||
))}
|
aria-label={`Include ${item.label} in total`}
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
<td style={{ textAlign: 'right' }}>
|
||||||
|
{numberFormat.format(item.words)}
|
||||||
|
</td>
|
||||||
|
<td style={{ textAlign: 'right' }}>
|
||||||
|
{numberFormat.format(item.chars)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</Row>
|
||||||
|
|
||||||
<Row>
|
<Row className="border-top py-2">
|
||||||
<Col style={{ textAlign: 'right' }}>
|
<Col xs={12}>
|
||||||
<span style={{ fontWeight: 'bold' }}>
|
<b>Headers:</b> {data.headers}
|
||||||
{t('total')}: {totals.words} words
|
</Col>
|
||||||
<br />
|
</Row>
|
||||||
{totals.chars} chars
|
|
||||||
</span>
|
<Row className="border-top py-2">
|
||||||
|
<Col xs={12}>
|
||||||
|
<b>Math Inline:</b> {data.mathInline}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row className="border-top py-2 pb-0">
|
||||||
|
<Col xs={12}>
|
||||||
|
<b>Math Display:</b> {data.mathDisplay}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { debugConsole } from '@/utils/debugging'
|
|||||||
import { findPreambleExtent } from '@/features/word-count-modal/utils/find-preamble-extent'
|
import { findPreambleExtent } from '@/features/word-count-modal/utils/find-preamble-extent'
|
||||||
import { Segmenters } from './segmenters'
|
import { Segmenters } from './segmenters'
|
||||||
|
|
||||||
const whiteSpaceRe = /^\s$/
|
// const whiteSpaceRe = /^\s$/
|
||||||
|
|
||||||
type Context = 'text' | 'header' | 'abstract' | 'caption' | 'footnote' | 'other'
|
type Context = 'text' | 'header' | 'abstract' | 'caption' | 'footnote' | 'other'
|
||||||
|
|
||||||
@@ -301,9 +301,8 @@ export const countWordsInFile = (
|
|||||||
for (const [context, text] of Object.entries(texts)) {
|
for (const [context, text] of Object.entries(texts)) {
|
||||||
const counter = counters[context as Context]
|
const counter = counters[context as Context]
|
||||||
|
|
||||||
// TODO: replace - and _ with a word character if hyphenated words should be counted as one word?
|
|
||||||
|
|
||||||
for (const value of segmenters.word.segment(
|
for (const value of segmenters.word.segment(
|
||||||
|
// replace - and _ with a word character, so that hyphenated words are counted as one word
|
||||||
text.replace(/\w[-_]\w/g, 'aaa')
|
text.replace(/\w[-_]\w/g, 'aaa')
|
||||||
)) {
|
)) {
|
||||||
if (value.isWordLike) {
|
if (value.isWordLike) {
|
||||||
@@ -311,13 +310,14 @@ export const countWordsInFile = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: count hyphens as characters?
|
for (const _value of segmenters.character.segment(
|
||||||
|
// replace multiple spaces with a single space
|
||||||
for (const value of segmenters.character.segment(text)) {
|
text.replace(/\s+/, ' ').trim()
|
||||||
|
)) {
|
||||||
// TODO: option for whether to include whitespace?
|
// TODO: option for whether to include whitespace?
|
||||||
if (!whiteSpaceRe.test(value.segment)) {
|
// if (!whiteSpaceRe.test(value.segment)) {
|
||||||
data[counter.character]++
|
data[counter.character]++
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,6 +317,7 @@
|
|||||||
"change_your_email": "Change your email",
|
"change_your_email": "Change your email",
|
||||||
"changing_the_position_of_your_figure": "Changing the position of your figure",
|
"changing_the_position_of_your_figure": "Changing the position of your figure",
|
||||||
"changing_the_position_of_your_table": "Changing the position of your table",
|
"changing_the_position_of_your_table": "Changing the position of your table",
|
||||||
|
"characters": "Characters",
|
||||||
"chat": "Chat",
|
"chat": "Chat",
|
||||||
"chat_error": "Could not load chat messages, please try again.",
|
"chat_error": "Could not load chat messages, please try again.",
|
||||||
"check_logs": "Check logs",
|
"check_logs": "Check logs",
|
||||||
@@ -1307,6 +1308,7 @@
|
|||||||
"main_bibliography_file_for_this_project": "Main bibliography file for this project",
|
"main_bibliography_file_for_this_project": "Main bibliography file for this project",
|
||||||
"main_document": "Main document",
|
"main_document": "Main document",
|
||||||
"main_file_not_found": "Unknown main document",
|
"main_file_not_found": "Unknown main document",
|
||||||
|
"main_text": "Main text",
|
||||||
"maintenance": "Maintenance",
|
"maintenance": "Maintenance",
|
||||||
"make_a_copy": "Make a copy",
|
"make_a_copy": "Make a copy",
|
||||||
"make_email_primary_description": "Make this the primary email, used to log in",
|
"make_email_primary_description": "Make this the primary email, used to log in",
|
||||||
@@ -2670,6 +2672,7 @@
|
|||||||
"with_premium_subscription_you_also_get": "With an Overleaf Premium subscription you also get",
|
"with_premium_subscription_you_also_get": "With an Overleaf Premium subscription you also get",
|
||||||
"word_count": "Word Count",
|
"word_count": "Word Count",
|
||||||
"word_count_lower": "Word count",
|
"word_count_lower": "Word count",
|
||||||
|
"words": "Words",
|
||||||
"work_in_vim_or_emacs_emulation_mode": "Work in Vim or Emacs emulation mode",
|
"work_in_vim_or_emacs_emulation_mode": "Work in Vim or Emacs emulation mode",
|
||||||
"work_offline": "Work offline",
|
"work_offline": "Work offline",
|
||||||
"work_offline_pull_to_overleaf": "Work offline, then pull to __appName__",
|
"work_offline_pull_to_overleaf": "Work offline, then pull to __appName__",
|
||||||
|
|||||||
Reference in New Issue
Block a user