clean useless plugins

This commit is contained in:
isUnknown 2026-06-18 14:50:11 +02:00
parent 22299c30d0
commit 591735957f
27 changed files with 40 additions and 2062 deletions

View file

@ -1,6 +0,0 @@
composer.lock
package-lock.json
.idea
.cache
.DS_Store
node_modules

View file

@ -1,177 +0,0 @@
# Kirby Navigation Field
A Navigation field for [Kirby CMS](https://getkirby.com).
## Preview
![](https://github.com/chrisbeluga/kirby-navigation/blob/main/navigation-demo-1.gif)
## Installation & Usage
Copy plugin files to your plugin's directory or install via composer with `composer require belugadigital/kirby-navigation`
Note that this Composer package name (belugadigital/kirby-navigation) differs from the GitHub repository URL (chrisbeluga/kirby-navigation).
## Kirby compatibility table
| Kirby version | Compatible plugin version |
|:--------------|:--------------------------|
| ^5.0 | ^5.0 |
| ^4.8 | ^4.2 |
| ^4.0 | ^4.0 |
| ^3.7 | ^3.0 |
| ^3.6 | ^2.0 |
| ^3.5 | ^1.0 |
## Usage
Add the following blueprint to wherever you would like the navigation field to appear:
```yaml
fields:
navigation:
label: Navigation
type: navigation
levels: 10
help: Description of menu or where it is used
width: 1/2
multilang: false
```
Or use the following minimalist blueprint without extra options:
```yaml
fields:
navigation:
label: Navigation
type: navigation
```
The following example shows how you can output a menu from a template file, regardless of how many levels deep the menu is. This example assumes that the "site" blueprint contains a navigation field called "navigation":
```php
<?php echo $site->navigation()->toNavigationMarkup(); ?>
```
If using the site as a headless CMS or would like to consume your menu in JS you can use the following field method to return a nested array of menu items:
```php
<?php $site->navigation()->toNavigationArray(); ?>
```
Or when using Kirby Query language
```json
{
"query": "site",
"select": {
"title": "site.title",
"navigation": "site.navigation.toNavigationArray"
}
}
```
If you want full control over your menu and want to customize the markup, you can copy the navigation.php and navigation_item.php files from the plugin's snippets directory to your /site/snippets directory, and customize them there.
This is the recommended way of markup customization.
For example, to add class="navigation-item navigation-item-X" to each link item, where X is the depth level of the given link, you can add the following line to your copy of navigation_item.php:
```php
$attributes['class']='navigation-item navigation-item-' . $depth;
```
If you prefer to use a foreach to create the menu, or if you are upgrading from an older version of this plugin, the foreach loop could look something like this:
```php
<?php if ($items=$site->navigation()->toNavigationStructure()): ?>
<ul>
<?php foreach($items as $item): ?>
<li>
<a href="<?php echo $item->url(); ?>" <?php e($item->isOpen()->value(), 'aria-current="page"') ?> <?php e($item->isChildOpen()->value(), 'class="active"') ?>>
<?php echo Str::esc($item->text(), 'html') ?>
</a>
<?php if($item->children()->isNotEmpty()): ?>
<ul>
<?php foreach($item->children()->toStructure() as $child): ?>
<li>
<a href="<?php echo $child->url() ?>" <?php e($child->isOpen()->value(), 'aria-current="page"') ?>>
<?php echo Str::esc($child->text(), 'html') ?>
</a>
</li>
<?php endforeach ?>
</ul>
<?php endif ?>
</li>
<?php endforeach ?>
</ul>
<?php endif ?>
```
As you can see, $item->isOpen()->value() can be used in this foreach() to check whether the given menu item is the current page, and $item->isChildOpen()->value() can be used to check whether any of the child menu items is the current page.
## Nesting limit
Nesting limit is set to 10 by default. To adjust the maximum number of levels, use the "levels" option in the blueprint.
## Multi-language support
The plugin supports multiple languages in two ways.
- In "normal" mode, the navigation field functions like any other content field: you need to add the necessary links to the field for each language, and set the link text and link title for each language (translating). For example, if you have 3 languages and a navigation field with 5 links, you will need to add the 5 links 3 times.
- In "multilang" mode, add the "multilang: true" option to the field blueprint. This allows you to add the necessary links only once for all languages, and edit the link text and link title separately for each language. For example, if you have 3 languages and a navigation field with 5 links, you will only need to add the 5 links once, and they will be shared across all languages. You can still translate the link text and link title, if needed.
This means that if you simply add 5 Kirby pages to the field in multilang mode and don't edit the links, the link text will be displayed in the language of the current page, and will link to the corresponding page URL in that language.
The plugin allows you to add "Kirby Pages" and "Custom Links" to the navigation field. For "Kirby Pages", the page title will be the default value of the link text, if no custom link text is entered. This means that if you simply add 5 Kirby pages to the field in multilang mode and don't edit the links, you will see the language-specific page title and page URL in the generated markup.
## What's new in version 4.0?
Changes worth mentioning:
- It works with Kirby 4
- New feature: Multi-language support, as described earlier
- New feature: The plugin now uses permanent page IDs to identify pages, instead of using the 'id' (slug) as identification. This allows pages to be renamed or moved without breaking the page links.
- Data: the structure of the field content has been changed to support permanent page IDs and multiple languages. However, the 4.0 version of the plugin is backwards compatible with the field content saved by the 3.7 version of the plugin.
- UI: The "edit" button has been moved out of the options dropdown menus.
- UI: Better icons are now used for editing the links.
- UI: The 'id' and 'url' values of 'Kirby page' links are no longer editable.
- Markup: the current language and the actual values of the page title and page URL are taken into account when generating the markup for the field in the template.
- Markup: link text and link attributes are properly escaped to prevent potential issues
## What's new in version 4.1?
New features:
- The 'class' and 'target' values of links are now editable
- The 'anchor' values of 'Kirby page' links are now editable
- The 'title' textfield can be hidden, if you do not need it
- The 'popup' toggle can be hidden, if you do not need it
Different sites have different needs, so the editable fields are configurable via /site/config/config.php.
Here are the available options that you can use in your config.php, and their default values:
```php
return [
'chrisbeluga.navigation' => [
'edit_title' => true,
'edit_popup' => true,
'edit_target' => false,
'edit_class' => false,
'edit_anchor' => false,
],
];
```
For example, if you want to customize the 'target' value of your links, then set 'chrisbeluga.navigation.edit_target' to TRUE. This will replace the simple 'Popup' toggle with a 'Target' textfield, allowing you to set a link target, such as '_parent' or '_top'.
If you want to add an anchor value to your 'Kirby page' links, for example to have an URL such as /en/contact#locations, set 'chrisbeluga.navigation.edit_anchor' to TRUE. You can enter 'locations' as anchor, and '#locations' will be appended to the page URL of the link.
If you use the recommended way to output the navigation markup from your template (such as $site->navigation()->toNavigationMarkup() in case of a field called 'navigation'), then any target, class and anchor values will be included automatically in the generated markup.
## What's new in version 5.0?
Works with Kirby 5.0
## Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
## License
[MIT](https://choosealicense.com/licenses/mit/)

View file

@ -1,49 +0,0 @@
{
"name": "chrisbeluga/kirby-navigation",
"description": "Kirby 5 field for hierarchical menus with drag & drop level indentation.",
"type": "kirby-plugin",
"keywords": [
"kirby",
"kirby-cms",
"kirby-plugin",
"navigation",
"menu",
"field"
],
"version": "5.0.11",
"license": "MIT",
"homepage": "https://github.com/chrisbeluga/kirby-navigation",
"authors": [
{
"name": "Chris Martin",
"email": "chris@builtbybeluga.com",
"homepage": "https://builtbybeluga.com/"
},
{
"name": "Ahmet Bora",
"email": "ahmet@getkirby.com",
"homepage": "https://owebstudio.com/"
},
{
"name": "Gabor Horvath"
},
{
"name": "Immo Seebörger",
"homepage": "https://diesachbearbeiter.de/"
}
],
"require": {
"php": ">=8.1.0 <8.6.0",
"getkirby/cms": "^4.0 || ^5.0",
"getkirby/composer-installer": "^1.2"
},
"config": {
"optimize-autoloader": true,
"allow-plugins": {
"getkirby/composer-installer": true
}
},
"extra": {
"installer-name": "kirby-navigation"
}
}

View file

@ -1,83 +0,0 @@
<?php
use Kirby\Uuid\Uuids;
/**
* Good to know:
* - stored Kirby links always have 'id' values, Custom links never have that
* - stored Kirby links may have 'uuid_uri' values, Custom links never have that
* - The uuid_uri can identify a page even after the page slug changes,
* so it is the preferred way of identification.
* - If uuid_uri value is not available for any reason, the plugin uses the 'id' value
* to find the desired page
* - The 'uuid' value used elsewhere in the plugin is not the same as 'uuid_uri' value.
* The 'uuid' value is kept for compatibility reasons.
*/
return function () {
return [
[
'pattern' => 'listings/([a-zA-Z0-9-]+)/(:all)',
'method' => 'GET',
'action' => function ($language_code, $path) {
$content = [];
$breadcrumbs = [];
$getData = $path !== 'site' ? true : false;
$data = $getData ? page($path) : site();
$multilang = $this->kirby()->languages()->isNotEmpty();
if ($multilang) {
if ($language_code=='default') {
// default language, use item title and url without translation
$multilang=false;
}
elseif (!$this->kirby()->languages()->find($language_code)) {
// invalid language, do nothing, just return an empty array
$data=null;
}
}
if (($data != null) && $data->hasChildren()) {
if ($getData) {
foreach ($data->children()->first()->parents()->flip() as $parent) {
if ($multilang && ($parent->content($language_code)->title()->value() != null)) {
$title=$parent->content($language_code)->title()->value();
}
else {
$title = $parent->title()->value();
}
array_push($breadcrumbs,[
'id' => $parent->id(),
'title' => $title,
]);
}
}
foreach ($data->children() as $item) {
$title = $item->title()->value();
if ($multilang && ($item->content($language_code)->title()->value() != null)) {
$title=$item->content($language_code)->title()->value();
}
array_push($content, [
// Values for page identification
'type' => 'page',
'uuid_uri' => Uuids::enabled() ? $item->uuid()->toString() : '',
'id' => $item->id(),
// Language-specific values that may change due to editing
$language_code . '_page_url' => $multilang ? $item->url($language_code) : $item->url(),
$language_code . '_page_title' => $title,
// Default values of page links that prop.php also provides
$language_code . '_link_text' => '',
$language_code . '_link_title' => '',
'children' => [],
'target' => '',
// Temporary helper values that will not be saved
'count' => $item->index()->count(),
]);
}
}
return [
'content' => $content,
'breadcrumbs' => $breadcrumbs,
];
}
],
];
};

View file

@ -1,123 +0,0 @@
<?php
use Kirby\Data\Yaml;
return [
// This method is the preferred way to get all the link items
// (with all child links) as a nested array.
// Do not use $field->toArray() in case of the navigation field.
'toNavigationArray' => function ($field) {
// Refresh items to get the current multilang URL and page titles
require __DIR__ . '/../includes/refresh_item.inc.php';
$items=$refresh_items($field->yaml(), $field->key(), $field->model());
$lang_code=kirby()->languages()->isNotEmpty() ? kirby()->language()->code() : 'default';
// Use anonymous recursive function to process child items
$process_item = function($item, $depth=1) use (&$process_item, $lang_code) {
$item['depth']=$depth;
// To help markup generation, set 'url', 'text', 'title'
// and the optional 'anchor' according to the current language
if ($item['type']=='page') {
$item['url']=$item[$lang_code . '_page_url'];
$item['text']=$item[$lang_code . '_link_text'];
$item['title']=$item[$lang_code . '_link_title'];
if (isset($item[$lang_code . '_link_anchor'])) {
$item['anchor']='#' . $item[$lang_code . '_link_anchor'];
}
else {
$item['anchor']='';
}
if ($item['text']=='') {
$item['text']=$item[$lang_code . '_page_title'];
}
if ($item['title']=='') {
$item['title']=$item[$lang_code . '_page_title'];
}
}
elseif ($item['type']=='custom') {
$item['text']=$item[$lang_code . '_link_text'];
$item['title']=$item[$lang_code . '_link_title'];
}
else {
$item['error']=TRUE;
return $item;
}
if ($item['title']==$item['text']) {
// No need to have a title
$item['title']='';
}
// Values used by previous plugin version
$item['isOpen']=kirby()->url('current') === $item['url'];
// To help markup generation and customization, put all attributes into an array
$item['attributes']=[];
if ($item['title']!='') {
$item['attributes']['title']=$item['title'];
}
if (isset($item['class']) && ($item['class']!=='')) {
$item['attributes']['class']=$item['class'];
}
if (isset($item['target']) && ($item['target']!='')) {
$item['attributes']['target']=$item['target'];
}
if ($item['url'] === kirby()->url('current')) {
$item['attributes']['aria-current']='page';
}
// process child items as well, if any
$item['isChildOpen']=FALSE;
if (!empty($item['children'])) {
foreach (array_keys($item['children']) as $key) {
$item['children'][$key]=$process_item($item['children'][$key], $depth+1);
// keep track whether any child item is active
if (!empty($item['children'][$key]['isOpen']) || !empty($item['children'][$key]['isChildOpen'])) {
$item['isChildOpen']=TRUE;
}
}
}
return $item;
};
if (is_array($items) && $items) {
foreach (array_keys($items) as $key) {
$items[$key]=$process_item($items[$key]);
}
}
return $items;
},
// This method is the preferred way to output the markup of a
// navigation field from your template files.
'toNavigationMarkup' => function ($field) {
// Refresh items to get the current multilang URL and page titles
// and set item values needed for markup generation
$items=$field->toNavigationArray();
// Generate HTML
return snippet('navigation', [
'children' => $items
]);
},
// This method is provided only for compatibility reasons,
// to help the users of older plugin versions
'toNavigationStructure' => function ($field) {
// Use anonymous recursive function to process child items
$process_item = function($key, $item) use (&$process_item) {
// Preserve any original 'id' value as 'link_id'
$item['link_id']=$item['id'] ?? '';
// Overwrite the 'id' (slug) value in link items,
// because the Structure class also uses it as index
$item['id']=$key;
// process child items as well, if any
if (!empty($item['children'])) {
foreach (array_keys($item['children']) as $child_key) {
$item['children'][$child_key]=$process_item($child_key, $item['children'][$child_key]);
}
}
return $item;
};
// Refresh items to get the current multilang URL and page titles
// and set item values needed for markup generation
$items=$field->toNavigationArray();
if (is_array($items) && $items) {
foreach ($items as $key => $item) {
$items[$key]=$process_item($key, $item);
}
}
$modified_field=$field->value(Yaml::encode($items));
return $modified_field->toStructure();
},
];

View file

@ -1,60 +0,0 @@
<?php
use Kirby\Data\Yaml;
return [
'value' => function ($value = []) {
$items=Yaml::decode($value);
// Check whether there is an item with type 'save',
// which indicates that the data was just submitted, and
// in this case no changes should be done.
// This 'save' item is always added by props.php
// This 'save' item is always removed by the save.php
// This 'save' trick is needed, because the props.php is not only
// executed when a field is loaded, but also when it is saved, and
// otherwise it would not be possible to distinguish between the two.
$save_in_progress=FALSE;
if (is_array($items) && $items) {
foreach ($items as $item) {
if (isset($item['type']) && ($item['type']==='save')) {
// saving is in progress, do nothing.
$save_in_progress=TRUE;
break;
}
}
}
if (!$save_in_progress) {
// Refresh items to get the current multilang URL and page titles
require __DIR__ . '/../includes/refresh_item.inc.php';
$items=$refresh_items($items, $this->name(), $this->model());
// Add a 'save' item. When the 'props' values are sent to Panel,
// the Field.vue will hide this item, but it will send it back,
// and then the props.php will detect it, and save.php will ignore it.
// Use a constant uuid value for this item, to avoid diff problems
$items[]=['type' => 'save', 'uuid' => 'save', 'children' => []];
}
return $items;
},
// Notify the Vue code which textfields should be editable by default.
// Notes:
// - The 'Popup' will be automatically hidden, if 'Target' is visible
// - If a certain value is set in the field, then Vue will show
// the editable textfield even if the corresponding option is false
// - These options can be set in /site/config/config.php
'edit_title' => function() {
return (bool) option('chrisbeluga.navigation.edit_title', true);
},
'edit_popup' => function() {
return (bool) option('chrisbeluga.navigation.edit_popup', true);
},
'edit_target' => function() {
return (bool) option('chrisbeluga.navigation.edit_target', false);
},
'edit_class' => function() {
return (bool) option('chrisbeluga.navigation.edit_class', false);
},
'edit_anchor' => function() {
return (bool) option('chrisbeluga.navigation.edit_anchor', false);
}
];

View file

@ -1,78 +0,0 @@
<?php
use Kirby\Data\Yaml;
use Kirby\Uuid\Uuids;
return function ($items) {
// Use anonymous recursive function to process child items
$prepare_item = function($item) use (&$prepare_item) {
if (!is_array($item) || !isset($item['type'])) {
throw new Exception('Unexpected data found in the navigation field while saving');
}
if ($item['type'] === 'page') {
// Do not store 'count' value (coming from api.php)
unset($item['count']);
// The primary way of page identification is by 'uuid_uri',
// if not available, then by 'id'.
// Although page url and page title are saved here,
// these values will be refreshed when the field data is loaded.
}
// Remove the 'error' value, it will be set again when loading field values
unset($item['error']);
// prepare child items, if any
if (!empty($item['children'])) {
foreach (array_keys($item['children']) as $key) {
$item['children'][$key]=$prepare_item($item['children'][$key]);
}
}
return $item;
};
// Remove any item with type 'save',
// indicating field data that was just submitted
// This 'save' item is always added by props.php
// This 'save' item is always removed by the save.php
// This 'save' trick is needed, because the props.php is not only
// executed when a field is loaded, but also when it is saved, and
// otherwise it would not be possible to distinguish between the two.
if (is_array($items) && $items) {
foreach (array_keys($items) as $key) {
if (isset($items[$key]['type']) && ($items[$key]['type']==='save')) {
unset($items[$key]);
}
}
if ($items) {
// When the 'save' item is deleted, there may be a gap in keys
// Make sure that array keys have no gaps, to avoid JS problems
$items=array_values($items);
}
}
if (is_array($items) && $items) {
foreach (array_keys($items) as $key) {
$items[$key]=$prepare_item($items[$key]);
}
}
// Get the 'multilang' option of the field blueprint
$blueprint_field=$this->model()->blueprint()->field($this->name());
if (!empty($blueprint_field) && !empty($blueprint_field['multilang'])) {
// If 'multilang' is set in the blueprint, then data is stored
// in external yaml file, so that it can be shared between languages
$filepathTMP = tempnam(sys_get_temp_dir(), 'navigation');
if (file_put_contents($filepathTMP, Yaml::encode($items))) {
if (rename($filepathTMP, $this->model()->root() . '/kirby-navigation---' . $this->name() . '.yml')) {
// The data was successfully saved to the external file,
// so change the field data to a simple flag that will tell
// the load function to load the external file.
$items=['multilang' => TRUE];
}
else {
}
}
else {
}
@unlink($filepathTMP);
}
return $items;
};

View file

@ -1,252 +0,0 @@
<?php
use Kirby\Data\Yaml;
use Kirby\Http\Uri;
use Kirby\Uuid\Uuids;
/**
* This file should be included wherever it is needed with the
* 'require' keyword (not 'include', and not 'require_once'!)
* Then it should be called like this to refresh an array of items
* and all its children:
* $items = $refresh_items($items, $field_name, $field_model);
* The purpose of these functions is to make it possible that only
* fixed values are saved in the field, that never (or rarely) change
* (for example id, or uuid_uri) and the values that may change
* (for example page title, page url) are calculated by these
* functions, when needed.
*
* A secondary purpose of these functions is to make multilanguage
* navigation field editing as convenient as possible.
*/
// Use anonymous recursive function to process child items
$refresh_item = function($item) use (&$refresh_item) {
if (!is_array($item)) {
throw new Exception('Unexpected data found in the navigation field');
}
if ($multilang = kirby()->languages()->isNotEmpty()) {
$language_code=kirby()->language()->code();
$default_language_code=kirby()->defaultLanguage()->code();
}
else {
$language_code='default';
$default_language_code='default';
}
// Upgrade from old data, to remain compatible with old plugin versions
if (!isset($item['type'])) {
// Add a 'type' value, that did not exist in the old plugin version
// Previous plugin versions stored Kirby links with 'id' values,
// and stored Custom links without that
$item['type'] = isset($item['id']) ? 'page' : 'custom';
if ($item['type']=='page') {
// Previous plugin versions used 'text' and 'title' values.
// The new version uses 'LANG_link_text', 'LANG_link_title',
// and 'LANG_page_title', where 'LANG' is the language code.
// The new version outputs the 'LANG_page_title' as link HTML,
// if the 'LANG_link_text' value is empty.
if (isset($item['text'])) {
$item[$default_language_code . '_link_text']=$item['text'];
unset($item['text']);
}
if (isset($item['title'])) {
$item[$default_language_code . '_link_title']=$item['title'];
unset($item['title']);
}
// Previous plugin versions used 'url' value.
// The new version uses 'LANG_page_url',
// where 'LANG' is the language code.
if (isset($item['url'])) {
$item[$default_language_code . '_page_url']=$item['url'];
unset($item['url']);
}
}
elseif ($item['type']=='custom') {
// Previous plugin versions used 'text' and 'title' values.
// The new version uses 'LANG_link_text', 'LANG_link_title',
// where 'LANG' is the language code.
if (isset($item['text'])) {
$item[$default_language_code . '_link_text']=$item['text'];
unset($item['text']);
}
if (isset($item['title'])) {
$item[$default_language_code . '_link_title']=$item['title'];
unset($item['title']);
}
}
}
if ($item['type']=='page') {
// Fetch page by the permanent 'uuid_uri', if possible, otherwise by 'id'
$page=null;
if (!empty($item['uuid_uri']) && Uuids::enabled()) {
if ($page=kirby()->page($item['uuid_uri'])) {
// Refresh the 'id' to handle any changes
$item['id'] = $page->id();
}
}
if (!$page) {
if ($page=kirby()->page($item['id'])) {
if (empty($item['uuid_uri']) && Uuids::enabled()) {
// Add a 'uuid_uri' value, that did not exist in the old plugin version
$item['uuid_uri'] = $page->uuid()->toString();
}
}
}
if ($page) {
// Refresh the 'url' and 'page_title' (in the current language)
// to handle any changes
// Remember, that a navigation field built on e.g. localhost
// should work perfectly when copied to the public site.
$item[$language_code . '_page_url'] = $multilang ? $page->url($language_code) : $page->url();
$item[$language_code . '_page_title'] = $multilang ? ($page->content($language_code)->title()->value() ?? ''): ($page->content()->title()->value() ?? '');
}
else {
// This page no longer exists.
// Set 'error' so that the item icon in panel will be 'question'
$item['error']=TRUE;
// Adjust the old url to the current site url using the latest known 'id' value
if ($multilang) {
kirby()->currentLanguage()->url() . '/' . $item['id'];
}
else {
$item[$language_code . '_page_url']=Uri::current(['path' => $item['id'], 'query' => '',])->toString();
}
}
// if no translation exists yet, add default values
if (!isset($item[$language_code . '_link_text'])) {
$item[$language_code . '_link_text']='';
}
if (!isset($item[$language_code . '_link_title'])) {
$item[$language_code . '_link_title']='';
}
if (option('chrisbeluga.navigation.edit_anchor')) {
if (!isset($item[$language_code . '_link_anchor'])) {
$item[$language_code . '_link_anchor']='';
}
}
}
elseif ($item['type']=='custom') {
// Validate the URL
if (empty($item['url']) || !V::url($item['url'])) {
$item['error'] = TRUE;
}
// if no translation exists yet, add default values
if (!isset($item[$language_code . '_link_text'])) {
$item[$language_code . '_link_text']='';
}
if (!isset($item[$language_code . '_link_title'])) {
$item[$language_code . '_link_title']='';
}
}
// Handle the popup -> target transition to remain compatible.
// Previous plugin versions used the 'popup' boolean value.
// The new version uses the 'target' string value.
// The new version uses the 'Popup' toggle UI by default,
// but provides the edit_target option to switch to 'Target' string UI.
if (isset($item['popup']) && !isset($item['target'])) {
$item['target']=$item['popup'] ? '_blank' : '';
}
unset($item['popup']);
// refresh child items, if any
if (!empty($item['children'])) {
foreach (array_keys($item['children']) as $key) {
$item['children'][$key]=$refresh_item($item['children'][$key]);
}
}
return $item;
};
$refresh_items = function($items, $field_name, $field_model) use ($refresh_item) {
// If 'multilang' is set in the field data, then the real data is stored
// in external yaml file, so that it can be shared between languages
// It is better to be prepared that the 'multilang' field option is
// enabled/disabled while
// - there is field data in primary or secondary languages
// - there is ['multilang' => TRUE] data in primary field data,
// but there is real field data in secondary language
// - there is real field data in primary language, but there is
// ['multilang' => TRUE] data in secondary language
// - the blueprint contains 'multilang: true', the primary language
// data is still stored in the page content file, not in external file
// (because it was not saved yet), but the field is edited in the
// secondary language in Panel.
// - and all kinds of similar cases.
//
$multilang_site=kirby()->languages()->isNotEmpty();
$multilang_blueprint=FALSE;
$multilang_field=FALSE;
$multilang_load=FALSE;
// Get the 'multilang' option of the field blueprint
$field_blueprint=$field_model->blueprint()->field($field_name);
if (!empty($field_blueprint) && !empty($field_blueprint['multilang'])) {
$multilang_blueprint=TRUE;
}
if (!empty($items['multilang'])) {
$multilang_field=TRUE;
}
if (!$multilang_blueprint && !$multilang_field) {
// normal operation: use $items
}
elseif ($multilang_blueprint && $multilang_field) {
// multilang operation with previous save: load from external file
$multilang_load=TRUE;
}
elseif ($multilang_blueprint && !$multilang_field) {
// multilang operation without previous save:
// - if site is multilang, and this is primary language: use $items
// - if site is multilang, and this is secondary language:
// load data from the primary language
// - if site has no languages: use $items
if (kirby()->multilang()) {
if (kirby()->language()->isDefault()) {
// good, this is easy!
}
else {
// The field data should be loaded from the primary language.
$model=$field_model;
$defaultContentTranslation=$model->translation(kirby()->defaultLanguage()->code());
$defaultContent=$defaultContentTranslation->content();
$fieldContent=$defaultContent[$field_name];
$items=Yaml::decode($fieldContent);
// Furthermore, the primary data may be ['multilang' => TRUE],
// instead of the real field data. Load the external file then.
if (is_array($items) && !empty($items['multilang'])) {
$multilang_load=TRUE;
}
}
}
else {
// good, this is easy!
}
}
else {
// not multilang operation, but data happens to be saved externally,
// so load it from there
$multilang_load=TRUE;
}
if ($multilang_load) {
$filepath=$field_model->root() . '/kirby-navigation---' . $field_name . '.yml';
if (!file_exists($filepath)) {
throw new Exception('Failed to load the navigation field data.' );
}
$contents=file_get_contents($filepath);
if ($contents===FALSE) {
throw new Exception('Failed to load the navigation field data.');
}
$items=Yaml::decode($contents);
if (!is_array($items)) {
$items=[];
}
}
if (is_array($items) && $items) {
foreach (array_keys($items) as $key) {
$items[$key]=$refresh_item($items[$key]);
}
}
return $items;
};

View file

@ -1 +0,0 @@
.k-form-input[data-v-e9c938c8]{width:100%;display:flex;position:relative;margin-bottom:2px}.k-form-input .k-form-inner[data-v-e9c938c8]{width:100%;display:flex;position:relative;align-items:center;justify-content:space-between;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.25rem;padding:.5rem .75rem}.k-form-input .k-menu-text[data-v-e9c938c8]{overflow:hidden;text-overflow:ellipsis}.k-form-input .k-form-actions[data-v-e9c938c8]{display:flex;align-items:center}.k-form-input .k-form-actions button[data-v-e9c938c8]{margin-left:20px}.k-form-input[data-v-34431234]{width:100%;display:flex;position:relative;margin-bottom:2px}.k-form-input .k-form-inner[data-v-34431234]{width:100%;display:flex;position:relative;flex-direction:column}.k-form-input .k-form-inner .k-form-config[data-v-34431234]{width:100%;border-top:0;display:flex;border:1px solid #ccc;flex-direction:column;margin-top:5px}.k-form-input .k-form-inner .k-form-config .k-form-group[data-v-34431234]{flex-grow:1;padding:1rem;display:flex;flex-direction:column}.k-form-input .k-form-inner .k-form-config .k-form-footer[data-v-34431234]{width:100%;display:flex;margin-top:2rem;align-items:center;padding:.6rem 1rem;border-top:1px solid #ccc;justify-content:space-between}.k-form-input .k-form-actions[data-v-34431234]{display:flex;flex-direction:column}.k-form-input .k-form-actions .input-handle[data-v-34431234]{padding:0 .4rem 1rem}.k-pages-dialog-navbar[data-v-571b2066]{display:flex;align-items:center;justify-content:center;margin-bottom:.5rem;padding-right:38px}.k-pages-dialog-navbar .k-button[data-v-571b2066]{width:38px}.k-pages-dialog-navbar .k-button[disabled][data-v-571b2066]{opacity:0}.k-pages-dialog-navbar .k-headline[data-v-571b2066]{flex-grow:1;text-align:center}.k-pages-dialog .k-list-item[data-v-571b2066]{cursor:pointer}.k-pages-dialog .k-list-item .k-button[data-theme=disabled][data-v-571b2066],.k-pages-dialog .k-list-item .k-button[disabled][data-v-571b2066]{opacity:.25}.k-pages-dialog .k-list-item .k-button[data-theme=disabled][data-v-571b2066]:hover{opacity:1}.navigation-field .k-field-depth{text-align:right}.navigation-field .k-field-header .k-dropdown-content{margin-top:10px}.navigation-field .k-field-header .k-dropdown-item{width:180px;height:auto;padding:8px;margin-bottom:8px}.navigation-field .k-field-header .k-dropdown-item .k-button-text{opacity:1;white-space:normal;text-align:left}.navigation-field .k-field-header .k-dropdown-item .k-button-text .k-menu-title{opacity:1;width:100%;display:block;margin-bottom:8px}.navigation-field .k-field-header .k-dropdown-item .k-button-text .k-menu-subtitle{opacity:.75;font-size:.675rem;line-height:.875rem}.navigation-field .nestable-handle{width:100%;display:block}.navigation-field .nestable-item-content{width:100%;display:flex;flex-wrap:nowrap;position:relative;align-items:center}.navigation-field .nestable-item-content:hover .nestable-handle .k-button{opacity:1;transition:all .3s ease-in-out}.navigation-field .nestable-handle{width:auto;height:auto;display:flex;flex-wrap:nowrap;position:relative;align-items:flex-start;margin-top:7px}.navigation-field .nestable-handle .k-button{opacity:.2;cursor:move;transition:all .3s ease-in-out}.navigation-field .nestable{position:relative}.navigation-field .nestable .k-column{margin-top:8px;margin-right:8px}.navigation-field .nestable .nestable-list{margin:0;padding:0 0 0 26px;list-style-type:none}.navigation-field .nestable>.nestable-list{padding:0}.navigation-field .nestable-item:first-child,.navigation-field .nestable-item-copy:first-child{margin-top:0}.navigation-field .nestable-item{position:relative}.navigation-field .nestable-item.is-dragging .nestable-list{pointer-events:none}.navigation-field .nestable-item.is-dragging *{opacity:0}.navigation-field .nestable-item.is-dragging:before{content:"";position:absolute;top:0;left:26px;right:0;bottom:0;transition:all .3s ease-in-out}.navigation-field .nestable-drag-layer{position:fixed;top:0;left:0;z-index:100;pointer-events:none}.navigation-field .nestable-rtl .nestable-drag-layer{left:auto;right:0}.navigation-field .nestable-handle{cursor:move}

File diff suppressed because one or more lines are too long

View file

@ -1,29 +0,0 @@
<?php
Kirby::plugin('chrisbeluga/navigation', [
'fields' => [
'navigation' => [
'api' => require_once __DIR__ . '/config/api.php',
'props' => require_once __DIR__ . '/config/props.php',
'save' => require_once __DIR__ . '/config/save.php',
]
],
'translations' => [
'de' => require_once __DIR__ . '/languages/de.php',
'en' => require_once __DIR__ . '/languages/en.php',
'fr' => require_once __DIR__ . '/languages/fr.php',
'tr' => require_once __DIR__ . '/languages/tr.php',
],
'snippets' => [
'navigation' => __DIR__ . '/snippets/navigation.php',
'navigation_item' => __DIR__ . '/snippets/navigation_item.php',
],
'fieldMethods' => require_once __DIR__ . '/config/methods.php',
'options' => [
'edit_title' => TRUE,
'edit_popup' => TRUE,
'edit_target' => FALSE,
'edit_class' => FALSE,
'edit_anchor' => FALSE,
],
]);

View file

@ -1,26 +0,0 @@
<?php
return [
'menu.link.add' => 'Hinzufügen',
'menu.link.title' => 'Kirby Link',
'menu.link.text' => 'Fügt dem Menü eine Kirby-Seite hinzu',
'menu.custom.title' => 'Benutzerdefinierten Link',
'menu.custom.text' => 'Fügt dem Menü einen benutzerdefinierten Link hinzu, nützlich für externe URLs usw.',
'editor.label.id' => 'Id',
'editor.label.url' => 'Url',
'editor.label.text' => 'Text',
'editor.label.title' => 'Titel',
'editor.label.anchor' => 'Anker',
'editor.label.class' => 'Klasse',
'editor.label.target' => 'Ziel',
'editor.label.popup' => 'Popup',
'editor.menu.close' => 'Schließen',
'editor.menu.edit' => 'Bearbeiten',
'editor.menu.remove' => 'Löschen',
'editor.menu.duplicate' => 'Duplikat',
'modal.link.title' => 'Seiten hinzufügen',
'modal.link.breadcrumb' => 'Zurück',
'modal.custom.title' => 'Benutzerdefinierten Link hinzufügen',
'help.depth.text' => 'Maximal zulässige Tiefe:',
'help.empty.text' => 'Noch keine Menüpunkte'
];

View file

@ -1,26 +0,0 @@
<?php
return [
'menu.link.add' => 'Add',
'menu.link.title' => 'Kirby Link',
'menu.link.text' => 'Adds a Kirby page to the menu',
'menu.custom.title' => 'Custom Link',
'menu.custom.text' => 'Adds a custom link to the menu, useful for external urls etc',
'editor.label.id' => 'Id',
'editor.label.url' => 'Url',
'editor.label.text' => 'Text',
'editor.label.title' => 'Title',
'editor.label.anchor' => 'Anchor',
'editor.label.class' => 'Class',
'editor.label.target' => 'Target',
'editor.label.popup' => 'Popup',
'editor.menu.close' => 'Close Item',
'editor.menu.edit' => 'Edit Item',
'editor.menu.remove' => 'Remove Item',
'editor.menu.duplicate' => 'Duplicate Item',
'modal.link.title' => 'Add Pages',
'modal.link.breadcrumb' => 'Back',
'modal.custom.title' => 'Add Custom Link',
'help.depth.text' => 'Maximum allowed depth:',
'help.empty.text' => 'No menu items yet'
];

View file

@ -1,26 +0,0 @@
<?php
return [
'menu.link.add' => 'Ajouter',
'menu.link.title' => 'Lien Kirby',
'menu.link.text' => 'Ajouter une page Kirby à ce menu',
'menu.custom.title' => 'Lien personnalisé',
'menu.custom.text' => 'Ajouter un lien personnalisé à ce menu, utile pour les urls externes etc',
'editor.label.id' => 'Id',
'editor.label.url' => 'Url',
'editor.label.text' => 'Texte',
'editor.label.title' => 'Titre',
'editor.label.anchor' => 'Ancre',
'editor.label.class' => 'Classe',
'editor.label.target' => 'Cible',
'editor.label.popup' => 'Popup',
'editor.menu.close' => 'Fermer l\'élement',
'editor.menu.edit' => 'Éditer l\'élement',
'editor.menu.remove' => 'Supprimer l\'élement',
'editor.menu.duplicate' => 'Dupliquer l\'élement',
'modal.link.title' => 'Ajouter des pages',
'modal.link.breadcrumb' => 'Retour',
'modal.custom.title' => 'Ajouter un lien personnalisé',
'help.depth.text' => 'Profondeur maximale autorisée :',
'help.empty.text' => 'Aucun élement de menu pour l\'instant'
];

View file

@ -1,26 +0,0 @@
<?php
return [
'menu.link.add' => 'Ekle',
'menu.link.title' => 'Sayfa Bağlantısı',
'menu.link.text' => 'Menüye bir sayfa ekler',
'menu.custom.title' => 'Özel Bağlantı',
'menu.custom.text' => 'Menüye harici url\'ler vb. için yararlı olan özel bir bağlantı ekler',
'editor.label.id' => 'Id',
'editor.label.url' => 'Url',
'editor.label.text' => 'Metin',
'editor.label.title' => 'Başlık',
'editor.label.anchor' => 'Çapa',
'editor.label.class' => 'Sınıf',
'editor.label.target' => 'Hedef',
'editor.label.popup' => 'Yeni pencere',
'editor.menu.close' => 'Kapat',
'editor.menu.edit' => 'Düzenle',
'editor.menu.remove' => 'Kaldır',
'editor.menu.duplicate' => 'Çiftleme',
'modal.link.title' => 'Sayfa Ekle',
'modal.link.breadcrumb' => 'Geri',
'modal.custom.title' => 'Özel Bağlantı Ekle',
'help.depth.text' => 'İzin verilen maksimum derinlik:',
'help.empty.text' => 'Henüz menü öğesi yok'
];

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

View file

@ -1,13 +0,0 @@
{
"name": "kirby-navigation",
"type": "module",
"scripts": {
"dev": "npx -y kirbyup src/index.js --watch",
"build": "npx -y kirbyup src/index.js"
},
"dependencies": {
"kirbyup": "^0.14.1",
"vue-nestable": "^2.6.0",
"vue-runtime-helpers": "^1.1.2"
}
}

View file

@ -1,34 +0,0 @@
<?php
/** @var array $children */
/**
* Call this snippet with the "refreshed" nested array
* of the navigation field items (and not with the field data).
* See toNavigationMarkup() in methods.php for details.
*
* This snippet recursively processes all child items.
* To generate the markup for each link item, it calls
* the 'navigation_item' snippet, allowing for easy customization.
*
* To customize this snippet, copy it to your /site/snippets/ folder
* and edit the copy.
*/
if (!isset($children) || !is_array($children) || !$children) {
return;
}
if (isset($children['multilang'])) {
// This data was not "refreshed" before calling this snippet.
return;
}
?>
<ul>
<?php foreach ($children as $item): ?>
<li>
<?php snippet('navigation_item', $item); ?>
<?php if (!empty($item['children'])): ?>
<?php snippet('navigation', ['children' => $item['children']]); ?>
<?php endif ?>
</li>
<?php endforeach; ?>
</ul>

View file

@ -1,32 +0,0 @@
<?php
/*
* This snippet is typically called by the 'navigation' snippet.
* It generates the markup for a single link item.
*
* Important variables:
* string $url Link url
* string $text Link text
* string $anchor Link anchor
* array $attributes Link attributes (title, target, class, aria-current)
*
* Other variables:
* string $type Link type (usually 'page' or 'custom')
* string $title Link title (also included in $attributes)
* int $depth Nesting level (starting with 1)
* bool $isOpen Flag indicating whether the link URL is the current URL
* bool $error Flag indicating whether the link has issues
*
* To customize this snippet, copy it to your /site/snippets/ folder
* and edit the copy.
*
* For example, to add class="navigation-item navigation-item-X"
* to each link item, where X is the depth level of the given link,
* you can add the following line to your copy of navigation_item.php:
* $attributes['class']='navigation-item navigation-item-' . $depth;
*
*/
?>
<?php
use Kirby\Cms\Html;
echo Html::tag('a', $text ?? '', array_merge(['href' => ($url ?? '') . ($anchor ?? '')], $attributes ?? []));

View file

@ -1,628 +0,0 @@
<template>
<k-field
class="k-form-field navigation-field"
v-bind:help="help"
v-bind:label="label"
v-bind:levels="levels"
v-bind:disabled="disabled"
v-bind:required="required">
<template v-slot:options>
<k-dropdown>
<k-button v-if="!disabled"
icon="add"
v-on:click="$refs.menu.toggle()">
{{ $t('menu.link.add') }}
</k-button>
<k-dropdown-content
ref="menu">
<k-dropdown-item v-on:click="modal_open('default')">
<span class="k-menu-title">
{{ $t('menu.link.title') }}
</span>
<p class="k-menu-subtitle">
{{ $t('menu.link.text') }}
</p>
</k-dropdown-item>
<k-dropdown-item
v-on:click="modal_open('custom')">
<span class="k-menu-title">
{{ $t('menu.custom.title') }}
</span>
<p class="k-menu-subtitle">
{{ $t('menu.custom.text') }}
</p>
</k-dropdown-item>
</k-dropdown-content>
</k-dropdown>
</template>
<vue-nestable
keyProp="uuid"
v-model="navigation"
childrenProp="children"
v-bind:maxDepth="computed_levels"
v-if="navigation.length">
<template
slot-scope="{ item, index }"
v-bind:item="item"
v-if="item.type!='save'">
<listDefault
v-bind:item="item"
v-bind:navigation="navigation"
v-bind:navigationdisabled="disabled"
v-on:action_add="action_add"
v-on:action_remove="action_remove">
<template
v-slot:handle
v-bind:item="item">
<VueNestableHandle v-bind:item="item">
<k-button
icon="sort"
class="input-handle"
v-bind:tooltip="$t('editor.menu.sort')">
</k-button>
</VueNestableHandle>
</template>
<template v-slot:dropdown_fields>
<k-grid v-if="item.type == 'page'">
<k-column width="1">
<k-info-field
v-bind:text="item[langkey('page_title')]"
icon="page">
</k-info-field>
<k-info-field
v-bind:text="item[langkey('page_url')]"
icon="url">
</k-info-field>
</k-column>
<k-column width="1/2">
<k-text-field
v-bind:label="$t('editor.label.text')"
v-model="item[langkey('link_text')]">
</k-text-field>
</k-column>
<k-column width="1/2" v-if="title_is_editable(item)">
<k-text-field
v-bind:label="$t('editor.label.title')"
v-model="item[langkey('link_title')]">
</k-text-field>
</k-column>
<k-column width="1/2" v-if="anchor_is_editable(item)">
<k-text-field
v-bind:label="$t('editor.label.anchor')"
v-model="item[langkey('link_anchor')]">
</k-text-field>
</k-column>
<k-column width="1/2" v-if="class_is_editable(item)">
<k-text-field
v-bind:label="$t('editor.label.class')"
v-model="item.class">
</k-text-field>
</k-column>
<k-column width="1/2" v-if="target_is_editable(item)">
<k-text-field
v-bind:label="$t('editor.label.target') + ' (_blank, _self, _parent, _top, ...)'"
v-model="item.target">
</k-text-field>
</k-column>
<k-column width="1/2" v-if="popup_is_editable(item)">
<k-toggle-field
@input="item.target=$event ? '_blank' : ''"
:value="(item.target=='_blank' ? true : false)"
v-bind:label="$t('editor.label.popup')">
</k-toggle-field>
</k-column>
</k-grid>
<k-grid v-else-if="item.type == 'custom'">
<k-column width="1/2">
<k-text-field
v-bind:label="$t('editor.label.text')"
v-model="item[langkey('link_text')]">
</k-text-field>
</k-column>
<k-column width="1/2" v-if="title_is_editable(item)">
<k-text-field
v-bind:label="$t('editor.label.title')"
v-model="item[langkey('link_title')]">
</k-text-field>
</k-column>
<k-column width="1/2">
<k-text-field
v-bind:label="$t('editor.label.url')"
v-model="item.url">
</k-text-field>
</k-column>
<k-column width="1/2" v-if="class_is_editable(item)">
<k-text-field
v-bind:label="$t('editor.label.class')"
v-model="item.class">
</k-text-field>
</k-column>
<k-column width="1/2" v-if="target_is_editable(item)">
<k-text-field
v-bind:label="$t('editor.label.target') + ' (_blank, _self, _parent, _top, ...)'"
v-model="item.target">
</k-text-field>
</k-column>
<k-column width="1/2" v-if="popup_is_editable(item)">
<k-toggle-field
@input="item.target=$event ? '_blank' : ''"
:value="(item.target=='_blank' ? true : false)"
v-bind:label="$t('editor.label.popup')">
</k-toggle-field>
</k-column>
</k-grid>
</template>
</listDefault>
</template>
</vue-nestable>
<k-empty
v-else
icon="page">
{{ $t('help.empty.text') }}
</k-empty>
<modalDefault
v-if="modal.status"
v-bind:modal="modal.status"
v-on:modal_close="modal_close"
v-on:modal_submit="modal_submit">
<template v-slot:modal_header>
<header class="k-pages-dialog-navbar">
<template v-if="modal.type === 'default'">
<k-button
icon="angle-left"
v-on:click="action_fetch(computed_breadcrumbs)"
v-if="query.breadcrumbs.length > 0">
{{ $t('modal.link.breadcrumb') }}
</k-button>
<k-headline>
{{ $t('modal.link.title') }}
</k-headline>
</template>
<template v-if="modal.type === 'custom'">
<k-headline>
{{ $t('modal.custom.title') }}
</k-headline>
</template>
</header>
</template>
<template v-slot:modal_body>
<template v-if="modal.type === 'default'">
<listModal
v-for="(item, index) in query.content"
v-bind:key="item.uuid"
v-bind:item="item">
<template v-slot:text>
<span class="k-menu-text">{{ item[langkey('page_title')] }}</span>
</template>
<template v-slot:fetch>
<k-button
icon="angle-right"
v-if="item.count > 0"
v-on:click="action_fetch(item.id)">
</k-button>
</template>
<template v-slot:add>
<k-button
icon="add"
v-on:click="action_add(item)">
</k-button>
</template>
</listModal>
</template>
<template v-if="modal.type === 'custom'">
<div class="k-fieldset">
<k-grid>
<k-column>
<k-text-field
v-bind:label="$t('editor.label.text')"
v-model="item[langkey('link_text')]">
</k-text-field>
</k-column>
<k-column>
<k-text-field
v-bind:label="$t('editor.label.url')"
v-model="item.url">
</k-text-field>
</k-column>
<k-column v-if="popup_is_editable(item)">
<k-toggle-field
@input="item.target=$event ? '_blank' : ''"
:value="(item.target=='_blank' ? true : false)"
v-bind:label="$t('editor.label.popup')">
</k-toggle-field>
</k-column>
<k-column v-if="target_is_editable(item)">
<k-text-field
v-bind:label="$t('editor.label.target') + ' (_blank, _self, _parent, _top, ...)'"
v-model="item.target">
</k-text-field>
</k-column>
</k-grid>
</div>
</template>
</template>
</modalDefault>
<template v-slot:help>
<k-grid>
<k-column width="1/2">
<k-help
v-if="help"
class="k-field-help"
v-html="help">
</k-help>
</k-column>
<k-column width="1/2">
<k-help
v-if="computed_levels<=5"
class="k-field-help k-field-depth">
{{ $t('help.depth.text') }} <strong>{{ computed_levels }}</strong>
</k-help>
</k-column>
</k-grid>
</template>
</k-field>
</template>
<script>
// Import Components
import ListModal from './components/Lists/Modal.vue'
import ListDefault from './components/Lists/Default.vue'
import ModalDefault from './components/Modal/Default.vue'
export default {
props: {
help: String,
value: Array,
label: String,
levels: Number,
edit_title: Boolean,
edit_popup: Boolean,
edit_target: Boolean,
edit_class: Boolean,
edit_anchor: Boolean,
disabled: Boolean,
required: Boolean,
endpoints: Object,
},
components: {
ListModal,
ListDefault,
ModalDefault
},
data() {
return {
navigation: this.value || [],
modal: {type: '', status: false},
query: {content: [], breadcrumbs: []},
item: {url: '', uuid_uri: '', text: '', target: ''}
}
},
watch: {
navigation: {
handler() {
this.$emit('input', this.navigation)
},
deep: true
},
panel_content_has_diff() {
if (!this.panel_content_has_diff) {
// If previously detected changes disappeared,
// probably because of the "Discard" button in Panel,
// then reset the navigation items to the initial values.
this.navigation = this.value;
}
}
},
methods: {
modal_close() {
this.modal = {type: '', status: false};
this.$emit('close');
},
modal_open(data) {
this.modal = {type: data, status: true}
panel.dialog.open(this);
},
modal_submit() {
if (this.modal.type === 'custom') {
this.item.type='custom'
this.action_add(this.item)
this.item = {url: '', uuid_uri: '', text: '', target: ''}
}
this.modal = {type: '', status: false}
this.$emit('close')
},
action_fetch(data) {
let language = this.$panel.language.code ?? 'default';
this.$api.get(this.endpoints.field + '/listings/' + language + '/' + data)
.then((response) => {
this.query = response
})
.catch((error) => {
this.query = {content: [], breadcrumbs: []};
console.log(error)
})
},
action_remove(data) {
return this.navigation = data.haystack.filter(item => item.uuid !== data.needle).map(item => {
if (item.children && item.children.length) {
item.children = this.action_remove({
haystack: item.children,
needle: data.needle
})
}
return item
})
},
action_add(data) {
if (data.type=='page') {
let newitem = {
type: data.type,
id: data.id,
uuid_uri: data.uuid_uri,
target: '',
uuid: Math.random().toString(36).substring(2, 15),
children: [],
};
newitem[this.langkey('link_text')]=data[this.langkey('link_text')];
newitem[this.langkey('page_url')]=data[this.langkey('page_url')];
newitem[this.langkey('page_title')]=data[this.langkey('page_title')];
this.navigation.push(newitem);
}
else if (data.type=='custom') {
let newitem = {
type: data.type,
url: data.url,
target: data.popup ? '_blank' : data.target,
uuid: Math.random().toString(36).substring(2, 15),
children: [],
};
newitem[this.langkey('link_text')]=data[this.langkey('link_text')] ?? '';
newitem[this.langkey('link_title')]='';
this.navigation.push(newitem);
}
else {
console.warn('Invalid data.type value');
}
},
langkey(key) {
let language = this.$panel.language.code ?? 'default';
return language + '_' + key;
},
title_is_editable(item) {
if (typeof item[this.langkey('link_title')] === 'string') {
if (item[this.langkey('link_title')]!=='') {
return true;
}
}
return this.edit_title;
},
anchor_is_editable(item) {
if (typeof item[this.langkey('link_anchor')] === 'string') {
if (item[this.langkey('link_anchor')]!=='') {
return true;
}
}
return this.edit_anchor;
},
popup_is_editable(item) {
// always hide popup, if target is enabled, or target contains data
if (typeof item.target === 'string') {
if ((item.target!=='') && (item.target!=='_blank')) {
return false;
}
}
if (this.edit_target) {
return false;
}
return this.edit_popup;
},
target_is_editable(item) {
if (typeof item.target === 'string') {
if ((item.target!=='') && (item.target!=='_blank')) {
return true;
}
}
return this.edit_target;
},
class_is_editable(item) {
if (typeof item.class === 'string') {
if (item.class!=='') {
return true;
}
}
return this.edit_class;
}
},
computed: {
computed_navigation() {
return this.navigation
},
computed_levels() {
if (this.levels && parseInt(this.levels) && (parseInt(this.levels)<1)) {
return parseInt(this.levels);
}
return 10;
},
computed_breadcrumbs() {
return this.query.breadcrumbs.length >= 2 ? this.query.breadcrumbs[this.query.breadcrumbs.length - 2].id : 'site'
},
panel_content_has_diff() {
return this.$panel.content.hasDiff();
},
},
mounted() {
this.action_fetch('site');
}
}
</script>
<style lang="scss">
.navigation-field {
.k-field-depth {
text-align: right;
}
.k-field-header {
.k-dropdown-content {
margin-top: 10px;
}
.k-dropdown-item {
width: 180px;
height: auto;
padding: 8px;
margin-bottom: 8px;
.k-button-text {
opacity: 1;
white-space: normal;
text-align: left;
.k-menu-title {
opacity: 1;
width: 100%;
display: block;
margin-bottom: 8px;
}
.k-menu-subtitle {
opacity: 0.75;
font-size: .675rem;
line-height: 0.875rem;
}
}
}
}
.nestable-handle {
width: 100%;
display: block;
}
.nestable-item-content {
width: 100%;
display: flex;
flex-wrap: nowrap;
position: relative;
align-items: center;
&:hover {
.nestable-handle {
.k-button {
opacity: 1;
transition: all 0.3s ease-in-out;
}
}
}
}
.nestable-handle {
width: auto;
height: auto;
display: flex;
flex-wrap: nowrap;
position: relative;
align-items: flex-start;
margin-top: 7px;
.k-button {
opacity: 0.2;
cursor: move;
transition: all 0.3s ease-in-out;
}
}
.nestable {
position: relative;
.k-column {
margin-top: 8px;
margin-right: 8px;
}
}
.nestable .nestable-list {
margin: 0;
padding: 0 0 0 26px;
list-style-type: none;
}
.nestable > .nestable-list {
padding: 0;
}
.nestable-item:first-child,
.nestable-item-copy:first-child {
margin-top: 0;
}
.nestable-item {
position: relative;
}
.nestable-item.is-dragging .nestable-list {
pointer-events: none;
}
.nestable-item.is-dragging * {
opacity: 0;
}
.nestable-item.is-dragging:before {
content: '';
position: absolute;
top: 0;
left: 26px;
right: 0;
bottom: 0;
transition: all 0.3s ease-in-out;
}
.nestable-drag-layer {
position: fixed;
top: 0;
left: 0;
z-index: 100;
pointer-events: none;
}
.nestable-rtl .nestable-drag-layer {
left: auto;
right: 0;
}
.nestable-handle {
cursor: move;
}
}
</style>

View file

@ -1,144 +0,0 @@
<template>
<div class="k-form-input">
<div class="k-form-actions">
<slot name="handle"/>
</div>
<div class="k-form-inner">
<k-item v-if="!navigationdisabled"
v-bind:text="computed_link_text(item)"
:buttons="[
{
icon: active ? 'collapse' : (item.error ? 'question' : (item.type=='custom' ? 'pen' : 'edit')),
click: function (e) { return item_action({type: 'edit'}) }
},
]"
:options="[
{
icon: 'copy',
text: $t('editor.menu.duplicate'),
click: function (e) { return item_action({ type: 'duplicate', item: item }) }
},
{
icon: 'trash',
text: $t('editor.menu.remove'),
click: function (e) { return item_action({ type: 'remove', needle: item.uuid, haystack: navigation}) }
}
]"
/>
<k-item v-else
v-bind:text="computed_link_text(item)"
/>
<div
ref="config"
v-if="active"
class="k-form-config">
<div
ref="config"
class="k-form-group">
<slot name="dropdown_fields"/>
</div>
<div class="k-form-footer">
<span></span>
<k-button
icon="hidden"
v-on:click="item_action({ type: 'edit' })">
{{ $t('editor.menu.close') }}
</k-button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
item: Object,
fields: Object,
navigation: Array,
navigationdisabled: Boolean,
},
data() {
return {
active: false
}
},
methods: {
item_action(data) {
if (data.type === 'edit') {
this.active = !this.active
}
if (data.type === 'remove') {
this.$emit('action_remove', data)
}
if (data.type === 'duplicate') {
this.$emit('action_add', data.item)
}
},
langkey(key) {
let language = this.$panel.language.code ?? 'default';
return language + '_' + key;
},
computed_link_text(item) {
if (item.type === 'page') {
if (item[this.langkey('link_text')] === '') {
// if link text of a page is empty, use page title
return item[this.langkey('page_title')];
}
}
return item[this.langkey('link_text')];
},
},
}
</script>
<style lang="scss" scoped>
.k-form-input {
width: 100%;
display: flex;
position: relative;
margin-bottom: 2px;
.k-form-inner {
width: 100%;
display: flex;
position: relative;
flex-direction: column;
.k-form-config {
width: 100%;
border-top: 0;
display: flex;
border: 1px solid #ccc;
flex-direction: column;
margin-top: 5px;
.k-form-group {
flex-grow: 1;
padding: 1rem;
display: flex;
flex-direction: column;
}
.k-form-footer {
width: 100%;
display: flex;
margin-top: 2rem;
align-items: center;
padding: 0.6rem 1rem;
border-top: 1px solid #ccc;
justify-content: space-between;
}
}
}
.k-form-actions {
display: flex;
flex-direction: column;
.input-handle {
padding: 0 0.4rem 1rem 0.4rem;
}
}
}
</style>

View file

@ -1,55 +0,0 @@
<template>
<div class="k-form-input">
<div class="k-form-inner">
<slot name="text"/>
<div class="k-form-actions">
<slot name="fetch"/>
<slot name="add"/>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
item: Object,
}
}
</script>
<style lang="scss" scoped>
.k-form-input {
width: 100%;
display: flex;
position: relative;
margin-bottom: 2px;
.k-form-inner {
width: 100%;
display: flex;
position: relative;
align-items: center;
justify-content: space-between;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.25rem;
padding: .5rem .75rem;
}
.k-menu-text {
overflow: hidden;
text-overflow: ellipsis;
}
.k-form-actions {
display: flex;
align-items: center;
button {
margin-left: 20px;
}
}
}
</style>

View file

@ -1,63 +0,0 @@
<template>
<k-dialog
size="medium"
class="k-pages-dialog"
v-bind:visible="modal"
v-on:cancel="modal_close"
v-on:submit="modal_submit">
<slot name="modal_header"/>
<slot name="modal_body"/>
</k-dialog>
</template>
<script>
export default {
props: {
modal: Object,
},
methods: {
modal_close() {
this.$emit('modal_close')
},
modal_submit() {
this.$emit('modal_submit')
}
}
}
</script>
<style lang="scss" scoped>
.k-pages-dialog-navbar {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0.5rem;
padding-right: 38px;
}
.k-pages-dialog-navbar .k-button {
width: 38px;
}
.k-pages-dialog-navbar .k-button[disabled] {
opacity: 0;
}
.k-pages-dialog-navbar .k-headline {
flex-grow: 1;
text-align: center;
}
.k-pages-dialog .k-list-item {
cursor: pointer;
}
.k-pages-dialog .k-list-item .k-button[data-theme="disabled"],
.k-pages-dialog .k-list-item .k-button[disabled] {
opacity: 0.25;
}
.k-pages-dialog .k-list-item .k-button[data-theme="disabled"]:hover {
opacity: 1;
}
</style>

View file

@ -1,9 +0,0 @@
import VueNestable from "vue-nestable/dist/index.esm";
import Field from './Field.vue'
panel.plugin('beluga/navigation', {
fields: {
navigation: Field
},
use: VueNestable
})