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