Browse Source

Merge pull request #10147 from weseek/imprv/plantuml-dark-mode

imprv: Add dark mode support to PlantUML rendering
mergify[bot] 9 months ago
parent
commit
1cc0041da5

+ 10 - 10
apps/app/src/client/services/renderer/renderer.tsx

@@ -22,14 +22,14 @@ import { RichAttachment } from '~/client/components/ReactMarkdownComponents/Rich
 import { TableWithEditButton } from '~/client/components/ReactMarkdownComponents/TableWithEditButton';
 import * as callout from '~/features/callout';
 import * as mermaid from '~/features/mermaid';
+import * as plantuml from '~/features/plantuml';
 import type { RendererOptions } from '~/interfaces/renderer-options';
-import type { RendererConfig } from '~/interfaces/services/renderer';
+import { type RendererConfigExt } from '~/interfaces/services/renderer';
 import * as addLineNumberAttribute from '~/services/renderer/rehype-plugins/add-line-number-attribute';
 import * as keywordHighlighter from '~/services/renderer/rehype-plugins/keyword-highlighter';
 import * as relocateToc from '~/services/renderer/rehype-plugins/relocate-toc';
 import * as attachment from '~/services/renderer/remark-plugins/attachment';
 import * as codeBlock from '~/services/renderer/remark-plugins/codeblock';
-import * as plantuml from '~/services/renderer/remark-plugins/plantuml';
 import * as xsvToTable from '~/services/renderer/remark-plugins/xsv-to-table';
 import {
   getCommonSanitizeOption, generateCommonOptions, verifySanitizePlugin,
@@ -51,7 +51,7 @@ assert(isClient(), 'This module must be loaded only from client modules.');
 
 export const generateViewOptions = (
     pagePath: string,
-    config: RendererConfig,
+    config: RendererConfigExt,
     storeTocNode: (toc: HtmlElementNode) => void,
 ): RendererOptions => {
 
@@ -62,7 +62,7 @@ export const generateViewOptions = (
   // add remark plugins
   remarkPlugins.push(
     math,
-    [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri }],
+    [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri, isDarkMode: config.isDarkMode }],
     drawio.remarkPlugin,
     mermaid.remarkPlugin,
     xsvToTable.remarkPlugin,
@@ -128,7 +128,7 @@ export const generateViewOptions = (
   return options;
 };
 
-export const generateTocOptions = (config: RendererConfig, tocNode: HtmlElementNode | undefined): RendererOptions => {
+export const generateTocOptions = (config: RendererConfigExt, tocNode: HtmlElementNode | undefined): RendererOptions => {
 
   const options = generateCommonOptions(undefined);
 
@@ -158,7 +158,7 @@ export const generateTocOptions = (config: RendererConfig, tocNode: HtmlElementN
 };
 
 export const generateSimpleViewOptions = (
-    config: RendererConfig,
+    config: RendererConfigExt,
     pagePath: string,
     highlightKeywords?: string | string[],
     overrideIsEnabledLinebreaks?: boolean,
@@ -170,7 +170,7 @@ export const generateSimpleViewOptions = (
   // add remark plugins
   remarkPlugins.push(
     math,
-    [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri }],
+    [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri, isDarkMode: config.isDarkMode }],
     drawio.remarkPlugin,
     mermaid.remarkPlugin,
     xsvToTable.remarkPlugin,
@@ -232,7 +232,7 @@ export const generateSimpleViewOptions = (
 };
 
 export const generatePresentationViewOptions = (
-    config: RendererConfig,
+    config: RendererConfigExt,
     pagePath: string,
 ): RendererOptions => {
   // based on simple view options
@@ -259,7 +259,7 @@ export const generatePresentationViewOptions = (
   return options;
 };
 
-export const generatePreviewOptions = (config: RendererConfig, pagePath: string): RendererOptions => {
+export const generatePreviewOptions = (config: RendererConfigExt, pagePath: string): RendererOptions => {
   const options = generateCommonOptions(pagePath);
 
   const { remarkPlugins, rehypePlugins, components } = options;
@@ -267,7 +267,7 @@ export const generatePreviewOptions = (config: RendererConfig, pagePath: string)
   // add remark plugins
   remarkPlugins.push(
     math,
-    [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri }],
+    [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri, isDarkMode: config.isDarkMode }],
     drawio.remarkPlugin,
     mermaid.remarkPlugin,
     xsvToTable.remarkPlugin,

+ 1 - 0
apps/app/src/features/plantuml/index.ts

@@ -0,0 +1 @@
+export * from './services';

+ 1 - 0
apps/app/src/features/plantuml/services/index.ts

@@ -0,0 +1 @@
+export { remarkPlugin } from './plantuml';

+ 31 - 0
apps/app/src/features/plantuml/services/plantuml.ts

@@ -0,0 +1,31 @@
+import plantuml from '@akebifiky/remark-simple-plantuml';
+import type { Code } from 'mdast';
+import type { Plugin } from 'unified';
+import { visit } from 'unist-util-visit';
+import urljoin from 'url-join';
+
+import carbonGrayDarkStyles from '../themes/carbon-gray-dark.puml';
+import carbonGrayLightStyles from '../themes/carbon-gray-light.puml';
+
+type PlantUMLPluginParams = {
+  plantumlUri: string,
+  isDarkMode?: boolean,
+}
+
+export const remarkPlugin: Plugin<[PlantUMLPluginParams]> = (options) => {
+  const { plantumlUri, isDarkMode } = options;
+
+  const baseUrl = urljoin(plantumlUri, '/svg');
+  const simplePlantumlPlugin = plantuml.bind(this)({ baseUrl });
+
+  return (tree, file) => {
+    visit(tree, 'code', (node: Code) => {
+      if (node.lang === 'plantuml') {
+        const themeStyles = isDarkMode ? carbonGrayDarkStyles : carbonGrayLightStyles;
+        node.value = `${themeStyles}\n${node.value}`;
+      }
+    });
+
+    simplePlantumlPlugin(tree, file);
+  };
+};

+ 8 - 0
apps/app/src/features/plantuml/themes/.eslintrc.js

@@ -0,0 +1,8 @@
+/**
+ * @type {import('eslint').Linter.Config}
+ */
+module.exports = {
+  ignorePatterns: [
+    '*.puml.ts',
+  ],
+};

+ 688 - 0
apps/app/src/features/plantuml/themes/carbon-gray-common.puml.ts

@@ -0,0 +1,688 @@
+/**
+ * ---
+ * name: growi-carbon-gray
+ * display_name: GROWI Carbon Gray
+ * description: A gray theme using the IBM Carbon Design Gray palette for GROWI
+ * author: Yuki Takei
+ * release:
+ * license:
+ * version:
+ * source:
+ * inspiration: https://carbondesignsystem.com/elements/color/overview/
+ * ---
+ */
+const style = `
+!$LINE_THICKNESS = 1
+!$BORDER_THICKNESS = 1
+
+!procedure $success($msg)
+  <font color=$SUCCESS><b>$msg
+!endprocedure
+
+!procedure $failure($msg)
+  <font color=$DANGER><b>$msg
+!endprocedure
+
+!procedure $warning($msg)
+  <font color=$WARNING><b>$msg
+!endprocedure
+
+!procedure $primary_scheme()
+	FontColor $PRIMARY_TEXT
+	BorderColor $PRIMARY_DARK
+	BackgroundColor $PRIMARY_LIGHT-$PRIMARY
+	RoundCorner 0
+!endprocedure
+
+''
+'' Global Default Values
+''
+skinparam defaultFontName        "IBM Plex Sans, Noto Sans, Verdana"
+skinparam defaultFontSize        12
+'skinparam dpi                    125
+skinparam shadowing              false
+skinparam roundcorner            0
+skinparam ParticipantPadding     30
+skinparam BoxPadding             30
+skinparam Padding                10
+skinparam ArrowColor             $GRAY
+skinparam stereotype {
+    CBackgroundColor $SECONDARY_LIGHT
+    CBorderColor $SECONDARY_DARK
+    ABackgroundColor $SUCCESS_LIGHT
+    ABorderColor $SUCCESS_DARK
+    IBackgroundColor $DANGER_LIGHT
+    IBorderColor $DANGER_DARK
+    EBackgroundColor $WARNING_LIGHT
+    EBorderColor $WARNING_DARK
+    NBackgroundColor $INFO_LIGHT
+    NBorderColor $INFO_DARK
+}
+skinparam title {
+	FontColor	                 $SECONDARY_TEXT
+	BorderColor	                 $SECONDARY
+	FontSize	    	         20
+	BorderRoundCorner            8
+	BorderThickness 	         0
+	BackgroundColor              $BGCOLOR
+}
+
+
+skinparam legend {
+	BackgroundColor $OTHER_BG
+	BorderColor $DARK
+	FontColor $PRIMARY_TEXT
+}
+
+!startsub swimlane
+skinparam swimlane {
+	BorderColor $PRIMARY
+	BorderThickness $LINE_THICKNESS
+	TitleBackgroundColor  $PRIMARY_LIGHT-$PRIMARY
+	TitleFontColor $PRIMARY_TEXT
+	BackgroundColor $BG_COLOR
+	TitleFontStyle bold
+}
+!endsub
+
+!startsub activity
+
+skinparam activity {
+	$primary_scheme()
+	BarColor $DARK
+	StartColor $LIGHT-$DARK
+	EndColor $LIGHT-$DARK
+	''
+	DiamondBackgroundColor $SECONDARY_LIGHT-$SECONDARY
+  	DiamondBorderColor $SECONDARY
+	DiamondFontColor $SECONDARY_TEXT
+}
+!endsub
+
+!startsub participant
+
+skinparam participant {
+	$primary_scheme()
+	ParticipantBorderThickness $BORDER_THICKNESS
+}
+!endsub
+
+!startsub actor
+
+skinparam actor {
+	FontColor $PRIMARY_TEXT
+	BorderColor $GRAY_50
+	BackgroundColor $PRIMARY
+	RoundCorner 0
+}
+!endsub
+
+!startsub arrow
+
+skinparam arrow {
+	Thickness $LINE_THICKNESS
+	Color $GRAY
+	FontColor $FGCOLOR
+}
+!endsub
+
+!startsub sequence
+
+skinparam sequence {
+	BorderColor $PRIMARY_DARK
+	' For some reason sequence title font color does not pick up from global
+	TitleFontColor $SECONDARY_TEXT
+	BackgroundColor $OTHER_BG
+	StartColor $PRIMARY
+	EndColor $PRIMARY
+	''
+	BoxBackgroundColor $OTHER_BG
+	BoxBorderColor $PRIMARY_DARK
+	BoxFontColor $PRIMARY_TEXT
+	''
+	DelayFontColor $PRIMARY_TEXT
+	''
+	LifeLineBorderColor $PRIMARY_DARK
+	LifeLineBorderThickness $LINE_THICKNESS
+	LifeLineBackgroundColor $PRIMARY
+	''
+	GroupBorderColor $PRIMARY_DARK
+	GroupFontColor $PRIMARY_TEXT
+	GroupFontStyle bold
+	GroupHeaderFontColor $INFO_TEXT
+	GroupBackgroundColor $PRIMARY
+	GroupBodyBackgroundColor $OTHER_BG
+	GroupHeaderBackgroundColor $PRIMARY
+	''
+	DividerBackgroundColor $PRIMARY
+    DividerBorderColor $PRIMARY_DARK
+    DividerBorderThickness $LINE_THICKNESS
+    DividerFontColor $PRIMARY_TEXT
+	''
+	ReferenceBackgroundColor $BGCOLOR
+	ReferenceHeaderBorderColor $PRIMARY_DARK
+	ReferenceHeaderBackgroundColor $PRIMARY
+	ReferenceBorderColor $PRIMARY_DARK
+	ReferenceFontColor $DARK
+	ReferenceHeaderFontColor $INFO_TEXT
+	''
+	StereotypeFontColor $PRIMARY_TEXT
+}
+!endsub
+
+!startsub partition
+
+skinparam partition {
+	BorderColor $PRIMARY
+	FontColor $PRIMARY_TEXT
+	BackgroundColor $OTHER_BG
+	fontStyle bold
+}
+!endsub
+
+!startsub collections
+
+skinparam collections {
+	$primary_scheme()
+}
+!endsub
+
+!startsub control
+
+skinparam control {
+	$primary_scheme()
+}
+!endsub
+
+!startsub entity
+
+skinparam entity {
+	$primary_scheme()
+}
+!endsub
+
+!startsub boundary
+
+skinparam boundary {
+	$primary_scheme()
+}
+!endsub
+
+!startsub agent
+
+skinparam agent {
+	BackgroundColor $PRIMARY_LIGHT
+	BorderColor $PRIMARY_DARK
+	FontColor $PRIMARY_TEXT
+	RoundCorner 0
+}
+!endsub
+
+!startsub note
+
+skinparam note {
+	BorderThickness 1
+	BackgroundColor $INFO_LIGHT-$INFO
+	BorderColor $DARK
+	FontColor $INFO_TEXT
+	RoundCorner 0
+}
+!endsub
+
+!startsub artifact
+
+skinparam artifact {
+	BackgroundColor $PRIMARY
+	BorderColor $PRIMARY_DARK
+	FontColor $DARK
+	RoundCorner 0
+}
+!endsub
+
+!startsub component
+
+skinparam component {
+	$primary_scheme()
+	BackgroundColor $PRIMARY
+	BorderColor $PRIMARY_DARK
+}
+!endsub
+
+!startsub interface
+
+skinparam interface {
+	BackgroundColor  $PRIMARY_DARK
+	BorderColor  $PRIMARY_DARK
+	FontColor $PRIMARY_TEXT
+}
+!endsub
+
+!startsub storage
+
+skinparam storage {
+	BackgroundColor $OTHER_BG
+  	BorderColor $DARK
+	FontColor $WARNING_TEXT
+}
+!endsub
+
+!startsub node
+
+skinparam node {
+	BackgroundColor $OTHER_BG
+	BorderColor $PRIMARY_DARK
+	FontColor $PRIMARY_TEXT
+	Roundcorner 0
+}
+!endsub
+
+!startsub cloud
+
+skinparam cloud {
+	BackgroundColor $OTHER_BG
+	BorderColor $PRIMARY_DARK
+	FontColor $PRIMARY_TEXT
+	Roundcorner 0
+}
+!endsub
+
+!startsub database
+
+skinparam database {
+	$primary_scheme()
+	BorderColor $PRIMARY_DARK
+	BackgroundColor  $OTHER_BG
+	Roundcorner 0
+}
+!endsub
+
+!startsub class
+
+skinparam class {
+	$primary_scheme()
+	HeaderBackgroundColor $PRIMARY_LIGHT-$PRIMARY
+	StereotypeFontColor $PRIMARY_TEXT
+	StereotypeFontSize 9
+	BorderThickness $LINE_THICKNESS
+	AttributeFontColor $PRIMARY_TEXT
+	AttributeFontSize 11
+}
+!endsub
+
+!startsub object
+
+skinparam object {
+	$primary_scheme()
+	StereotypeFontColor $PRIMARY_TEXT
+	BorderThickness $BORDER_THICKNESS
+	AttributeFontColor $PRIMARY_TEXT
+	AttributeFontSize 11
+}
+!endsub
+
+!startsub usecase
+
+skinparam usecase {
+	$primary_scheme()
+	BorderThickness $BORDER_THICKNESS
+	StereotypeFontColor $PRIMARY_TEXT
+}
+!endsub
+
+!startsub rectangle
+
+skinparam rectangle {
+	$primary_scheme()
+	BackgroundColor $OTHER_BG
+	BorderThickness $BORDER_THICKNESS
+	StereotypeFontColor $PRIMARY_TEXT
+}
+!endsub
+
+!startsub package
+
+skinparam package {
+	$primary_scheme()
+	BackgroundColor $OTHER_BG
+	BorderThickness $BORDER_THICKNESS
+}
+!endsub
+
+!startsub folder
+
+skinparam folder {
+	BackgroundColor $OTHER_BG
+  	BorderColor $PRIMARY_DARK
+	FontColor $WARNING_TEXT
+	BorderThickness $BORDER_THICKNESS
+	Roundcorner 0
+}
+!endsub
+
+!startsub frame
+
+skinparam frame {
+	BackgroundColor $OTHER_BG
+  	BorderColor $PRIMARY_DARK
+	FontColor $DARK
+	BorderThickness $BORDER_THICKNESS
+	Roundcorner 0
+}
+!endsub
+
+!startsub state
+
+skinparam state {
+	$primary_scheme()
+	BorderColor $PRIMARY_DARK
+	StartColor $INFO
+	EndColor $INFO
+	AttributeFontColor $SECONDARY_TEXT
+	AttributeFontSize 11
+}
+!endsub
+
+!startsub queue
+
+skinparam queue {
+	$primary_scheme()
+
+}
+!endsub
+
+!startsub card
+
+skinparam card {
+	BackgroundColor $OTHER_BG
+	BorderColor $PRIMARY_DARK
+	FontColor $INFO_TEXT
+	RoundCorner 0
+}
+!endsub
+
+!startsub file
+
+skinparam file {
+	BackgroundColor $SECONDARY_LIGHT-$SECONDARY
+	BorderColor $SECONDARY_DARK
+	FontColor $SECONDARY_TEXT
+	RoundCorner 0
+
+}
+!endsub
+
+!startsub stack
+
+skinparam stack {
+	$primary_scheme()
+}
+!endsub
+
+!startsub person
+
+skinparam person {
+	$primary_scheme()
+}
+!endsub
+
+
+!if (%variable_exists("LEGACY"))
+!$LEGACY = "true"
+!endif
+
+!if (%getenv("LEGACY") == "true")
+!$LEGACY = "true"
+!endif
+
+'!if (not %variable_exists("$LEGACY"))
+
+skinparam useBetaStyle true
+
+!startsub mindmap
+
+<style>
+
+boardDiagram {
+	node {
+	$primary_scheme()
+    BackGroundColor  $PRIMARY
+    LineColor $PRIMARY_DARK
+    FontName "IBM Plex Sans, Noto Sans, Verdana"
+    'FontColor
+    FontSize 12
+    'FontStyle bold
+    RoundCorner 0
+    'LineThickness 2
+    'LineStyle 10-5
+    separator {
+      LineThickness $LINE_THICKNESS
+      LineColor $PRIMARY_DARK
+      'LineStyle 1-5
+    }
+  }
+}
+
+ganttDiagram {
+
+  task {
+    BackGroundColor $PRIMARY
+    LineColor $PRIMARY_DARK
+	FontStyle Bold
+	FontSize 12
+    unstarted {
+      BackGroundColor $PRIMARY_LIGHT
+      LineColor $PRIMARY_DARK
+	  'FontColor $RED_80
+    }
+	Padding 3
+	Margin 3
+
+  }
+  timeline {
+	LineColor $PRIMARY
+	FontColor !$OTHER_TEXT
+	BackgroundColor $DARK
+    FontName Helvetica
+    'FontSize 12
+    FontStyle bold
+	YearFontColor !$OTHER_TEXT
+	QuarterFontColor !$OTHER_TEXT
+	MonthFontColor !$OTHER_TEXT
+	WeekFontColor !$OTHER_TEXT
+	WeekdayFontColor !$OTHER_TEXT
+	DayFontColor !$OTHER_TEXT
+  }
+  arrow {
+		'FontName Helvetica
+		'FontColor red
+		FontSize 12
+		FontStyle bold
+		'BackGroundColor GreenYellow
+		LineColor $PRIMARY_DARK
+		'LineStyle 8.0-13.0
+		'LineThickness 3.0
+	}
+
+  milestone {
+		FontColor $PRIMARY_TEXT
+		FontSize 12
+		FontStyle  bold
+		BackGroundColor $DARK
+		LineColor $DARK
+	}
+  separator {
+		BackgroundColor $PRIMARY
+		'LineStyle 8.0-3.0
+		LineColor $PRIMARY
+		LineThickness 1.0
+		FontSize 12
+		FontStyle bold
+		FontColor  $PRIMARY_TEXT
+		Margin 3
+		'Padding 20
+	}
+	closed {
+		BackgroundColor $RED_20
+		FontColor $RED_20
+	}
+}
+
+jsonDiagram {
+  node {
+	$primary_scheme()
+    BackGroundColor  $PRIMARY
+    LineColor $PRIMARY_DARK
+    FontName "IBM Plex Sans, Noto Sans, Verdana"
+    'FontColor
+    FontSize 12
+    'FontStyle bold
+    RoundCorner 0
+    'LineThickness 2
+    'LineStyle 10-5
+    separator {
+      LineThickness $LINE_THICKNESS
+      LineColor $PRIMARY_DARK
+      'LineStyle 1-5
+    }
+  }
+  arrow {
+    BackGroundColor $PRIMARY_DARK
+    LineColor $PRIMARY_DARK
+    LineThickness $LINE_THICKNESS
+    LineStyle 3-6
+  }
+  highlight {
+    BackGroundColor $PRIMARY_DARK
+    FontColor $PRIMARY_TEXT
+    FontStyle italic
+  }
+}
+
+
+mindmapDiagram {
+  'Padding 8
+  'Margin 8
+  LineThickness $LINE_THICKNESS
+  FontColor $PRIMARY_TEXT
+  LineColor $PRIMARY_DARK
+  'BackgroundColor $PRIMARY_LIGHT-$PRIMARY
+  Roundcorner 0
+  node {
+    'Padding 12
+    'Margin 3
+    'HorizontalAlignment center
+    LineColor $PRIMARY_DARK
+    LineThickness $BORDER_THICKNESS
+    BackgroundColor $PRIMARY_LIGHT-$PRIMARY
+    RoundCorner 0
+    MaximumWidth 100
+	FontColor $DARK
+	'FontStyle bold
+  }
+}
+
+'Salt Diagram only has limited skinning
+saltDiagram {
+  BackGroundColor $BG_COLOR
+  'Fontname Monospaced
+  'FontSize 10
+  'FontStyle italic
+  'LineThickness 0.5
+  'LineColor PRIMARY_DARK
+}
+
+timingDiagram {
+  document {
+    BackGroundColor $BG_COLOR
+	LineColor $PRIMARY
+	BorderColor $PRIMARY
+	FontColor $PRIMARY_TEXT
+
+  }
+  highlight {
+	 BackGroundColor $PRIMARY
+  }
+
+ constraintArrow {
+  'LineStyle 2-1
+  LineThickness 2
+  LineColor $RED_80
+  FontColor $RED_80
+  FontStyle bold
+ }
+}
+
+wbsDiagram {
+  'Padding 8
+  node {
+    'Padding 12
+    'Margin 3
+    'HorizontalAlignment center
+    LineColor $PRIMARY_DARK
+    LineThickness $BORDER_THICKNESS
+    BackgroundColor $PRIMARY_LIGHT-$PRIMARY
+    RoundCorner 0
+    MaximumWidth 100
+	FontColor $PRIMARY_TEXT
+	'FontStyle bold
+  }
+  'Margin 8
+  'LineThickness $LINE_THICKNESS
+  'FontColor $PRIMARY_TEXT
+  'LineColor $PRIMARY
+  'BorderColor $PRIMARY
+  'BackgroundColor $PRIMARY_LIGHT-$PRIMARY
+  'RoundCorner 0
+
+  arrow {
+	' note that Connectors are actually "Arrows"; this may change in the future
+	' so this means all Connectors and Arrows are now going to be green
+
+    lineColor $PRIMARY_DARK
+    fontColor $PRIMARY_TEXT
+    thickness $LINE_THICKNESS
+  }
+
+  noteBorderColor $DARK
+}
+
+'Placeholder for adding wirediagram skins
+wireDiagram {
+}
+
+yamlDiagram {
+  node {
+	$primary_scheme()
+    BackGroundColor  $PRIMARY
+    LineColor $PRIMARY_DARK
+    FontName "IBM Plex Sans, Noto Sans, Verdana"
+    'FontColor
+    FontSize 12
+    'FontStyle bold
+    RoundCorner 0
+    'LineThickness 2
+    'LineStyle 10-5
+    separator {
+      LineThickness $LINE_THICKNESS
+      LineColor $PRIMARY_DARK
+      'LineStyle 1-5
+    }
+  }
+  arrow {
+    BackGroundColor $PRIMARY_DARK
+    LineColor $PRIMARY_DARK
+    LineThickness $LINE_THICKNESS
+    LineStyle 3-6
+  }
+  highlight {
+    BackGroundColor $PRIMARY_DARK
+    FontColor $PRIMARY_TEXT
+    FontStyle italic
+  }
+}
+</style>
+!endsub
+'!endif
+`;
+
+export default style;

+ 118 - 0
apps/app/src/features/plantuml/themes/carbon-gray-dark.puml.ts

@@ -0,0 +1,118 @@
+import commonStyles from './carbon-gray-common.puml';
+
+const style = `
+---
+name: growi-carbon-gray-dark
+display_name: GROWI Carbon Gray
+description: A gray theme using the IBM Carbon Design Gray palette for GROWI dark themes
+author: Yuki Takei
+release:
+license:
+version:
+source:
+inspiration: https://carbondesignsystem.com/elements/color/overview/
+---
+
+!$THEME = "growi-carbon-gray-dark"
+
+'!$BGCOLOR = "#f5f5f5"
+
+!if %not(%variable_exists("$BGCOLOR"))
+!$BGCOLOR = "transparent"
+!endif
+
+skinparam backgroundColor $BGCOLOR
+skinparam useBetaStyle false
+
+!$RED_80 = '#750e13'
+!$RED_70 = '#a2191f'
+!$RED_60 = '#da1e28'
+!$RED_50 = '#fa4d56'
+!$RED_40 = '#ff8389'
+!$RED_30 = '#ffb3b8'
+!$RED_20 = '#ffd7d9'
+!$RED_10 =  '#fff1f1'
+
+!$CYAN_10 = '#e5f6ff'
+!$CYAN_20 = '#bae6ff'
+!$CYAN_30 = '#82cfff'
+!$CYAN_40 = '#33b1ff'
+!$CYAN_50 = '#1192e8'
+!$CYAN_60 = '#0072c3'
+!$CYAN_70 = '#00539a'
+!$CYAN_80 = '#003a6d'
+
+
+!$PURPLE_80 ='#491d8b'
+!$PURPLE_70 = '#6929c4'
+!$PURPLE_60 = '#8a3ffc'
+!$PURPLE_50 = '#a56eff'
+!$PURPLE_40 = '#be95ff'
+!$PURPLE_30 = '#d4bbff'
+!$PURPLE_20 = '#e8daff'
+!$PURPLE_10 = '#f6f2ff'
+
+!$TEAL_10 = '#d9fbfb'
+!$TEAL_20 = '#9ef0f0'
+!$TEAL_30 = '#3ddbd9'
+!$TEAL_40 = '#08bdba'
+!$TEAL_50 = '#009d9a'
+!$TEAL_60 = '#007d79'
+!$TEAL_70 = '#005d5d'
+!$TEAL_80 = '#004144'
+
+!$GRAY_10 = '#f4f4f4'
+!$GRAY_20 = '#e0e0e0'
+!$GRAY_30 = '#c6c6c6'
+!$GRAY_40 = '#a8a8a8'
+!$GRAY_50 = '#8d8d8d'
+!$GRAY_60 = '#6f6f6f'
+!$GRAY_70 = '#525252'
+!$GRAY_80 = '#393939'
+!$GRAY_90 = '#262626'
+!$GRAY_100 = '#161616'
+!$BLACK = '#000000'
+
+!$GRAY = $GRAY_30
+!$LIGHT = $GRAY_30
+!$DARK = $GRAY_90
+
+'' *_LIGHT = tint (lighter) of the main color of 80%
+''          where TINT is calculated by clr + (255-clr) * tint_factor
+'' *_DARK = shade (darker) of the main color of 80%
+''          and SHADE is calculated by clr * (1 - shade_factor)
+''
+!$FGCOLOR = $DARK
+!$PRIMARY = $GRAY_90
+!$PRIMARY_LIGHT = $GRAY_90
+!$PRIMARY_DARK = $GRAY_70
+!$PRIMARY_TEXT = $LIGHT
+!$SECONDARY = $GRAY_90
+!$SECONDARY_LIGHT = $GRAY_90
+!$SECONDARY_DARK = $GRAY_70
+!$SECONDARY_TEXT = $LIGHT
+!$INFO = $BLACK
+!$INFO_LIGHT = $GRAY_80
+!$INFO_DARK = $GRAY_70
+!$INFO_TEXT = $LIGHT
+!$SUCCESS = $LIGHT
+!$SUCCESS_LIGHT = $LIGHT
+!$SUCCESS_DARK = $LIGHT
+!$SUCCESS_TEXT = $BLACK
+!$WARNING = $BLACK
+!$WARNING_LIGHT = $BLACK
+!$WARNING_DARK = $BLACK
+!$WARNING_TEXT = $LIGHT
+!$DANGER = $BLACK
+!$DANGER_LIGHT = $BLACK
+!$DANGER_DARK = $BLACK
+!$DANGER_TEXT = $LIGHT
+
+!$OTHER_BG = $BLACK
+!$OTHER_TEXT = $BLACK
+!$DB_BG = $GRAY
+
+${commonStyles}
+`;
+
+export default style;

+ 118 - 0
apps/app/src/features/plantuml/themes/carbon-gray-light.puml.ts

@@ -0,0 +1,118 @@
+import commonStyles from './carbon-gray-common.puml';
+
+const style = `
+---
+name: growi-carbon-gray-light
+display_name: GROWI Carbon Gray
+description: A gray theme using the IBM Carbon Design Gray palette for GROWI light themes
+author: Yuki Takei
+release:
+license:
+version:
+source:
+inspiration: https://carbondesignsystem.com/elements/color/overview/
+---
+
+!$THEME = "growi-carbon-gray-light"
+
+'!$BGCOLOR = "#f5f5f5"
+
+!if %not(%variable_exists("$BGCOLOR"))
+!$BGCOLOR = "transparent"
+!endif
+
+skinparam backgroundColor $BGCOLOR
+skinparam useBetaStyle false
+
+!$RED_80 = '#750e13'
+!$RED_70 = '#a2191f'
+!$RED_60 = '#da1e28'
+!$RED_50 = '#fa4d56'
+!$RED_40 = '#ff8389'
+!$RED_30 = '#ffb3b8'
+!$RED_20 = '#ffd7d9'
+!$RED_10 =  '#fff1f1'
+
+!$CYAN_10 = '#e5f6ff'
+!$CYAN_20 = '#bae6ff'
+!$CYAN_30 = '#82cfff'
+!$CYAN_40 = '#33b1ff'
+!$CYAN_50 = '#1192e8'
+!$CYAN_60 = '#0072c3'
+!$CYAN_70 = '#00539a'
+!$CYAN_80 = '#003a6d'
+
+
+!$PURPLE_80 ='#491d8b'
+!$PURPLE_70 = '#6929c4'
+!$PURPLE_60 = '#8a3ffc'
+!$PURPLE_50 = '#a56eff'
+!$PURPLE_40 = '#be95ff'
+!$PURPLE_30 = '#d4bbff'
+!$PURPLE_20 = '#e8daff'
+!$PURPLE_10 = '#f6f2ff'
+
+!$TEAL_10 = '#d9fbfb'
+!$TEAL_20 = '#9ef0f0'
+!$TEAL_30 = '#3ddbd9'
+!$TEAL_40 = '#08bdba'
+!$TEAL_50 = '#009d9a'
+!$TEAL_60 = '#007d79'
+!$TEAL_70 = '#005d5d'
+!$TEAL_80 = '#004144'
+
+!$GRAY_10 = '#f4f4f4'
+!$GRAY_20 = '#e0e0e0'
+!$GRAY_30 = '#c6c6c6'
+!$GRAY_40 = '#a8a8a8'
+!$GRAY_50 = '#8d8d8d'
+!$GRAY_60 = '#6f6f6f'
+!$GRAY_70 = '#525252'
+!$GRAY_80 = '#393939'
+!$GRAY_90 = '#262626'
+!$GRAY_100 = '#161616'
+!$WHITE = '#FFFFF'
+
+!$GRAY = $GRAY_30
+!$LIGHT = $GRAY_70
+!$DARK = $GRAY_90
+
+'' *_LIGHT = tint (lighter) of the main color of 80%
+''          where TINT is calculated by clr + (255-clr) * tint_factor
+'' *_DARK = shade (darker) of the main color of 80%
+''          and SHADE is calculated by clr * (1 - shade_factor)
+''
+!$FGCOLOR = $DARK
+!$PRIMARY = $GRAY_10
+!$PRIMARY_LIGHT = $GRAY_10
+!$PRIMARY_DARK = $GRAY_30
+!$PRIMARY_TEXT = $DARK
+!$SECONDARY = $GRAY_10
+!$SECONDARY_LIGHT = $GRAY_10
+!$SECONDARY_DARK = $GRAY_30
+!$SECONDARY_TEXT = $DARK
+!$INFO = $WHITE
+!$INFO_LIGHT = $GRAY_20
+!$INFO_DARK = $GRAY_30
+!$INFO_TEXT = $DARK
+!$SUCCESS = $DARK
+!$SUCCESS_LIGHT = $DARK
+!$SUCCESS_DARK = $DARK
+!$SUCCESS_TEXT = $WHITE
+!$WARNING = $WHITE
+!$WARNING_LIGHT = $WHITE
+!$WARNING_DARK = $WHITE
+!$WARNING_TEXT = $DARK
+!$DANGER = $WHITE
+!$DANGER_LIGHT = $WHITE
+!$DANGER_DARK = $WHITE
+!$DANGER_TEXT = $DARK
+
+!$OTHER_BG = $WHITE
+!$OTHER_TEXT = $WHITE
+!$DB_BG = $GRAY
+
+${commonStyles}
+`;
+
+export default style;

+ 4 - 0
apps/app/src/interfaces/services/renderer.ts

@@ -12,3 +12,7 @@ export type RendererConfig = {
   drawioUri: string,
   plantumlUri: string,
 } & RehypeSanitizeConfiguration;
+
+export type RendererConfigExt = RendererConfig & {
+  isDarkMode?: boolean,
+};

+ 0 - 15
apps/app/src/services/renderer/remark-plugins/plantuml.ts

@@ -1,15 +0,0 @@
-import plantuml from '@akebifiky/remark-simple-plantuml';
-import type { Plugin } from 'unified';
-import urljoin from 'url-join';
-
-type PlantUMLPluginParams = {
-  plantumlUri: string,
-}
-
-export const remarkPlugin: Plugin<[PlantUMLPluginParams]> = (options) => {
-  const plantumlUri = options.plantumlUri;
-
-  const baseUrl = urljoin(plantumlUri, '/svg');
-
-  return plantuml.bind(this)({ baseUrl });
-};

+ 21 - 10
apps/app/src/stores/renderer.tsx

@@ -5,17 +5,28 @@ import useSWR, { type SWRConfiguration, type SWRResponse } from 'swr';
 
 import { getGrowiFacade } from '~/features/growi-plugin/client/utils/growi-facade-utils';
 import type { RendererOptions } from '~/interfaces/renderer-options';
-import {
-  useRendererConfig,
-} from '~/stores-universal/context';
+import type { RendererConfigExt } from '~/interfaces/services/renderer';
+import { useRendererConfig } from '~/stores-universal/context';
+import { useNextThemes } from '~/stores-universal/use-next-themes';
 
 import { useCurrentPagePath } from './page';
 import { useCurrentPageTocNode } from './ui';
 
 
+const useRendererConfigExt = (): RendererConfigExt | null => {
+  const { data: rendererConfig } = useRendererConfig();
+  const { isDarkMode } = useNextThemes();
+
+  return rendererConfig == null ? null : {
+    ...rendererConfig,
+    isDarkMode,
+  } satisfies RendererConfigExt;
+};
+
+
 export const useViewOptions = (): SWRResponse<RendererOptions, Error> => {
   const { data: currentPagePath } = useCurrentPagePath();
-  const { data: rendererConfig } = useRendererConfig();
+  const rendererConfig = useRendererConfigExt();
   const { mutate: mutateCurrentPageTocNode } = useCurrentPageTocNode();
 
   const storeTocNodeHandler = useCallback((toc: HtmlElementNode) => {
@@ -47,7 +58,7 @@ export const useViewOptions = (): SWRResponse<RendererOptions, Error> => {
 
 export const useTocOptions = (): SWRResponse<RendererOptions, Error> => {
   const { data: currentPagePath } = useCurrentPagePath();
-  const { data: rendererConfig } = useRendererConfig();
+  const rendererConfig = useRendererConfigExt();
   const { data: tocNode } = useCurrentPageTocNode();
 
   const isAllDataValid = currentPagePath != null && rendererConfig != null && tocNode != null;
@@ -70,7 +81,7 @@ export const useTocOptions = (): SWRResponse<RendererOptions, Error> => {
 
 export const usePreviewOptions = (): SWRResponse<RendererOptions, Error> => {
   const { data: currentPagePath } = useCurrentPagePath();
-  const { data: rendererConfig } = useRendererConfig();
+  const rendererConfig = useRendererConfigExt();
 
   const isAllDataValid = currentPagePath != null && rendererConfig != null;
   const customGenerater = getGrowiFacade().markdownRenderer?.optionsGenerators?.customGeneratePreviewOptions;
@@ -97,7 +108,7 @@ export const usePreviewOptions = (): SWRResponse<RendererOptions, Error> => {
 
 export const useCommentForCurrentPageOptions = (): SWRResponse<RendererOptions, Error> => {
   const { data: currentPagePath } = useCurrentPagePath();
-  const { data: rendererConfig } = useRendererConfig();
+  const rendererConfig = useRendererConfigExt();
 
   const isAllDataValid = currentPagePath != null && rendererConfig != null;
 
@@ -124,7 +135,7 @@ export const useCommentForCurrentPageOptions = (): SWRResponse<RendererOptions,
 export const useCommentPreviewOptions = useCommentForCurrentPageOptions;
 
 export const useSelectedPagePreviewOptions = (pagePath: string, highlightKeywords?: string | string[]): SWRResponse<RendererOptions, Error> => {
-  const { data: rendererConfig } = useRendererConfig();
+  const rendererConfig = useRendererConfigExt();
 
   const isAllDataValid = rendererConfig != null;
 
@@ -147,7 +158,7 @@ export const useSearchResultOptions = useSelectedPagePreviewOptions;
 export const useTimelineOptions = useSelectedPagePreviewOptions;
 
 export const useCustomSidebarOptions = (config?: SWRConfiguration): SWRResponse<RendererOptions, Error> => {
-  const { data: rendererConfig } = useRendererConfig();
+  const rendererConfig = useRendererConfigExt();
 
   const isAllDataValid = rendererConfig != null;
 
@@ -170,7 +181,7 @@ export const useCustomSidebarOptions = (config?: SWRConfiguration): SWRResponse<
 
 export const usePresentationViewOptions = (): SWRResponse<RendererOptions, Error> => {
   const { data: currentPagePath } = useCurrentPagePath();
-  const { data: rendererConfig } = useRendererConfig();
+  const rendererConfig = useRendererConfigExt();
 
   const isAllDataValid = currentPagePath != null && rendererConfig != null;