Addedf documents search
This commit is contained in:
parent
65215de545
commit
b3dfeaba40
|
@ -32,7 +32,7 @@ const APP_EMAIL_SECURITY = 'security@localhost.test'; // Default security email
|
|||
const APP_USERAGENT = APP_NAME.'-Server v%s. Please report abuse at %s';
|
||||
const APP_MODE_ADMIN = 'admin';
|
||||
const APP_PAGING_LIMIT = 15;
|
||||
const APP_CACHE_BUSTER = 119;
|
||||
const APP_CACHE_BUSTER = 122;
|
||||
const APP_VERSION_STABLE = '0.5.3';
|
||||
const APP_STORAGE_UPLOADS = '/storage/uploads';
|
||||
const APP_STORAGE_CACHE = '/storage/cache';
|
||||
|
|
|
@ -102,7 +102,7 @@ $rules = $collection->getAttribute('rules', []);
|
|||
$type = (isset($rule['type'])) ? $rule['type'] : '';
|
||||
$array = (isset($rule['array'])) ? $rule['array'] : '';
|
||||
?>
|
||||
<td title="<?php echo $this->escape($label); ?>" class="text-size-small text-height-small">
|
||||
<td data-title="<?php echo $this->escape($label); ?>: " class="text-size-small text-height-small">
|
||||
<a data-ls-attrs="href=/console/database/document?id={{node.$id}}&collection={{router.params.id}}&project={{router.params.project}}">
|
||||
<?php if(!$array): ?>
|
||||
<?php switch($type):
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
use Appwrite\Database\Database;
|
||||
use Utopia\View;
|
||||
use Appwrite\Database\Validator\Authorization;
|
||||
|
||||
|
@ -13,6 +14,39 @@ $namespace = 'project-document';
|
|||
$collections = [];
|
||||
?>
|
||||
|
||||
<?php echo $searchFiles->render(); ?>
|
||||
|
||||
<?php foreach($rules as $rule): // Form to append child document
|
||||
$key = (isset($rule['key'])) ? $rule['key'] : '';
|
||||
$label = (isset($rule['label'])) ? $rule['label'] : '';
|
||||
$type = (isset($rule['type'])) ? $rule['type'] : '';
|
||||
$list = (isset($rule['list'])) ? $rule['list'] : [];
|
||||
?>
|
||||
<?php foreach($list as $item):
|
||||
if($item === $collection->getId()) {
|
||||
continue; // Do not allow rec recrusion
|
||||
}
|
||||
|
||||
if(!isset($collections[$item])) {
|
||||
Authorization::disable(); //TODO Try and avoid calling the DB from the template
|
||||
$collections[$item] = $db->getDocument($item, false);
|
||||
Authorization::reset();
|
||||
}
|
||||
|
||||
$childCollection = $collections[$item];
|
||||
|
||||
if(!$childCollection->getId() || $childCollection->getCollection() !== Database::SYSTEM_COLLECTION_COLLECTIONS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$searchDocuments
|
||||
->setParam('collection', $childCollection)
|
||||
;
|
||||
|
||||
echo $searchDocuments->render(); ?>
|
||||
<?php endforeach; ?>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php foreach($rules as $rule): // Form to append child document
|
||||
$key = (isset($rule['key'])) ? $rule['key'] : '';
|
||||
$label = (isset($rule['label'])) ? $rule['label'] : '';
|
||||
|
@ -45,15 +79,17 @@ $collections = [];
|
|||
continue; // Do not allow rec recrusion
|
||||
}
|
||||
|
||||
Authorization::disable(); //TODO Try and avoid calling the DB from the template
|
||||
$childCollection = $db->getDocument($item, false);
|
||||
Authorization::reset();
|
||||
|
||||
if(!$childCollection->getId()) {
|
||||
continue;
|
||||
if(!isset($collections[$item])) {
|
||||
Authorization::disable(); //TODO Try and avoid calling the DB from the template
|
||||
$collections[$item] = $db->getDocument($item, false);
|
||||
Authorization::reset();
|
||||
}
|
||||
|
||||
$collections[$childCollection->getId()] = $childCollection->getAttribute('name');
|
||||
$childCollection = $collections[$item];
|
||||
|
||||
if(!$childCollection->getId() || $childCollection->getCollection() !== Database::SYSTEM_COLLECTION_COLLECTIONS) {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<form class="margin-bottom-no"
|
||||
data-service="container.path"
|
||||
|
@ -261,9 +297,4 @@ $collections = [];
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
echo $searchFiles->render();
|
||||
echo $searchDocuments->render();
|
||||
?>
|
||||
</div>
|
|
@ -44,7 +44,7 @@ if($type === 'document') {
|
|||
<div class="drop-list" data-ls-ui-open="" data-button-text="Add" data-button-icon="" data-button-selector="[data-toggler]" data-button-class="reverse margin-bottom-small" data-blur="1" tabindex="1">
|
||||
<ul>
|
||||
<?php foreach($list as $item):
|
||||
$name = (isset($collections[$item])) ? $collections[$item] : '';
|
||||
$name = (isset($collections[$item])) ? $collections[$item]->getAttribute('name', '') : '';
|
||||
?>
|
||||
<li>
|
||||
<button type="button" class="link" data-ls-ui-trigger="collection-child-<?php echo $this->escape($namespace); ?>-<?php echo $this->escape($item); ?>"><i class="icon-edit"></i> Add <?php echo $this->escape($name); ?></button>
|
||||
|
|
|
@ -6,14 +6,17 @@ $collections = $this->getParam('collections', []);
|
|||
$array = $this->getParam('array', false);
|
||||
$list = $this->getParam('list', []);
|
||||
$list = (is_array($list)) ? $list : [];
|
||||
|
||||
var_dump($collections);
|
||||
?>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
name="<?php echo $this->escape($key); ?>"
|
||||
data-ls-bind="{{<?php echo $this->escape($namespace); ?>}}"
|
||||
data-forms-document=""
|
||||
data-search="<?php echo $this->escape($namespace); ?>"
|
||||
class="margin-bottom-no">
|
||||
class="margin-bottom-no">
|
||||
|
||||
<?php foreach($collections as $collection): ?>
|
||||
|
||||
<button class="reverse margin-end-small" type="button"
|
||||
data-forms-document="open-document-serach-<?php echo $this->escape($collection->getId()); ?>"
|
||||
data-search="<?php echo $this->escape($namespace); ?>"><i class="icon-search"></i> <?php echo $this->escape($collection->getAttribute('name')); ?></button>
|
||||
<?php endforeach; ?>
|
|
@ -1,15 +1,20 @@
|
|||
|
||||
<?php
|
||||
$collection = $this->getParam('collection', []);
|
||||
$id = $collection->getId();
|
||||
$name = $collection->getAttribute('name', 'Collection');
|
||||
$rules = $collection->getAttribute('rules', []);
|
||||
?>
|
||||
|
||||
|
||||
<div data-ui-modal class="modal sticky-footer width-large box close" data-button-hide="on" data-open-event="open-document-serach" data-close-event="none">
|
||||
<div data-ui-modal class="modal sticky-footer width-large box close" data-button-hide="on" data-open-event="open-document-serach-<?php echo $this->escape($id); ?>" data-close-event="none">
|
||||
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
|
||||
|
||||
<h2>Document Search</h2>
|
||||
<h2><?php echo $this->escape($name); ?> Search</h2>
|
||||
|
||||
<form class="search margin-bottom"
|
||||
data-service="database.listDocuments"
|
||||
data-event="submit"
|
||||
data-param-collection-id="5e63e2dca1d10"
|
||||
data-param-collection-id="<?php echo $this->escape($id); ?>"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-offset="0"
|
||||
data-param-order-type="DESC"
|
||||
|
@ -17,13 +22,13 @@
|
|||
data-name="project-documents"
|
||||
data-success="state"
|
||||
data-success-param-state-keys="search,offset">
|
||||
<input name="search" id="searchDocuments" type="search" autocomplete="off" placeholder="Search" class="margin-bottom-no" data-ls-bind="{{router.params.search}}">
|
||||
<input name="search" id="searchDocuments-<?php echo $this->escape($id); ?>" type="search" autocomplete="off" placeholder="Search" class="margin-bottom-no" data-ls-bind="{{router.params.search}}">
|
||||
</form>
|
||||
|
||||
<div
|
||||
data-service="database.listDocuments"
|
||||
data-event="open-document-serach"
|
||||
data-param-collection-id="5e63e2dca1d10"
|
||||
data-event="open-document-serach-<?php echo $this->escape($id); ?>"
|
||||
data-param-collection-id="<?php echo $this->escape($id); ?>"
|
||||
data-param-search="{{router.params.search}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-offset="0"
|
||||
|
@ -41,21 +46,47 @@
|
|||
<div class="scroll">
|
||||
<table class="margin-top-no margin-bottom-no">
|
||||
<thead>
|
||||
<tr data-ls-loop="project-collection.rules" data-ls-as="rule">
|
||||
<th width="120" data-ls-bind="{{rule.label}}"></th>
|
||||
<tr>
|
||||
<th width="40"> </th>
|
||||
<?php foreach($rules as $rule):
|
||||
$label = (isset($rule['label'])) ? $rule['label'] : '';
|
||||
?>
|
||||
<th width="120"><?php echo $this->escape($label); ?></th>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-ls-loop="project-documents.documents" data-ls-as="node">
|
||||
<tr data-ls-loop="project-collection.rules" data-ls-as="rule">
|
||||
<td data-ls-attrs="data-title={{rule.label}}:" class="text-size-small text-height-small">
|
||||
<a data-ls-attrs="href=/console/database/document?id={{node.$id}}&collection={{router.params.id}}&project={{router.params.project}}">
|
||||
<span data-ls-if="{{rule.type}} !== 'document' && {{rule.type}} !== 'fileId'" data-ls-bind="{{node|documentLabel}}" data-ls-attrs="title={{node|documentLabel}}"></span>
|
||||
<span data-ls-if="{{rule.type}} == 'document' && {{rule.array}}">[...]</span>
|
||||
<span data-ls-if="{{rule.type}} == 'document' && !{{rule.array}}">{...}</span>
|
||||
<img data-ls-if="{{rule.type}} == 'fileId' && !{{rule.array}} && {{node|documentLabel|isEmpty}}" src="" data-ls-attrs="src=//{{env.DOMAIN}}/v1/storage/files/{{node|documentLabel}}/preview?width=65&height=65&project={{router.params.project}}&mode=admin" class="avatar" width="30" height="30" loading="lazy" />
|
||||
<tr>
|
||||
<td data-title="x" class="">
|
||||
<input type="radio" id="selected-document-<?php echo $this->escape($id); ?>" name="selected" data-ls-attrs="value={{node.$id}}" data-ls-bind="{{search.selected}}" />
|
||||
</td>
|
||||
<?php foreach($rules as $rule):
|
||||
$label = (isset($rule['label'])) ? $rule['label'] : '';
|
||||
$key = (isset($rule['key'])) ? $rule['key'] : '';
|
||||
$type = (isset($rule['type'])) ? $rule['type'] : '';
|
||||
$array = (isset($rule['array'])) ? $rule['array'] : '';
|
||||
?>
|
||||
<td data-title="<?php echo $this->escape($label); ?>" class="text-size-small text-height-small">
|
||||
<a data-ls-attrs="href=/console/database/document?id={{node.$id}}&collection=<?php echo $this->escape($id); ?>&project={{router.params.project}}" target="_blank">
|
||||
<?php if(!$array): ?>
|
||||
<?php switch($type):
|
||||
case 'fileId': ?>
|
||||
<img data-ls-if="{{node.<?php echo $this->escape($key); ?>}} != ''" src="" data-ls-attrs="src=//{{env.DOMAIN}}/v1/storage/files/{{node.<?php echo $this->escape($key); ?>}}/preview?width=65&height=65&project={{router.params.project}}&mode=admin" class="avatar" width="30" height="30" loading="lazy" />
|
||||
<?php break; ?>
|
||||
<?php case 'document': ?>
|
||||
{...}
|
||||
<?php break; ?>
|
||||
<?php default: ?>
|
||||
<span data-ls-bind="{{node.<?php echo $this->escape($key); ?>}}" data-ls-attrs="title={{node.<?php echo $this->escape($key); ?>}}"></span>
|
||||
<?php break; ?>
|
||||
<?php endswitch; ?>
|
||||
<?php else: ?>
|
||||
[...]
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</td>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -67,7 +98,7 @@
|
|||
<form
|
||||
data-service="database.listDocuments"
|
||||
data-event="submit"
|
||||
data-param-collection-id="{{router.params.id}}"
|
||||
data-param-collection-id="<?php echo $this->escape($id); ?>"
|
||||
data-param-search="{{router.params.search}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-order-type="DESC"
|
||||
|
@ -83,7 +114,7 @@
|
|||
<form
|
||||
data-service="database.listDocuments"
|
||||
data-event="submit"
|
||||
data-param-collection-id="{{router.params.id}}"
|
||||
data-param-collection-id="<?php echo $this->escape($id); ?>"
|
||||
data-param-search="{{router.params.search}}"
|
||||
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
|
||||
data-param-order-type="DESC"
|
||||
|
|
2
public/dist/scripts/app-all.js
vendored
2
public/dist/scripts/app-all.js
vendored
|
@ -2629,7 +2629,7 @@ code.innerHTML=value;Prism.highlightElement(code);div.scrollTop=0;};element.addE
|
|||
function syncA(){element.value=picker.value;update();}
|
||||
function syncB(){picker.value=element.value;}
|
||||
element.parentNode.insertBefore(preview,element);update();syncB();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-copy",controller:function(element,alerts,document,window){var button=window.document.createElement("i");button.type="button";button.className="icon-docs note copy";button.style.cursor="pointer";element.parentNode.insertBefore(button,element.nextSibling);var copy=function(event){let disabled=element.disabled;element.disabled=false;element.focus();element.select();document.execCommand("Copy");if(document.selection){document.selection.empty();}else if(window.getSelection){window.getSelection().removeAllRanges();}
|
||||
element.disabled=disabled;element.blur();alerts.add({text:"Copied to clipboard",class:""},3000);};button.addEventListener("click",copy);}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-document",controller:function(element,container,search){var searchButton=(element.dataset["search"]||0);if(searchButton){let searchOpen=document.createElement("button");searchOpen.type='button';searchOpen.innerHTML='<i class="icon icon-search"></i> Search';searchOpen.classList.add('reverse');let path=container.scope(searchButton);searchOpen.addEventListener('click',function(){search.selected=element.value;search.path=path;document.dispatchEvent(new CustomEvent("open-document-serach",{bubbles:false,cancelable:true}));});element.parentNode.insertBefore(searchOpen,element.nextSibling)}}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-forms-filter",controller:function(document,container,expression,element,form,di){let name=element.dataset["formsFilter"]||"";let events=element.dataset["event"]||"";let serialize=function(obj,prefix){let str=[],p;for(p in obj){if(obj.hasOwnProperty(p)){let k=prefix?prefix+"["+p+"]":p,v=obj[p];if(v===""){continue;}
|
||||
element.disabled=disabled;element.blur();alerts.add({text:"Copied to clipboard",class:""},3000);};button.addEventListener("click",copy);}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-document",controller:function(element,container,search){var formsDocument=(element.dataset["formsDocument"]||'');var searchButton=(element.dataset["search"]||0);let path=container.scope(searchButton);element.addEventListener('click',function(){search.selected=element.value;search.path=path;document.dispatchEvent(new CustomEvent(formsDocument,{bubbles:false,cancelable:true}));});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-forms-filter",controller:function(document,container,expression,element,form,di){let name=element.dataset["formsFilter"]||"";let events=element.dataset["event"]||"";let serialize=function(obj,prefix){let str=[],p;for(p in obj){if(obj.hasOwnProperty(p)){let k=prefix?prefix+"["+p+"]":p,v=obj[p];if(v===""){continue;}
|
||||
str.push(v!==null&&typeof v==="object"?serialize(v,k):encodeURIComponent(k)+"="+encodeURIComponent(v));}}
|
||||
return str.join("&");};let parse=function(filter){if(filter===""){return null;}
|
||||
let operatorsMap=["!=",">=","<=","=",">","<"];let operator=null;for(let key=0;key<operatorsMap.length;key++){if(filter.indexOf(operatorsMap[key])>-1){operator=operatorsMap[key];}}
|
||||
|
|
2
public/dist/scripts/app.js
vendored
2
public/dist/scripts/app.js
vendored
|
@ -345,7 +345,7 @@ code.innerHTML=value;Prism.highlightElement(code);div.scrollTop=0;};element.addE
|
|||
function syncA(){element.value=picker.value;update();}
|
||||
function syncB(){picker.value=element.value;}
|
||||
element.parentNode.insertBefore(preview,element);update();syncB();}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-copy",controller:function(element,alerts,document,window){var button=window.document.createElement("i");button.type="button";button.className="icon-docs note copy";button.style.cursor="pointer";element.parentNode.insertBefore(button,element.nextSibling);var copy=function(event){let disabled=element.disabled;element.disabled=false;element.focus();element.select();document.execCommand("Copy");if(document.selection){document.selection.empty();}else if(window.getSelection){window.getSelection().removeAllRanges();}
|
||||
element.disabled=disabled;element.blur();alerts.add({text:"Copied to clipboard",class:""},3000);};button.addEventListener("click",copy);}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-document",controller:function(element,container,search){var searchButton=(element.dataset["search"]||0);if(searchButton){let searchOpen=document.createElement("button");searchOpen.type='button';searchOpen.innerHTML='<i class="icon icon-search"></i> Search';searchOpen.classList.add('reverse');let path=container.scope(searchButton);searchOpen.addEventListener('click',function(){search.selected=element.value;search.path=path;document.dispatchEvent(new CustomEvent("open-document-serach",{bubbles:false,cancelable:true}));});element.parentNode.insertBefore(searchOpen,element.nextSibling)}}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-forms-filter",controller:function(document,container,expression,element,form,di){let name=element.dataset["formsFilter"]||"";let events=element.dataset["event"]||"";let serialize=function(obj,prefix){let str=[],p;for(p in obj){if(obj.hasOwnProperty(p)){let k=prefix?prefix+"["+p+"]":p,v=obj[p];if(v===""){continue;}
|
||||
element.disabled=disabled;element.blur();alerts.add({text:"Copied to clipboard",class:""},3000);};button.addEventListener("click",copy);}});})(window);(function(window){"use strict";window.ls.container.get("view").add({selector:"data-forms-document",controller:function(element,container,search){var formsDocument=(element.dataset["formsDocument"]||'');var searchButton=(element.dataset["search"]||0);let path=container.scope(searchButton);element.addEventListener('click',function(){search.selected=element.value;search.path=path;document.dispatchEvent(new CustomEvent(formsDocument,{bubbles:false,cancelable:true}));});}});})(window);(function(window){window.ls.container.get("view").add({selector:"data-forms-filter",controller:function(document,container,expression,element,form,di){let name=element.dataset["formsFilter"]||"";let events=element.dataset["event"]||"";let serialize=function(obj,prefix){let str=[],p;for(p in obj){if(obj.hasOwnProperty(p)){let k=prefix?prefix+"["+p+"]":p,v=obj[p];if(v===""){continue;}
|
||||
str.push(v!==null&&typeof v==="object"?serialize(v,k):encodeURIComponent(k)+"="+encodeURIComponent(v));}}
|
||||
return str.join("&");};let parse=function(filter){if(filter===""){return null;}
|
||||
let operatorsMap=["!=",">=","<=","=",">","<"];let operator=null;for(let key=0;key<operatorsMap.length;key++){if(filter.indexOf(operatorsMap[key])>-1){operator=operatorsMap[key];}}
|
||||
|
|
2
public/dist/styles/default-ltr.css
vendored
2
public/dist/styles/default-ltr.css
vendored
File diff suppressed because one or more lines are too long
2
public/dist/styles/default-rtl.css
vendored
2
public/dist/styles/default-rtl.css
vendored
File diff suppressed because one or more lines are too long
|
@ -372,24 +372,6 @@ window.ls.filter
|
|||
|
||||
return result.length;
|
||||
})
|
||||
.add("documentLabel", function($value, rule) {
|
||||
let value = ($value !== null && $value[rule['key']] !== undefined) ? $value[rule['key']] : null;
|
||||
|
||||
switch (rule['type']) {
|
||||
case 'bool':
|
||||
case 'boolean':
|
||||
return (value) ? 'true' : 'false';
|
||||
break;
|
||||
|
||||
case 'numeric':
|
||||
return (value && value.toString) ? value.toString() : value;
|
||||
break;
|
||||
|
||||
default:
|
||||
return value;
|
||||
break;
|
||||
}
|
||||
})
|
||||
.add("documentAction", function(container) {
|
||||
let collection = container.get('project-collection');
|
||||
let document = container.get('project-document');
|
||||
|
|
|
@ -4,31 +4,22 @@
|
|||
window.ls.container.get("view").add({
|
||||
selector: "data-forms-document",
|
||||
controller: function(element, container, search) {
|
||||
var formsDocument = (element.dataset["formsDocument"] || '');
|
||||
var searchButton = (element.dataset["search"] || 0);
|
||||
|
||||
if(searchButton) {
|
||||
let searchOpen = document.createElement("button");
|
||||
let path = container.scope(searchButton);
|
||||
|
||||
searchOpen.type = 'button';
|
||||
searchOpen.innerHTML = '<i class="icon icon-search"></i> Search';
|
||||
searchOpen.classList.add('reverse');
|
||||
element.addEventListener('click', function() {
|
||||
search.selected = element.value;
|
||||
search.path = path;
|
||||
|
||||
let path = container.scope(searchButton);
|
||||
document.dispatchEvent(
|
||||
new CustomEvent(formsDocument, {
|
||||
bubbles: false,
|
||||
cancelable: true
|
||||
}));
|
||||
});
|
||||
|
||||
searchOpen.addEventListener('click', function() {
|
||||
search.selected = element.value;
|
||||
search.path = path;
|
||||
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("open-document-serach", {
|
||||
bubbles: false,
|
||||
cancelable: true
|
||||
}));
|
||||
});
|
||||
|
||||
element.parentNode.insertBefore(searchOpen, element.nextSibling)
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
})(window);
|
||||
|
|
|
@ -116,16 +116,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
@media @phones, @tablets {
|
||||
.scroll {
|
||||
margin: 0 -30px;
|
||||
overflow-y: scroll;
|
||||
|
||||
table {
|
||||
margin: 0;
|
||||
}
|
||||
.scroll {
|
||||
margin: 0 -30px;
|
||||
overflow-y: scroll;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ul.sortable {
|
||||
|
|
Loading…
Reference in a new issue