Files
Hydro/packages/ui-default/components/editor/theme.ts
T
2022-05-03 23:59:47 +08:00

381 lines
9.4 KiB
TypeScript

/* Copyright 2021, Milkdown by Mirone. */
/* eslint-disable consistent-return */
import {
Emotion, ThemeBorder, ThemeFont, ThemeManager, ThemeScrollbar, ThemeShadow, ThemeSize,
hex2rgb, ThemeColor, themeFactory, ThemeGlobal, ThemeIcon,
} from '@milkdown/core';
import { getPalette } from '@milkdown/design-system';
import { injectProsemirrorView, useAllPresetRenderer } from '@milkdown/theme-pack-helper';
import { code, typography } from '@milkdown/theme-nord/src/font';
import { getIcon } from '@milkdown/theme-nord/src/icon';
import { darkColor, lightColor } from '@milkdown/theme-nord/src/nord';
export const font = {
typography,
code,
};
export const size = {
radius: '4px',
lineWidth: '1px',
};
export const getStyle = (manager: ThemeManager, emotion: Emotion) => {
const { injectGlobal, css } = emotion;
const palette = getPalette(manager);
const radius = manager.get(ThemeSize, 'radius');
const neutral = palette('neutral', 0.87);
const surface = palette('surface');
const line = palette('line');
const highlight = palette('secondary', 0.38);
const selection = css`
.ProseMirror-selectednode {
outline: ${manager.get(ThemeSize, 'lineWidth')} solid ${line};
}
li.ProseMirror-selectednode {
outline: none;
}
li.ProseMirror-selectednode::after {
${manager.get(ThemeBorder, undefined)};
}
& ::selection {
background: ${highlight};
}
`;
const editorLayout = css`
padding: 1.25em;
outline: none;
`;
const blockquote = css`
blockquote {
padding-left: 1.875em;
line-height: 1.75em;
border-left: 4px solid ${palette('primary')};
margin-left: 0;
margin-right: 0;
* {
font-size: 1em;
line-height: 1.5em;
}
}
`;
const hr = css`
hr {
height: ${manager.get(ThemeSize, 'lineWidth')};
background-color: ${line};
border-width: 0;
}
`;
const list = css`
.list-item,
.list-item > * {
margin: 0.5em 0;
}
li {
&::marker {
color: ${palette('primary')};
}
}
.task-list-item {
display: flex;
flex-direction: row;
align-items: flex-start;
&_checkbox {
margin: 0.5em 0.5em 0.5em 0;
height: 1em;
}
}
`;
// eslint-disable-next-line no-shadow
const code = css`
.code-fence {
pre {
font-family: ${manager.get(ThemeFont, 'code')};
margin: 0 1.2em !important;
white-space: pre;
overflow: auto;
${manager.get(ThemeScrollbar, ['x'])}
background-color: ${palette('background')};
color: ${palette('neutral')};
font-size: 0.875em;
border-radius: ${radius};
code {
line-height: 1.5;
font-family: ${manager.get(ThemeFont, 'code')};
}
}
}
`;
const img = css`
.image {
display: inline-block;
margin: 0 auto;
object-fit: contain;
width: 100%;
position: relative;
height: auto;
text-align: center;
}
`;
const inline = css`
.code-inline {
background-color: ${palette('neutral')};
color: ${palette('background')};
border-radius: ${radius};
font-weight: 500;
font-family: ${code};
padding: 0 0.2em;
font-size: 1.2em;
}
.strong {
font-weight: 600;
}
.link,
a {
color: ${palette('secondary')};
cursor: pointer;
transition: all 0.4s ease-in-out;
font-weight: 500;
&:hover {
background-color: ${palette('line')};
box-shadow: 0 0.2em ${palette('line')}, 0 -0.2em ${palette('line')};
}
}
.strike-through {
text-decoration-color: ${palette('secondary')};
}
`;
const footnote = css`
.footnote-definition {
${manager.get(ThemeBorder, undefined)};
border-radius: ${manager.get(ThemeSize, 'radius')};
background-color: ${palette('background')};
padding: 1em;
display: flex;
flex-direction: row;
& > .footnote-definition_content {
flex: 1;
width: calc(100% - 1em);
& > dd {
margin-inline-start: 1em;
}
& > dt {
color: ${palette('secondary')};
font-weight: 500;
}
}
& > .footnote-definition_anchor {
width: 1em;
}
}
`;
const table = css`
.tableWrapper {
overflow-x: auto;
margin: 0;
${manager.get(ThemeScrollbar, ['x'])}
width: 100%;
* {
margin: 0;
box-sizing: border-box;
font-size: 1em;
}
}
table {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
overflow: auto;
border-radius: ${manager.get(ThemeSize, 'radius')};
p {
line-height: unset;
}
}
tr {
${manager.get(ThemeBorder, 'bottom')};
}
td,
th {
padding: 0 1em;
vertical-align: top;
box-sizing: border-box;
position: relative;
min-width: 100px;
${manager.get(ThemeBorder, undefined)};
text-align: left;
line-height: 3;
height: 3em;
}
th {
background: ${palette('background', 0.5)};
font-weight: 400;
}
.column-resize-handle {
position: absolute;
right: -2px;
top: 0;
bottom: 0;
z-index: 20;
pointer-events: none;
background: ${palette('secondary')};
width: ${manager.get(ThemeSize, 'lineWidth')};
}
.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}
.selectedCell {
&::after {
z-index: 2;
position: absolute;
content: '';
left: 0;
right: 0;
top: 0;
bottom: 0;
background: ${palette('secondary', 0.38)};
pointer-events: none;
}
& ::selection {
background: transparent;
}
}
`;
injectProsemirrorView(emotion);
// eslint-disable-next-line no-unused-expressions
injectGlobal`
.milkdown {
.material-icons-outlined {
font-size: 1.5em;
}
position: relative;
margin-left: auto;
margin-right: auto;
box-sizing: border-box;
color: ${neutral};
background: ${surface};
font-family: ${manager.get(ThemeFont, 'typography')};
${manager.get(ThemeShadow, undefined)}
${manager.get(ThemeScrollbar, undefined)}
${selection};
.editor {
${editorLayout};
${blockquote};
${hr};
${list};
${code};
${img};
${table};
${footnote};
${inline};
}
}
`;
};
export const createTheme = (isDarkMode: boolean) => (emotion: Emotion, manager: ThemeManager) => {
const { css } = emotion;
const colorSet = isDarkMode ? darkColor : lightColor;
manager.set(ThemeColor, (options) => {
if (!options) return;
const [key, opacity] = options;
const hex = colorSet[key];
const rgb = hex2rgb(hex);
if (!rgb) return;
return `rgba(${rgb?.join(', ')}, ${opacity || 1})`;
});
manager.set(ThemeSize, (key) => {
if (!key) return;
return size[key];
});
manager.set(ThemeFont, (key) => {
if (!key) return;
return font[key].join(', ');
});
manager.set(ThemeScrollbar, ([direction = 'y', type = 'normal'] = ['y', 'normal'] as never) => {
const main = manager.get(ThemeColor, ['secondary', 0.38]);
const bg = manager.get(ThemeColor, ['secondary', 0.12]);
const hover = manager.get(ThemeColor, ['secondary']);
return css`
scrollbar-width: thin;
scrollbar-color: ${main} ${bg};
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar {
${direction === 'y' ? 'width' : 'height'}: ${type === 'thin' ? 2 : 12}px;
background-color: transparent;
}
&::-webkit-scrollbar-track {
border-radius: 999px;
background: transparent;
border: 4px solid transparent;
}
&::-webkit-scrollbar-thumb {
border-radius: 999px;
background-color: ${main};
border: ${type === 'thin' ? 0 : 4}px solid transparent;
background-clip: content-box;
}
&::-webkit-scrollbar-thumb:hover {
background-color: ${hover};
}
`;
});
manager.set(ThemeShadow, () => {
const lineWidth = manager.get(ThemeSize, 'lineWidth');
const getShadow = (opacity: number) => manager.get(ThemeColor, ['shadow', opacity]);
return css`
box-shadow: 0 ${lineWidth} ${lineWidth} ${getShadow(0.14)}, 0 2px ${lineWidth} ${getShadow(0.12)},
0 ${lineWidth} 3px ${getShadow(0.2)};
`;
});
manager.set(ThemeBorder, (direction) => {
const lineWidth = manager.get(ThemeSize, 'lineWidth');
const line = manager.get(ThemeColor, ['line']);
if (!direction) {
return css`border: ${lineWidth} solid ${line};`;
}
return css`${`border-${direction}`}: ${lineWidth} solid ${line};`;
});
manager.set(ThemeIcon, (icon) => {
if (!icon) return;
return getIcon(icon);
});
manager.set(ThemeGlobal, () => {
getStyle(manager, emotion);
});
useAllPresetRenderer(manager, emotion);
};
export const getNord = (isDarkMode = false) => themeFactory((emotion, manager) => createTheme(isDarkMode)(emotion, manager));
export const nordDark = getNord(true);
export const nordLight = getNord(false);
const darkMode = Boolean(window.matchMedia?.('(prefers-color-scheme: dark)').matches);
export const nord = getNord(darkMode);
export { color, darkColor, lightColor } from '@milkdown/theme-nord/src/nord';