diff --git a/extensions/src/platform-scripture-editor/contributions/localizedStrings.json b/extensions/src/platform-scripture-editor/contributions/localizedStrings.json index 94803085bd..e61a6b0a48 100644 --- a/extensions/src/platform-scripture-editor/contributions/localizedStrings.json +++ b/extensions/src/platform-scripture-editor/contributions/localizedStrings.json @@ -13,6 +13,7 @@ "%webView_platformScriptureEditor_charactersInventory%": "Inventory: Characters...", "%webView_platformScriptureEditor_repeatedWordsInventory%": "Inventory: Repeated Words...", "%webView_platformScriptureEditor_markersInventory%": "Inventory: Markers...", + "%webView_platformScriptureEditor_punctuationInventory%": "Inventory: Punctuation...", "%webView_platformScriptureEditor_configureChecks%": "Configure Checks...", "%webView_platformScriptureEditor_showCheckResults%": "Show Check Results...", "%webView_platformScriptureEditor_publisherInfo%": "Publisher Info", diff --git a/extensions/src/platform-scripture-editor/contributions/menus.json b/extensions/src/platform-scripture-editor/contributions/menus.json index bb7fa686ff..482bfb4b5e 100644 --- a/extensions/src/platform-scripture-editor/contributions/menus.json +++ b/extensions/src/platform-scripture-editor/contributions/menus.json @@ -90,6 +90,12 @@ "order": 3, "command": "platformScripture.openMarkersInventory" }, + { + "label": "%webView_platformScriptureEditor_punctuationInventory%", + "group": "platformScriptureEditor.inventory", + "order": 4, + "command": "platformScripture.openPunctuationInventory" + }, { "label": "%webView_platformScriptureEditor_configureChecks%", "group": "platformScriptureEditor.checks", diff --git a/extensions/src/platform-scripture/contributions/localizedStrings.json b/extensions/src/platform-scripture/contributions/localizedStrings.json index e6a5ff5ad2..5d47cc2d27 100644 --- a/extensions/src/platform-scripture/contributions/localizedStrings.json +++ b/extensions/src/platform-scripture/contributions/localizedStrings.json @@ -24,11 +24,16 @@ "%project_settings_platformScripture_validMarkers_description%": "List of markers that are accepted in this project.", "%project_settings_platformScripture_invalidMarkers_label%": "Invalid Markers", "%project_settings_platformScripture_invalidMarkers_description%": "List of markers that are not accepted in this project.", + "%project_settings_platformScripture_validPunctuation_label%": "Valid Punctuation", + "%project_settings_platformScripture_validPunctuation_description%": "List of punctuation characters that are accepted in this project.", + "%project_settings_platformScripture_invalidPunctuation_label%": "Invalid Punctuation", + "%project_settings_platformScripture_invalidPunctuation_description%": "List of punctuation characters that are not accepted in this project.", "%webView_platformScripture_tools%": "Tools", "%webView_platformScripture_showCheckResults%": "Show Check Results...", "%webView_characterInventory_title%": "Character Inventory: {projectName}", - "%webView_repeatedWordsInventory_title%": "Repeated Words Inventory: {projectName}", "%webView_markersInventory_title%": "Markers Inventory: {projectName}", + "%webView_punctuationInventory_title%": "Punctuation Inventory: {projectName}", + "%webView_repeatedWordsInventory_title%": "Repeated Words Inventory: {projectName}", "%webView_configureChecks_title%": "Configure Checks: {projectName}", "%webView_configureChecks_checks%": "Checks", "%webView_configureChecks_loadingChecks%": "Loading checks", @@ -50,14 +55,22 @@ "%webView_inventory_scope_verse%": "Current verse", "%webView_inventory_filter_text%": "Filter text...", "%webView_inventory_show_additional_items%": "Show Additional Items", - "%webView_inventory_table_header_repeated_words%": "Repeated Word", "%webView_inventory_table_header_character%": "Character", + "%webView_inventory_table_header_context%": "Context", + "%webView_inventory_table_header_count%": "Count", "%webView_inventory_table_header_marker%": "Marker", "%webView_inventory_table_header_preceding_marker%": "Preceding Marker", + "%webView_inventory_table_header_punctuation%": "Punctuation", + "%webView_inventory_table_header_repeated_words%": "Repeated Word", + "%webView_inventory_table_header_status%": "Status", "%webView_inventory_table_header_style_name%": "Style Name", "%webView_inventory_table_header_unicode_value%": "Unicode Value", - "%webView_inventory_table_header_count%": "Count", - "%webView_inventory_table_header_status%": "Status", + "%webView_inventory_table_punctuation_context_isolated%": "Isolated", + "%webView_inventory_table_punctuation_context_wordInitial%": "Word Initial", + "%webView_inventory_table_punctuation_context_wordFinal%": "Word Final", + "%webView_inventory_table_punctuation_context_wordMedial%": "Word Medial", + "%webView_inventory_table_punctuation_showSequences%": "Show Sequences", + "%webView_inventory_table_punctuation_showSinglePunctuationCharacter%": "Show Single Punctuation Character", "%webView_inventory_show_preceding_marker%": "Show Preceding Marker", "%webView_inventory_unknown_marker%": "Unknown Marker", "%webView_inventory_occurrences_table_header_reference%": "Reference", diff --git a/extensions/src/platform-scripture/contributions/projectSettings.json b/extensions/src/platform-scripture/contributions/projectSettings.json index 220051cbea..a2b944d0e4 100644 --- a/extensions/src/platform-scripture/contributions/projectSettings.json +++ b/extensions/src/platform-scripture/contributions/projectSettings.json @@ -49,6 +49,18 @@ "description": "%project_settings_platformScripture_invalidMarkers_description%", "default": "", "includeProjectInterfaces": ["Paratext", "Scripture"] + }, + "platformScripture.validPunctuation": { + "label": "%project_settings_platformScripture_validPunctuation_label%", + "description": "%project_settings_platformScripture_validPunctuation_description%", + "default": "", + "includeProjectInterfaces": ["Paratext", "Scripture"] + }, + "platformScripture.invalidPunctuation": { + "label": "%project_settings_platformScripture_invalidPunctuation_label%", + "description": "%project_settings_platformScripture_invalidPunctuation_description%", + "default": "", + "includeProjectInterfaces": ["Paratext", "Scripture"] } } } diff --git a/extensions/src/platform-scripture/src/checks/inventories/punctuation-inventory.component.tsx b/extensions/src/platform-scripture/src/checks/inventories/punctuation-inventory.component.tsx new file mode 100644 index 0000000000..97fcfd1365 --- /dev/null +++ b/extensions/src/platform-scripture/src/checks/inventories/punctuation-inventory.component.tsx @@ -0,0 +1,395 @@ +// import { LanguageStrings, LocalizeKey, ScriptureReference, substring } from 'platform-bible-utils'; +import { LanguageStrings, LocalizeKey, ScriptureReference } from 'platform-bible-utils'; +import { + Button, + ColumnDef, + Inventory, + // InventoryItemOccurrence, + InventoryTableData, + Scope, + // getBookNumFromId, + // getLinesFromUSFM, + // getNumberFromUSFM, + // getStatusForItem, + inventoryCountColumn, + inventoryItemColumn, + inventoryStatusColumn, +} from 'platform-bible-react'; +import { useLocalizedStrings } from '@papi/frontend/react'; +import { useMemo, useState } from 'react'; + +const PUNCTUATION_INVENTORY_STRING_KEYS: LocalizeKey[] = [ + '%webView_inventory_table_header_count%', + '%webView_inventory_table_header_context%', + '%webView_inventory_table_header_punctuation%', + '%webView_inventory_table_header_status%', + '%webView_inventory_table_header_unicode_value%', + '%webView_inventory_table_punctuation_context_isolated%', + '%webView_inventory_table_punctuation_context_wordInitial%', + '%webView_inventory_table_punctuation_context_wordFinal%', + '%webView_inventory_table_punctuation_context_wordMedial%', + '%webView_inventory_table_punctuation_showSequences%', + '%webView_inventory_table_punctuation_showSinglePunctuationCharacter%', +]; + +const punctuationRegex: RegExp = /[\p{P}]/gu; + +// Logic like this is probably not needed anymore after +// https://github.com/paranext/paranext-core/issues/1384 +// is fixed. I'll leave it in here for now, just in case + +// const extractPunctuation = ( +// showSequences: boolean, +// ): (( +// text: string | undefined, +// scriptureRef: ScriptureReference, +// approvedItems: string[], +// unapprovedItems: string[], +// ) => InventoryTableData[]) => { +// return ( +// text: string | undefined, +// scriptureRef: ScriptureReference, +// approvedItems: string[], +// unapprovedItems: string[], +// ) => { +// if (!text) return []; + +// const tableData: InventoryTableData[] = []; + +// let currentBook: number | undefined = scriptureRef.bookNum; +// let currentChapter: number | undefined = scriptureRef.chapterNum; +// let currentVerse: number | undefined = scriptureRef.verseNum; + +// // Matches all punctuation characters +// const punctuationRegex: RegExp = showSequences ? /[\p{P}]+/gu : /[\p{P}]/gu; + +// const lines = getLinesFromUSFM(text); + +// lines.forEach((line: string) => { +// if (line.startsWith('\\id')) { +// currentBook = getBookNumFromId(line); +// currentChapter = 0; +// currentVerse = 0; +// } +// if (line.startsWith('\\c')) { +// currentChapter = getNumberFromUSFM(line); +// currentVerse = 0; +// } +// if (line.startsWith('\\v')) { +// currentVerse = getNumberFromUSFM(line); +// if (currentChapter === 0) { +// currentChapter = scriptureRef.chapterNum; +// } +// } + +// let match: RegExpExecArray | undefined = punctuationRegex.exec(line) ?? undefined; +// while (match) { +// // For this code to work correctly we need our regular expression to match a single +// // punctuation character per match +// if (match.length > 1) +// throw new Error('Multiple punctuation characters found in a single match'); + +// const punctuation = match[0]; +// const { index } = match; + +// let prefix = ''; +// let suffix = ''; + +// // Check if preceding character is whitespace or not +// if (index === 0) { +// prefix = '_'; +// } else { +// for (let i = index - 1; i >= 0; i--) { +// const precedingChar = line[i]; +// if (/\s/.test(precedingChar)) { +// prefix = '_'; +// break; +// } else if (!/[\p{P}]/u.test(precedingChar)) { +// break; +// } +// } +// } + +// // Check if following character is whitespace or not +// if (index === line.length - punctuation.length) { +// suffix = '_'; +// } else { +// for (let i = index + punctuation.length; i < line.length; i++) { +// const followingChar = line[i]; +// if (/\s/.test(followingChar)) { +// suffix = '_'; +// break; +// } else if (!/[\p{P}]/u.test(followingChar)) { +// break; +// } +// } +// } + +// const item = `${prefix}${punctuation}${suffix}`; + +// const itemIndex = match.index; +// const existingItem = tableData.find((tableEntry) => tableEntry.items[0] === item); +// const newReference: InventoryItemOccurrence = { +// reference: { +// bookNum: currentBook !== undefined ? currentBook : -1, +// chapterNum: currentChapter !== undefined ? currentChapter : -1, +// verseNum: currentVerse !== undefined ? currentVerse : -1, +// }, +// text: substring(line, Math.max(0, itemIndex - 25), Math.min(itemIndex + 25, line.length)), +// }; +// if (existingItem) { +// existingItem.count += 1; +// existingItem.occurrences.push(newReference); +// } else { +// const newItem: InventoryTableData = { +// items: [item], +// count: 1, +// status: getStatusForItem(item, approvedItems, unapprovedItems), +// occurrences: [newReference], +// }; +// tableData.push(newItem); +// } + +// match = punctuationRegex.exec(line) ?? undefined; +// } +// }); + +// return tableData; +// }; +// }; + +// function getPunctuationContext( +// item: string, +// isolatedLabel: string, +// wordInitialLabel: string, +// wordFinalLabel: string, +// wordMedialLabel: string, +// ): string { +// if (item.startsWith('_')) { +// if (item.endsWith('_')) { +// return isolatedLabel; +// } + +// return wordInitialLabel; +// } + +// if (item.endsWith('_')) { +// return wordFinalLabel; +// } + +// return wordMedialLabel; +// } + +/** + * Function that constructs the column for the inventory component + * + * @param itemLabel Localized label for the item column (e.g. 'Character', 'Repeated Word', etc.) + * @param unicodeValueLabel Localized label for the Unicode Value column + * @param countLabel Localized label for the count column + * @param statusLabel Localized label for the status column + * @param isolatedLabel Localized label for the context when punctuation appears in isolation + * @param wordInitialLabel Localized label for the context when punctuation appears word initial + * @param wordFinalLabel Localized label for the context when punctuation appears word final + * @param wordMedialLabel Localized label for the context when punctuation appears word medial + * @param approvedItems Array of approved items, typically as defined in `Settings.xml` + * @param onApprovedItemsChange Callback function that stores the updated list of approved items + * @param unapprovedItems Array of unapproved items, typically as defined in `Settings.xml` + * @param onUnapprovedItemsChange Callback function that stores the updated list of unapproved items + * @param showSequences True if inventory shows sequences of punctuation. False if it only considers + * single punctuation characters + * @returns An array of columns that can be passed into the inventory component + */ +const createColumns = ( + itemLabel: string, + unicodeValueLabel: string, + // contextLabel: string, + countLabel: string, + statusLabel: string, + // isolatedLabel: string, + // wordInitialLabel: string, + // wordFinalLabel: string, + // wordMedialLabel: string, + approvedItems: string[], + onApprovedItemsChange: (items: string[]) => void, + unapprovedItems: string[], + onUnapprovedItemsChange: (items: string[]) => void, + // showSequences: boolean, +): ColumnDef[] => { + // const contextColumn: ColumnDef = { + // accessorKey: 'context', + // header: () => , + // cell: ({ row }) => { + // const item: string = row.getValue('item'); + // return getPunctuationContext( + // item, + // isolatedLabel, + // wordInitialLabel, + // wordFinalLabel, + // wordMedialLabel, + // ); + // }, + // }; + + return [ + inventoryItemColumn(itemLabel), + { + accessorKey: 'unicodeValue', + header: () => , + cell: ({ row }) => { + const item: string = row.getValue('item'); + return item.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0'); + }, + }, + inventoryCountColumn(countLabel), + // ...(showSequences ? [] : [contextColumn]), + inventoryStatusColumn( + statusLabel, + approvedItems, + onApprovedItemsChange, + unapprovedItems, + onUnapprovedItemsChange, + ), + ]; +}; + +type PunctuationInventoryProps = { + scriptureReference: ScriptureReference; + setScriptureReference: (scriptureReference: ScriptureReference) => void; + localizedStrings: LanguageStrings; + approvedItems: string[]; + onApprovedItemsChange: (items: string[]) => void; + unapprovedItems: string[]; + onUnapprovedItemsChange: (items: string[]) => void; + text: string | undefined; + scope: Scope; + onScopeChange: (scope: Scope) => void; +}; + +function PunctuationInventory({ + scriptureReference, + setScriptureReference, + localizedStrings, + approvedItems, + onApprovedItemsChange, + unapprovedItems, + onUnapprovedItemsChange, + text, + scope, + onScopeChange, +}: PunctuationInventoryProps) { + const [punctuationInventoryStrings] = useLocalizedStrings(PUNCTUATION_INVENTORY_STRING_KEYS); + const itemLabel = useMemo( + () => punctuationInventoryStrings['%webView_inventory_table_header_punctuation%'], + [punctuationInventoryStrings], + ); + const unicodeValueLabel = useMemo( + () => punctuationInventoryStrings['%webView_inventory_table_header_unicode_value%'], + [punctuationInventoryStrings], + ); + // const contextLabel = useMemo( + // () => punctuationInventoryStrings['%webView_inventory_table_header_context%'], + // [punctuationInventoryStrings], + // ); + const countLabel = useMemo( + () => punctuationInventoryStrings['%webView_inventory_table_header_count%'], + [punctuationInventoryStrings], + ); + const statusLabel = useMemo( + () => punctuationInventoryStrings['%webView_inventory_table_header_status%'], + [punctuationInventoryStrings], + ); + // const isolatedLabel = useMemo( + // () => punctuationInventoryStrings['%webView_inventory_table_punctuation_context_isolated%'], + // [punctuationInventoryStrings], + // ); + // const wordInitialLabel = useMemo( + // () => punctuationInventoryStrings['%webView_inventory_table_punctuation_context_wordInitial%'], + // [punctuationInventoryStrings], + // ); + // const wordFinalLabel = useMemo( + // () => punctuationInventoryStrings['%webView_inventory_table_punctuation_context_wordFinal%'], + // [punctuationInventoryStrings], + // ); + // const wordMedialLabel = useMemo( + // () => punctuationInventoryStrings['%webView_inventory_table_punctuation_context_wordMedial%'], + // [punctuationInventoryStrings], + // ); + const showSequencesLabel = useMemo( + () => punctuationInventoryStrings['%webView_inventory_table_punctuation_showSequences%'], + [punctuationInventoryStrings], + ); + const showSinglePunctuationCharacterLabel = useMemo( + () => + punctuationInventoryStrings[ + '%webView_inventory_table_punctuation_showSinglePunctuationCharacter%' + ], + [punctuationInventoryStrings], + ); + + const [showSequences, setShowSequences] = useState(false); + + const columns = useMemo( + () => + createColumns( + itemLabel, + unicodeValueLabel, + // contextLabel, + countLabel, + statusLabel, + // isolatedLabel, + // wordInitialLabel, + // wordFinalLabel, + // wordMedialLabel, + approvedItems, + onApprovedItemsChange, + unapprovedItems, + onUnapprovedItemsChange, + // showSequences, + ), + [ + itemLabel, + unicodeValueLabel, + // contextLabel, + countLabel, + statusLabel, + // isolatedLabel, + // wordInitialLabel, + // wordFinalLabel, + // wordMedialLabel, + approvedItems, + onApprovedItemsChange, + unapprovedItems, + onUnapprovedItemsChange, + // showSequences, + ], + ); + + return ( +
+ + +
+ ); +} + +export default PunctuationInventory; diff --git a/extensions/src/platform-scripture/src/inventory.web-view.tsx b/extensions/src/platform-scripture/src/inventory.web-view.tsx index b333f6c824..e51378cc1c 100644 --- a/extensions/src/platform-scripture/src/inventory.web-view.tsx +++ b/extensions/src/platform-scripture/src/inventory.web-view.tsx @@ -9,6 +9,7 @@ import papi from '@papi/frontend'; import CharacterInventory from './checks/inventories/character-inventory.component'; import RepeatedWordsInventory from './checks/inventories/repeated-words-inventory.component'; import MarkerInventory from './checks/inventories/marker-inventory.component'; +import PunctuationInventory from './checks/inventories/punctuation-inventory.component'; /** * Get scripture text for the provided scope and reference for the specified projectId @@ -78,6 +79,11 @@ global.webViewComponent = function InventoryWebView({ validItemsSetting = 'platformScripture.validMarkers'; invalidItemsSetting = 'platformScripture.invalidMarkers'; break; + case 'platformScripture.punctuationInventory': + InventoryVariant = PunctuationInventory; + validItemsSetting = 'platformScripture.validPunctuation'; + invalidItemsSetting = 'platformScripture.invalidPunctuation'; + break; default: throw new Error(`${webViewType} is not a valid inventory type`); } diff --git a/extensions/src/platform-scripture/src/main.ts b/extensions/src/platform-scripture/src/main.ts index 5109a112c9..f98a1b7cac 100644 --- a/extensions/src/platform-scripture/src/main.ts +++ b/extensions/src/platform-scripture/src/main.ts @@ -19,6 +19,7 @@ import CheckResultsWebViewProvider, { const characterInventoryWebViewType = 'platformScripture.characterInventory'; const repeatedWordsInventoryWebViewType = 'platformScripture.repeatedWordsInventory'; const markersInventoryWebViewType = 'platformScripture.markersInventory'; +const punctuationInventoryWebViewType = 'platformScripture.punctuationInventory'; // #region Project Setting Validators @@ -47,6 +48,11 @@ const markersValidator: ProjectSettingValidator< 'platformScripture.validMarkers' | 'platformScripture.invalidMarkers' > = async (newValue) => typeof newValue === 'string'; +// A marker can be any string value +const punctuationValidator: ProjectSettingValidator< + 'platformScripture.validPunctuation' | 'platformScripture.invalidPunctuation' +> = async (newValue) => typeof newValue === 'string'; + // #endregion async function openPlatformCharactersInventory( @@ -67,6 +73,12 @@ async function openPlatformMarkersInventory( return openInventory(webViewId, markersInventoryWebViewType); } +async function openPlatformPunctuationInventory( + webViewId: string | undefined, +): Promise { + return openInventory(webViewId, punctuationInventoryWebViewType); +} + async function openInventory( webViewId: string | undefined, webViewType: string, @@ -157,6 +169,10 @@ export async function activate(context: ExecutionActivationContext) { '%webView_markersInventory_title%', markersInventoryWebViewType, ); + const punctuationInventoryWebViewProvider = new InventoryWebViewProvider( + '%webView_punctuationInventory_title%', + punctuationInventoryWebViewType, + ); const checkResultsWebViewProvider = new CheckResultsWebViewProvider(); const configureChecksWebViewProvider = new ConfigureChecksWebViewProvider( '%webView_configureChecks_title%', @@ -234,7 +250,7 @@ export async function activate(context: ExecutionActivationContext) { }, }, ); - const characterInventoryWebViewProviderPromise = papi.webViewProviders.register( + const characterInventoryWebViewProviderPromise = papi.webViewProviders.registerWebViewProvider( characterInventoryWebViewType, characterInventoryWebViewProvider, ); @@ -268,10 +284,11 @@ export async function activate(context: ExecutionActivationContext) { }, }, ); - const repeatableWordsInventoryWebViewProviderPromise = papi.webViewProviders.register( - repeatedWordsInventoryWebViewType, - repeatedWordsInventoryWebViewProvider, - ); + const repeatableWordsInventoryWebViewProviderPromise = + papi.webViewProviders.registerWebViewProvider( + repeatedWordsInventoryWebViewType, + repeatedWordsInventoryWebViewProvider, + ); const validMarkersPromise = papi.projectSettings.registerValidator( 'platformScripture.validMarkers', markersValidator, @@ -302,10 +319,26 @@ export async function activate(context: ExecutionActivationContext) { }, }, ); - const markersInventoryWebViewProviderPromise = papi.webViewProviders.register( + const markersInventoryWebViewProviderPromise = papi.webViewProviders.registerWebViewProvider( markersInventoryWebViewType, markersInventoryWebViewProvider, ); + const validPunctuationPromise = papi.projectSettings.registerValidator( + 'platformScripture.validPunctuation', + punctuationValidator, + ); + const invalidPunctuationPromise = papi.projectSettings.registerValidator( + 'platformScripture.invalidPunctuation', + punctuationValidator, + ); + const openPunctuationInventoryPromise = papi.commands.registerCommand( + 'platformScripture.openPunctuationInventory', + openPlatformPunctuationInventory, + ); + const punctuationInventoryWebViewProviderPromise = papi.webViewProviders.registerWebViewProvider( + punctuationInventoryWebViewType, + punctuationInventoryWebViewProvider, + ); const configureChecksPromise = papi.commands.registerCommand( 'platformScripture.openConfigureChecks', configureChecks, @@ -380,6 +413,10 @@ export async function activate(context: ExecutionActivationContext) { await invalidMarkersPromise, await openMarkersInventoryPromise, await markersInventoryWebViewProviderPromise, + await validPunctuationPromise, + await invalidPunctuationPromise, + await openPunctuationInventoryPromise, + await punctuationInventoryWebViewProviderPromise, await configureChecksPromise, await configureChecksWebViewProviderPromise, await showCheckResultsPromise, diff --git a/extensions/src/platform-scripture/src/types/platform-scripture.d.ts b/extensions/src/platform-scripture/src/types/platform-scripture.d.ts index 3b72b135dc..47430eda7d 100644 --- a/extensions/src/platform-scripture/src/types/platform-scripture.d.ts +++ b/extensions/src/platform-scripture/src/types/platform-scripture.d.ts @@ -850,6 +850,10 @@ declare module 'papi-shared-types' { projectId?: string | undefined, ) => Promise; + 'platformScripture.openPunctuationInventory': ( + projectId?: string | undefined, + ) => Promise; + 'platformScripture.openConfigureChecks': ( projectId?: string | undefined, ) => Promise; @@ -900,5 +904,9 @@ declare module 'papi-shared-types' { 'platformScripture.validMarkers': string; 'platformScripture.invalidMarkers': string; + + 'platformScripture.validPunctuation': string; + + 'platformScripture.invalidPunctuation': string; } }