directus-extension-admin-polish 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/dist/api.js +26 -0
- package/dist/app.js +1 -0
- package/dist/client.js +70 -0
- package/package.json +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# directus-extension-admin-polish
|
|
2
|
+
|
|
3
|
+
A Directus 11 bundle extension with two small quality-of-life improvements for the admin app.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### Clean page titles
|
|
8
|
+
Removes the "Directus · " prefix from browser tab titles so your project name is shown on its own.
|
|
9
|
+
|
|
10
|
+
Before: `Directus · My Project`
|
|
11
|
+
After: `My Project`
|
|
12
|
+
|
|
13
|
+
### Theme toggle
|
|
14
|
+
Adds a light/dark theme toggle button to the bottom of the module bar (next to the user avatar), so you can switch themes without digging into user settings.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
Drop the extension folder into your Directus `extensions/` directory and restart. No configuration needed.
|
|
19
|
+
|
|
20
|
+
Requires `MARKETPLACE_TRUST=all`.
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { fileURLToPath } from 'url';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const clientScript = readFileSync(join(__dirname, 'client.js'), 'utf8');
|
|
9
|
+
|
|
10
|
+
function hookConfig({ embed }) {
|
|
11
|
+
embed('head', '<script src="/admin-tools-endpoint"></script>');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function endpointConfig(router) {
|
|
15
|
+
router.get('/', (req, res) => {
|
|
16
|
+
res.setHeader('Content-Type', 'application/javascript');
|
|
17
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
18
|
+
res.send(clientScript);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
hooks: [{ name: 'admin-tools-hook', config: hookConfig }],
|
|
24
|
+
endpoints: [{ name: 'admin-tools-endpoint', config: endpointConfig }],
|
|
25
|
+
operations: [],
|
|
26
|
+
};
|
package/dist/app.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const interfaces=[];export const displays=[];export const layouts=[];export const modules=[];export const panels=[];export const themes=[];export const operations=[];
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
function cleanTitle(val) {
|
|
3
|
+
var s = String(val);
|
|
4
|
+
s = s.replace(/^Directus\s*[·|\-]\s*/i, '');
|
|
5
|
+
s = s.replace(/\s*[·|\-]\s*Directus\s*$/i, '');
|
|
6
|
+
return s.trim() || val;
|
|
7
|
+
}
|
|
8
|
+
var titleDesc = Object.getOwnPropertyDescriptor(Document.prototype, 'title');
|
|
9
|
+
if (titleDesc && titleDesc.set) {
|
|
10
|
+
Object.defineProperty(document, 'title', {
|
|
11
|
+
configurable: true,
|
|
12
|
+
get: function () { return titleDesc.get.call(document); },
|
|
13
|
+
set: function (val) { titleDesc.set.call(document, cleanTitle(val)); },
|
|
14
|
+
});
|
|
15
|
+
document.title = document.title;
|
|
16
|
+
}
|
|
17
|
+
var _authToken = null;
|
|
18
|
+
var _origFetch = window.fetch;
|
|
19
|
+
window.fetch = function (url, opts) {
|
|
20
|
+
if (!_authToken && opts && opts.headers) {
|
|
21
|
+
var h = opts.headers;
|
|
22
|
+
var auth = (typeof h.get === 'function' ? h.get('Authorization') : null) || h['Authorization'] || h['authorization'] || '';
|
|
23
|
+
if (auth.indexOf('Bearer ') === 0) _authToken = auth.slice(7);
|
|
24
|
+
}
|
|
25
|
+
return _origFetch.apply(this, arguments);
|
|
26
|
+
};
|
|
27
|
+
function isDark() { return document.body.classList.contains('dark'); }
|
|
28
|
+
function findUserStore() {
|
|
29
|
+
try {
|
|
30
|
+
var pinia = document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
|
|
31
|
+
var found = null;
|
|
32
|
+
pinia._s.forEach(function (store) { if (!found && store.currentUser && 'appearance' in store.currentUser) found = store; });
|
|
33
|
+
return found;
|
|
34
|
+
} catch (e) { return null; }
|
|
35
|
+
}
|
|
36
|
+
function applyTheme(appearance) {
|
|
37
|
+
var store = findUserStore();
|
|
38
|
+
if (store) { store.$patch(function (s) { s.currentUser.appearance = appearance; }); }
|
|
39
|
+
else { document.body.classList.remove('dark', 'light'); document.body.classList.add(appearance); }
|
|
40
|
+
var opts = { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ appearance: appearance }) };
|
|
41
|
+
if (_authToken) opts.headers['Authorization'] = 'Bearer ' + _authToken; else opts.credentials = 'include';
|
|
42
|
+
fetch('/users/me', opts).catch(function () {});
|
|
43
|
+
}
|
|
44
|
+
var SUN = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/></svg>';
|
|
45
|
+
var MOON = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
|
|
46
|
+
var btn = null;
|
|
47
|
+
function syncIcon() {
|
|
48
|
+
if (!btn) return;
|
|
49
|
+
var dark = isDark();
|
|
50
|
+
btn.innerHTML = dark ? SUN : MOON;
|
|
51
|
+
btn.title = dark ? 'Switch to light mode' : 'Switch to dark mode';
|
|
52
|
+
}
|
|
53
|
+
function injectButton() {
|
|
54
|
+
if (btn && !document.contains(btn)) btn = null;
|
|
55
|
+
var bar = document.querySelector('.module-bar');
|
|
56
|
+
if (!bar || btn) return;
|
|
57
|
+
btn = document.createElement('button');
|
|
58
|
+
btn.id = 'directus-theme-toggle';
|
|
59
|
+
btn.style.cssText = 'display:flex;align-items:center;justify-content:center;width:60px;height:44px;border:none;background:transparent;cursor:pointer;color:var(--theme--navigation--modules--button--foreground,#fff);transition:background .15s;flex-shrink:0;';
|
|
60
|
+
btn.addEventListener('mouseenter', function () { btn.style.background = 'var(--theme--navigation--modules--button--background-hover,rgba(255,255,255,.1))'; });
|
|
61
|
+
btn.addEventListener('mouseleave', function () { btn.style.background = 'transparent'; });
|
|
62
|
+
btn.addEventListener('click', function () { applyTheme(isDark() ? 'light' : 'dark'); });
|
|
63
|
+
syncIcon();
|
|
64
|
+
var avatar = bar.querySelector('.module-bar-avatar');
|
|
65
|
+
if (avatar) bar.insertBefore(btn, avatar); else bar.appendChild(btn);
|
|
66
|
+
new MutationObserver(syncIcon).observe(document.body, { attributes: true, attributeFilter: ['class'] });
|
|
67
|
+
}
|
|
68
|
+
new MutationObserver(injectButton).observe(document.documentElement, { childList: true, subtree: true });
|
|
69
|
+
injectButton();
|
|
70
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "directus-extension-admin-polish",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Removes Directus from page titles and adds a theme toggle to the module bar",
|
|
5
|
+
"keywords": ["directus-extension"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"directus:extension": {
|
|
9
|
+
"host": "^11.0.0",
|
|
10
|
+
"type": "bundle",
|
|
11
|
+
"path": { "app": "./dist/app.js", "api": "./dist/api.js" },
|
|
12
|
+
"entries": [
|
|
13
|
+
{ "type": "hook", "name": "admin-tools-hook", "source": "./src/hook/index.js" },
|
|
14
|
+
{ "type": "endpoint", "name": "admin-tools-endpoint", "source": "./src/endpoint/index.js" }
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
}
|