Yuki Takei 1 месяц назад
Родитель
Сommit
c21f62938c

+ 2 - 5
apps/app/public/static/locales/en_US/translation.json

@@ -1139,14 +1139,11 @@
       "copy_done": "Copied to clipboard!",
       "copy_done": "Copied to clipboard!",
       "style": "Style",
       "style": "Style",
       "alert": "Alert",
       "alert": "Alert",
+      "alert_with_custom_title": "Alert with label",
+      "alert_with_custom_title_text": "Custom Title",
       "badge": "Badge",
       "badge": "Badge",
       "text_color": "Text Color",
       "text_color": "Text Color",
       "back_color": "Background Color",
       "back_color": "Background Color",
-      "alert_block": "Alert Block",
-      "important_label": "Important",
-      "important_text": "Important information",
-      "caution_label": "Caution",
-      "caution_text": "Prohibited items or warnings",
       "placeholder": "Sample text goes here",
       "placeholder": "Sample text goes here",
       "docs_title": "Bootstrap 5 Official Documentation",
       "docs_title": "Bootstrap 5 Official Documentation",
       "docs_badge": "Learn more about Badges",
       "docs_badge": "Learn more about Badges",

+ 3 - 5
apps/app/public/static/locales/fr_FR/translation.json

@@ -1131,14 +1131,12 @@
       "copy_done": "Copié dans le presse-papiers !",
       "copy_done": "Copié dans le presse-papiers !",
       "style": "Style",
       "style": "Style",
       "alert": "Alerte",
       "alert": "Alerte",
+      "alert_with_custom_title": "Alerte avec étiquette",
+      "alert_with_custom_title_text": "Titre personnalisé",
       "badge": "Badge",
       "badge": "Badge",
       "text_color": "Couleur du texte",
       "text_color": "Couleur du texte",
       "back_color": "Couleur d'arrière-plan",
       "back_color": "Couleur d'arrière-plan",
-      "alert_block": "Bloc d'alerte",
-      "important_label": "Important",
-      "important_text": "Informations importantes",
-      "caution_label": "Attention",
-      "caution_text": "Avertissements ou éléments interdits",
+
       "placeholder": "Le texte s'affiche ici",
       "placeholder": "Le texte s'affiche ici",
       "docs_title": "Documentation officielle de Bootstrap 5",
       "docs_title": "Documentation officielle de Bootstrap 5",
       "docs_badge": "En savoir plus sur les Badges",
       "docs_badge": "En savoir plus sur les Badges",

+ 3 - 5
apps/app/public/static/locales/ja_JP/translation.json

@@ -1172,14 +1172,12 @@
       "copy_done": "コピーしました!",
       "copy_done": "コピーしました!",
       "style": "スタイル",
       "style": "スタイル",
       "alert": "アラート",
       "alert": "アラート",
+      "alert_with_custom_title": "ラベル付きアラート",
+      "alert_with_custom_title_text": "カスタムタイトル",
       "badge": "バッジ",
       "badge": "バッジ",
       "text_color": "テキストカラー",
       "text_color": "テキストカラー",
       "back_color": "背景色",
       "back_color": "背景色",
-      "alert_block": "アラートブロック",
-      "important_label": "Important",
-      "important_text": "重要な情報",
-      "caution_label": "Caution",
-      "caution_text": "禁止事項や警告",
+
       "placeholder": "テキストが入ります",
       "placeholder": "テキストが入ります",
       "docs_title": "Bootstrap5 公式ドキュメント",
       "docs_title": "Bootstrap5 公式ドキュメント",
       "docs_badge": "バッジの詳細はこちら",
       "docs_badge": "バッジの詳細はこちら",

+ 3 - 5
apps/app/public/static/locales/ko_KR/translation.json

@@ -1099,14 +1099,12 @@
       "copy_done": "클립보드에 복사되었습니다!",
       "copy_done": "클립보드에 복사되었습니다!",
       "style": "스타일",
       "style": "스타일",
       "alert": "알림",
       "alert": "알림",
+      "alert_with_custom_title": "레이블이 있는 알림",
+      "alert_with_custom_title_text": "사용자 정의 제목",
       "badge": "배지",
       "badge": "배지",
       "text_color": "텍스트 색상",
       "text_color": "텍스트 색상",
       "back_color": "배경 색상",
       "back_color": "배경 색상",
-      "alert_block": "알림 블록",
-      "important_label": "Important",
-      "important_text": "중요한 정보",
-      "caution_label": "Caution",
-      "caution_text": "금지 사항 및 경고",
+
       "placeholder": "텍스트가 입력됩니다",
       "placeholder": "텍스트가 입력됩니다",
       "docs_title": "Bootstrap 5 공식 문서",
       "docs_title": "Bootstrap 5 공식 문서",
       "docs_badge": "배지 상세 정보",
       "docs_badge": "배지 상세 정보",

+ 3 - 5
apps/app/public/static/locales/zh_CN/translation.json

@@ -1144,14 +1144,12 @@
       "copy_done": "已复制到剪贴板!",
       "copy_done": "已复制到剪贴板!",
       "style": "样式",
       "style": "样式",
       "alert": "提示",
       "alert": "提示",
+      "alert_with_custom_title": "带标签的提示",
+      "alert_with_custom_title_text": "自定义标题",
       "badge": "徽章",
       "badge": "徽章",
       "text_color": "文本颜色",
       "text_color": "文本颜色",
       "back_color": "背景颜色",
       "back_color": "背景颜色",
-      "alert_block": "警告框",
-      "important_label": "Important",
-      "important_text": "重要信息",
-      "caution_label": "Caution",
-      "caution_text": "禁止事项及警告",
+
       "placeholder": "此处显示文本内容",
       "placeholder": "此处显示文本内容",
       "docs_title": "Bootstrap 5 官方文档",
       "docs_title": "Bootstrap 5 官方文档",
       "docs_badge": "了解更多关于徽章的信息",
       "docs_badge": "了解更多关于徽章的信息",

+ 7 - 0
apps/app/src/client/components/PageEditor/EditorGuideModal/contents/DecorationTab.module.scss

@@ -7,6 +7,13 @@
   overflow-y: auto;
   overflow-y: auto;
 }
 }
 
 
+// Override the margin of callout in the preview
+.decorationBody {
+  :global(.callout) {
+    margin: 0.2rem 0;
+  }
+}
+
 .dropdownMenu {
 .dropdownMenu {
   max-height: 300px;
   max-height: 300px;
   margin-top: bs.$dropdown-spacer;
   margin-top: bs.$dropdown-spacer;

+ 81 - 87
apps/app/src/client/components/PageEditor/EditorGuideModal/contents/DecorationTab.tsx

@@ -1,6 +1,7 @@
 import type React from 'react';
 import type React from 'react';
 import { useMemo, useState } from 'react';
 import { useMemo, useState } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
+import ReactMarkdown from 'react-markdown';
 import {
 import {
   Dropdown,
   Dropdown,
   DropdownItem,
   DropdownItem,
@@ -8,97 +9,95 @@ import {
   DropdownToggle,
   DropdownToggle,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
+import { usePreviewOptions } from '~/stores/renderer';
+
 import type { LayoutGuideItem } from '../components/GuideRow';
 import type { LayoutGuideItem } from '../components/GuideRow';
 import { GuideRow } from '../components/GuideRow';
 import { GuideRow } from '../components/GuideRow';
 
 
 import styles from './DecorationTab.module.scss';
 import styles from './DecorationTab.module.scss';
 
 
-const BOOTSTRAP_COLORS = [
+const BOOTSTRAP_STYLES = [
   'primary',
   'primary',
-  'danger',
   'secondary',
   'secondary',
+  'info',
   'success',
   'success',
   'warning',
   'warning',
-  'info',
-  'light',
-  'dark',
+  'danger',
 ] as const;
 ] as const;
-type BootstrapColor = (typeof BOOTSTRAP_COLORS)[number];
+type BOOTSTRAP_STYLES = (typeof BOOTSTRAP_STYLES)[number];
+
+// exclude 'secondary' as it's not suitable for callout style
+const CALLOUT_STYLES = BOOTSTRAP_STYLES.filter(
+  (style) => style !== 'secondary',
+);
+type CALLOUT_STYLES = (typeof CALLOUT_STYLES)[number];
+
+const BOOTSTRAP_STYLES_TO_CALLOUT_CONFIGS_MAPPINGS: Record<
+  CALLOUT_STYLES,
+  { icon: string; calloutType: string }
+> = {
+  primary: {
+    icon: 'feedback',
+    calloutType: 'important',
+  },
+  info: { icon: 'info', calloutType: 'note' },
+  success: { icon: 'lightbulb', calloutType: 'tip' },
+  warning: { icon: 'warning', calloutType: 'warning' },
+  danger: { icon: 'report', calloutType: 'caution' },
+};
 
 
 export const DecorationTab: React.FC = () => {
 export const DecorationTab: React.FC = () => {
   const { t } = useTranslation();
   const { t } = useTranslation();
   const i18nKey = 'editor_guide.decoration';
   const i18nKey = 'editor_guide.decoration';
-  const [currentStyle, setCurrentStyle] = useState<BootstrapColor>('primary');
+  const [currentStyle, setCurrentStyle] = useState<
+    BOOTSTRAP_STYLES | CALLOUT_STYLES
+  >('primary');
   const [isOpen, setIsOpen] = useState(false);
   const [isOpen, setIsOpen] = useState(false);
 
 
-  const colorConfigs: Record<BootstrapColor, { icon: string; prefix: string }> =
-    {
-      primary: { icon: 'chat', prefix: '[!IMPORTANT]' },
-      danger: { icon: 'error', prefix: '[!CAUTION]' },
-      secondary: { icon: 'sell', prefix: '[!NOTE]' },
-      success: { icon: 'check_circle', prefix: '[!TIP]' },
-      warning: { icon: 'warning', prefix: '[!WARNING]' },
-      info: { icon: 'info', prefix: '[!NOTE]' },
-      light: { icon: 'light_mode', prefix: '[!NOTE]' },
-      dark: { icon: 'dark_mode', prefix: '[!IMPORTANT]' },
-    };
+  const { data: previewOptions } = usePreviewOptions();
 
 
-  const styleConfig = useMemo(() => {
-    const config = colorConfigs[currentStyle];
-    return {
-      colorName: currentStyle,
-      displayName: currentStyle.charAt(0).toUpperCase() + currentStyle.slice(1),
-      iconName: config.icon,
-      alertPrefix: config.prefix,
-      alertLabel: t(`${i18nKey}.${currentStyle}_label`, {
-        defaultValue: currentStyle.toUpperCase(),
-      }),
-      alertText: t(`${i18nKey}.${currentStyle}_text`, {
-        defaultValue: t(`${i18nKey}.placeholder`),
-      }),
-    };
-  }, [currentStyle, t]);
+  const calloutConfig: { icon: string; calloutType: string } =
+    BOOTSTRAP_STYLES_TO_CALLOUT_CONFIGS_MAPPINGS[currentStyle];
+  const displayName =
+    currentStyle.charAt(0).toUpperCase() + currentStyle.slice(1);
 
 
   const LAYOUT_GUIDES: LayoutGuideItem[] = useMemo(
   const LAYOUT_GUIDES: LayoutGuideItem[] = useMemo(
     () => [
     () => [
       {
       {
         id: 'alert',
         id: 'alert',
         title: t(`${i18nKey}.alert`),
         title: t(`${i18nKey}.alert`),
-        code: `> ${styleConfig.alertPrefix}\n> ${styleConfig.alertText}`,
+        code: `> [!${calloutConfig.calloutType.toUpperCase()}]\n> ${t(`${i18nKey}.${currentStyle}_text`, { defaultValue: t(`${i18nKey}.placeholder`) })}`,
         preview: (
         preview: (
-          <div
-            className={`d-flex align-items-center border-start border-4 border-${styleConfig.colorName} ps-3 py-1`}
-            style={{ minHeight: '52px' }}
-          >
-            <div className="d-flex flex-column justify-content-center">
-              <div
-                className={`d-flex align-items-center fw-bold mb-1 ${
-                  styleConfig.colorName === 'light' ||
-                  styleConfig.colorName === 'dark'
-                    ? 'text-body'
-                    : `text-${styleConfig.colorName}`
-                }`}
-              >
-                <span className="me-2 d-flex align-items-center">
-                  <span className="material-symbols-outlined align-middle fs-6">
-                    {styleConfig.iconName}
-                  </span>
-                </span>
-                <span style={{ lineHeight: 1 }}>{styleConfig.alertLabel}</span>
-              </div>
-              <div className="text-body small lh-base">
-                {styleConfig.alertText}
-              </div>
-            </div>
-          </div>
+          <ReactMarkdown
+            {...previewOptions}
+          >{`> [!${calloutConfig.calloutType.toUpperCase()}]\n> ${t(`${i18nKey}.${currentStyle}_text`, { defaultValue: t(`${i18nKey}.placeholder`) })}`}</ReactMarkdown>
+        ),
+      },
+      {
+        id: 'alert2',
+        code: `:::${calloutConfig.calloutType}\n${t(`${i18nKey}.${currentStyle}_text`, { defaultValue: t(`${i18nKey}.placeholder`) })}\n:::`,
+        preview: (
+          <ReactMarkdown
+            {...previewOptions}
+          >{`:::${calloutConfig.calloutType}\n${t(`${i18nKey}.${currentStyle}_text`, { defaultValue: t(`${i18nKey}.placeholder`) })}\n:::`}</ReactMarkdown>
+        ),
+      },
+      {
+        id: 'alert3',
+        title: t(`${i18nKey}.alert_with_custom_title`),
+        code: `:::${calloutConfig.calloutType}[${t(`${i18nKey}.alert_with_custom_title_text`)}]\n${t(`${i18nKey}.${currentStyle}_text`, { defaultValue: t(`${i18nKey}.placeholder`) })}\n:::`,
+        preview: (
+          <ReactMarkdown
+            {...previewOptions}
+          >{`:::${calloutConfig.calloutType}[${t(`${i18nKey}.alert_with_custom_title_text`)}]\n${t(`${i18nKey}.${currentStyle}_text`, { defaultValue: t(`${i18nKey}.placeholder`) })}\n:::`}</ReactMarkdown>
         ),
         ),
       },
       },
       {
       {
         id: 'badge',
         id: 'badge',
         title: t(`${i18nKey}.badge`),
         title: t(`${i18nKey}.badge`),
-        code: `<span class="badge text-bg-${styleConfig.colorName}">${t(`${i18nKey}.badge`)}</span>`,
+        code: `<span class="badge text-bg-${currentStyle}">${t(`${i18nKey}.badge`)}</span>`,
         preview: (
         preview: (
-          <span className={`badge text-bg-${styleConfig.colorName}`}>
+          <span className={`badge text-bg-${currentStyle}`}>
             {t(`${i18nKey}.badge`)}
             {t(`${i18nKey}.badge`)}
           </span>
           </span>
         ),
         ),
@@ -106,9 +105,9 @@ export const DecorationTab: React.FC = () => {
       {
       {
         id: 'text-color',
         id: 'text-color',
         title: t(`${i18nKey}.text_color`),
         title: t(`${i18nKey}.text_color`),
-        code: `<p class="text-${styleConfig.colorName}" >${t(`${i18nKey}.placeholder`)}</p>`,
+        code: `<p class="text-${currentStyle}">${t(`${i18nKey}.placeholder`)}</p>`,
         underContent: (
         underContent: (
-          <p className={`text-${styleConfig.colorName} m-0`}>
+          <p className={`text-${currentStyle} m-0`}>
             {t(`${i18nKey}.placeholder`)}
             {t(`${i18nKey}.placeholder`)}
           </p>
           </p>
         ),
         ),
@@ -116,9 +115,9 @@ export const DecorationTab: React.FC = () => {
       {
       {
         id: 'back-color',
         id: 'back-color',
         title: t(`${i18nKey}.back_color`),
         title: t(`${i18nKey}.back_color`),
-        code: `<p class="text-bg-${styleConfig.colorName}">${t(`${i18nKey}.placeholder`)}</p>`,
+        code: `<p class="text-bg-${currentStyle}">${t(`${i18nKey}.placeholder`)}</p>`,
         underContent: (
         underContent: (
-          <p className={`text-bg-${styleConfig.colorName} px-2 m-0`}>
+          <p className={`text-bg-${currentStyle} px-2 m-0`}>
             {t(`${i18nKey}.placeholder`)}
             {t(`${i18nKey}.placeholder`)}
           </p>
           </p>
         ),
         ),
@@ -126,15 +125,15 @@ export const DecorationTab: React.FC = () => {
       {
       {
         id: 'alert-block',
         id: 'alert-block',
         title: t(`${i18nKey}.alert_block`),
         title: t(`${i18nKey}.alert_block`),
-        code: `<div class="alert alert-${styleConfig.colorName}" role="alert">\n  ${t(`${i18nKey}.placeholder`)}\n</div>`,
+        code: `<div class="alert alert-${currentStyle}" role="alert">\n  ${t(`${i18nKey}.placeholder`)}\n</div>`,
         underContent: (
         underContent: (
-          <div className={`alert alert-${styleConfig.colorName} m-0`}>
+          <div className={`alert alert-${currentStyle} m-0`}>
             {t(`${i18nKey}.placeholder`)}
             {t(`${i18nKey}.placeholder`)}
           </div>
           </div>
         ),
         ),
       },
       },
     ],
     ],
-    [styleConfig, t],
+    [currentStyle, t, previewOptions, calloutConfig.calloutType],
   );
   );
 
 
   return (
   return (
@@ -146,33 +145,28 @@ export const DecorationTab: React.FC = () => {
             outline
             outline
             color="body"
             color="body"
             caret
             caret
-            className={`border d-flex align-items-center gap-2 text-${styleConfig.colorName === 'light' ? 'dark' : styleConfig.colorName}`}
+            className={`border d-flex align-items-center gap-2 text-${currentStyle}`}
             style={{ minWidth: '160px' }}
             style={{ minWidth: '160px' }}
           >
           >
-            <span className="material-symbols-outlined align-middle fs-6">
-              {styleConfig.iconName}
+            <span className="flex-grow-1 justify-content-start d-flex align-items-center gap-1">
+              <span className="material-symbols-outlined align-middle fs-6">
+                {calloutConfig.icon}
+              </span>
+              {displayName}
             </span>
             </span>
-            <span className="flex-grow-1">{styleConfig.displayName}</span>
           </DropdownToggle>
           </DropdownToggle>
           <DropdownMenu className={styles.dropdownMenu}>
           <DropdownMenu className={styles.dropdownMenu}>
-            {BOOTSTRAP_COLORS.map((color) => (
+            {CALLOUT_STYLES.map((style) => (
               <DropdownItem
               <DropdownItem
-                key={color}
-                className={`d-flex align-items-center gap-2 ${currentStyle === color ? 'active' : ''}`}
-                onClick={() => setCurrentStyle(color)}
-                style={
-                  currentStyle === color
-                    ? {
-                        backgroundColor: `var(--bs-${color})`,
-                        color: color === 'light' ? 'black' : 'white',
-                      }
-                    : {}
-                }
+                key={style}
+                active={currentStyle === style}
+                className="d-flex align-items-center gap-2"
+                onClick={() => setCurrentStyle(style)}
               >
               >
                 <span className="material-symbols-outlined">
                 <span className="material-symbols-outlined">
-                  {colorConfigs[color].icon}
+                  {BOOTSTRAP_STYLES_TO_CALLOUT_CONFIGS_MAPPINGS[style].icon}
                 </span>
                 </span>
-                {color.charAt(0).toUpperCase() + color.slice(1)}
+                {style.charAt(0).toUpperCase() + style.slice(1)}
               </DropdownItem>
               </DropdownItem>
             ))}
             ))}
           </DropdownMenu>
           </DropdownMenu>
@@ -181,7 +175,7 @@ export const DecorationTab: React.FC = () => {
 
 
       <hr />
       <hr />
 
 
-      <div key={currentStyle}>
+      <div key={currentStyle} className={styles.decorationBody}>
         {LAYOUT_GUIDES.map((item) => (
         {LAYOUT_GUIDES.map((item) => (
           <GuideRow key={item.id} {...item} minWidth="280px" />
           <GuideRow key={item.id} {...item} minWidth="280px" />
         ))}
         ))}