Skip to content

Commit 6cf2d98

Browse files
authored
Merge pull request #287 from devforth/AdminForth/735
Admin forth/735
2 parents 1ba495b + f1c91a0 commit 6cf2d98

File tree

9 files changed

+105
-12
lines changed

9 files changed

+105
-12
lines changed

adminforth/documentation/docs/tutorial/03-Customization/01-branding.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,37 @@ auth: {
183183
//diff-add
184184
removeBackgroundBlendMode: true,
185185
}
186+
```
187+
188+
## Custom items in html head
189+
190+
If you want to add custom elements to the HTML head, you can define them in the configuration:
191+
192+
```ts title='./index.ts'
193+
customization: {
194+
customHeadItems: [
195+
{
196+
tagName: 'link',
197+
attributes: {
198+
rel: 'stylesheet',
199+
href: 'https://example.com/custom.css'
200+
}
201+
},
202+
{
203+
tagName: 'script',
204+
attributes: {
205+
src: 'https://example.com/custom.js',
206+
defer: true
207+
}
208+
},
209+
{
210+
tagName: 'meta',
211+
attributes: {
212+
name: 'theme-color',
213+
content: ' #000000'
214+
}
215+
}
216+
]
217+
}
218+
186219
```

adminforth/modules/codeInjector.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -545,8 +545,23 @@ class CodeInjector implements ICodeInjector {
545545
||
546546
`/assets/favicon.png`
547547
);
548-
await fs.promises.writeFile(indexHtmlPath, indexHtmlContent);
549548

549+
// inject heads to index.html
550+
const headItems = this.adminforth.config.customization?.customHeadItems;
551+
if(headItems){
552+
const renderedHead = headItems.map(({ tagName, attributes }) => {
553+
const attrs = Object.entries(attributes)
554+
.map(([key, value]) => `${key}="${value}"`)
555+
.join(' ');
556+
const isVoid = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'source', 'track', 'wbr'].includes(tagName);
557+
return isVoid
558+
? `<${tagName} ${attrs}>`
559+
: `<${tagName} ${attrs}></${tagName}>`;
560+
}).join('\n ');
561+
562+
indexHtmlContent = indexHtmlContent.replace(" <!-- /* IMPORTANT:ADMINFORTH HEAD */ -->", `${renderedHead}` );
563+
}
564+
await fs.promises.writeFile(indexHtmlPath, indexHtmlContent);
550565

551566
/* generate custom routes */
552567
let homepageMenuItem: AdminForthConfigMenuItem = findHomePage(this.adminforth.config.menu);

adminforth/modules/restApi.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
225225
},
226226
rememberMeDays: this.adminforth.config.auth.rememberMeDays,
227227
singleTheme: this.adminforth.config.customization.singleTheme,
228+
customHeadItems: this.adminforth.config.customization.customHeadItems,
228229
};
229230
},
230231
});
@@ -305,6 +306,7 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
305306
loginPageInjections: this.adminforth.config.customization.loginPageInjections,
306307
rememberMeDays: this.adminforth.config.auth.rememberMeDays,
307308
singleTheme: this.adminforth.config.customization.singleTheme,
309+
customHeadItems: this.adminforth.config.customization.customHeadItems,
308310
}
309311

310312
const loggedInPart = {

adminforth/modules/styles.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,14 @@ export const styles = () => ({
6060
lightButtonsDisabledText: "#f3f4f6", // button disabled text
6161
lightButtonsIcon: "#333333", // button icon
6262

63+
lightDropdownButtonsBackground: "#f9fafb", // dropdown button/input background color
64+
lightDropownButtonsBorder: "#d1d5db", //border color
65+
lightDropdownButtonsText: "#6b7280", //text color
66+
lightDropdownButtonsPlaceholderText: "#6b7280", //placeholder text color
6367

68+
lightDropdownOptionsBackground: "#FFFFFF", //dropdown menu background color
69+
lightDropdownOptionsHoverBackground: "#f3f4f6", //dropdown menu hover background color
70+
lightDropdownOptionsText: "#000000", //dropdown menu hover background color
6471

6572
// colors for dark theme
6673
darkHtml: "#111827",
@@ -102,7 +109,16 @@ export const styles = () => ({
102109

103110
darkForm: "#111111",
104111
darkFormBorder: "#222222",
105-
darkFormHeading: "alias:darkListTableHeading"
112+
darkFormHeading: "alias:darkListTableHeading",
113+
114+
darkDropdownButtonsBackground: "#374151",
115+
darkDropownButtonsBorder: "#4b5563",
116+
darkDropdownButtonsText: "#ffffff",
117+
darkDropdownButtonsPlaceholderText: "#9ca3af",
118+
119+
darkDropdownOptionsBackground: "#374151",
120+
darkDropdownOptionsHoverBackground: "#4b5563",
121+
darkDropdownOptionsText: "#9ca3af",
106122

107123
},
108124
boxShadow: {

adminforth/spa/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
document.documentElement.classList.remove('dark')
1515
}
1616
</script> -->
17-
17+
<!-- /* IMPORTANT:ADMINFORTH HEAD */ -->
1818
</head>
1919
<body class="min-h-screen flex flex-col">
2020
<div id="app" class="grow bg-lightHtml dark:bg-darkHtml w-full"></div>

adminforth/spa/src/afcl/Select.vue

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
v-model="search"
1111
@click="inputClick"
1212
@input="inputInput"
13-
class="block w-full pl-3 pr-10 py-2.5 border border-gray-300 rounded-md leading-5 bg-gray-50 placeholder-gray-500 sm:text-sm transition duration-150 ease-in-out dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary"
13+
class="block w-full pl-3 pr-10 py-2.5 border border-lightDropownButtonsBorder rounded-md leading-5 bg-lightDropdownButtonsBackground
14+
placeholder-lightDropdownButtonsPlaceholderText text-lightDropdownButtonsText sm:text-sm transition duration-150 ease-in-out dark:bg-darkDropdownButtonsBackground dark:border-darkDropownButtonsBorder dark:placeholder-darkDropdownButtonsPlaceholderText
15+
dark:text-darkDropdownButtonsText focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary"
1416
autocomplete="off" data-custom="no-autofill"
1517
:placeholder="
1618
selectedItems.length && !multiple ? '' : (showDropdown ? $t('Search') : placeholder || $t('Select...'))
@@ -36,19 +38,19 @@
3638
</div>
3739
<teleport to="body" v-if="teleportToBody && showDropdown">
3840
<div ref="dropdownEl" :style="getDropdownPosition" :class="{'shadow-none': isTop}"
39-
class="fixed z-[5] w-full bg-white shadow-lg dark:shadow-black dark:bg-gray-700
41+
class="fixed z-[5] w-full bg-lightDropdownOptionsBackground shadow-lg dark:shadow-black dark:bg-darkDropdownOptionsBackground
4042
dark:border-gray-600 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm max-h-48">
4143
<div
4244
v-for="item in filteredItems"
4345
:key="item.value"
44-
class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-400"
46+
class="px-4 py-2 cursor-pointer hover:bg-lightDropdownOptionsHoverBackground dark:hover:bg-darkDropdownOptionsHoverBackground text-lightDropdownOptionsText dark:text-darkDropdownOptionsText"
4547
:class="{ 'bg-lightPrimaryOpacity dark:bg-darkPrimaryOpacity': selectedItems.includes(item) }"
4648
@click="toogleItem(item)"
4749
>
4850
<slot name="item" :option="item"></slot>
4951
<label v-if="!$slots.item" :for="item.value">{{ item.label }}</label>
5052
</div>
51-
<div v-if="!filteredItems.length" class="px-4 py-2 cursor-pointer text-gray-400 dark:text-gray-300">
53+
<div v-if="!filteredItems.length" class="px-4 py-2 cursor-pointer text-lightDropdownOptionsText dark:text-darkDropdownOptionsText">
5254
{{ options.length ? $t('No results found') : $t('No items here') }}
5355
</div>
5456

@@ -59,22 +61,22 @@
5961
</teleport>
6062

6163
<div v-if="!teleportToBody && showDropdown" ref="dropdownEl" :style="dropdownStyle" :class="{'shadow-none': isTop}"
62-
class="absolute z-10 mt-1 w-full bg-white shadow-lg dark:shadow-black dark:bg-gray-700
64+
class="absolute z-10 mt-1 w-full bg-lightDropdownOptionsBackground shadow-lg text-lightDropdownOptionsText dark:shadow-black dark:bg-darkDropdownOptionsBackground
6365
dark:border-gray-600 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm max-h-48">
6466
<div
6567
v-for="item in filteredItems"
6668
:key="item.value"
67-
class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-400"
69+
class="px-4 py-2 cursor-pointer hover:bg-lightDropdownOptionsHoverBackground dark:hover:bg-darkDropdownOptionsHoverBackground dark:text-darkDropdownOptionsText"
6870
:class="{ 'bg-lightPrimaryOpacity dark:bg-darkPrimaryOpacity': selectedItems.includes(item) }"
6971
@click="toogleItem(item)"
7072
>
7173
<slot name="item" :option="item"></slot>
7274
<label v-if="!$slots.item" :for="item.value">{{ item.label }}</label>
7375
</div>
74-
<div v-if="!filteredItems.length" class="px-4 py-2 cursor-pointer text-gray-400 dark:text-gray-300">
76+
<div v-if="!filteredItems.length" class="px-4 py-2 cursor-pointer text-lightDropdownOptionsText dark:text-darkDropdownOptionsText">
7577
{{ options.length ? $t('No results found') : $t('No items here') }}
7678
</div>
77-
<div v-if="$slots['extra-item']" class="px-4 py-2 dark:text-gray-400">
79+
<div v-if="$slots['extra-item']" class="px-4 py-2 dark:text-darkDropdownOptionsText">
7880
<slot name="extra-item"></slot>
7981
</div>
8082

adminforth/spa/src/spa_types/core.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ export type CoreConfig = {
4141
show?: string,
4242
list?: string,
4343
} | string,
44+
45+
customHeadItems?: {
46+
tagName: string;
47+
attributes: { [key: string]: string | boolean };
48+
}[],
4449
}
4550

4651

adminforth/types/Back.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,16 @@ interface AdminForthInputConfigCustomization {
764764
sidebar?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
765765
everyPageBottom?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
766766
}
767+
768+
/**
769+
* Allows adding custom elements (e.g., <link>, <script>, <meta>) to the <head> of the HTML document.
770+
* Each item must include a tag name and a set of attributes.
771+
*/
772+
customHeadItems?: {
773+
tagName: string;
774+
attributes: Record<string, string | boolean>;
775+
}[];
776+
767777
}
768778

769779
export interface AdminForthActionInput {
@@ -1076,6 +1086,12 @@ export interface AdminForthConfigCustomization extends Omit<AdminForthInputConfi
10761086
sidebar: Array<AdminForthComponentDeclarationFull>,
10771087
everyPageBottom: Array<AdminForthComponentDeclarationFull>,
10781088
},
1089+
1090+
customHeadItems?: {
1091+
tagName: string;
1092+
attributes: Record<string, string | boolean>;
1093+
}[];
1094+
10791095
}
10801096

10811097
export interface AdminForthConfig extends Omit<AdminForthInputConfig, 'customization' | 'resources'> {

adminforth/types/Common.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1082,7 +1082,11 @@ export interface AdminForthConfigForFrontend {
10821082
header: Array<AdminForthComponentDeclarationFull>,
10831083
sidebar: Array<AdminForthComponentDeclarationFull>,
10841084
everyPageBottom: Array<AdminForthComponentDeclarationFull>,
1085-
}
1085+
},
1086+
customHeadItems?: {
1087+
tagName: string;
1088+
attributes: Record<string, string | boolean>;
1089+
}[],
10861090
}
10871091

10881092
export interface GetBaseConfigResponse {

0 commit comments

Comments
 (0)