indexation : add mentions
Some checks failed
Deploy / Deploy to Production (push) Failing after 6s

This commit is contained in:
isUnknown 2026-06-18 15:31:50 +02:00
parent a1c5e1ed36
commit 31d30d9c8f
51 changed files with 20 additions and 784 deletions

View file

Before

Width:  |  Height:  |  Size: 926 KiB

After

Width:  |  Height:  |  Size: 926 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 529 KiB

After

Width:  |  Height:  |  Size: 529 KiB

Before After
Before After

View file

@ -2,6 +2,10 @@ Title: Actualités
----
Mentions: art, design, recherche
----
Categories: Dans les murs, Hors les murs
----

View file

@ -13,20 +13,21 @@ tabs:
- width: 1/2
fields:
category:
label: Categorie
label: Types
type: select
options: query
query: page.parent.categories.split
width: 1/2
help: |
<a href="/panel/pages/news?tab=indexation">Administrer la liste</a>
<a href="/panel/pages/actualites?tab=indexation">Administrer la liste</a>
tags:
label: Mots-clés
type: multiselect
options: query
query: page.parent.tags.split
width: 1/2
help: |
<a href="/panel/pages/news?tab=indexation">Administrer la liste</a>
<a href="/panel/pages/actualites?tab=indexation">Administrer la liste</a>
cover:
label: Image de couverture
type: files

View file

@ -20,12 +20,17 @@ tabs:
query: page.cover.toFile
indexation:
icon: folder-structure
fields:
categories:
type: tags
width: 1/2
tags:
type: tags
width: 1/2
columns:
- width: 1/2
fields:
mentions:
type: tags
disabled: true
categories:
type: tags
- width: 1/2
fields:
tags:
type: tags
files: tabs/files
seo: seo/page

View file

@ -1,12 +0,0 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
[*.php]
indent_size = 2

View file

@ -1,10 +0,0 @@
# System files
# ---------------------
.DS_Store
node_modules
package-lock.json
Icon
# Lock Files
# ---------------------
.lock

View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2025 Scott Boms
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,111 +0,0 @@
# Promote Button for Kirby
![Plugin Preview](src/assets/kirby-promote-button.png)
A customizable View Button for Kirby 5 that builds on Bastian's demo from the [Kirby 5 Release Show](https://youtube.com/watch?v=o2xkzqiLEUM) adding missing functionality and configuration settings for Mastodon, Bluesky, and LinkedIn as well as other user-experience enhancements.
## Requirements
This plugin requires Kirby 5.x and newer. It will not work with earlier versions of Kirby.
## Installation
### [Kirby CLI](https://github.com/getkirby/cli)
kirby plugin:install scottboms/kirby-promote-button
### Git submodule
git submodule add https://github.com/scottboms/kirby-promote-button.git site/plugins/promote-button
### Copy and Paste
1. [Download](https://github.com/scottboms/kirby-promote-button/archive/master.zip) the contents of this repository as Zip file.
2. Rename the extracted folder to `promote-button` and copy it into the `site/plugins/` directory in your project.
## Configuration
To function, the plugin requires configuration as outlined below.
### Required Settings
Add these settings to your `/site/config/config.php` or `/site/config/env.php` file. Define which services you wish to use and then replace the CAPITALIZED PLACEHOLDERS with the necessary values.
#### General
```php
<?php
return [
'scottboms.promote' => [
'services' => [
'mastodon',
'bluesky',
'linkedin'
],
'mastodon' => [
'username' => 'USERNAME', // e.g. scottboms
'url' => 'MASTODON_HOST', // e.g. mastodon.social
],
'bluesky' => [
'base_url' => 'BLUESKY_HOST', // e.g. bsky.social
'handle' => 'USERNAME', // e.g. example.bsky.social
]
],
]
```
#### Tokens and Passwords
To post to [Mastodon](https://mastodon.social/settings/applications), [Bluesky](https://bsky.app/settings/app-passwords) or [LinkedIn](https://linkedin.com/developers/apps), you will need the necessary authentication tokens or app passwords. Because this information is sensitive, you should not include these settings in your `/site/config/config.php` file and instead place them in the [env.php config file](https://getkirby.com/docs/guide/configuration#multi-environment-setup__deployment-configuration) which should be added to a `.gitignore` file to avoid sharing this info publicly.
```php
<?php
return [
'scottboms.promote.mastodon.token' => 'MASTODON_API_TOKEN',
'scottboms.promote.bluesky.password' => 'BLUESKY_APP_PASSWORD',
'scottboms.promote.linkedin.token' => 'LINKEDIN_OAUTH_TOKEN',
],
```
### Optional Settings
If you run your Kirby site locally, the Promote button will function but page urls added to the dialog will use the local hostname (e.g. localhost) which isn't very helpful when posting to public services. You can override this behaviour by setting `host_url` in the configuration.
```php
<?php
return [
'scottboms.promote' => [
'host_url' => 'SHARED_LINK_HOST', // e.g. https://example.com
],
]
```
## Blueprint Configuration
There are multiple methods to add [View Buttons](https://getkirby.com/releases/5/view-buttons) to your Kirby installation. This plugin includes two distinct View Buttons -- the Promote button to access the core features of this plugin, and the Profile button which currently allows a way to quickly go to a Mastodon profile. The buttons can be added to any page by adding the `buttons` [option](https://getkirby.com/docs/reference/panel/blueprints/page#view-buttons) in a Page or Site Blueprint.
```yml
buttons:
promote: true
profile: true
```
## Credits
* Original Concept and Starting Points: [Bastian Allgeier](https://github.com/bastianallgeier/)
* Supported Services: [Mastodon](https://mastodon.social), [Bluesky](https://bsky.app), [LinkedIn](https://linkedin.com)
## Disclaimer
This plugin is provided "as is" with no guarantee. Use it at your own risk and always test before using it in a production environment. If you identify an issue, typo, etc, please [create a new issue](/issues/new) so I can investigate.
## License
[MIT](https://opensource.org/licenses/MIT)

View file

@ -1,303 +0,0 @@
<?php
namespace ScottBoms\Promote;
use Kirby\Http\Remote;
use Kirby\Toolkit\Dir;
use Kirby\Toolkit\F;
use Kirby\Toolkit\Str;
use Exception;
class PlatformPromoter
{
protected $config;
protected const USER_AGENT = 'KirbyPromote/1.0 (+https://scottboms.com)';
protected bool $cacheEnabled = true;
protected int $cacheTimeout = 3600; // 1 hour in seconds
public function __construct()
{
$this->config = [
'mastodon' => [
'token' => option('scottboms.promote.mastodon.token'),
'username' => option('scottboms.promote.mastodon.username'),
'url' => option('scottboms.promote.mastodon.url', 'mastodon.social'),
],
'linkedin' => [
'username' => option('scottboms.promote.linkedin.username'),
'token' => option('scottboms.promote.linkedin.token'),
],
'bluesky' => [
'base_url' => option('scottboms.promote.bluesky.base_url', 'bsky.social'),
'handle' => option('scottboms.promote.bluesky.handle'),
'password' => option('scottboms.promote.bluesky.password'),
],
];
}
// --------------------------------------------------------------------------
// Posting...
public function post(string $platform, string $text): void
{
if (!method_exists($this, $platform)) {
throw new Exception("Platform '$platform' is not supported.");
}
$this->{$platform}($text);
}
// --------------------------------------------------------------------------
// Mastodon
protected function mastodon(string $text): void
{
$text = trim($text); // remove whitespace
if ($text === '') {
// $this->log('mastodon', 'error', 'Aborted: status text is empty');
throw new Exception('Mastodon post failed: status text is empty');
}
$token = $this->config['mastodon']['token'];
$baseUrl = 'https://' . rtrim($this->config['mastodon']['url'], '/');
$url = $baseUrl . '/api/v1/statuses';
// $this->log('mastodon', 'debug', 'Posting status: ' . $text);
// Ensure proper encoding
$text = mb_convert_encoding($text, 'UTF-8', 'auto');
$idempotencyKey = hash('sha256', $text); // hash of the content
$payloadArray = [
'status' => $text,
'language' => 'en',
'visibility' => 'public'
];
$payload = json_encode($payloadArray);
if ($payload === false) {
// $this->log('mastodon', 'error', 'Failed to encode payload: ' . json_last_error_msg());
throw new Exception('Mastodon post failed: JSON encoding error');
}
$this->log('mastodon', 'debug', 'Final payload: ' . $payload);
// Use cURL to post to Mastodon
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
'Content-Length: ' . strlen($payload),
'Idempotency-Key: ' . $idempotencyKey,
'User-Agent: ' . self::USER_AGENT,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$this->log('mastodon', 'debug', 'Response HTTP code: ' . $httpCode);
// $this->log('mastodon', 'debug', 'Response: ' . $response);
if (!in_array($httpCode, [200, 202])) {
throw new Exception('Mastodon post failed with code ' . $httpCode . ': ' . $response);
}
$this->log('mastodon', 'info', 'Post succeeded.');
}
// --------------------------------------------------------------------------
// Bluesky
protected function bluesky(string $text): void
{
// Step 1: Authenticate
$identifier = preg_replace('/[^\x21-\x7E]/', '', ltrim($this->config['bluesky']['handle'], '@'));
$password = $this->config['bluesky']['password'];
$baseUrl = 'https://' . rtrim($this->config['bluesky']['base_url'] ?? 'https://bsky.social', '/');
$payload = json_encode([
'identifier' => $identifier,
'password' => $password,
]);
// Use native cURL, json_encode/decode seemed to fail
$ch = curl_init($baseUrl . '/xrpc/com.atproto.server.createSession');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: ' . strlen($payload),
'User-Agent: ' . self::USER_AGENT
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$this->log('bluesky', 'debug', "cURL auth code: $httpCode");
// $this->log('bluesky', 'debug', "cURL auth response: $response");
$authData = json_decode($response, true);
if ($httpCode !== 200 || !isset($authData['accessJwt'])) {
throw new Exception('Bluesky authentication failed: ' . $response);
}
$accessJwt = $authData['accessJwt'];
$did = $authData['did'];
// Step 2: Post
$postPayload = json_encode([
'collection' => 'app.bsky.feed.post',
'repo' => $did,
'record' => [
'$type' => 'app.bsky.feed.post',
'text' => $text,
'langs' => ['en-US'], // must be array
'createdAt' => date('c'),
]
]);
$ch = curl_init($baseUrl . '/xrpc/com.atproto.repo.createRecord');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postPayload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $accessJwt,
'Content-Length: ' . strlen($postPayload),
'User-Agent: ' . self::USER_AGENT
]);
$postResponse = curl_exec($ch);
$postHttpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$this->log('bluesky', 'debug', 'Post HTTP code: ' . $postHttpCode);
// $this->log('bluesky', 'debug', 'Post response: ' . $postResponse);
if ($postHttpCode !== 200) {
throw new Exception('Bluesky post failed: ' . $postResponse);
}
$this->log('bluesky', 'info', 'Post succeeded.');
}
// --------------------------------------------------------------------------
// LinkedIn
protected function getLinkedInAuthorUrn(): string
{
$cache = kirby()->cache('linkedin');
$cacheKey = 'author_urn';
// Check cache first
if($this->cacheEnabled) {
$cachedUrn = $cache->get($cacheKey);
if($cachedUrn !== null) {
$this->log('linked', 'debug', 'Using cached LinkedIn author URN');
return $cachedUrn;
}
}
$token = $this->config['linkedin']['token'];
$response = Remote::get('https://api.linkedin.com/v2/userinfo', [
'headers' => [
'Authorization' => 'Bearer ' . $token,
'Content-Type' => 'application/json',
],
]);
if ($response->code() !== 200) {
throw new Exception('LinkedIn /userinfo failed: ' . $response->content());
}
$data = json_decode($response->content(), true);
if (!isset($data['sub'])) {
throw new \Exception('LinkedIn /userinfo response missing "sub" field.');
}
$urn = 'urn:li:person:' . $data['sub'];
if ($this->cacheEnabled) {
$cache->set($cacheKey, $urn, $this->cacheTimeout);
$this->log('linkedin', 'debug', 'Cached LinkedIn author URN');
}
return $urn;
}
protected function linkedin(string $text): void
{
$token = $this->config['linkedin']['token'];
$author = $this->getLinkedInAuthorUrn();
$payload = [
'author' => $author,
'lifecycleState' => 'PUBLISHED',
'specificContent' => [
'com.linkedin.ugc.ShareContent' => [
'shareCommentary' => [
'text' => $text
],
'shareMediaCategory' => 'NONE'
]
],
'visibility' => [
'com.linkedin.ugc.MemberNetworkVisibility' => 'PUBLIC'
]
];
// $this->log('linkedin', 'debug', 'Post JSON: ' . json_encode($payload));
$json = json_encode($payload);
if ($json === false) {
$this->log('linkedin', 'error', 'Failed to encode payload: ' . json_last_error_msg());
throw new Exception('LinkedIn post failed: JSON encoding error');
}
$ch = curl_init('https://api.linkedin.com/v2/ugcPosts');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
'X-Restli-Protocol-Version: 2.0.0',
'Content-Length: ' . strlen($json),
'User-Agent: ' . self::USER_AGENT
]);
$responseBody = curl_exec($ch);
$responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$this->log('linkedin', 'debug', 'Response Code: ' . $responseCode);
// $this->log('linkedin', 'debug', 'Response Body: ' . $responseBody);
if ($responseCode !== 201) {
throw new Exception('LinkedIn post failed: ' . $responseBody);
}
$this->log('linkedin', 'info', 'Post succeeded.');
}
// --------------------------------------------------------------------------
// Logging
// Implements custom logging to /site/logs/promote.log
private function log(string $platform, string $level, string $message): void
{
$timestamp = date('Y-m-d H:i:s');
$logDir = kirby()->root('logs');
$logFile = $logDir . '/promote.log';
// Ensure log directory exists
Dir::make($logDir);
$entry = Str::unhtml("[$timestamp][$level][$platform] $message") . PHP_EOL;
F::append($logFile, $entry);
}
}

View file

@ -1,33 +0,0 @@
{
"name": "scottboms/promote-button",
"description": "Promote Panel Button for Kirby.",
"type": "kirby-plugin",
"homepage": "https://github.com/scottboms/kirby-promote-button",
"authors": [
{
"name": "Scott Boms",
"email": "plugins@scottboms.com",
"homepage": "https://scottboms.com"
}
],
"support": {
"docs": "https://github.com/scottboms/kirby-promote-button/blob/main/README.md",
"source": "https://github.com/scottboms/kirby-promote-button"
},
"license": "MIT",
"keywords": [
"kirby",
"kirby5",
"kirby-cms",
"kirby-plugin",
"kirby5-plugin"
],
"require": {
"php": ">8.1.0 <8.4.0",
"getkirby/cms": "^5.0",
"getkirby/composer-installer": "^1.1"
},
"extra": {
"installer-name": "promote-button"
}
}

View file

@ -1 +0,0 @@
(function(){"use strict";function r(t,n,e,u,f,d,p,m){var o=typeof t=="function"?t.options:t;return n&&(o.render=n,o.staticRenderFns=e,o._compiled=!0),{exports:t,options:o}}const i={name:"KProfilesButton",props:{text:{String,default:"Profiles"},icon:{String,default:"account"},theme:{String,default:"pink-icon"},title:{String,default:""},items:{Array,default:()=>[]}},computed:{options(){return this.items.map(t=>({text:t.text,icon:t.icon,disabled:!!t.disabled,click:()=>this.go(t),target:t.target||null}))},anchor(){const t=this.$refs.btn;return t&&(t.$el||t)}},mounted(){console.log("[ProfilesButton] mounted",this.options)},methods:{toggle(){const t=this.$refs.menu;t&&this.anchor&&t.toggle()},onClose(){},go(t){t.disabled||t!=null&&t.link&&window.open(t.link,t.target||"_blank")}}};var s=function(){var n=this,e=n._self._c;return e("div",{staticClass:"k-profiles-button"},[e("k-button",{ref:"btn",attrs:{dropdown:!0,title:n.title,variant:"filled",icon:n.icon,size:"sm",text:n.text,theme:n.theme},on:{click:function(u){return n.$refs.menu.toggle()}}}),e("k-dropdown-content",{ref:"menu",attrs:{alignX:"end",anchor:n.anchor,options:n.options},on:{close:n.onClose}})],1)},l=[],c=r(i,s,l);const a=c.exports;panel.plugin("scottboms/promote-button",{components:{"k-profiles-button":a}})})();

View file

@ -1,179 +0,0 @@
<?php
load(['scottboms\Promote\PlatformPromoter' => __DIR__ . '/classes/PlatformPromoter.php']);
use ScottBoms\Promote\PlatformPromoter;
use Kirby\Http\Remote;
use Kirby\Cms\App;
// shamelessly borrowed from distantnative/retour-for-kirby
if (
version_compare(App::version() ?? '0.0.0', '5.0.0', '<') === true ||
version_compare(App::version() ?? '0.0.0', '6.0.0', '>=') === true
) {
throw new Exception('Promote Button requires Kirby v4 or v5');
}
Kirby::plugin('scottboms/promote-button', [
'areas' => [
'site' => function () {
return [
'buttons' => [
'promote' => function ($page) {
return [
'icon' => 'megaphone',
'text' => 'Promouvoir',
'theme' => 'pink-icon',
'title' => 'Share this Page',
'dialog' => 'promote/?page=' . $page->uuid()->toString(),
];
},
'profiles' => function () {
$services = option('scottboms.promote.services', []);
$items = [];
foreach ($services as $service) {
if ($service === 'mastodon') {
$host = trim((string) option('scottboms.promote.mastodon.url'));
$user = trim((string) option('scottboms.promote.mastodon.username'));
if ($host && $user) {
$items[] = [
'text' => 'Mastodon',
'icon' => 'mastodon', // keep generic to rule out custom icon issues
'link' => 'https://' . $host . '/@' . $user,
'target' => '_blank',
];
}
} elseif ($service === 'bluesky') {
$handle = trim((string) option('scottboms.promote.bluesky.handle'));
if ($handle) {
$items[] = [
'text' => 'Bluesky',
'icon' => 'bluesky',
'link' => 'https://bsky.app/profile/' . $handle,
'target' => '_blank',
];
}
} elseif ($service === 'linkedin') {
$username = trim((string) option('scottboms.promote.linkedin.username'));
if ($username) {
$items[] = [
'text' => 'LinkedIn',
'icon' => 'linkedin',
'link' => 'https://linkedin.com/in/' . $username,
'target' => '_blank',
];
}
} elseif ($service === 'instagram') {
$username = trim((string) option('scottboms.promote.instagram.username'));
if ($username) {
$items[] = [
'text' => 'Instagram',
'icon' => 'instagram',
'link' => 'https://instagram.com/' . $username,
'target' => '_blank',
];
}
}
}
if (!$items) {
$items[] = [
'text' => 'No profiles configured',
'icon' => 'alert',
'disabled' => true,
];
}
return [
'component' => 'k-profiles-button',
'props' => [
'text' => 'Profiles',
'icon' => 'account',
'theme' => 'pink-icon',
'title' => 'Profiles',
'items' => $items,
],
];
},
],
'dialogs' => [
'promote' => [
'load' => function () {
$page = page(get('page'));
return [
'component' => 'k-form-dialog',
'props' => [
'size' => 'large',
'fields' => [
'text' => [
'label' => 'Texte',
'type' => 'textarea',
'buttons' => false,
'size' => 'small',
'required' => true,
],
'platforms' => [
'label' => 'Publier sur',
'type' => 'checkboxes',
'columns' => max(1, min(3, count(option('scottboms.promote.services', [])))),
'options' => array_map(function ($service) {
$labelMap = [
'mastodon' => 'Mastodon',
'bluesky' => 'Bluesky',
'linkedin' => 'LinkedIn',
'instagram' => 'Instagram',
];
return [
'value' => $service,
'text' => $labelMap[$service] ?? ucfirst($service)
];
}, option('scottboms.promote.services', []))
],
],
'value' => [
'text' => 'Just posted ' . $page->title()->value() . ' ' . (
option('scottboms.promote.host_url')
? rtrim(option('scottboms.promote.host_url'), '/') . '/' . $page->uri()
: $page->url()
),
'platforms' => ['mastodon','bluesky','linkedin'],
],
'submitButton' => [
'icon' => 'megaphone',
'text' => 'Poster',
'theme' => 'pink'
],
],
];
},
'submit' => function () {
$text = get('text');
$enabled = option('scottboms.promote.services', []);
$platforms = array_intersect(get('platforms', []), $enabled);
if (!is_array($platforms)) return false;
$promoter = new ScottBoms\Promote\PlatformPromoter();
foreach ($platforms as $platform) {
$promoter->post($platform, $text);
}
return true;
},
],
],
];
},
],
'info' => [
'homepage' => 'https://github.com/scottboms/kirby-promote-button',
'version' => '1.1.1',
'license' => 'MIT',
'authors' => [[ 'name' => 'Scott Boms' ]],
],
]);

View file

@ -1,15 +0,0 @@
{
"name": "promote-button",
"description": "Promote Button",
"author": "Scott Boms <plugins@scottboms.com>",
"version": "1.1.1",
"type": "kirby-plugin",
"license": "MIT",
"scripts": {
"dev": "kirbyup serve src/index.js",
"build": "kirbyup src/index.js"
},
"devDependencies": {
"kirbyup": "^3.1.5"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

View file

@ -1,82 +0,0 @@
<template>
<div class="k-profiles-button">
<k-button
:dropdown="true"
:title="title"
variant="filled"
:icon="icon"
ref="btn"
size="sm"
:text="text"
:theme="theme"
@click="$refs.menu.toggle()"
/>
<k-dropdown-content
ref="menu"
alignX="end"
:anchor="anchor"
:options="options"
@close="onClose"
/>
</div>
</template>
<script>
export default {
name: "KProfilesButton",
props: {
text: { String, default: 'Profiles' },
icon: { String, default: 'account' },
theme: { String, default: 'pink-icon' },
title: { String, default: '' },
items: { Array, default: () => [] }
},
computed: {
options() {
// k-dropdown-content expects standard dropdown option shape:
// { text, icon, link, target, disabled, click, ... }
return this.items.map((it) => ({
text: it.text,
icon: it.icon,
disabled: !!it.disabled,
click: () => this.go(it),
target: it.target || null,
}));
},
anchor() {
// k-dropdown expects a real dom node
const r = this.$refs.btn;
return r && (r.$el || r);
}
},
mounted() {
console.log('[ProfilesButton] mounted', this.options);
},
methods: {
toggle() {
const menu = this.$refs.menu;
if (!menu) return;
// ensure we have an anchor before toggling
if (!this.anchor) return;
menu.toggle();
},
onClose() {
// console.log('[ProfilesButton] dropdown closed');
},
go(item) {
// console.log('[ProfilesButton] go ', item);
if (item.disabled) return;
if (item?.link) window.open(item.link, item.target || "_blank");
},
}
}
</script>

View file

@ -1,7 +0,0 @@
import ProfilesButton from "./components/ProfilesButton.vue";
panel.plugin("scottboms/promote-button", {
components: {
"k-profiles-button": ProfilesButton,
}
});