|
@@ -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,
|