Skip to content

Commit

Permalink
Merge pull request #1396 from plone/petschki-contentbrowser-component…
Browse files Browse the repository at this point in the history
…registry

`pat-contentbrowser` component registry
  • Loading branch information
petschki authored Dec 18, 2024
2 parents 5d52a41 + 7c6eca4 commit 9906aff
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 79 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@11ty/eleventy-upgrade-help": "2",
"@patternslib/pat-code-editor": "4.0.1",
"@patternslib/patternslib": "9.9.16",
"@plone/registry": "^1.7.0",
"@plone/registry": "^2.1.0",
"backbone": "1.4.1",
"backbone.paginator": "2.0.8",
"bootstrap": "5.3.3",
Expand Down Expand Up @@ -134,5 +134,6 @@
],
"publishConfig": {
"access": "public"
}
},
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
42 changes: 42 additions & 0 deletions src/pat/contentbrowser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Show a widget to select items in an offcanvas miller-column browser.
| recentlyUsed | boolean | false | Show the recently used items dropdown. |
| recentlyUsedKey | integer | | Storage key for saving the recently used items. This is generated with fieldname and username in the patternoptions. |
| recentlyUsedMaxItems | integer | 20 | Maximum items to keep in recently used list. 0: no restriction. |
| customComponentKeys | dict | {} | Register custom components. Currently only "SelectedItem" implemented |


## Default
Expand Down Expand Up @@ -81,3 +82,44 @@ Show a widget to select items in an offcanvas miller-column browser.
data-pat-contentbrowser='{"selectableTypes": ["Image", "File"], "vocabularyUrl": "contentbrowser-test.json", "upload": true}'
/>
```

## Register custom component

Currently only for `SelectedItem` component available.

```html
<input
type="text"
class="pat-contentbrowser"
data-pat-contentbrowser='{
"customComponentKeys": {
"SelectedItem": "pat-contentbrowser.myfield.MySelectedItemComponent",
},
}'
/>
```

Copy the existing component `src/SelectedItem.svelte` to your addon, customize it and register it in your JS bundle as follows:

```javascript
...
import plone_registry from "@plone/registry";
...

async function register_selecteditem_component() {
// we register our component to a custom keyname,
// which later can be used for pattern_options
const SelectedImages = (await import("./MySelectedItemComponent.svelte")).default;
plone_registry.registerComponent({
name: "pat-contentbrowser.myfield.MySelectedItemComponent",
component: SelectedImages,
});
}
register_selecteditem_component();

...

```

Note: this needs the `svelte-loader` plugin in your webpack.config.js ... see mockups webpack config for info.

9 changes: 9 additions & 0 deletions src/pat/contentbrowser/contentbrowser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { BasePattern } from "@patternslib/patternslib/src/core/basepattern";
import Parser from "@patternslib/patternslib/src/core/parser";
import registry from "@patternslib/patternslib/src/core/registry";
import utils from "../../core/utils";
import plone_registry from "@plone/registry";

// Contentbrowser pattern

Expand Down Expand Up @@ -48,6 +49,14 @@ class Pattern extends BasePattern {
async init() {
this.el.style.display = "none";

// register default components in @plone/registry
const SelectedItem = (await import("./src/SelectedItem.svelte")).default;

plone_registry.registerComponent({
name: "pat-contentbrowser.SelectedItem",
component: SelectedItem,
});

// ensure an id on our element (TinyMCE doesn't have one)
let nodeId = this.el.getAttribute("id");
if (!nodeId) {
Expand Down
2 changes: 2 additions & 0 deletions src/pat/contentbrowser/src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
export let recentlyUsedKey;
export let recentlyUsedMaxItems;
export let bSize = 20;
export let componentRegistryKeys = {};
const log = logger.getLogger("pat-contentbrowser");
Expand Down Expand Up @@ -83,6 +84,7 @@
recentlyUsedKey: recentlyUsedKey,
recentlyUsedMaxItems: recentlyUsedMaxItems,
pageSize: bSize,
componentRegistryKeys: componentRegistryKeys,
};
log.debug(`Initialized App<${fieldId}> with config ${JSON.stringify($config)}`);
Expand Down
2 changes: 1 addition & 1 deletion src/pat/contentbrowser/src/RecentlyUsed.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
{#each items.reverse() as recentlyUsed}
<li>
<a
href={recentlyUsed.getURL}
href={recentlyUsed.getURL || "#"}
on:click|preventDefault={() => select(recentlyUsed)}
class="dropdown-item"
>
Expand Down
59 changes: 59 additions & 0 deletions src/pat/contentbrowser/src/SelectedItem.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<script>
import { getContext } from "svelte";
import { resolveIcon } from "./utils";
// current item index of parent iteration
export let idx;
// item data
export let item;
// parent method to remove selected item from list
const unselectItem = getContext("unselectItem");
</script>

<div class="selected-item border border-secondary-subtle rounded p-2 mb-1 bg-body-tertiary" data-uuid={item.UID}>
<div class="item-info">
<!-- svelte-ignore a11y-missing-attribute -->
<button
class="btn btn-link btn-sm link-secondary"
on:click={() => unselectItem(idx)}
><svg use:resolveIcon={{ iconName: "x-circle" }} /></button
>
<div>
<span class="item-title">{item.Title}</span><br />
<span class="small">{item.path}</span>
</div>
</div>
{#if item.getURL && (item.getIcon || item.portal_type === "Image")}<img
src="{item.getURL}/@@images/image/mini"
alt={item.Title}
/>{/if}
</div>

<style>
.selected-item {
display: flex;
flex-wrap: nowrap;
align-items: start;
justify-content: space-between;
cursor: move;
}
.selected-item > * {
margin-right: 0.5rem;
display: block;
}
.selected-item button {
cursor: pointer;
padding: 0 0.375rem 0.374rem 0;
}
.selected-item .item-info {
display: flex;
align-items: start;
}
.selected-item > img {
object-fit: cover;
width: 95px;
height: 95px;
}
</style>
72 changes: 19 additions & 53 deletions src/pat/contentbrowser/src/SelectedItems.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script>
import { getContext, onMount } from "svelte";
import { flip } from "svelte/animate";
import { get_items_from_uids, resolveIcon } from "./utils.js";
import { getContext, onMount, setContext } from "svelte";
import { get_items_from_uids } from "./utils.js";
import Sortable from "sortablejs";
import _t from "../../../core/i18n-wrapper";
import events from "@patternslib/patternslib/src/core/events";
import plone_registry from "@plone/registry";
let ref;
let initializing = true;
Expand All @@ -21,6 +21,13 @@
// showContentBrowser reactive state
const showContentBrowser = getContext("showContentBrowser");
// get selectedItem component from registry.
// the registry key can be customized with pattern_options
// if an addon registers a custom component to a custom key
const RegisteredSelectedItem = plone_registry.getComponent(
$config.componentRegistryKeys?.selectedItem || "pat-contentbrowser.SelectedItem"
);
onMount(async () => {
await initializeSelectedItemsStore();
initializeSorting();
Expand All @@ -35,6 +42,10 @@
selectedUids.update(() => $selectedItems.map((x) => x.UID));
}
// use this function in "SelectedItem" component with
// const unselectItem = getContext("unselectItem")
setContext("unselectItem", unselectItem);
async function initializeSelectedItemsStore() {
const initialValue = $config.selection.length
? $config.selection
Expand Down Expand Up @@ -85,6 +96,10 @@
selectedItemsNode.dispatchEvent(events.change_event());
}
function LoadSelectedItemComponent(node, props) {
const component = new RegisteredSelectedItem.component({target: node, props: props});
}
$: {
$selectedItems;
if ($selectedItems.length || !initializing) {
Expand All @@ -105,27 +120,7 @@
on:click={() => $showContentBrowser = $selectedItems.length ? false : true }>
{#if $selectedItems}
{#each $selectedItems as selItem, i (selItem.UID)}
<div
class="selected-item"
animate:flip={{ duration: 500 }}
data-uuid={selItem.UID}
>
<div class="item-info">
<button
class="btn btn-link btn-sm link-secondary"
on:click|stopPropagation={() => unselectItem(i)}
><svg use:resolveIcon={{ iconName: "x-circle" }} /></button
>
<div>
<span class="item-title">{selItem.Title}</span><br />
<span class="small">{selItem.path}</span>
</div>
</div>
{#if selItem.getURL && (selItem.getIcon || selItem.portal_type === "Image")}<img
src="{selItem.getURL}/@@images/image/mini"
alt={selItem.Title}
/>{/if}
</div>
<div use:LoadSelectedItemComponent={{idx:i, item:selItem}} />
{/each}
{/if}
{#if !$selectedItems}
Expand Down Expand Up @@ -155,33 +150,4 @@
padding: 0.5rem 0.5rem 0 0.5rem;
flex: 1 1 auto;
}
.content-browser-selected-items .selected-item {
border-radius: var(--bs-border-radius);
background-color: var(--bs-tertiary-bg);
border: var(--bs-border-style) var(--bs-border-color) var(--bs-border-width);
padding: 0.5rem;
margin-bottom: 0.5rem;
display: flex;
flex-wrap: nowrap;
align-items: start;
justify-content: space-between;
cursor: move;
}
.content-browser-selected-items .selected-item > * {
margin-right: 0.5rem;
display: block;
}
.content-browser-selected-items .selected-item button {
cursor: pointer;
padding: 0 0.375rem 0.374rem 0;
}
.content-browser-selected-items .selected-item .item-info {
display: flex;
align-items: start;
}
.content-browser-selected-items .selected-item > img {
object-fit: cover;
width: 95px;
height: 95px;
}
</style>
Loading

0 comments on commit 9906aff

Please sign in to comment.