@ihabdevteam/core 0.149.0 โ†’ 0.151.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/components.cjs.js +199 -186
  3. package/dist/components.esm.css +5 -5
  4. package/dist/hooks.cjs.js +5 -1
  5. package/dist/i18n.cjs.js +3 -3
  6. package/dist/ihabdevteam-core-tokens.css +1 -1
  7. package/dist/ihabdevteam-core.cjs.js +200 -187
  8. package/dist/ihabdevteam-core.css +1 -1
  9. package/dist/ihabdevteam-core.d.ts +407 -69
  10. package/dist/packages/core/src/components/Accordion.d.ts +6 -0
  11. package/dist/packages/core/src/components/Accordion.js +5 -2
  12. package/dist/packages/core/src/components/Alert.d.ts +7 -1
  13. package/dist/packages/core/src/components/Alert.js +12 -3
  14. package/dist/packages/core/src/components/BarChart.js +7 -2
  15. package/dist/packages/core/src/components/Board.d.ts +5 -2
  16. package/dist/packages/core/src/components/Board.js +9 -3
  17. package/dist/packages/core/src/components/BottomNav.js +5 -4
  18. package/dist/packages/core/src/components/Breadcrumb.css +9 -0
  19. package/dist/packages/core/src/components/Breadcrumb.d.ts +12 -1
  20. package/dist/packages/core/src/components/Breadcrumb.js +8 -2
  21. package/dist/packages/core/src/components/BulkBar.css +2 -0
  22. package/dist/packages/core/src/components/BulkBar.js +21 -2
  23. package/dist/packages/core/src/components/Button.d.ts +12 -2
  24. package/dist/packages/core/src/components/Button.js +70 -13
  25. package/dist/packages/core/src/components/Calendar.d.ts +6 -1
  26. package/dist/packages/core/src/components/Calendar.js +3 -3
  27. package/dist/packages/core/src/components/DatePicker.d.ts +7 -1
  28. package/dist/packages/core/src/components/DatePicker.js +25 -15
  29. package/dist/packages/core/src/components/Dropdown.d.ts +1 -0
  30. package/dist/packages/core/src/components/Dropdown.js +7 -5
  31. package/dist/packages/core/src/components/Effect/EffectPartyPopperHost.d.ts +12 -1
  32. package/dist/packages/core/src/components/Effect/EffectPartyPopperHost.js +25 -8
  33. package/dist/packages/core/src/components/EmptyState.d.ts +5 -1
  34. package/dist/packages/core/src/components/EmptyState.js +4 -1
  35. package/dist/packages/core/src/components/ErrorState.d.ts +5 -2
  36. package/dist/packages/core/src/components/ErrorState.js +3 -1
  37. package/dist/packages/core/src/components/FileUpload.d.ts +9 -1
  38. package/dist/packages/core/src/components/FileUpload.js +8 -2
  39. package/dist/packages/core/src/components/FilterSectionInfo.js +14 -2
  40. package/dist/packages/core/src/components/Header.js +8 -5
  41. package/dist/packages/core/src/components/HearingDeviceInfo.js +2 -1
  42. package/dist/packages/core/src/components/Icon.d.ts +2 -1
  43. package/dist/packages/core/src/components/Icon.js +4 -1
  44. package/dist/packages/core/src/components/Loader.css +28 -0
  45. package/dist/packages/core/src/components/Loader.d.ts +5 -1
  46. package/dist/packages/core/src/components/Loader.js +4 -2
  47. package/dist/packages/core/src/components/NoticeBox.d.ts +1 -1
  48. package/dist/packages/core/src/components/NoticeBox.js +15 -6
  49. package/dist/packages/core/src/components/PanelRow.js +2 -1
  50. package/dist/packages/core/src/components/PassCard/PassCard.d.ts +8 -2
  51. package/dist/packages/core/src/components/PassCard/PassCard.js +25 -10
  52. package/dist/packages/core/src/components/PassCard/passCardProduct.d.ts +1 -1
  53. package/dist/packages/core/src/components/PassCard/passCardProduct.js +11 -2
  54. package/dist/packages/core/src/components/Popup/ConfirmPopup.d.ts +11 -2
  55. package/dist/packages/core/src/components/Popup/ConfirmPopup.js +18 -3
  56. package/dist/packages/core/src/components/Popup/DeviceSideTable.js +10 -8
  57. package/dist/packages/core/src/components/Popup/ListPopup.js +6 -6
  58. package/dist/packages/core/src/components/Popup/LoadTablePopup.css +7 -0
  59. package/dist/packages/core/src/components/Popup/LoadTablePopup.js +27 -5
  60. package/dist/packages/core/src/components/Popup/Popup.d.ts +8 -0
  61. package/dist/packages/core/src/components/Popup/Popup.js +6 -3
  62. package/dist/packages/core/src/components/Popup/SaveTablePopup.d.ts +5 -1
  63. package/dist/packages/core/src/components/Popup/TrainingResultPopup.css +3 -0
  64. package/dist/packages/core/src/components/Popup/TrainingResultPopup.d.ts +5 -0
  65. package/dist/packages/core/src/components/Popup/TrainingResultPopup.js +10 -4
  66. package/dist/packages/core/src/components/Popup/WordDataUpdatePopup.css +9 -0
  67. package/dist/packages/core/src/components/ProgressIndicator.css +5 -0
  68. package/dist/packages/core/src/components/ProgressIndicator.d.ts +3 -1
  69. package/dist/packages/core/src/components/ProgressIndicator.js +5 -3
  70. package/dist/packages/core/src/components/PromoBannerBg/promoBannerBg.d.ts +4 -1
  71. package/dist/packages/core/src/components/PromoBannerBg/promoBannerBg.js +27 -15
  72. package/dist/packages/core/src/components/Rating.d.ts +8 -2
  73. package/dist/packages/core/src/components/Rating.js +22 -8
  74. package/dist/packages/core/src/components/Report/ReportDocument.d.ts +10 -7
  75. package/dist/packages/core/src/components/Report/ReportDocument.js +39 -13
  76. package/dist/packages/core/src/components/RingChart.d.ts +1 -1
  77. package/dist/packages/core/src/components/RingChart.js +1 -1
  78. package/dist/packages/core/src/components/SegmentedControl.d.ts +4 -0
  79. package/dist/packages/core/src/components/SegmentedControl.js +9 -1
  80. package/dist/packages/core/src/components/ShortcutBadge.css +16 -1
  81. package/dist/packages/core/src/components/ShortcutBadge.d.ts +1 -0
  82. package/dist/packages/core/src/components/SideNav.js +8 -2
  83. package/dist/packages/core/src/components/SummaryStrip.d.ts +1 -1
  84. package/dist/packages/core/src/components/SummaryStrip.js +2 -2
  85. package/dist/packages/core/src/components/Table.d.ts +1 -1
  86. package/dist/packages/core/src/components/Table.js +4 -1
  87. package/dist/packages/core/src/components/Tabs.d.ts +1 -0
  88. package/dist/packages/core/src/components/Tabs.js +4 -1
  89. package/dist/packages/core/src/components/TextField.css +5 -6
  90. package/dist/packages/core/src/components/TextField.js +46 -3
  91. package/dist/packages/core/src/components/Toast.js +16 -2
  92. package/dist/packages/core/src/components/Tooltip/Tooltip.css +3 -1
  93. package/dist/packages/core/src/components/Tooltip/Tooltip.js +5 -2
  94. package/dist/packages/core/src/components/Tooltip/tooltipConstants.d.ts +10 -2
  95. package/dist/packages/core/src/components/Tooltip/tooltipConstants.js +10 -2
  96. package/dist/packages/core/src/components/TopBanner.d.ts +1 -1
  97. package/dist/packages/core/src/components/TopBanner.js +66 -8
  98. package/dist/packages/core/src/components/Training/AudioPlayerWaveForm.d.ts +1 -0
  99. package/dist/packages/core/src/components/Training/AudioPlayerWaveForm.js +65 -15
  100. package/dist/packages/core/src/components/Training/NoisePlayerContext.d.ts +3 -2
  101. package/dist/packages/core/src/components/Training/NoisePlayerContext.js +10 -4
  102. package/dist/packages/core/src/components/Training/TrainingBlockButton.css +7 -0
  103. package/dist/packages/core/src/components/Training/TrainingBlockButton.js +3 -1
  104. package/dist/packages/core/src/components/Training/TrainingPagePopup.d.ts +18 -5
  105. package/dist/packages/core/src/components/Training/TrainingPagePopup.js +93 -28
  106. package/dist/packages/core/src/components/Training/TrainingPagePopupBottom.d.ts +1 -0
  107. package/dist/packages/core/src/components/Training/TrainingPagePopupBottom.js +16 -3
  108. package/dist/packages/core/src/components/Training/TrainingStepAudioPlayer.js +29 -11
  109. package/dist/packages/core/src/components/Training/TrainingStepCard.js +19 -3
  110. package/dist/packages/core/src/components/Training/TrainingStepTitle.js +15 -1
  111. package/dist/packages/core/src/components/Training/TrainingWordCard.css +16 -0
  112. package/dist/packages/core/src/components/Training/TrainingWordCard.d.ts +6 -0
  113. package/dist/packages/core/src/components/Training/TrainingWordCard.js +7 -2
  114. package/dist/packages/core/src/components/Training/trainingBlockButtonTypes.d.ts +2 -2
  115. package/dist/packages/core/src/components/Training/trainingMissStats.d.ts +10 -0
  116. package/dist/packages/core/src/components/Training/trainingMissStats.js +19 -1
  117. package/dist/packages/core/src/components/Training/useFloatingFocus.d.ts +13 -0
  118. package/dist/packages/core/src/components/Training/useFloatingFocus.js +59 -0
  119. package/dist/packages/core/src/components/Training/useNoiseFade.js +20 -11
  120. package/dist/packages/core/src/components/Training/useProgressTicker.d.ts +0 -18
  121. package/dist/packages/core/src/components/Training/useProgressTicker.js +59 -14
  122. package/dist/packages/core/src/components/Training/useTTSPlayback.d.ts +1 -1
  123. package/dist/packages/core/src/components/Training/useTTSPlayback.js +56 -25
  124. package/dist/packages/core/src/components/Training/useWaveReady.d.ts +4 -1
  125. package/dist/packages/core/src/components/Training/useWaveReady.js +17 -8
  126. package/dist/packages/core/src/components/UpdateAlert.css +4 -2
  127. package/dist/packages/core/src/components/UpdateAlert.d.ts +3 -1
  128. package/dist/packages/core/src/components/UpdateAlert.js +21 -1
  129. package/dist/packages/core/src/components/Word/PhonemeSelectBlock.d.ts +2 -0
  130. package/dist/packages/core/src/components/Word/PhonemeSelectBlock.js +12 -4
  131. package/dist/packages/core/src/components/Word/WordSearchResultBlock.d.ts +6 -0
  132. package/dist/packages/core/src/components/Word/WordSearchResultBlock.js +26 -13
  133. package/dist/packages/core/src/components/Word/WordTableBlock.js +24 -21
  134. package/dist/packages/core/src/components/Word/WordTableThumbnail.js +1 -1
  135. package/dist/packages/core/src/components/Word/useWordTableDisplaySettings.js +7 -10
  136. package/dist/packages/core/src/components/Word/useWordTablePrint.d.ts +1 -1
  137. package/dist/packages/core/src/components/Word/useWordTablePrint.js +9 -3
  138. package/dist/packages/core/src/components/Word/wordScoreStats.d.ts +14 -1
  139. package/dist/packages/core/src/components/Word/wordScoreStats.js +7 -3
  140. package/dist/packages/core/src/components/Word/wordTablePrint.js +15 -3
  141. package/dist/packages/core/src/components/Word/wordTableReview.d.ts +58 -1
  142. package/dist/packages/core/src/components/Word/wordTableReview.js +75 -17
  143. package/dist/packages/core/src/components/Word/wordTableSettings.d.ts +9 -0
  144. package/dist/packages/core/src/components/Word/wordTableSettings.js +11 -0
  145. package/dist/packages/core/src/components/WordTable.css +10 -0
  146. package/dist/packages/core/src/components/WordTable.js +39 -26
  147. package/dist/packages/core/src/components/barChartTypes.d.ts +2 -2
  148. package/dist/packages/core/src/components/buttonTypes.d.ts +8 -0
  149. package/dist/packages/core/src/components/updateAlertStack.d.ts +2 -0
  150. package/dist/packages/core/src/components/updateAlertStack.js +68 -0
  151. package/dist/packages/core/src/components/useCalendarMonth.d.ts +4 -1
  152. package/dist/packages/core/src/components/useCalendarMonth.js +6 -3
  153. package/dist/packages/core/src/components/useComboboxSuggestions.d.ts +7 -0
  154. package/dist/packages/core/src/components/useComboboxSuggestions.js +21 -2
  155. package/dist/packages/core/src/components/wordTableLayout.d.ts +4 -0
  156. package/dist/packages/core/src/components/wordTableLayout.js +6 -0
  157. package/dist/packages/core/src/components.d.ts +2 -1
  158. package/dist/packages/core/src/components.js +1 -1
  159. package/dist/packages/core/src/hooks/bodyScrollLock.js +8 -2
  160. package/dist/packages/core/src/hooks/focusReturn.d.ts +12 -0
  161. package/dist/packages/core/src/hooks/focusReturn.js +49 -0
  162. package/dist/packages/core/src/hooks/touchToggle.d.ts +22 -0
  163. package/dist/packages/core/src/hooks/touchToggle.js +114 -0
  164. package/dist/packages/core/src/hooks/useAnchoredMenu.d.ts +12 -3
  165. package/dist/packages/core/src/hooks/useAnchoredMenu.js +37 -3
  166. package/dist/packages/core/src/hooks/useDailyTrainingSelection.d.ts +4 -2
  167. package/dist/packages/core/src/hooks/useDailyTrainingSelection.js +23 -11
  168. package/dist/packages/core/src/hooks/useIcon.js +29 -5
  169. package/dist/packages/core/src/hooks/useLucideIcon.d.ts +4 -0
  170. package/dist/packages/core/src/hooks/useLucideIcon.js +4 -0
  171. package/dist/packages/core/src/hooks/usePendingPress.d.ts +9 -0
  172. package/dist/packages/core/src/hooks/usePendingPress.js +26 -0
  173. package/dist/packages/core/src/hooks/usePrefersReducedMotion.d.ts +15 -0
  174. package/dist/packages/core/src/hooks/usePrefersReducedMotion.js +26 -0
  175. package/dist/packages/core/src/hooks/useRelativeDay.d.ts +2 -2
  176. package/dist/packages/core/src/hooks/useRelativeDay.js +12 -5
  177. package/dist/packages/core/src/hooks/useTablePaging.d.ts +27 -2
  178. package/dist/packages/core/src/hooks/useTablePaging.js +37 -5
  179. package/dist/packages/core/src/hooks/useWordAttributeFilter.d.ts +7 -0
  180. package/dist/packages/core/src/hooks/useWordAttributeFilter.js +25 -0
  181. package/dist/packages/core/src/i18n/index.js +9 -1
  182. package/dist/packages/core/src/i18n/locales/en.json +24 -2
  183. package/dist/packages/core/src/i18n/locales/ko.json +24 -2
  184. package/dist/packages/core/src/tokens.css +5 -0
  185. package/dist/packages/core/src/types/wordSearch.d.ts +12 -0
  186. package/dist/packages/core/src/utils/csv.d.ts +7 -2
  187. package/dist/packages/core/src/utils/csv.js +12 -5
  188. package/dist/packages/core/src/utils/dailySelectionUtils.js +44 -14
  189. package/dist/packages/core/src/utils/date.d.ts +8 -0
  190. package/dist/packages/core/src/utils/date.js +44 -10
  191. package/dist/packages/core/src/utils/format.d.ts +1 -1
  192. package/dist/packages/core/src/utils/format.js +6 -7
  193. package/dist/packages/core/src/utils/noiseSettingsStore.d.ts +7 -3
  194. package/dist/packages/core/src/utils/phonemeConvert.d.ts +9 -0
  195. package/dist/packages/core/src/utils/phonemeConvert.js +11 -0
  196. package/dist/packages/core/src/utils/phonemePairSearch.d.ts +7 -1
  197. package/dist/packages/core/src/utils/phonemePairSearch.js +45 -21
  198. package/dist/packages/core/src/utils/safeHref.d.ts +8 -0
  199. package/dist/packages/core/src/utils/safeHref.js +24 -0
  200. package/dist/packages/core/src/utils/savedWordTables.d.ts +19 -3
  201. package/dist/packages/core/src/utils/savedWordTables.js +27 -19
  202. package/dist/packages/core/src/utils/textSort.d.ts +9 -0
  203. package/dist/packages/core/src/utils/textSort.js +37 -1
  204. package/dist/packages/core/src/utils/wordAttributeFilterCodes.d.ts +31 -0
  205. package/dist/packages/core/src/utils/wordAttributeFilterCodes.js +163 -0
  206. package/dist/packages/core/src/utils/wordContrast.d.ts +4 -2
  207. package/dist/packages/core/src/utils/wordContrast.js +8 -2
  208. package/dist/packages/core/src/utils/wordTrainingGenerators.d.ts +5 -2
  209. package/dist/packages/core/src/utils/wordTrainingGenerators.js +52 -2
  210. package/dist/packages/core/src/utils/wordTrainingPool.js +2 -2
  211. package/dist/packages/core/src/utils/wordTrainingRuntime.js +15 -7
  212. package/dist/packages/core/src/utils/wordTrainingRuntimeTypes.d.ts +1 -1
  213. package/dist/packages/core/src/utils/wordTrainingRuntimeTypes.js +5 -4
  214. package/dist/packages/core/src/utils/wornDate.d.ts +12 -0
  215. package/dist/packages/core/src/utils/wornDate.js +15 -0
  216. package/dist/packages/core/src/version.d.ts +1 -1
  217. package/dist/packages/core/src/version.js +1 -1
  218. package/dist/utils.cjs.js +2 -2
  219. package/package.json +1 -1
@@ -10,9 +10,15 @@ export interface WordSearchResultGroup {
10
10
  /** @public */
11
11
  export interface WordSearchResultBlockProps {
12
12
  titleIcon?: string;
13
+ /**
14
+ * ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ์ˆ˜ โ€” ์ œ๋ชฉ "์ด N๊ฐœ" ์— ์“ด๋‹ค. **๊ฒ€์ƒ‰ ํ™”๋ฉด์ด๋ผ๋Š” ํ‘œ์‹œ์ด๊ธฐ๋„ ํ•˜๋‹ค**: ์ด๊ฒƒ๋„ `searchQuery` ๋„ ์ฃผ์ง€ ์•Š์œผ๋ฉด
15
+ * ์ €์žฅ๋œ ๋ชฉ๋ก์„ ๋ณด์—ฌ ์ฃผ๋Š” ๊ฒƒ์œผ๋กœ ๋ณด๊ณ (๊ณต์œ  ์—ด๋žŒ) ๋ฐ›์€ ์ˆœ์„œ๋ฅผ ๊ธฐ๋ณธ ์ •๋ ฌ๋กœ ๋‘”๋‹ค('๋ฐ›์€ ์ˆœ์„œ' ์„ ํƒ์ง€๊ฐ€ ์ƒ๊ธด๋‹ค).
16
+ */
13
17
  resultCount?: number;
14
18
  resultUnit?: string;
19
+ /** ๊ฒ€์ƒ‰์–ด โ€” ํ•œ ๋‚ฑ๋ง์ด๋ฉด '์‹œ์ž‘ ์šฐ์„ ' ์„ ๊ธฐ๋ณธ ์ •๋ ฌ๋กœ ์“ด๋‹ค */
15
20
  searchQuery?: string;
21
+ /** ๋ณด์ผ ํ•ญ๋ชฉ. ๊ฒ€์ƒ‰์–ดยท๊ฒฐ๊ณผ ์ˆ˜ ์—†์ด ์ด๊ฒƒ๋งŒ ์ฃผ๋ฉด ๋ฐ›์€ ์ˆœ์„œ ๊ทธ๋Œ€๋กœ๊ฐ€ ๊ธฐ๋ณธ์ด๋‹ค */
16
22
  items: WordSearchResultItem[];
17
23
  groups?: WordSearchResultGroup[];
18
24
  selected?: string[];
@@ -37,31 +37,38 @@ export default function WordSearchResultBlock({ titleIcon, resultCount, resultUn
37
37
  const rawQuery = (searchQuery ?? '').trim();
38
38
  const q = /^[^,[\]\s]+$/.test(rawQuery) ? rawQuery : '';
39
39
  const hasQuery = q.length > 0;
40
+ /* **๊ฒ€์ƒ‰์–ด๋„ ๊ฒฐ๊ณผ ์ˆ˜๋„ ์—†์ด items ๋งŒ ๋ฐ›์œผ๋ฉด ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์•„๋‹ˆ๋ผ ์ €์žฅ๋œ ๋ชฉ๋ก์„ ๋ณด์—ฌ ์ฃผ๋Š” ๊ฒƒ**(๋ง๊ท€ ๊ณต์œ  ๋งํฌ ์—ด๋žŒ โ€”
41
+ ์น˜๋ฃŒ์‚ฌ๊ฐ€ ํ‘œ์—์„œ ๋Œ์–ด ์˜ฎ๊ธด ์ˆœ์„œ๊ฐ€ ์ €์žฅ๋ผ ์žˆ๋‹ค). ๊ทธ๋•Œ๋Š” ๋ฐ›์€ ์ˆœ์„œ๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ๋‘”๋‹ค โ€” ๊ฐ€๋‚˜๋‹ค๋กœ ๋‹ค์‹œ ๋Š˜์–ด์„œ
42
+ ํ‘œ์˜ ์นธ ์ˆœ์„œ๊ฐ€ ์‚ฌ๋ผ์กŒ๋‹ค. ๊ฒ€์ƒ‰ ํ™”๋ฉด๋“ค์€ ๊ฒฐ๊ณผ ์ˆ˜(resultCount)๋ฅผ ๋Š˜ ์ฃผ๋ฏ€๋กœ ์ง€๊ธˆ์ฒ˜๋Ÿผ ๊ฐ€๋‚˜๋‹ค ยท ์‹œ์ž‘ ์šฐ์„ ์ด๋‹ค. */
43
+ const ์—ด๋žŒ = searchQuery === undefined && resultCount === undefined;
44
+ const ๊ธฐ๋ณธ์ •๋ ฌ = hasQuery ? 'match' : ์—ด๋žŒ ? 'original' : 'ganada';
40
45
  // ์ฒซ ๋ Œ๋”์— ์ด๋ฏธ ๊ฒ€์ƒ‰์–ด๊ฐ€ ์žˆ์–ด๋„ '์‹œ์ž‘ ์šฐ์„ '์œผ๋กœ ์‹œ์ž‘ํ•œ๋‹ค โ€” ์•„๋ž˜ ์กฐ์ •์€ ๊ฒ€์ƒ‰์–ด ์œ ๋ฌด๊ฐ€ **๋ฐ”๋€” ๋•Œ**๋งŒ ๋Œ์•„์„œ,
41
46
  // ์ดˆ๊นƒ๊ฐ’์„ 'ganada' ๋กœ ๊ณ ์ •ํ•˜๋ฉด ๊ฒ€์ƒ‰์–ด๋ฅผ ๋“ค๊ณ  ๋งˆ์šดํŠธํ•œ ํ™”๋ฉด์€ ๊ฐ€๋‚˜๋‹ค์ˆœ์— ๋จธ๋ฌผ๋ €๋‹ค.
42
- const [sortKey, setSortKey] = useState(() => (hasQuery ? 'match' : 'ganada'));
47
+ const [sortKey, setSortKey] = useState(() => ๊ธฐ๋ณธ์ •๋ ฌ);
43
48
  const [pageSizeStr, setPageSizeStr] = useState('50');
44
49
  const [page, setPage] = useState(1);
45
50
  // ๊ฒ€์ƒ‰์–ด ์œ ๋ฌด๊ฐ€ ๋ฐ”๋€Œ๋ฉด ๊ทธ์— ๋งž๋Š” ๊ธฐ๋ณธ ์ •๋ ฌ๋กœ ๋˜๋Œ๋ฆฐ๋‹ค(๋ Œ๋” ์ค‘ ์ƒํƒœ ์กฐ์ •).
46
51
  const [prevHasQuery, setPrevHasQuery] = useState(hasQuery);
47
52
  if (prevHasQuery !== hasQuery) {
48
53
  setPrevHasQuery(hasQuery);
49
- setSortKey(hasQuery ? 'match' : 'ganada');
54
+ setSortKey(๊ธฐ๋ณธ์ •๋ ฌ);
50
55
  }
51
- const effectiveSortKey = (sortKey === 'match' && !hasQuery) ? 'ganada' : sortKey;
56
+ const effectiveSortKey = (sortKey === 'match' && !hasQuery) || (sortKey === 'original' && !์—ด๋žŒ) ? ๊ธฐ๋ณธ์ •๋ ฌ : sortKey;
52
57
  const sortOptions = useMemo(() => [
53
58
  ...(hasQuery ? [{ value: 'match', label: t('wordSearch.sort.match', { defaultValue: '์‹œ์ž‘ ์šฐ์„ ' }) }] : []),
59
+ ...(์—ด๋žŒ ? [{ value: 'original', label: t('wordSearch.sort.original', { defaultValue: '๋ฐ›์€ ์ˆœ์„œ' }) }] : []),
54
60
  { value: 'ganada', label: t('wordSearch.sort.ganada', { defaultValue: '๊ฐ€๋‚˜๋‹ค์ˆœ โ†‘' }) },
55
61
  { value: 'ganada_desc', label: t('wordSearch.sort.ganadaDesc', { defaultValue: '๊ฐ€๋‚˜๋‹ค์ˆœ โ†“' }) },
56
62
  { value: 'syllable', label: t('wordSearch.sort.syllable', { defaultValue: '์Œ์ ˆ ์ˆ˜ โ†‘' }) },
57
63
  { value: 'syllable_desc', label: t('wordSearch.sort.syllableDesc', { defaultValue: '์Œ์ ˆ ์ˆ˜ โ†“' }) },
58
64
  { value: 'grade', label: t('wordSearch.sort.grade', { defaultValue: '๋“ฑ๊ธ‰์ˆœ โ†‘' }) },
59
65
  { value: 'grade_desc', label: t('wordSearch.sort.gradeDesc', { defaultValue: '๋“ฑ๊ธ‰์ˆœ โ†“' }) },
60
- ], [t, hasQuery]);
66
+ ], [t, hasQuery, ์—ด๋žŒ]);
61
67
  const pageSizeOptions = useMemo(() => ['10', '25', '50', '100'].map(v => ({
62
68
  value: v, label: t('wordSearch.pageSize', { count: Number(v), defaultValue: `${v}๊ฐœ์”ฉ ๋ณด๊ธฐ` }),
63
69
  })), [t]);
64
- const firstWord = (it) => it.words[0];
70
+ /* ์ €์žฅ์†Œ์—์„œ ์˜จ ํ•ญ๋ชฉ์— words ๊ฐ€ ๋น ์ ธ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค({ id }) โ€” ์ด ํŒŒ์ผ์€ ๋‚ฑ๋ง์„ ๋Š˜ `?? []` ๋กœ ์ฝ๋Š”๋‹ค */
71
+ const firstWord = (it) => it.words?.[0];
65
72
  /* '์‹œ์ž‘ ์šฐ์„ ' ์ˆœ์œ„๋Š” ๊ฒ€์ƒ‰์–ด๊ฐ€ ๋ฐ”๋€” ๋•Œ ํŒ๋ณ„๊ธฐ๋ฅผ ํ•œ ๋ฒˆ ๋งŒ๋“ค๊ณ  ํ•ญ๋ชฉ๋งˆ๋‹ค ํ•œ ๋ฒˆ๋งŒ ์„ผ๋‹ค โ€” ๋น„๊ตํ•  ๋•Œ๋งˆ๋‹ค ๊ฒ€์ƒ‰์–ด๋ฅผ ๋‹ค์‹œ ํŽผ์น˜๊ณ 
66
73
  localeCompare ๋ฅผ ๋ถˆ๋Ÿฌ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐ”๋€” ๋•Œ 23k ๊ฐœ์— 38ms ๊ฑธ๋ ธ๋‹ค. */
67
74
  const ์‹œ์ž‘์ˆœ์œ„ = useMemo(() => {
@@ -71,7 +78,7 @@ export default function WordSearchResultBlock({ titleIcon, resultCount, resultUn
71
78
  return (it) => {
72
79
  let r = memo.get(it);
73
80
  if (r === undefined) {
74
- r = m((it.words[0]?.label ?? '').slice(0, qLen)) ? 0 : 1;
81
+ r = m((it.words?.[0]?.label ?? '').slice(0, qLen)) ? 0 : 1;
75
82
  memo.set(it, r);
76
83
  }
77
84
  return r;
@@ -81,7 +88,12 @@ export default function WordSearchResultBlock({ titleIcon, resultCount, resultUn
81
88
  const sortCompare = useCallback((a, b) => {
82
89
  const label = (it) => firstWord(it)?.label ?? '';
83
90
  const syll = (it) => firstWord(it)?.syllables ?? [...label(it)].length;
84
- const grade = (it) => { const n = parseInt(firstWord(it)?.grade ?? '', 10); return Number.isFinite(n) ? n : 999; };
91
+ /* ๋“ฑ๊ธ‰์€ **์นธ์˜ ๋‚ฑ๋ง ์ค‘ ๋‚ฎ์€(์ˆซ์ž๊ฐ€ ์ž‘์€) ๋“ฑ๊ธ‰** โ€” ๋Œ€๋ฆฝ์Œ์˜ ์ฒซ ๋‚ฑ๋ง๋งŒ ๋ณด๋ฉด ๋‘˜์งธ๊ฐ€ 1๋“ฑ๊ธ‰์ธ ์Œ์ด 3ยท3 ์Œ ๋’ค๋กœ ๊ฐ”๋‹ค.
92
+ ์—”์ง„ ๊ธฐ๋ณธ ์ •๋ ฌ(searchPairs ๊ฒฐ๊ณผ)๊ณผ ๊ฐ™์€ ์…ˆ์ด๋ผ, ๋“ฑ๊ธ‰์ˆœ์„ ๊ณ ๋ฅด๋Š” ์ˆœ๊ฐ„ ์ฐจ๋ก€๊ฐ€ ๋’ค๋ฐ”๋€Œ์ง€ ์•Š๋Š”๋‹ค. ๋“ฑ๊ธ‰ ์—†๋Š” ๋‚ฑ๋ง์€ ๋งจ ๋’ค. */
93
+ const grade = (it) => (it.words ?? []).reduce((min, w) => {
94
+ const n = parseInt(w.grade ?? '', 10);
95
+ return Number.isFinite(n) && n < min ? n : min;
96
+ }, 999);
85
97
  switch (effectiveSortKey) {
86
98
  // ์‹œ์ž‘ ์šฐ์„  โ€” ๊ฒ€์ƒ‰์–ด๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋‹จ์–ด๋ฅผ ์œ„๋กœ. ์•ž q ๊ธ€์ž๋งŒ ์ž˜๋ผ ๋งค์นญํ•˜๋ฉด "์ฒซ ์Œ์ ˆ๋ถ€ํ„ฐ ์ผ์น˜"๋งŒ
87
99
  // ๋‚จ๋Š”๋‹ค(์ดˆ์„ฑ ๊ฒ€์ƒ‰๋„ ๋™์ผ: 'ใ„ด' โ†’ '๋‚˜๋ฌด'๊ฐ€ ์œ„). ๋™์ˆœ์œ„๋Š” ๊ฐ€๋‚˜๋‹ค.
@@ -94,6 +106,7 @@ export default function WordSearchResultBlock({ titleIcon, resultCount, resultUn
94
106
  case 'syllable_desc': return syll(b) - syll(a) || label(a).localeCompare(label(b), 'ko');
95
107
  case 'grade': return grade(a) - grade(b) || label(a).localeCompare(label(b), 'ko');
96
108
  case 'grade_desc': return grade(b) - grade(a) || label(a).localeCompare(label(b), 'ko');
109
+ // ๋ฐ›์€ ์ˆœ์„œ โ€” Array.prototype.sort ๋Š” ์•ˆ์ • ์ •๋ ฌ์ด๋ผ 0 ์ด๋ฉด ๋ฐ›์€ ์ฐจ๋ก€๊ฐ€ ๋‚จ๋Š”๋‹ค
97
110
  default: return 0;
98
111
  }
99
112
  }, [effectiveSortKey, ์‹œ์ž‘์ˆœ์œ„]);
@@ -149,17 +162,17 @@ export default function WordSearchResultBlock({ titleIcon, resultCount, resultUn
149
162
  const isSelected = selected.includes(item.id);
150
163
  return (_jsx("div", { onMouseEnter: e => {
151
164
  openHover(e.currentTarget);
152
- setHoverWords(item.words);
153
- }, onMouseLeave: handleHoverLeave, children: _jsx(Button, { htmlType: "button", size: "word", align: "grid", label: item.words.map(w => w.label), subLabel: item.words.map(w => w.subLabel).filter(Boolean).length > 0
154
- ? item.words.map(w => w.subLabel)
165
+ setHoverWords(item.words ?? []);
166
+ }, onMouseLeave: handleHoverLeave, children: _jsx(Button, { htmlType: "button", size: "word", align: "grid", label: (item.words ?? []).map(w => w.label), subLabel: (item.words ?? []).some(w => w.subLabel)
167
+ ? (item.words ?? []).map(w => w.subLabel)
155
168
  : undefined, selected: isSelected, onClick: () => handleClick(item.id), minWidth: 200 }) }, item.id));
156
169
  }) }) }, group.label)))) : (_jsx("div", { className: "word-search-result-block__items", children: pagedItems.map((item) => {
157
170
  const isSelected = selected.includes(item.id);
158
171
  return (_jsx("div", { onMouseEnter: e => {
159
172
  openHover(e.currentTarget);
160
- setHoverWords(item.words);
161
- }, onMouseLeave: handleHoverLeave, children: _jsx(Button, { htmlType: "button", size: "word", align: "grid", label: item.words.map(w => w.label), subLabel: item.words.map(w => w.subLabel).filter(Boolean).length > 0
162
- ? item.words.map(w => w.subLabel)
173
+ setHoverWords(item.words ?? []);
174
+ }, onMouseLeave: handleHoverLeave, children: _jsx(Button, { htmlType: "button", size: "word", align: "grid", label: (item.words ?? []).map(w => w.label), subLabel: (item.words ?? []).some(w => w.subLabel)
175
+ ? (item.words ?? []).map(w => w.subLabel)
163
176
  : undefined, selected: isSelected, onClick: () => handleClick(item.id), minWidth: 200 }) }, item.id));
164
177
  }) })), hoverWords && hoverAnchorRect && (_jsx(WordImageTooltip, { words: hoverWords, anchorRect: hoverAnchorRect }))] }), totalPages > 1 && (_jsx("div", { className: "word-search-result-block__pagination-bottom", children: _jsx(Pagination, { page: pageClamped, totalPages: totalPages, onChange: setPage }) })), (onClearSelection || onPrint) && (_jsxs("div", { className: "word-search-result-block__actions", children: [onClearSelection && (_jsx(Button, { htmlType: "button", size: "56", type: "bar", label: t('common.resetSelection', '์„ ํƒ ์ดˆ๊ธฐํ™”'), selected: false, disabled: selected.length === 0, onClick: selected.length === 0 ? undefined : onClearSelection })), onPrint && (_jsx("div", { onClick: selected.length === 0 ? onPrintEmpty : undefined, children: _jsx(Button, { htmlType: "button", size: "56", type: "bar", variant: "main", label: t('wordTable.title', '๋‹จ์–ด ํ‘œ ๋งŒ๋“ค๊ธฐ'), disabled: selected.length === 0, onClick: selected.length === 0 ? undefined : onPrint }) }))] }))] }));
165
178
  }
@@ -12,7 +12,7 @@ import Popup from '../Popup/Popup';
12
12
  import WordTable, { WORD_TABLE_PAPER } from '../WordTable';
13
13
  import { chunkItems } from '../wordTableLayout';
14
14
  import TableFilterPanel from './TableFilterPanel';
15
- import { buildItemsPerPageSection, buildPageNumberSection, PAGE_NUM_POSITION_VALUE, parsePageNumberPosition } from './wordTableSettings';
15
+ import { buildItemsPerPageSection, buildPageNumberSection, PAGE_NUM_POSITION_VALUE, parsePageNumberPosition, itemsPerPageFromChoice } from './wordTableSettings';
16
16
  import Toast from '../Toast';
17
17
  import { useToastState } from '../../hooks/useToastState';
18
18
  import { getImageResolver, checkImageExists } from '../../utils/imageConfig';
@@ -20,7 +20,7 @@ import { formatYmd, getTodayKst } from '../../utils/date';
20
20
  import { getSavedTables } from '../../utils/savedWordTables';
21
21
  import { devWarnOnce } from '../../utils/devWarn';
22
22
  import { useWordTablePrint } from './useWordTablePrint';
23
- import { CELL_COUNTS, ITEMS_PER_PAGE_OPTIONS } from './wordTableBlockTypes';
23
+ import { CELL_COUNTS } from './wordTableBlockTypes';
24
24
  // โ”€โ”€ ์ปดํฌ๋„ŒํŠธ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
25
25
  /** @public */
26
26
  /** ํŽธ์ง‘ ์นธ์˜ ๋‚ฑ๋ง โ†’ ์ €์žฅ ํ•ญ๋ชฉ์˜ ๋‚ฑ๋ง. ํŽธ์ง‘ ์นธ์—์„œ ๋ณด์ด๋˜ ๊ตฌ์–ด์ฒด ๋ฐœ์Œยท๋“ฑ๊ธ‰ยทํ’ˆ์‚ฌยท์œ ํ˜•์„ ๊ทธ๋Œ€๋กœ ์˜ฎ๊ธด๋‹ค. */
@@ -102,19 +102,15 @@ export default function WordTableBlock({ title, titleIcon, disc, items, onSaveCo
102
102
  if (onAddItem && !isEditable && !showCellCount) {
103
103
  devWarnOnce('word-table-block-add-without-cell-count', '[WordTableBlock] ํ‘œ์‹œ ๋ชจ๋“œ์—์„œ onAddItem ์€ showCellCount ์™€ ํ•จ๊ป˜ ์ค˜์•ผ "์ถ”๊ฐ€ํ•˜๊ธฐ" ์นธ์ด ์ƒ๊ธด๋‹ค.');
104
104
  }
105
- // display ๋ชจ๋“œ์—์„œ ์นธ ์ˆ˜ ์กฐ์ ˆ์šฉ ์ƒํƒœ (showCellCount์ผ ๋•Œ๋งŒ ์‚ฌ์šฉ)
106
- const [displayCellCount, setDisplayCellCount] = useState(() => Math.max(items?.length ?? 1, 1));
107
- /* eslint-disable react-hooks/set-state-in-effect */
108
- useEffect(() => {
109
- if (!isEditable)
110
- setDisplayCellCount(prev => Math.max(prev, items?.length ?? 1));
111
- }, [items?.length, isEditable]);
112
- /* eslint-enable react-hooks/set-state-in-effect */
105
+ /* display ๋ชจ๋“œ์—์„œ ์นธ ์ˆ˜ ์กฐ์ ˆ์šฉ ์ƒํƒœ (showCellCount์ผ ๋•Œ๋งŒ ์‚ฌ์šฉ) โ€” **๊ณ ๋ฅธ ๊ฐ’๋งŒ** ๋“ ๋‹ค. ๋ณด์ด๋Š” ์นธ ์ˆ˜๋Š”
106
+ max(๊ณ ๋ฅธ ๊ฐ’, ํ•ญ๋ชฉ ์ˆ˜) ๋กœ ๊ทธ๋•Œ๊ทธ๋•Œ ๋‚ธ๋‹ค. ์ „์—๋Š” ๊ณ ๋ฅผ ๋•Œ์™€ ํ•ญ๋ชฉ์ด ๋Š˜ ๋•Œ max ๋กœ ๊ตณํ˜€, 6์นธ์„ ๊ณจ๋ผ ๋‘” ํ‘œ์— 8๊ฐœ๋ฅผ
107
+ ๋‹ด์•˜๋‹ค 3๊ฐœ๋กœ ์ค„์—ฌ๋„ 8์นธ์ด ๋‚จ์•˜๋‹ค. ์ฒ˜์Œ ๊ฐ’์€ ์ฒ˜์Œ ํ•ญ๋ชฉ ์ˆ˜ โ€” ๊ณ ๋ฅด๊ธฐ ์ „์— ํ•˜๋‚˜๋ฅผ ๋นผ๋„ ํ‘œ๊ฐ€ ํ•œ ์นธ ์ค„์–ด ํ”๋“ค๋ฆฌ์ง€ ์•Š๊ฒŒ. */
108
+ const [chosenCellCount, setChosenCellCount] = useState(() => Math.max(items?.length ?? 1, 1));
113
109
  // ๋ ˆ์ด์•„์›ƒ ์นธ ์ˆ˜
114
110
  const count = isEditable
115
111
  ? cellCount
116
112
  : showCellCount
117
- ? Math.max(displayCellCount, items?.length ?? 1)
113
+ ? Math.max(chosenCellCount, items?.length ?? 1, 1)
118
114
  : Math.max(items?.length ?? 0, 1);
119
115
  const { width, height } = WORD_TABLE_PAPER[orientation];
120
116
  // ํŽ˜์ด์ง€ ๋ถ„ํ•  โ€” ํŽธ์ง‘/์นธ์ˆ˜์กฐ์ ˆ(showCellCount) ๋ชจ๋“œ์—์„œ๋Š” ์ ์šฉํ•˜์ง€ ์•Š๊ณ , ์™„์„ฑ๋œ ํ‘œ์‹œ ๋ชจ๋“œ์—์„œ๋งŒ ์ ์šฉ
@@ -128,7 +124,7 @@ export default function WordTableBlock({ title, titleIcon, disc, items, onSaveCo
128
124
  const isPaginated = !!pages && pages.length > 1;
129
125
  const hasPairs = isEditable
130
126
  ? visibleCells.some(c => c !== null && c.length > 1)
131
- : (items ?? []).some(item => item.words.length > 1);
127
+ : (items ?? []).some(item => (item.words ?? []).length > 1);
132
128
  // PDF๋กœ ์ €์žฅํ•  ๋•Œ OS๊ฐ€ ์ œ์•ˆํ•˜๋Š” ๊ธฐ๋ณธ ํŒŒ์ผ๋ช…. ์ €์žฅ๋œ ํ‘œ ์ด๋ฆ„์ด ์žˆ์œผ๋ฉด ๊ทธ๋Œ€๋กœ, ์—†์œผ๋ฉด "์˜ค๋Š˜ ๋‚ ์งœ + ๋‹จ์–ดํ‘œ/๋Œ€๋ฆฝ์Œํ‘œ"
133
129
  const printFileName = tableName?.trim()
134
130
  || `${formatYmd(getTodayKst())} ${hasPairs ? t('wordTable.printFileNamePair', '๋Œ€๋ฆฝ์Œํ‘œ') : t('wordTable.printFileNameSingle', '๋‹จ์–ดํ‘œ')}`;
@@ -164,7 +160,7 @@ export default function WordTableBlock({ title, titleIcon, disc, items, onSaveCo
164
160
  };
165
161
  const hasColloquial = isEditable
166
162
  ? visibleCells.some(c => c?.some(e => !!e.colloquialPronunciation))
167
- : (items ?? []).some(item => item.words.some(w => !!w.colloquialLabel));
163
+ : (items ?? []).some(item => (item.words ?? []).some(w => !!w.colloquialLabel));
168
164
  // ํ‘œ์— ์‹ค์ œ๋กœ ์ด๋ฏธ์ง€๊ฐ€ ์žˆ๋Š”์ง€ โ€” resolver(word)๋Š” ์กด์žฌ ์—ฌ๋ถ€์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ํ•ญ์ƒ URL์„ ๋งŒ๋“ค์–ด ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ
169
165
  // ๊ทธ truthiness๋งŒ์œผ๋กœ๋Š” ํŒ์ •ํ•  ์ˆ˜ ์—†๋‹ค(ํ•ญ์ƒ true๊ฐ€ ๋˜์–ด ์ด๋ฏธ์ง€ ์—†๋Š” ํ‘œ์—์„œ๋„ '์ด๋ฏธ์ง€' ํ† ๊ธ€์ด
170
166
  // ํ™œ์„ฑํ™”๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค). checkImageExists๋กœ ์‹ค์ œ HEAD ์š”์ฒญ(+์บ์‹œ) ํ™•์ธ์„ ๊ฑฐ์นœ๋‹ค.
@@ -176,7 +172,7 @@ export default function WordTableBlock({ title, titleIcon, disc, items, onSaveCo
176
172
  return [];
177
173
  return isEditable
178
174
  ? cells.slice(0, cellCount).flatMap(c => (c ?? []).map(e => [e.word, e.homonum]))
179
- : (items ?? []).flatMap(item => item.words.map(w => [w.label, w.homonum]));
175
+ : (items ?? []).flatMap(item => (item.words ?? []).map(w => [w.label, w.homonum]));
180
176
  }, [isEditable, cells, cellCount, items]);
181
177
  /* ์‹ค์ œ๋กœ ํŒŒ์ผ์ด ์žˆ๋Š”์ง€๋Š” ๋ฌผ์–ด๋ด์•ผ ์•ˆ๋‹ค(HEAD ์š”์ฒญ + ์บ์‹œ). ๊ทธ ๋‹ต๋งŒ ์ƒํƒœ๋กœ ๋“ ๋‹ค.
182
178
  `imagePairs` ๊ฐ€ ๋น„๋ฉด ๋ฌผ์–ด๋ณผ ๊ฒƒ๋„ ์—†์œผ๋ฏ€๋กœ ์•„๋ž˜ ์œ ๋„์—์„œ ๊ณง๋ฐ”๋กœ false ๊ฐ€ ๋œ๋‹ค. */
@@ -209,15 +205,13 @@ export default function WordTableBlock({ title, titleIcon, disc, items, onSaveCo
209
205
  if (sectionLabel === t('wordTable.filter.columns', '์นธ ์ˆ˜')) {
210
206
  if (isEditable)
211
207
  handleCellCountChange(parseInt(value));
212
- // ํ•ญ๋ชฉ ์ˆ˜๋ณด๋‹ค ์ ๊ฒŒ๋Š” ๋ชป ์ค„์ธ๋‹ค(๊ทธ๋ฆฌ๋“œ๋Š” ํ•ญ๋ชฉ ์ˆ˜๋งŒํผ ๊ทธ๋ฆฐ๋‹ค) โ€” ๊ณ ๋ฅธ ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ๋‹ด์œผ๋ฉด ์ผœ์ง„ ์นธ ์ˆ˜์™€ ๋ณด์ด๋Š” ํ‘œ๊ฐ€ ๋‹ฌ๋ž๋‹ค
208
+ /* ๊ณ ๋ฅธ ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ๊ธฐ์–ตํ•œ๋‹ค โ€” ํ•ญ๋ชฉ ์ˆ˜๋ณด๋‹ค ์ ๊ฒŒ ๊ณจ๋ผ๋„ ๋ณด์ด๋Š” ์นธ ์ˆ˜(count)๋Š” max ๋กœ ํ•ญ๋ชฉ ์ˆ˜๋ฅผ ์ง€ํ‚ค๊ณ ,
209
+ ํ•ญ๋ชฉ์ด ์ค„๋ฉด ๊ณ ๋ฅธ ๊ฐ’์œผ๋กœ ๋Œ์•„๊ฐ„๋‹ค */
213
210
  else
214
- setDisplayCellCount(Math.max(parseInt(value), items?.length ?? 1));
211
+ setChosenCellCount(parseInt(value));
215
212
  }
216
213
  else if (sectionLabel === t(hasPairs ? 'wordTable.filter.itemsPerPagePair' : 'wordTable.filter.itemsPerPageWord')) {
217
- /* ๋ชฉ๋ก์— ์—†๋Š” ๊ฐœ์ˆ˜(= ๊ทธ๋•Œ์˜ '์ „์ฒด' ์นธ)๋ฅผ ๊ณ ๋ฅด๋ฉด **์ „์ฒด** ๋กœ ๊ธฐ์–ตํ•œ๋‹ค โ€” ์ˆซ์ž๋กœ ๋‹ด์œผ๋ฉด ํ•ญ๋ชฉ์ด ๋Š˜์—ˆ์„ ๋•Œ ๊ณ ๋ฅธ ๊ฐ’('10๊ฐœ')์ด
218
- ์„ ํƒ์ง€([20, 8, 4, 2, 1])์— ์—†์–ด ์ผœ์ง„ ์นธ์ด ์—†๋Š”๋ฐ ํ‘œ๋Š” 10๊ฐœ์”ฉ ๋‚˜๋‰˜์—ˆ๋‹ค. */
219
- const n = parseInt(value);
220
- setItemsPerPageSetting(ITEMS_PER_PAGE_OPTIONS.includes(n) ? n : Number.POSITIVE_INFINITY);
214
+ setItemsPerPageSetting(itemsPerPageFromChoice(parseInt(value), itemCount));
221
215
  }
222
216
  else if (sectionLabel === t('wordTable.filter.pageNumber', 'ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ')) {
223
217
  setPageNumberPosition(parsePageNumberPosition(value));
@@ -230,6 +224,12 @@ export default function WordTableBlock({ title, titleIcon, disc, items, onSaveCo
230
224
  words: c.map(ํ•ญ๋ชฉ๋‚ฑ๋ง),
231
225
  })))
232
226
  : items ?? [];
227
+ /* ์ €์žฅํ•œ ๋’ค์—๋Š” **์ˆจ์€ ์นธ(cellCount ๋’ค)์„ ๋น„์šด๋‹ค** โ€” 2์นธ์œผ๋กœ ์ค„์—ฌ ์ €์žฅํ•œ ํ‘œ๋ฅผ 4์นธ์œผ๋กœ ๋Š˜๋ฆฌ๋ฉด ์ €์žฅ๋ณธ์— ์—†๋˜
228
+ ์…‹์งธยท๋„ท์งธ ๋‚ฑ๋ง์ด ๋‹ค์‹œ ๋– , ์ €์žฅํ•œ ๊ฒƒ๊ณผ ํ™”๋ฉด์ด ๋‹ฌ๋ž๋‹ค. ์ €์žฅํ•˜์ง€ ์•Š๊ณ  ์ค„์˜€๋‹ค ๋Š˜๋ฆฌ๋Š” ๋™์•ˆ์—๋Š” ๊ทธ๋Œ€๋กœ ๋“ค๊ณ  ์žˆ๋Š”๋‹ค. */
229
+ const ์ˆจ์€์นธ๋น„์šฐ๊ธฐ = () => {
230
+ if (isEditable)
231
+ setCells(prev => (prev.length > cellCount ? prev.slice(0, cellCount) : prev));
232
+ };
233
233
  const handleSave = (asNew = false) => {
234
234
  setSaveAsNew(asNew);
235
235
  // ์ธ๋ผ์ธ ์ด๋ฆ„ยท์„ค๋ช… ๋ชจ๋“œ: ํŒ์—… ์—†์ด ํ™”๋ฉด์˜ ์ด๋ฆ„ยท์„ค๋ช…์œผ๋กœ ์ฆ‰์‹œ ์ €์žฅ(๋ฎ์–ด์“ฐ๊ธฐ). ๋นˆ ๊ฐ’์€ ์ œ์•ˆ(placeholder)์œผ๋กœ ๋Œ€์ฒด.
@@ -240,11 +240,13 @@ export default function WordTableBlock({ title, titleIcon, disc, items, onSaveCo
240
240
  if (savedLower)
241
241
  setSavedNames(prev => prev.includes(savedLower) ? prev : [...prev, savedLower]);
242
242
  onSaveComplete(trimmedName, saveItems, trimmedDescription || undefined, asNew);
243
+ ์ˆจ์€์นธ๋น„์šฐ๊ธฐ();
243
244
  return;
244
245
  }
245
246
  // saveOnly: ์ด๋ฆ„ ์ž…๋ ฅ ํŒ์—… ์—†์ด ๋ฐ”๋กœ ์ €์žฅ(์ด๋ฆ„์€ ์ƒ์œ„์—์„œ ์ฒ˜๋ฆฌ)
246
247
  if (saveOnly && onSaveComplete) {
247
248
  onSaveComplete('', saveItems, undefined, asNew);
249
+ ์ˆจ์€์นธ๋น„์šฐ๊ธฐ();
248
250
  return;
249
251
  }
250
252
  if (onSaveComplete) {
@@ -267,6 +269,7 @@ export default function WordTableBlock({ title, titleIcon, disc, items, onSaveCo
267
269
  if (!saveAsTrimmed || saveAsCollides || !onSaveComplete)
268
270
  return;
269
271
  onSaveComplete(saveAsTrimmed, saveItems, trimmedDescription || undefined, true);
272
+ ์ˆจ์€์นธ๋น„์šฐ๊ธฐ();
270
273
  setShowSaveAsPopup(false);
271
274
  };
272
275
  // ์ €์žฅ ๋ฒ„ํŠผ ํ™œ์„ฑ ์กฐ๊ฑด โ€” ์ธ๋ผ์ธ ๋ชจ๋“œ์—์„  ์ด๋ฆ„ ํ•„์ˆ˜ยท์ค‘๋ณต(others) ๋ถˆ๊ฐ€. ํ•ญ๋ชฉ์ด ๋น„์–ด๋„ ๋ถˆ๊ฐ€.
@@ -294,7 +297,7 @@ export default function WordTableBlock({ title, titleIcon, disc, items, onSaveCo
294
297
  items: saveItems,
295
298
  existingNames: getSavedTables().map(tb => tb.name),
296
299
  saveAsNew,
297
- onSave: (name, description) => { onSaveComplete(name, saveItems, description, saveAsNew); setShowSavePopup(false); },
300
+ onSave: (name, description) => { onSaveComplete(name, saveItems, description, saveAsNew); ์ˆจ์€์นธ๋น„์šฐ๊ธฐ(); setShowSavePopup(false); },
298
301
  onCancel: () => setShowSavePopup(false),
299
302
  }), (isEditable || onAddItem) && activeCell !== null && renderWordSearch?.({
300
303
  cellIndex: activeCell,
@@ -16,6 +16,6 @@ export default function WordTableThumbnail({ items, className }) {
16
16
  const item = items[i];
17
17
  if (!item)
18
18
  return _jsx("div", { className: "word-table-thumb__cell word-table-thumb__cell--empty" }, i);
19
- return (_jsx("div", { className: "word-table-thumb__cell", children: (item.words.length ? item.words : [{ label: item.id }]).map((w, wi) => (_jsxs(Fragment, { children: [wi > 0 && _jsx("span", { className: "word-table-thumb__divider" }), _jsx("span", { className: "word-table-thumb__word body-3", children: w.label })] }, wi))) }, i));
19
+ return (_jsx("div", { className: "word-table-thumb__cell", children: (item.words?.length ? item.words : [{ label: item.id }]).map((w, wi) => (_jsxs(Fragment, { children: [wi > 0 && _jsx("span", { className: "word-table-thumb__divider" }), _jsx("span", { className: "word-table-thumb__word body-3", children: w.label })] }, wi))) }, i));
20
20
  }) }));
21
21
  }
@@ -2,9 +2,8 @@
2
2
  import { useMemo, useState, useCallback, useEffect } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { chunkItems } from '../wordTableLayout';
5
- import { ITEMS_PER_PAGE_OPTIONS } from './wordTableBlockTypes';
6
5
  import { getImageResolver, checkImageExists } from '../../utils/imageConfig';
7
- import { buildCompositionSection, buildOrientationSection, buildBorderSection, buildCutLineSection, buildDivLineSection, buildItemsPerPageSection, buildPageNumberSection, buildTableSettingsSelected, applyTableSettingsSelect, PAGE_NUM_POSITION_VALUE, parsePageNumberPosition, } from './wordTableSettings';
6
+ import { buildCompositionSection, buildOrientationSection, buildBorderSection, buildCutLineSection, buildDivLineSection, buildItemsPerPageSection, buildPageNumberSection, buildTableSettingsSelected, applyTableSettingsSelect, PAGE_NUM_POSITION_VALUE, parsePageNumberPosition, itemsPerPageFromChoice, } from './wordTableSettings';
8
7
  /**
9
8
  * ํ‘œ์‹œ ์ „์šฉ ๋‹จ์–ดํ‘œ(ํŽธ์ง‘ ์—†์Œ)์˜ 'ํ‘œ ์„ค์ •' FilterPanel ์ƒํƒœยท์„น์…˜์„ ์ œ๊ณตํ•˜๋Š” ํ›….
10
9
  * ๊ตฌ์„ฑยท์šฉ์ง€ ๋ฐฉํ–ฅยท์™ธ๊ณฝ์„ , ๋‚ด์šฉ์ด ์žˆ์œผ๋ฉด ํŽ˜์ด์ง€๋‹น ๊ฐœ์ˆ˜, 2ํŽ˜์ด์ง€ ์ด์ƒ์ด๋ฉด ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ, ํ•ญ๋ชฉ์ด 2๊ฐœ ์ด์ƒ์ด๋ฉด
@@ -22,8 +21,9 @@ export function useWordTableDisplaySettings(items) {
22
21
  const [itemsPerPageSetting, setItemsPerPageSetting] = useState(8);
23
22
  const [pageNumberPosition, setPageNumberPosition] = useState('right');
24
23
  const itemCount = items.length;
25
- const hasPairs = items.some(item => item.words.length > 1);
26
- const hasColloquial = items.some(item => item.words.some(w => !!w.colloquialLabel));
24
+ // ์ €์žฅ์†Œ์—์„œ ์˜จ ํ•ญ๋ชฉ์— words ๊ฐ€ ๋น ์ ธ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค({ id }) โ€” ๋นˆ ๋‚ฑ๋ง๋กœ ๋ณธ๋‹ค
25
+ const hasPairs = items.some(item => (item.words ?? []).length > 1);
26
+ const hasColloquial = items.some(item => (item.words ?? []).some(w => !!w.colloquialLabel));
27
27
  // ํŽ˜์ด์ง€ ๋ถ„ํ•  โ€” ํŽ˜์ด์ง€๋‹น ๊ฐœ์ˆ˜(๊ธฐ๋ณธ 8, ์‹ค์ œ ๊ฐœ์ˆ˜๋ณด๋‹ค ํฌ๋ฉด ์ „์ฒด๋กœ ํด๋žจํ”„)๋กœ ๋‚˜๋ˆˆ๋‹ค. ํ•ญ๋ชฉ์ด ์—†์œผ๋ฉด 1์นธ์งœ๋ฆฌ ๋นˆ ํ‘œ.
28
28
  const effectiveItemsPerPage = Math.min(itemsPerPageSetting, Math.max(itemCount, 1));
29
29
  const pages = useMemo(() => chunkItems(items, effectiveItemsPerPage), [items, effectiveItemsPerPage]);
@@ -41,7 +41,7 @@ export function useWordTableDisplaySettings(items) {
41
41
  setHasImage(false);
42
42
  return;
43
43
  }
44
- Promise.all(items.flatMap(item => item.words.map(w => checkImageExists(w.label, w.homonum))))
44
+ Promise.all(items.flatMap(item => (item.words ?? []).map(w => checkImageExists(w.label, w.homonum))))
45
45
  .then(results => { if (!cancelled)
46
46
  setHasImage(results.some(Boolean)); }, () => { }); // ๊ฑฐ์ ˆ์€ ์ด๋ฏธ์ง€ ์—†์Œ์œผ๋กœ ๋‘”๋‹ค
47
47
  return () => { cancelled = true; };
@@ -69,15 +69,12 @@ export function useWordTableDisplaySettings(items) {
69
69
  if (applyTableSettingsSelect(t, value, sectionLabel, { setContentTypes, setOrientation, setShowOutline, setCutLine, setShowDivLine }))
70
70
  return;
71
71
  if (sectionLabel === t('wordTable.filter.itemsPerPagePair', 'ํŽ˜์ด์ง€๋‹น ๋Œ€๋ฆฝ์Œ ์ˆ˜') || sectionLabel === t('wordTable.filter.itemsPerPageWord', 'ํŽ˜์ด์ง€๋‹น ๋‹จ์–ด ์ˆ˜')) {
72
- /* ๋ชฉ๋ก์— ์—†๋Š” ๊ฐœ์ˆ˜(= ๊ทธ๋•Œ์˜ '์ „์ฒด' ์นธ)๋ฅผ ๊ณ ๋ฅด๋ฉด **์ „์ฒด** ๋กœ ๊ธฐ์–ตํ•œ๋‹ค โ€” ์ˆซ์ž๋กœ ๋‹ด์œผ๋ฉด ํ•ญ๋ชฉ์ด ๋Š˜์—ˆ์„ ๋•Œ ๊ณ ๋ฅธ ๊ฐ’('10๊ฐœ')์ด
73
- ์„ ํƒ์ง€([20, 8, 4, 2, 1])์— ์—†์–ด ์ผœ์ง„ ์นธ์ด ์—†๋Š”๋ฐ ํ‘œ๋Š” 10๊ฐœ์”ฉ ๋‚˜๋‰˜์—ˆ๋‹ค. */
74
- const n = parseInt(value);
75
- setItemsPerPageSetting(ITEMS_PER_PAGE_OPTIONS.includes(n) ? n : Number.POSITIVE_INFINITY);
72
+ setItemsPerPageSetting(itemsPerPageFromChoice(parseInt(value), itemCount));
76
73
  }
77
74
  else if (sectionLabel === t('wordTable.filter.pageNumber', 'ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ')) {
78
75
  setPageNumberPosition(parsePageNumberPosition(value));
79
76
  }
80
- }, [t]);
77
+ }, [t, itemCount]);
81
78
  return {
82
79
  settings: { orientation, contentTypes, cutLine, showOutline, showDivLine, count },
83
80
  filterSections, filterSelected, handleSelect,
@@ -20,7 +20,7 @@ export interface UseWordTablePrintResult {
20
20
  print: () => void;
21
21
  /** ์ƒˆ ํƒญ์— ๋‹จ์–ดํ‘œ๋ฅผ ์—ด์–ด ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์ธ์‡„/์ €์žฅํ•˜๊ฒŒ ํ•œ๋‹ค. */
22
22
  openNewWindow: () => void;
23
- /** ๋‹จ์–ดํ‘œ๋ฅผ ์ •์  HTML ํŒŒ์ผ๋กœ ๋‹ค์šด๋กœ๋“œํ•œ๋‹ค. */
23
+ /** ๋‹จ์–ดํ‘œ๋ฅผ ์ •์  HTML ํŒŒ์ผ๋กœ ๋‹ค์šด๋กœ๋“œํ•œ๋‹ค. ํŒŒ์ผ๋ช…์ด ๋น„๋ฉด(๊ฑธ๋Ÿฌ ๋‚ด๊ณ  ๋‚จ๋Š” ๊ธ€์ž๊ฐ€ ์—†์œผ๋ฉด) ๊ธฐ๋ณธ ์ด๋ฆ„('๋‹จ์–ดํ‘œ'). */
24
24
  downloadHtml: () => void;
25
25
  }
26
26
  /**
@@ -1,6 +1,9 @@
1
1
  'use client';
2
2
  import { useCallback } from 'react';
3
+ import { useTranslation } from 'react-i18next';
3
4
  import { captureHtml, printWordTableElements, sanitizeFileName } from './wordTablePrint';
5
+ /** ๋‚ด๋ ค๋ฐ›๊ธฐ๋ฅผ ๋ˆ„๋ฅธ ๋’ค Blob ์ฃผ์†Œ๋ฅผ ํšŒ์ˆ˜ํ•˜๊ธฐ๊นŒ์ง€(utils/csv ์™€ ๊ฐ™์€ ๊ฐ’) */
6
+ const BLOB_REVOKE_DELAY_MS = 1000;
4
7
  /**
5
8
  * ๋‹จ์–ดํ‘œ ์ธ์‡„/์ƒˆ์ฐฝ/๋‹ค์šด๋กœ๋“œ ์•ก์…˜์„ ํ•œ ๊ณณ์—์„œ ์ œ๊ณตํ•˜๋Š” ํ›…. WordTableBlockยทWordTablePopup ๋“ฑ
6
9
  * ๋‹จ์–ดํ‘œ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ณต์šฉ์œผ๋กœ ์“ด๋‹ค. ์‹ค์ œ ๋ฌธ์„œ ์ƒ์„ฑ์€ wordTablePrint์˜ ์ˆœ์ˆ˜ ํ•จ์ˆ˜๊ฐ€,
@@ -8,6 +11,7 @@ import { captureHtml, printWordTableElements, sanitizeFileName } from './wordTab
8
11
  * @public
9
12
  */
10
13
  export function useWordTablePrint({ getElements, paperW, paperH, fileName = '', onPopupBlocked, header = '', }) {
14
+ const { t } = useTranslation();
11
15
  const print = useCallback(() => {
12
16
  printWordTableElements(getElements(), paperW, paperH, fileName, header);
13
17
  }, [getElements, paperW, paperH, fileName, header]);
@@ -32,12 +36,14 @@ export function useWordTablePrint({ getElements, paperW, paperH, fileName = '',
32
36
  const url = URL.createObjectURL(blob);
33
37
  // fileName์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ง€์ •ํ•œ ๋‹จ์–ดํ‘œ ์ด๋ฆ„ ๋“ฑ์—์„œ ์˜ฌ ์ˆ˜ ์žˆ์–ด, ํŒŒ์ผ์‹œ์Šคํ…œ์—์„œ ๋ฌธ์ œ๋˜๋Š”
34
38
  // ๋ฌธ์ž(\/:*?"<>|)๋ฅผ ์น˜ํ™˜ํ•˜์ง€ ์•Š์œผ๋ฉด ๋‹ค์šด๋กœ๋“œ ํŒŒ์ผ๋ช…์ด ๊นจ์ง€๊ฑฐ๋‚˜ ์ €์žฅ์ด ์‹คํŒจํ•  ์ˆ˜ ์žˆ๋‹ค.
35
- const safeFileName = fileName ? sanitizeFileName(fileName) : fileName;
39
+ /* ์ด๋ฆ„์ด ๋น„๋ฉด ๊ธฐ๋ณธ ์ด๋ฆ„ โ€” ๊ทธ๋Œ€๋กœ ๋‘๋ฉด '.html' ์ด๋ผ๋Š” ์ด๋ฆ„ ์—†๋Š”(์ˆจ๊น€) ํŒŒ์ผ์ด ๋‚ด๋ ค์™”๋‹ค. */
40
+ const safeFileName = (fileName ? sanitizeFileName(fileName) : '') || t('wordTable.printFileNameSingle', '๋‹จ์–ดํ‘œ');
36
41
  const a = Object.assign(document.createElement('a'), { href: url, download: `${safeFileName}.html` });
37
42
  document.body.appendChild(a);
38
43
  a.click();
39
44
  document.body.removeChild(a);
40
- URL.revokeObjectURL(url);
41
- }, [getElements, paperW, paperH, fileName, header]);
45
+ /* ๊ณง๋ฐ”๋กœ ํšŒ์ˆ˜ํ•˜๋ฉด Safari ๊ฐ€ ๋‚ด๋ ค๋ฐ›๊ธฐ๋ฅผ ์‹œ์ž‘ํ•˜๊ธฐ ์ „์— ์ฃผ์†Œ๊ฐ€ ์‚ฌ๋ผ์ ธ ๋นˆ ํŒŒ์ผยท์‹คํŒจ๊ฐ€ ๋๋‹ค โ€” ์กฐ๊ธˆ ๋Šฆ์ถ˜๋‹ค */
46
+ setTimeout(() => URL.revokeObjectURL(url), BLOB_REVOKE_DELAY_MS);
47
+ }, [getElements, paperW, paperH, fileName, header, t]);
42
48
  return { print, openNewWindow, downloadHtml };
43
49
  }
@@ -13,12 +13,22 @@ export interface ScoreRecord {
13
13
  tableId: string | null;
14
14
  /** ์นธ ๋ฒˆํ˜ธ โ†’ ํ‘œ์‹œ */
15
15
  marks: Record<string, ScoreMark>;
16
- /** ์นธ ๋ฒˆํ˜ธ โ†’ ๊ทธ๋•Œ ๋ณด์ธ ๋‚ฑ๋ง. ์˜› ๊ธฐ๋ก์—๋Š” ์—†์„ ์ˆ˜ ์žˆ๋‹ค */
16
+ /**
17
+ * ์นธ ๋ฒˆํ˜ธ โ†’ ๊ทธ๋•Œ ๋ณด์ธ ๋‚ฑ๋ง. ์˜› ๊ธฐ๋ก์—๋Š” ์—†์„ ์ˆ˜ ์žˆ๋‹ค.
18
+ * `cellKey` ๋กœ ๋™ํ˜•์–ด ๋ฒˆํ˜ธ๊นŒ์ง€ ์ ์–ด ๋‘๋ฉด(`๋ฐฐ#2`) `weakItems` ๊ฐ€ ๋™ํ˜•์–ด๋ฅผ ๋”ฐ๋กœ ์„ผ๋‹ค.
19
+ */
17
20
  labels: Record<string, string>;
18
21
  }
19
22
  /** ๋‚ฑ๋ง ํ•˜๋‚˜์— ๋Œ€ํ•ด ์Œ“์ธ ์„ฑ์ . */
20
23
  export interface WeakItem {
24
+ /** ๋ณด์ด๋Š” ๋‚ฑ๋ง โ€” ๋™ํ˜•์–ด ๋ฒˆํ˜ธ๋ฅผ ๋—€ ๊ธ€์ž(`๋ฐฐ`, `๋ฐฐ / ํŒจ`) */
21
25
  label: string;
26
+ /**
27
+ * ๋ฌถ์€ ์—ด์‡  โ€” ์ €์žฅ๋œ ์นธ ๋‚ฑ๋ง ๊ทธ๋Œ€๋กœ. ๋™ํ˜•์–ด ๋ฒˆํ˜ธ๊ฐ€ ์ €์žฅ๋œ ๊ธฐ๋ก์ด๋ฉด `๋ฐฐ#2` ์ฒ˜๋Ÿผ ๋ฒˆํ˜ธ๊ฐ€ ๋ถ™๊ณ ,
28
+ * ์—†๋Š” ์˜› ๊ธฐ๋ก์ด๋ฉด `label` ๊ณผ ๊ฐ™๋‹ค. ๊ฐ™์€ `label` ์ด ๋‘˜ ์ด์ƒ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์ค„ ์—ด์‡ ๋กœ๋Š” ์ด๊ฒƒ์„ ์“ด๋‹ค.
29
+ * `weakItems` ๋Š” ๋Š˜ ์ฑ„์šด๋‹ค โ€” ์„ ํƒ ํ•„๋“œ์ธ ๊ฒƒ์€ ์ด ๋ชจ์–‘์„ ์†์œผ๋กœ ์ง“๋Š” ์ชฝ(์‹œํ—˜ ๋“ฑ)์„ ๊นจ์ง€ ์•Š์œผ๋ ค๋Š” ๊ฒƒ๋ฟ์ด๋‹ค.
30
+ */
31
+ key?: string;
22
32
  /** ์ด ๋‚ฑ๋ง์„ ๋งค๊ธด ํšŸ์ˆ˜ */
23
33
  tried: number;
24
34
  o: number;
@@ -40,6 +50,9 @@ export interface WeakItem {
40
50
  *
41
51
  * **๋‚ฑ๋ง์ด ์•ˆ ๋‚จ์€ ์˜› ๊ธฐ๋ก์€ ๊ฑด๋„ˆ๋›ด๋‹ค.** ์นธ ๋ฒˆํ˜ธ๋กœ ํ‘œ๋ฅผ ๋˜์งš์œผ๋ฉด ๊ทธ ํ‘œ๊ฐ€ ๊ทธ๋™์•ˆ ๋ฐ”๋€Œ์—ˆ์„ ๋•Œ
42
52
  * ์—‰๋šฑํ•œ ๋‚ฑ๋ง์˜ ๊ธฐ๋ก์ด ๋œ๋‹ค โ€” ์กฐ์šฉํžˆ ํ‹€๋ฆฌ๋А๋‹ˆ ๋นผ๋Š” ํŽธ์ด ๋‚ซ๋‹ค.
53
+ *
54
+ * **๋™ํ˜•์–ด๋Š” ์ €์žฅ๋œ ๋‚ฑ๋ง์— ๋ฒˆํ˜ธ๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ ๋”ฐ๋กœ ๋ฌถ๋Š”๋‹ค**(`๋ฐฐ#1`ยท`๋ฐฐ#2`, `cellKey`). ๋ฒˆํ˜ธ ์—†์ด ์ €์žฅ๋œ ์˜› ๊ธฐ๋ก์€
55
+ * ์ง€๊ธˆ์ฒ˜๋Ÿผ ๊ธ€์ž๋กœ ๋ฌถ๋Š”๋‹ค โ€” ๊ทธ๋ž˜์„œ ๊ฐ™์€ '๋ฐฐ' ๊ฐ€ ์˜› ๊ธฐ๋ก ํ•œ ์ค„ยท๋ฒˆํ˜ธ ์žˆ๋Š” ๊ธฐ๋ก ํ•œ ์ค„๋กœ ๋‚˜๋‰˜์–ด ๋ณด์ผ ์ˆ˜ ์žˆ๋‹ค.
43
56
  */
44
57
  export declare function weakItems(records: readonly ScoreRecord[]): WeakItem[];
45
58
  /** ํ‘œ ํ•œ ๊ฐœ์— ๋Œ€ํ•œ ํ๋ฆ„ โ€” ๊ฐ™์€ ํ‘œ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ๋Œ๋ ธ์„ ๋•Œ ๋‚˜์•„์ง€๊ณ  ์žˆ๋Š”๊ฐ€. */
@@ -1,4 +1,5 @@
1
1
  import { ํ•œ๊ธ€์ˆœ } from '../../utils/textSort';
2
+ import { labelOfCellKey } from './wordTableReview';
2
3
  /** โ–ณ ๋ฅผ ๋ฐ˜ ์ ์œผ๋กœ ์น˜๋Š” ์…ˆ โ€” ์ €์žฅํ•  ๋•Œ ์„œ๋ฒ„๊ฐ€ ์“ฐ๋Š” ๊ทœ์น™๊ณผ ๊ฐ™๋‹ค. */
3
4
  function ์ ์ˆ˜(o, triangle, marked) {
4
5
  return marked === 0 ? 0 : (o + triangle * 0.5) / marked;
@@ -12,6 +13,9 @@ function ์ ์ˆ˜(o, triangle, marked) {
12
13
  *
13
14
  * **๋‚ฑ๋ง์ด ์•ˆ ๋‚จ์€ ์˜› ๊ธฐ๋ก์€ ๊ฑด๋„ˆ๋›ด๋‹ค.** ์นธ ๋ฒˆํ˜ธ๋กœ ํ‘œ๋ฅผ ๋˜์งš์œผ๋ฉด ๊ทธ ํ‘œ๊ฐ€ ๊ทธ๋™์•ˆ ๋ฐ”๋€Œ์—ˆ์„ ๋•Œ
14
15
  * ์—‰๋šฑํ•œ ๋‚ฑ๋ง์˜ ๊ธฐ๋ก์ด ๋œ๋‹ค โ€” ์กฐ์šฉํžˆ ํ‹€๋ฆฌ๋А๋‹ˆ ๋นผ๋Š” ํŽธ์ด ๋‚ซ๋‹ค.
16
+ *
17
+ * **๋™ํ˜•์–ด๋Š” ์ €์žฅ๋œ ๋‚ฑ๋ง์— ๋ฒˆํ˜ธ๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ ๋”ฐ๋กœ ๋ฌถ๋Š”๋‹ค**(`๋ฐฐ#1`ยท`๋ฐฐ#2`, `cellKey`). ๋ฒˆํ˜ธ ์—†์ด ์ €์žฅ๋œ ์˜› ๊ธฐ๋ก์€
18
+ * ์ง€๊ธˆ์ฒ˜๋Ÿผ ๊ธ€์ž๋กœ ๋ฌถ๋Š”๋‹ค โ€” ๊ทธ๋ž˜์„œ ๊ฐ™์€ '๋ฐฐ' ๊ฐ€ ์˜› ๊ธฐ๋ก ํ•œ ์ค„ยท๋ฒˆํ˜ธ ์žˆ๋Š” ๊ธฐ๋ก ํ•œ ์ค„๋กœ ๋‚˜๋‰˜์–ด ๋ณด์ผ ์ˆ˜ ์žˆ๋‹ค.
15
19
  */
16
20
  export function weakItems(records) {
17
21
  const ๋ชจ์Œ = new Map();
@@ -38,11 +42,11 @@ export function weakItems(records) {
38
42
  }
39
43
  }
40
44
  const out = [];
41
- for (const [label, v] of ๋ชจ์Œ) {
45
+ for (const [key, v] of ๋ชจ์Œ) {
42
46
  const tried = v.o + v.triangle + v.x;
43
- out.push({ label, tried, o: v.o, triangle: v.triangle, x: v.x, rate: ์ ์ˆ˜(v.o, v.triangle, tried), latest: v.latest, latestAt: v.latestAt });
47
+ out.push({ label: labelOfCellKey(key), key, tried, o: v.o, triangle: v.triangle, x: v.x, rate: ์ ์ˆ˜(v.o, v.triangle, tried), latest: v.latest, latestAt: v.latestAt });
44
48
  }
45
- out.sort((a, b) => a.rate - b.rate || b.tried - a.tried || ํ•œ๊ธ€์ˆœ(a.label, b.label));
49
+ out.sort((a, b) => a.rate - b.rate || b.tried - a.tried || ํ•œ๊ธ€์ˆœ(a.label, b.label) || ํ•œ๊ธ€์ˆœ(a.key ?? a.label, b.key ?? b.label));
46
50
  return out;
47
51
  }
48
52
  /**
@@ -229,19 +229,31 @@ export function captureHtml(els, paperW, paperH, autoPrint = false, fileName = '
229
229
  ${autoPrint ? `
230
230
  // document.write ์งํ›„ ๋ฐ”๋กœ window.print()๋ฅผ ๋ถ€๋ฅด๋ฉด ์ด๋ฏธ์ง€(๋น„๋™๊ธฐ ๋กœ๋“œ)์™€
231
231
  // ๋ ˆ์ด์•„์›ƒ์ด ์•„์ง ๋ฐ˜์˜๋˜๊ธฐ ์ „์ด๋ผ ๋นˆ ํŽ˜์ด์ง€๋กœ ์ธ์‡„๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์–ด,
232
- // ๋ชจ๋“  <img> ๋กœ๋“œ ์™„๋ฃŒ + rAF 2ํ‹ฑ(๋ ˆ์ด์•„์›ƒ/ํŽ˜์ธํŠธ ํ™•์ •) ์ดํ›„์— ์ธ์‡„ํ•œ๋‹ค.
232
+ // ๋ชจ๋“  <img> ๋กœ๋“œ ์™„๋ฃŒ + ๊ธ€๊ผด ์ค€๋น„(์ตœ๋Œ€ 1์ดˆ) + rAF 2ํ‹ฑ(๋ ˆ์ด์•„์›ƒ/ํŽ˜์ธํŠธ ํ™•์ •) ์ดํ›„์— ์ธ์‡„ํ•œ๋‹ค.
233
+ // ๊ธ€๊ผด์„ ์•ˆ ๊ธฐ๋‹ค๋ฆฌ๋ฉด ์›น ๊ธ€๊ผด์ด ์˜ค๊ธฐ ์ „์— ์ธ์‡„๋ผ ๊ธฐ๋ณธ ๊ธ€๊ผด ์ข…์ด๊ฐ€ ๋‚˜์™”๋‹ค(document.fonts ๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๋Š”๋‹ค).
233
234
  (function () {
234
235
  var MAX_WAIT_MS = 3000;
236
+ var FONT_WAIT_MS = 1000;
235
237
  var timedOut = false;
238
+ var printed = false;
236
239
  var timer = setTimeout(function () { timedOut = true; afterReady(); }, MAX_WAIT_MS);
237
240
  var pending = Array.prototype.filter.call(document.images, function (img) { return !img.complete; });
238
241
  var remaining = pending.length;
239
- function afterReady() {
240
- if (timer) { clearTimeout(timer); timer = null; }
242
+ function printNow() {
243
+ if (printed) return;
244
+ printed = true;
241
245
  requestAnimationFrame(function () {
242
246
  requestAnimationFrame(function () { window.print(); });
243
247
  });
244
248
  }
249
+ function afterReady() {
250
+ if (timer) { clearTimeout(timer); timer = null; }
251
+ var fonts = null;
252
+ try { fonts = document.fonts && document.fonts.ready; } catch (e) { fonts = null; }
253
+ if (!fonts || typeof fonts.then !== 'function') { printNow(); return; }
254
+ setTimeout(printNow, FONT_WAIT_MS);
255
+ fonts.then(printNow, printNow);
256
+ }
245
257
  if (remaining === 0) { afterReady(); return; }
246
258
  pending.forEach(function (img) {
247
259
  function done() { if (timedOut) return; remaining--; if (remaining <= 0) afterReady(); }
@@ -14,14 +14,53 @@ import type { ScoreMark } from './wordTableScoring';
14
14
  * ์ด๋ฏธ ์•„๋Š” ํ‘œ์™€ ๋‹ฎ์•„ ์žˆ๋Š” ํŽธ์ด ์“ฐ๊ธฐ์—๋„ ๋‚ซ๋‹ค.
15
15
  */
16
16
  export declare function reviewCells<T>(items: readonly T[], marks: Record<string, ScoreMark | null | undefined>): T[];
17
+ /**
18
+ * ๋ณต์Šตํ•  ์›๋ž˜ ํ‘œ โ€” ์ด๋ฆ„๊ณผ, ์žˆ์œผ๋ฉด **๋ณต์Šต ๋ฉ”ํƒ€**.
19
+ *
20
+ * ์ด๋ฆ„๋งŒ์œผ๋กœ๋Š” ํšŒ์ฐจ๋ฅผ ํ‹€๋ฆฌ๊ฒŒ ์ฝ์„ ๋•Œ๊ฐ€ ์žˆ๋‹ค. `์ˆ˜์—… 3์ฐจ` ๋ผ๋Š” ํ‘œ์˜ ๋ณต์Šต(`์ˆ˜์—… 3์ฐจ ๋ณต์Šต 09.15`)๊ณผ
21
+ * `์ˆ˜์—…` ์˜ 3์ฐจ ๋ณต์Šต(`์ˆ˜์—… 3์ฐจ ๋ณต์Šต 09.15`)์ด **๊ฐ™์€ ์ด๋ฆ„**์ด๋ผ, ๋‹ค์‹œ ๋ณต์Šตํ•˜๋ฉด ๋‘˜ ๋‹ค `์ˆ˜์—… 4์ฐจ ๋ณต์Šต` ์ด ๋๋‹ค.
22
+ * ๋ณต์Šตํ‘œ๋ฅผ ์ €์žฅํ•  ๋•Œ {@link reviewMeta} ์˜ ๊ฐ’์„ ํ•จ๊ป˜ ๋‚จ๊ฒจ ๋‘๋ฉด ์ด๋ฆ„์„ ํŒŒ์‹ฑํ•˜์ง€ ์•Š๊ณ  ๊ทธ ๊ฐ’์„ ์“ด๋‹ค.
23
+ * ๋ฉ”ํƒ€๊ฐ€ ์—†๋Š” ์˜› ํ‘œ๋Š” ์ง€๊ธˆ์ฒ˜๋Ÿผ ์ด๋ฆ„์„ ํŒŒ์‹ฑํ•œ๋‹ค.
24
+ *
25
+ * @public
26
+ */
27
+ export interface ReviewSource {
28
+ /** ํ‘œ ์ด๋ฆ„ */
29
+ name: string;
30
+ /** ํ‘œ id โ€” ์ด ํ‘œ๊ฐ€ ์›๋ณธ์ด๋ฉด ๋ณต์Šตํ‘œ์˜ `reviewOf` ๊ฐ€ ๋œ๋‹ค */
31
+ id?: string;
32
+ /** ์ด ํ‘œ๊ฐ€ ๋ณต์Šตํ‘œ๋ฉด **๋งจ ์ฒ˜์Œ ์›๋ณธ ํ‘œ**์˜ id */
33
+ reviewOf?: string | null;
34
+ /**
35
+ * ์ด ํ‘œ๊ฐ€ ๋ช‡ ๋ฒˆ์งธ ๋ณต์Šต์ธ๊ฐ€(1๋ถ€ํ„ฐ). `0` ์€ "๋ณต์Šตํ‘œ๊ฐ€ ์•„๋‹ˆ๋‹ค" โ€” ์ด๋ฆ„์ด `โ€ฆ ๋ณต์Šต 09.01` ๊ผด์ด์–ด๋„ ๊ผฌ๋ฆฌ๋กœ ๋–ผ์ง€ ์•Š๋Š”๋‹ค.
36
+ * ์—†์œผ๋ฉด(`undefined`ยท`null`) ์˜› ํ‘œ๋กœ ๋ณด๊ณ  ์ด๋ฆ„์„ ํŒŒ์‹ฑํ•œ๋‹ค.
37
+ */
38
+ reviewRound?: number | null;
39
+ }
17
40
  /**
18
41
  * ๋ณต์Šตํ‘œ ์ด๋ฆ„.
19
42
  *
20
43
  * ์›๋ž˜ ์ด๋ฆ„์„ ์•ž์— ๋‘์–ด **๋ชฉ๋ก์—์„œ ๋‚˜๋ž€ํžˆ ๋ถ™์–ด ๋ณด์ด๊ฒŒ** ํ•œ๋‹ค(๋Œ€๊ฐœ ์ด๋ฆ„์ˆœยท์ตœ์‹ ์ˆœ์œผ๋กœ ๋ณธ๋‹ค).
21
44
  * ๋‚ ์งœ๋ฅผ ๋ถ™์ด๋Š” ๊ฒƒ์€ ๊ฐ™์€ ํ‘œ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ๋Œ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค โ€” ์—†์œผ๋ฉด ๊ฐ™์€ ์ด๋ฆ„์ด ์Œ“์—ฌ
22
45
  * ์–ด๋А ๊ฒƒ์ด ์ด๋ฒˆ ๊ฒƒ์ธ์ง€ ์•Œ ์ˆ˜ ์—†๋‹ค.
46
+ *
47
+ * ์ด๋ฆ„ ํ˜•์‹(`๋ฐ”ํƒ• [N์ฐจ ]๋ณต์Šต MM.DD`)์€ ์ €์žฅ๋œ ์ด๋ฆ„ยทํŒŒ์‹ฑ๊ณผ ๋งž์ถ”๋А๋ผ ๋ฐ”๊พธ์ง€ ์•Š๋Š”๋‹ค.
48
+ * ์›๋ž˜ ํ‘œ๋ฅผ {@link ReviewSource} ๋กœ ๋„˜๊ธฐ๋ฉด ๋ณต์Šต ๋ฉ”ํƒ€(`reviewRound`)๋ฅผ ์ด๋ฆ„ ํŒŒ์‹ฑ๋ณด๋‹ค ๋จผ์ € ์“ด๋‹ค โ€”
49
+ * ์ƒˆ ๋ณต์Šตํ‘œ๋ฅผ ์ €์žฅํ•  ๋•Œ๋Š” {@link reviewMeta} ์˜ ๊ฐ’์„ ํ•จ๊ป˜ ๋‚จ๊ธด๋‹ค. ๊ธ€์ž๋งŒ ๋„˜๊ธฐ๋ฉด ์ง€๊ธˆ์ฒ˜๋Ÿผ ์ด๋ฆ„์„ ํŒŒ์‹ฑํ•œ๋‹ค.
50
+ */
51
+ export declare function reviewTableName(original: string | ReviewSource, today: Date): string;
52
+ /**
53
+ * ์ƒˆ๋กœ ๋งŒ๋“ค ๋ณต์Šตํ‘œ์— **ํ•จ๊ป˜ ์ €์žฅํ•  ๋ฉ”ํƒ€** โ€” ๋งจ ์ฒ˜์Œ ์›๋ณธ ํ‘œ id ์™€ ์ด๋ฒˆ์ด ๋ช‡ ๋ฒˆ์งธ ๋ณต์Šต์ธ์ง€.
54
+ *
55
+ * `reviewOf` ๋Š” ์›๋ž˜ ํ‘œ๊ฐ€ ๋ณต์Šตํ‘œ๋ฉด ๊ทธ ํ‘œ์˜ `reviewOf` ๋ฅผ, ์•„๋‹ˆ๋ฉด ์›๋ž˜ ํ‘œ์˜ `id` ๋ฅผ ์ž‡๋Š”๋‹ค(๋‘˜ ๋‹ค ์—†์œผ๋ฉด `undefined`).
56
+ * `reviewRound` ๋Š” {@link reviewTableName} ์ด ์ด๋ฆ„์— ์ ๋Š” ํšŒ์ฐจ์™€ ๊ฐ™๋‹ค(1 = ์ฒซ ๋ณต์Šต).
57
+ *
58
+ * @public
23
59
  */
24
- export declare function reviewTableName(original: string, today: Date): string;
60
+ export declare function reviewMeta(source: ReviewSource): {
61
+ reviewOf: string | undefined;
62
+ reviewRound: number;
63
+ };
25
64
  /**
26
65
  * ์นธ ๋ฒˆํ˜ธ โ†’ ๊ทธ๋•Œ ๋ณด์ธ ๋‚ฑ๋ง. ์ฑ„์ ๊ณผ **ํ•จ๊ป˜ ์ €์žฅํ•œ๋‹ค.**
27
66
  *
@@ -37,6 +76,24 @@ export declare function labelsForSave(labels: readonly string[], marks: Record<s
37
76
  export declare function cellLabel(words: readonly {
38
77
  label: string;
39
78
  }[]): string;
79
+ /**
80
+ * ํ•œ ์นธ์˜ ๋‚ฑ๋ง๋“ค์„ **๋™ํ˜•์–ด ๋ฒˆํ˜ธ๊นŒ์ง€** ํ•œ ์ค„๋กœ โ€” `๋ฐฐ#2 / ํŒจ`. ๋ฒˆํ˜ธ๊ฐ€ ์—†๋Š” ๋‚ฑ๋ง์€ {@link cellLabel} ๊ณผ ๊ฐ™๋‹ค.
81
+ *
82
+ * ์ฑ„์ ๊ณผ ํ•จ๊ป˜ ์ €์žฅํ•˜๋Š” ์นธ ๋‚ฑ๋ง(`labelsForSave` ์— ๋„˜๊ธธ ๊ฒƒ)๋กœ ์“ฐ๋ฉด `weakItems` ๊ฐ€ '๋ฐฐ'(1)ยท'๋ฐฐ'(2) ๋ฅผ ๋”ฐ๋กœ ์„ผ๋‹ค.
83
+ * ๊ธ€์ž๋งŒ(`cellLabel`) ์ €์žฅํ•˜๋ฉด ๋‘˜์˜ ์„ฑ์ ์ด ํ•œ ์ค„๋กœ ํ•ฉ์ณ์ง„๋‹ค. ํ™”๋ฉด์— ๋ณด์ผ ๊ธ€์ž๋Š” {@link labelOfCellKey} ๋กœ ๋˜๋Œ๋ฆฐ๋‹ค.
84
+ *
85
+ * @public
86
+ */
87
+ export declare function cellKey(words: readonly {
88
+ label: string;
89
+ homonum?: number | null;
90
+ }[]): string;
91
+ /**
92
+ * {@link cellKey} ๋กœ ๋งŒ๋“  ์นธ ๋‚ฑ๋ง์—์„œ ๋™ํ˜•์–ด ๋ฒˆํ˜ธ๋ฅผ ๋—€ ๋ณด์ด๋Š” ๊ธ€์ž โ€” `๋ฐฐ#2 / ํŒจ` โ†’ `๋ฐฐ / ํŒจ`. ๋ฒˆํ˜ธ๊ฐ€ ์—†์œผ๋ฉด ๊ทธ๋Œ€๋กœ.
93
+ *
94
+ * @public
95
+ */
96
+ export declare function labelOfCellKey(key: string): string;
40
97
  /**
41
98
  * ์ฑ„์ ํ•œ ํ‘œ๋ฅผ ์ธ์‡„ํ•  ๋•Œ ์ข…์ด ๋งจ ์œ„์— ์ฐ๋Š” ํ•œ ์ค„.
42
99
  *