Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dashboard/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default {
...frappeUIContent,
'./index.html',
'./src/**/*.{vue,js,ts,jsx,tsx}',
...readPluginApps().map((app) => `${sourceDirOf(app)}/**/*.vue`),
...readPluginApps().map((app) => `${sourceDirOf(app)}/**/*.{vue,js,ts}`),
],
safelist: pluginIcons.map((name) => `lucide-${name}`),
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/implementation/media/plugin-page-dialog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion packages/plugin-kit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ Folder and `plugin` problems are collected and reported together.
| A command with a template or `<script setup>` | `commands/sync/index.vue` with a `<template>` |
| An icon not in the list | `icon: 'printr'` |
| A dotted path outside the app | `condition: 'frappe.client.get_list'` |
| Drawing a frame the dashboard owns | importing `AppPageHeader`, `PageBody` or `PluginCard`, or frappe-ui's `Dialog` |
| Drawing a frame the dashboard owns | importing `AppPageHeader`, `PageBody` or `PluginCard` |
| A frappe-ui resource that calls Frappe's v1 API | importing `createResource`, `createListResource`, `createDocumentResource`, `useCall`, `useList`, `useDoc`, `useDoctype`, `useNewDoc`, `frappeRequest` or `call` from `frappe-ui` |
| A `plugin-icon.svg` that is not a plain SVG | over 20 kB, not an `<svg>`, a `<script>`, an `on*` attribute, or an `href` that is not `#id` |
| A `<style>` block or a stylesheet import | `<style>.x { color: red }</style>` |
Expand Down
34 changes: 8 additions & 26 deletions packages/plugin-kit/test/kit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -412,20 +412,15 @@ describe('guards', () => {
});
}

for (const [name, specifier, hint] of [
['Dialog', 'frappe-ui', /useAction\(\)/],
]) {
test(`importing ${name} fails and points at the host`, async () => {
await buildFails(
{
'pages/jobs/index.vue': `${page}<script setup>\nimport { ${name} } from '${specifier}'\nconsole.log(${name})\n</script>\n`,
},
new RegExp(
`do not import ${name} from '${specifier}'[\\s\\S]*${hint.source}`,
),
);
test('a page and an action may each open their own Dialog', async () => {
await buildApp({
'pages/jobs/index.vue': `${page}<script setup>\nimport { Dialog } from 'frappe-ui'\nconsole.log(Dialog)\n</script>\n`,
'order/actions/resend/index.vue': vue(
`{ label: 'Resend' }`,
`<template><div class="p-2" /></template>\n<script setup>\nimport { Dialog } from 'frappe-ui'\nconsole.log(Dialog)\n</script>`,
),
});
}
});

for (const name of ['createResource', 'useCall', 'useList', 'call']) {
test(`importing ${name} from frappe-ui fails and points at useMethodRead`, async () => {
Expand All @@ -446,19 +441,6 @@ describe('guards', () => {
);
});
}

test('a Dialog in shared code fails too', async () => {
await buildFails(
{
'order/actions/resend/index.vue': vue(
`{ label: 'Resend' }`,
`<template><div class="p-2" /></template>\n<script setup>\nimport { open } from '../../../shared/open.js'\nopen()\n</script>`,
),
'shared/open.js': `import { Dialog } from 'frappe-ui'\nexport const open = () => console.log(Dialog)\n`,
},
/do not import Dialog from 'frappe-ui'/,
);
});
});

describe('detail pages', () => {
Expand Down
21 changes: 2 additions & 19 deletions packages/plugin-kit/vite.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,6 @@ const SKIPPED_DIRS = new Set(['node_modules', 'dist']);
const ICON_FILE = 'plugin-icon.svg';
const ICON_LIMIT = 20 * 1024;

// The host draws these frames itself, so an app that draws its own gets two headers or a dialog on a dialog.
const HOST_FRAMES = {
'frappe-ui': {
Dialog:
'Commera draws action dialogs; put the form in an action and drive its button with useAction()',
},
};

const FRAPPE_V1 = new Set([
'createResource',
'createListResource',
Expand Down Expand Up @@ -378,7 +370,7 @@ export function discoverPlugins(
isIndex
? `${display} declares a plugin block but ${placeOf(
folder,
)} isn't a Commera placement; valid places:\n${placementTable()}`
)} isn't a Commera placement; valid places:\n${placementTable()}`
: `${display} declares a plugin block, but only a placement's index.vue may; valid places:\n${placementTable()}`,
);
continue;
Expand Down Expand Up @@ -556,12 +548,6 @@ function guard({ hostDir, compiler, pluginFiles, pageEntries }) {
for (const [specifier, names] of Object.entries(
chunk.importedBindings,
)) {
const frames = HOST_FRAMES[specifier] ?? {};
for (const name of names.filter((name) => frames[name])) {
problems.push(
`${chunk.fileName}: do not import ${name} from '${specifier}': ${frames[name]}`,
);
}
const callsV1 = (name) =>
specifier === 'frappe-ui' && FRAPPE_V1.has(name);
for (const name of names.filter(callsV1)) {
Expand All @@ -573,10 +559,7 @@ function guard({ hostDir, compiler, pluginFiles, pageEntries }) {
const available = sharedExports[specifier] ?? [];
const missing = names.filter(
(name) =>
name !== '*' &&
!frames[name] &&
!callsV1(name) &&
!available.includes(name),
name !== '*' && !callsV1(name) && !available.includes(name),
);
if (missing.length) {
problems.push(
Expand Down
Loading