1import {readFileSync} from 'node:fs';
2import {env} from 'node:process';
3import {parse} from 'postcss';
4import plugin from 'tailwindcss/plugin.js';
5
6const isProduction = env.NODE_ENV !== 'development';
7
8function extractRootVars(css) {
9 const root = parse(css);
10 const vars = new Set();
11 root.walkRules((rule) => {
12 if (rule.selector !== ':root') return;
13 rule.each((decl) => {
14 if (decl.value && decl.prop.startsWith('--')) {
15 vars.add(decl.prop.substring(2));
16 }
17 });
18 });
19 return Array.from(vars);
20}
21
22const vars = extractRootVars([
23 readFileSync(new URL('web_src/css/themes/theme-gitea-light.css', import.meta.url), 'utf8'),
24 readFileSync(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url), 'utf8'),
25].join('\n'));
26
27export default {
28 prefix: 'tw-',
29 important: true, // the frameworks are mixed together, so tailwind needs to override other framework's styles
30 content: [
31 isProduction && '!./templates/devtest/**/*',
32 isProduction && '!./web_src/js/standalone/devtest.js',
33 '!./templates/swagger/v1_json.tmpl',
34 '!./templates/user/auth/oidc_wellknown.tmpl',
35 './templates/**/*.tmpl',
36 './web_src/js/**/*.{js,vue}',
37 // explicitly list Go files that contain tailwind classes
38 'models/avatars/avatar.go',
39 'modules/markup/file_preview.go',
40 'modules/markup/sanitizer.go',
41 'services/auth/source/oauth2/*.go',
42 'routers/web/repo/{view,blame,issue_content_history}.go',
43 ].filter(Boolean),
44 blocklist: [
45 // classes that don't work without CSS variables from "@tailwind base" which we don't use
46 'transform', 'shadow', 'ring', 'blur', 'grayscale', 'invert', '!invert', 'filter', '!filter',
47 'backdrop-filter',
48 // we use double-class tw-hidden defined in web_src/css/helpers.css for increased specificity
49 'hidden',
50 ],
51 theme: {
52 colors: {
53 // make `tw-bg-red` etc work with our CSS variables
54 ...Object.fromEntries(vars.filter((prop) => prop.startsWith('color-')).map((prop) => {
55 const color = prop.substring(6);
56 return [color, `var(--color-${color})`];
57 })),
58 inherit: 'inherit',
59 current: 'currentcolor',
60 transparent: 'transparent',
61 },
62 borderRadius: {
63 'none': '0',
64 'sm': '2px',
65 'DEFAULT': 'var(--border-radius)', // 4px
66 'md': 'var(--border-radius-medium)', // 6px
67 'lg': '8px',
68 'xl': '12px',
69 '2xl': '16px',
70 '3xl': '24px',
71 'full': 'var(--border-radius-full)',
72 },
73 fontFamily: {
74 sans: 'var(--fonts-regular)',
75 mono: 'var(--fonts-monospace)',
76 },
77 fontWeight: {
78 light: 'var(--font-weight-light)',
79 normal: 'var(--font-weight-normal)',
80 medium: 'var(--font-weight-medium)',
81 semibold: 'var(--font-weight-semibold)',
82 bold: 'var(--font-weight-bold)',
83 },
84 fontSize: { // not using `rem` units because our root is currently 14px
85 'xs': '12px',
86 'sm': '14px',
87 'base': '16px',
88 'lg': '18px',
89 'xl': '20px',
90 '2xl': '24px',
91 '3xl': '30px',
92 '4xl': '36px',
93 '5xl': '48px',
94 '6xl': '60px',
95 '7xl': '72px',
96 '8xl': '96px',
97 '9xl': '128px',
98 ...Object.fromEntries(Array.from({length: 100}, (_, i) => {
99 return [`${i}`, `${i === 0 ? '0' : `${i}px`}`];
100 })),
101 },
102 },
103 plugins: [
104 plugin(({addUtilities}) => {
105 // base variables required for transform utilities
106 // added as utilities since base is not imported
107 // note: required when using tailwind's transform classes
108 addUtilities({
109 '.transform-reset': {
110 '--tw-translate-x': 0,
111 '--tw-translate-y': 0,
112 '--tw-rotate': 0,
113 '--tw-skew-x': 0,
114 '--tw-skew-y': 0,
115 '--tw-scale-x': '1',
116 '--tw-scale-y': '1',
117 },
118 });
119 }),
120 plugin(({addUtilities}) => {
121 addUtilities({
122 // tw-hidden must win all other "display: xxx !important" classes to get the chance to "hide" an element.
123 // do not use:
124 // * "[hidden]" attribute: it's too weak, can not be applied to an element with "display: flex"
125 // * ".hidden" class: it has been polluted by Fomantic UI in many cases
126 // * inline style="display: none": it's difficult to tweak
127 // * jQuery's show/hide/toggle: it can not show/hide elements with "display: xxx !important"
128 // only use:
129 // * this ".tw-hidden" class
130 // * showElem/hideElem/toggleElem functions in "utils/dom.js"
131 '.hidden.hidden': {
132 'display': 'none',
133 },
134 // proposed class from https://github.com/tailwindlabs/tailwindcss/pull/12128
135 '.break-anywhere': {
136 'overflow-wrap': 'anywhere',
137 },
138 });
139 }),
140 ],
141};