Explorar o código

Merge branch 'feat/7305-migrate-to-unified-rules' into feat/7317-get-preference-from-db

Steven Fukase %!s(int64=4) %!d(string=hai) anos
pai
achega
d3605c97cf

+ 14 - 2
packages/app/resource/locales/en_US/translation.json

@@ -261,7 +261,16 @@
       "common_settings": "Common Settings",
       "common_settings": "Common Settings",
       "common_misspellings": "Textlint rules to find common misspellings from Wikipedia.",
       "common_misspellings": "Textlint rules to find common misspellings from Wikipedia.",
       "max_comma": "Textlint rule is that limit maximum ten(、) count of sentence. Default: 4",
       "max_comma": "Textlint rule is that limit maximum ten(、) count of sentence. Default: 4",
-      "sentence_length": "Textlint rules that limit Maximum Length of Sentence. Default: 100"
+      "sentence_length": "Textlint rules that limit Maximum Length of Sentence. Default: 100",
+      "en_capitalization": "Textlint rule that check capitalization in english text.",
+      "no_unmatched_pair": "Textlint rule that check unmatched pairs like ( and ]",
+      "date_weekday_mismatch": "Textlint rule that found mismatch between date and weekday.",
+      "no_kangxi_radicals": "Textlint rule to prevent using kangxi radicals.",
+      "no_surrogate_pair": "Detects surrogate pairs (D800-DBFF and DC00-DFFF) in sentences.",
+      "no_zero_width_spaces": "Textlint rule that disallow zero width spaces.",
+      "period_in_list_item": "Textlint rule that check with or without period in list item.",
+      "use_si_units": "Use of units other than SI unit units is prohibited."
+
       },
       },
     "japanese_settings": {
     "japanese_settings": {
       "japanese_settings": "Japanese Settings",
       "japanese_settings": "Japanese Settings",
@@ -277,7 +286,10 @@
       "no_doubled_joshi": "Textlint rules that checks that the same particle appears consecutively in one sentence.",
       "no_doubled_joshi": "Textlint rules that checks that the same particle appears consecutively in one sentence.",
       "no_dropping_the_ra": "Textlint rules that detects the word dropping the ra.",
       "no_dropping_the_ra": "Textlint rules that detects the word dropping the ra.",
       "no_hankaku_kana": "Textlint rules that disallow to use Half-width kana.",
       "no_hankaku_kana": "Textlint rules that disallow to use Half-width kana.",
-      "prefer_tari_tari": "Textlint rules that checks tari tari."
+      "prefer_tari_tari": "Textlint rules that checks tari tari.",
+      "ja_unnatural_alphabet": "Detects unnatural alphabets.",
+      "no_mixed_zenkaku_and_hankaku_alphabet": "Check for mixed full-width and half-width alphabets.",
+      "no_nfd": "textlint rule that disallow to use NFD like UTF8-MAC Sonant mark."
     }
     }
   },
   },
   "copy_to_clipboard": {
   "copy_to_clipboard": {

+ 13 - 2
packages/app/resource/locales/ja_JP/translation.json

@@ -264,7 +264,15 @@
       "common_settings": "共通設定",
       "common_settings": "共通設定",
       "common_misspellings": "ウィキペディアから一般的なスペルミスを見つけます。",
       "common_misspellings": "ウィキペディアから一般的なスペルミスを見つけます。",
       "max_comma": "文の読点(、)を最大10個に制限します。初期値: 4。",
       "max_comma": "文の読点(、)を最大10個に制限します。初期値: 4。",
-      "sentence_length": "文の最大文字数を制限します。初期値: 100。"
+      "sentence_length": "文の最大文字数を制限します。初期値: 100。",
+      "en_capitalization": "英文の大文字化をチェックします",
+      "no_unmatched_pair": "( と ] のような一致しないペアをチェックします",
+      "date_weekday_mismatch": "日付と平日の不一致を検出したTextlintルール。",
+      "no_kangxi_radicals": "康熙帝の部首の使用を防ぐためのTextlintルール。",
+      "no_surrogate_pair": "文中のサロゲートペア(D800-DBFFおよびDC00-DFFF)を検出します。",
+      "no_zero_width_spaces": "ゼロ幅スペースを許可しないTextlintルール。",
+      "period_in_list_item": "リストアイテムのピリオドの有無をチェックするTextlintルール。",
+      "use_si_units": "SI単位系以外の使用を禁止します。"
       },
       },
     "japanese_settings": {
     "japanese_settings": {
       "japanese_settings": "日本語設定",
       "japanese_settings": "日本語設定",
@@ -280,7 +288,10 @@
       "no_doubled_joshi": "1つの文中に同じ助詞が連続して出てくるのをチェックします。",
       "no_doubled_joshi": "1つの文中に同じ助詞が連続して出てくるのをチェックします。",
       "no_dropping_the_ra": "ら抜き言葉を検出します。",
       "no_dropping_the_ra": "ら抜き言葉を検出します。",
       "no_hankaku_kana": "半角カナの利用を禁止します。",
       "no_hankaku_kana": "半角カナの利用を禁止します。",
-      "prefer_tari_tari": "「〜たり〜たりする」をチェックします。"
+      "prefer_tari_tari": "「〜たり〜たりする」をチェックします。",
+      "ja_unnatural_alphabet": "不自然なアルファベットを検知します。",
+      "no_mixed_zenkaku_and_hankaku_alphabet": "全角と半角アルファベットを混在をチェックします。",
+      "no_nfd": "UTF8-MAC濁点のようなNFDの使用を禁止します。"
     }
     }
   },
   },
   "copy_to_clipboard": {
   "copy_to_clipboard": {

+ 13 - 2
packages/app/resource/locales/zh_CN/translation.json

@@ -243,7 +243,15 @@
       "common_settings": "常用设置",
       "common_settings": "常用设置",
       "common_misspellings": "从 Wikipedia 中查找常见拼写错误的 Textlint。",
       "common_misspellings": "从 Wikipedia 中查找常见拼写错误的 Textlint。",
       "max_comma": "Textlint 规则是限制句子的最大十(、)个计数。默认:4。",
       "max_comma": "Textlint 规则是限制句子的最大十(、)个计数。默认:4。",
-      "sentence_length": "限制最大句子长度的 Textlint 默认: 100。"
+      "sentence_length": "限制最大句子长度的 Textlint 默认: 100。",
+      "en_capitalization": "检查英文文本大小写的 Textlint 规则。",
+      "no_unmatched_pair": "检查不匹配对的 Textlint 规则,如 ( 和 ]",
+      "date_weekday_mismatch": "发现日期和工作日之间不匹配的 Textlint 规则。",
+      "no_kangxi_radicals": "防止使用康熙部首的 Textlint 规则。",
+      "no_surrogate_pair": "检测句子中的代理对(D800-DBFF 和 DC00-DFFF)。",
+      "no_zero_width_spaces": "不允许零宽度空格的 Textlint 规则。",
+      "period_in_list_item": "在列表项中检查是否有句点的 Textlint 规则。",
+      "use_si_units": "禁止使用 SI 单位以外的单位。"
       },
       },
     "japanese_settings": {
     "japanese_settings": {
       "japanese_settings": "日语设置",
       "japanese_settings": "日语设置",
@@ -259,7 +267,10 @@
       "no_doubled_joshi": "Textlint 规则,用于检查同一个粒子是否连续出现在一个句子中。",
       "no_doubled_joshi": "Textlint 规则,用于检查同一个粒子是否连续出现在一个句子中。",
       "no_dropping_the_ra": "检测丢弃 ra 的单词的 Textlint 规则。",
       "no_dropping_the_ra": "检测丢弃 ra 的单词的 Textlint 规则。",
       "no_hankaku_kana": "不允许使用半角假名的 Textlint 规则。",
       "no_hankaku_kana": "不允许使用半角假名的 Textlint 规则。",
-      "prefer_tari_tari": "检查 tari tari 的 Textlint 规则。"
+      "prefer_tari_tari": "检查 tari tari 的 Textlint 规则。",
+      "ja_unnatural_alphabet": "检测不自然的字母。",
+      "no_mixed_zenkaku_and_hankaku_alphabet": "检查混合的全角和半角字母。",
+      "no_nfd": "禁止使用 UTF8-MAC 浊音等 NFD。"
     }
     }
   },
   },
 	"copy_to_clipboard": {
 	"copy_to_clipboard": {

+ 158 - 144
packages/app/src/components/Me/EditorSettings.tsx

@@ -1,19 +1,38 @@
 import React, {
 import React, {
-  FC, useEffect, useState,
+  Dispatch,
+  FC, SetStateAction, useEffect, useState,
 } from 'react';
 } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
 
 
-import EditorContainer from '~/client/services/EditorContainer';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import { withUnstatedContainers } from '../UnstatedUtils';
 
 
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 
 
-type Props = {
-  appContainer: AppContainer,
+type EditorSettingsBodyProps = {
+  appContainer: AppContainer
 }
 }
 
 
+type RuleListGroupProps = {
+  title: string;
+  ruleList: RulesMenuItem[]
+  textlintRules: LintRule[]
+  setTextlintRules: Dispatch<SetStateAction<LintRule[]>>
+}
+
+type LintRule = {
+  name: string
+  options?: unknown
+  isEnabled?: boolean
+}
+
+type RulesMenuItem = {
+  name: string
+  description: string
+}
+
+
 const commonRulesMenuItems = [
 const commonRulesMenuItems = [
   {
   {
     name: 'common-misspellings',
     name: 'common-misspellings',
@@ -27,6 +46,38 @@ const commonRulesMenuItems = [
     name: 'sentence-length',
     name: 'sentence-length',
     description: 'editor_settings.common_settings.sentence_length',
     description: 'editor_settings.common_settings.sentence_length',
   },
   },
+  {
+    name: 'en-capitalization',
+    description: 'editor_settings.common_settings.en_capitalization',
+  },
+  {
+    name: 'no-unmatched-pair',
+    description: 'editor_settings.common_settings.no_unmatched_pair',
+  },
+  {
+    name: 'date-weekday-mismatch',
+    description: 'editor_settings.common_settings.date_weekday_mismatch',
+  },
+  {
+    name: 'no-kangxi-radicals',
+    description: 'editor_settings.common_settings.no_kangxi_radicals',
+  },
+  {
+    name: 'no-surrogate-pair',
+    description: 'editor_settings.common_settings.no_surrogate_pair',
+  },
+  {
+    name: 'no-zero-width-spaces',
+    description: 'editor_settings.common_settings.no_zero_width_spaces',
+  },
+  {
+    name: 'period-in-list-item',
+    description: 'editor_settings.common_settings.period_in_list_item',
+  },
+  {
+    name: 'use-si-units',
+    description: 'editor_settings.common_settings.use_si_units',
+  },
 ];
 ];
 
 
 const japaneseRulesMenuItems = [
 const japaneseRulesMenuItems = [
@@ -82,109 +133,45 @@ const japaneseRulesMenuItems = [
     name: 'prefer-tari-tari',
     name: 'prefer-tari-tari',
     description: 'editor_settings.japanese_settings.prefer_tari_tari',
     description: 'editor_settings.japanese_settings.prefer_tari_tari',
   },
   },
+  {
+    name: 'ja-unnatural-alphabet',
+    description: 'editor_settings.japanese_settings.ja_unnatural_alphabet',
+  },
+  {
+    name: 'no-mixed-zenkaku-and-hankaku-alphabet',
+    description: 'editor_settings.japanese_settings.no_mixed_zenkaku_and_hankaku_alphabet',
+  },
+  {
+    name: 'no-nfd',
+    description: 'editor_settings.japanese_settings.no_nfd',
+  },
 
 
 ];
 ];
 
 
-type LintRules = {
-  name: string;
-  options?: unknown;
-  isEnabled?: boolean;
-}
-
 
 
-const EditorSettingsBody: FC<Props> = (props) => {
+const RuleListGroup: FC<RuleListGroupProps> = ({
+  title, ruleList, textlintRules, setTextlintRules,
+}) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
-  const { appContainer } = props;
-  const [commonTextlintRules, setCommonTextlintRules] = useState<LintRules[]>([]);
-  const [japaneseTextlintRules, setJapaneseTextlintRules] = useState<LintRules[]>([]);
-
-  const initializeEditorSettings = async() => {
-    const { data } = await appContainer.apiv3Get('/personal-setting/editor-settings');
-
-    if (data?.commonTextlintRules != null) {
-      setCommonTextlintRules(data?.commonTextlintRules);
-    }
-    if (data?.japaneseTextlintRules != null) {
-      setJapaneseTextlintRules(data?.japaneseTextlintRules);
-    }
 
 
-    // If database is empty, add default rules to state
-    if (data?.commonTextlintRules.length === 0 || data?.commonTextlintRules == null) {
-      const defaultCommonRules = commonRulesMenuItems.map(rule => (
-        {
-          name: rule.name,
-          isEnabled: true,
-        }
-      ));
-      setCommonTextlintRules(defaultCommonRules);
-    }
-    if (data?.japaneseTextlintRules.length === 0 || data?.japaneseTextlintRules == null) {
-      const defaultJapaneseRules = japaneseRulesMenuItems.map(rule => (
-        {
-          name: rule.name,
-          isEnabled: true,
-        }
-      ));
-      setJapaneseTextlintRules(defaultJapaneseRules);
-    }
-  };
-
-  useEffect(() => {
-    initializeEditorSettings();
-  }, []);
-
-  const commonRuleCheckboxHandler = (isChecked: boolean, ruleName: string) => {
-    setCommonTextlintRules(prevState => (
-      prevState.filter(rule => rule.name !== ruleName).concat({ name: ruleName, isEnabled: isChecked })
-    ));
-  };
-
-  const japaneseRuleCheckboxHandler = (isChecked: boolean, ruleName: string) => {
-    setJapaneseTextlintRules(prevState => (
-      prevState.filter(rule => rule.name !== ruleName).concat({ name: ruleName, isEnabled: isChecked })
-    ));
-  };
-
-  const updateCommonRuleHandler = async() => {
-    try {
-      const { data } = await appContainer.apiv3Put('/personal-setting/editor-settings', { commonTextlintRules });
-      setCommonTextlintRules(data?.commonTextlintRules);
-      toastSuccess(t('toaster.update_successed', { target: 'Updated Textlint Settings' }));
-    }
-    catch (err) {
-      toastError(err);
-    }
-  };
-
-  const updateJapaneseRuleHandler = async() => {
-    try {
-      const { data } = await appContainer.apiv3Put('/personal-setting/editor-settings', { japaneseTextlintRules });
-      setJapaneseTextlintRules(data?.japaneseTextlintRules);
-      toastSuccess(t('toaster.update_successed', { target: 'Updated Textlint Settings' }));
-    }
-    catch (err) {
-      toastError(err);
-    }
-  };
-
-  const isCheckedInCommonRules = (ruleName: string) => (
-    commonTextlintRules.filter(stateRule => (
+  const isCheckedRule = (ruleName: string) => (
+    textlintRules.filter(stateRule => (
       stateRule.name === ruleName
       stateRule.name === ruleName
     ))[0]?.isEnabled
     ))[0]?.isEnabled
   );
   );
 
 
-  const isCheckedInJapaneseRules = (ruleName: string) => (
-    japaneseTextlintRules.filter(stateRule => (
-      stateRule.name === ruleName
-    ))[0]?.isEnabled
-  );
+  const ruleCheckboxHandler = (isChecked: boolean, ruleName: string) => {
+    setTextlintRules(prevState => (
+      prevState.filter(rule => rule.name !== ruleName).concat({ name: ruleName, isEnabled: isChecked })
+    ));
+  };
 
 
   return (
   return (
     <>
     <>
-      <h2 className="border-bottom my-4">{t('editor_settings.common_settings.common_settings')}</h2>
+      <h2 className="border-bottom my-4">{t(title)}</h2>
       <div className="form-group row">
       <div className="form-group row">
         <div className="offset-md-3 col-md-6 text-left">
         <div className="offset-md-3 col-md-6 text-left">
-          {commonRulesMenuItems.map(rule => (
+          {ruleList.map(rule => (
             <div
             <div
               key={rule.name}
               key={rule.name}
               className="custom-control custom-switch custom-checkbox-success"
               className="custom-control custom-switch custom-checkbox-success"
@@ -193,8 +180,8 @@ const EditorSettingsBody: FC<Props> = (props) => {
                 type="checkbox"
                 type="checkbox"
                 className="custom-control-input"
                 className="custom-control-input"
                 id={rule.name}
                 id={rule.name}
-                checked={isCheckedInCommonRules(rule.name)}
-                onChange={e => commonRuleCheckboxHandler(e.target.checked, rule.name)}
+                checked={isCheckedRule(rule.name)}
+                onChange={e => ruleCheckboxHandler(e.target.checked, rule.name)}
               />
               />
               <label className="custom-control-label" htmlFor={rule.name}>
               <label className="custom-control-label" htmlFor={rule.name}>
                 <strong>{rule.name}</strong>
                 <strong>{rule.name}</strong>
@@ -204,68 +191,95 @@ const EditorSettingsBody: FC<Props> = (props) => {
               </p>
               </p>
             </div>
             </div>
           ))}
           ))}
-
-          <div className="row my-3">
-            <div className="offset-4 col-5">
-              <button
-                type="button"
-                className="btn btn-primary"
-                onClick={updateCommonRuleHandler}
-              >
-                {t('Update')}
-              </button>
-            </div>
-          </div>
-
         </div>
         </div>
       </div>
       </div>
+    </>
+  );
+};
 
 
 
 
-      <h2 className="border-bottom my-4">{t('editor_settings.japanese_settings.japanese_settings')}</h2>
+RuleListGroup.propTypes = {
+  title: PropTypes.string.isRequired,
+  ruleList: PropTypes.array.isRequired,
+  textlintRules: PropTypes.array.isRequired,
+  setTextlintRules: PropTypes.func.isRequired,
+};
 
 
-      <div className="form-group row">
-        <div className="offset-md-3 col-md-6 text-left">
-          {japaneseRulesMenuItems.map(rule => (
-            <div
-              key={rule.name}
-              className="custom-control custom-switch custom-checkbox-success"
-            >
-              <input
-                type="checkbox"
-                className="custom-control-input"
-                id={rule.name}
-                checked={isCheckedInJapaneseRules(rule.name)}
-                onChange={e => japaneseRuleCheckboxHandler(e.target.checked, rule.name)}
-              />
-              <label className="custom-control-label" htmlFor={rule.name}>
-                <strong>{rule.name}</strong>
-              </label>
-              <p className="form-text text-muted small">
-                {t(rule.description)}
-              </p>
-            </div>
-          ))}
-          <div className="row my-3">
-            <div className="offset-4 col-5">
-              <button
-                type="button"
-                className="btn btn-primary"
-                onClick={updateJapaneseRuleHandler}
-              >
-                {t('Update')}
-              </button>
-            </div>
-          </div>
 
 
-        </div>
-      </div>
+const EditorSettingsBody: FC<EditorSettingsBodyProps> = (props) => {
+  const { t } = useTranslation();
+  const { appContainer } = props;
+  const [textlintRules, setTextlintRules] = useState<LintRule[]>([]);
+
+  const initializeEditorSettings = async() => {
+    const { data } = await appContainer.apiv3Get('/personal-setting/editor-settings');
+
+    if (data?.textlintSettings?.textlintRules != null) {
+      setTextlintRules(data.textlintSettings.textlintRules);
+    }
+
+    // If database is empty, add default rules to state
+    if (data?.textlintSettings?.textlintRules == null) {
+
+      const createRulesFromDefaultList = (rule: { name: string }) => (
+        {
+          name: rule.name,
+          isEnabled: true,
+        }
+      );
+
+      const defaultCommonRules = commonRulesMenuItems.map(rule => createRulesFromDefaultList(rule));
+      const defaultJapaneseRules = japaneseRulesMenuItems.map(rule => createRulesFromDefaultList(rule));
+      setTextlintRules([...defaultCommonRules, ...defaultJapaneseRules]);
+    }
+  };
+
+  useEffect(() => {
+    initializeEditorSettings();
+  }, []);
+
+  const updateRulesHandler = async() => {
+    try {
+      const { data } = await appContainer.apiv3Put('/personal-setting/editor-settings', { textlintSettings: textlintRules });
+      setTextlintRules(data.textlintSettings.textlintRules);
+      toastSuccess(t('toaster.update_successed', { target: 'Updated Textlint Settings' }));
+    }
+    catch (err) {
+      toastError(err);
+    }
+  };
 
 
+  return (
+    <>
+      <RuleListGroup
+        title="editor_settings.common_settings.common_settings"
+        ruleList={commonRulesMenuItems}
+        textlintRules={textlintRules}
+        setTextlintRules={setTextlintRules}
+      />
+      <RuleListGroup
+        title="editor_settings.japanese_settings.japanese_settings"
+        ruleList={japaneseRulesMenuItems}
+        textlintRules={textlintRules}
+        setTextlintRules={setTextlintRules}
+      />
 
 
+      <div className="row my-3">
+        <div className="offset-4 col-5">
+          <button
+            type="button"
+            className="btn btn-primary"
+            onClick={updateRulesHandler}
+          >
+            {t('Update')}
+          </button>
+        </div>
+      </div>
     </>
     </>
   );
   );
 };
 };
 
 
-export const EditorSettings = withUnstatedContainers(EditorSettingsBody, [AppContainer, EditorContainer]);
+export const EditorSettings = withUnstatedContainers(EditorSettingsBody, [AppContainer]);
 
 
 EditorSettingsBody.propTypes = {
 EditorSettingsBody.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,