aboutsummaryrefslogtreecommitdiff
path: root/p
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2018-09-29 20:47:17 +0200
committerGravatar GitHub <noreply@github.com> 2018-09-29 20:47:17 +0200
commit8ee8a573f1f7e9cc45f9b3c46627d15670f14f3a (patch)
tree14200758ab43e4031f60b46b8c6e9018b43e53af /p
parent3ae1b57c9d2e23157be54e8fe9865b85872ff9e7 (diff)
Custom labels (#2027)
* First draft of custom tags https://github.com/FreshRSS/FreshRSS/issues/928 https://github.com/FreshRSS/FreshRSS/issues/1367 * SMALLINT to BIGINT for id_entry And uppercase SQL types * Fix layout for unreads * Start UI menu * Change menu order * Clean database helpers https://github.com/FreshRSS/FreshRSS/pull/2027#discussion_r217971535 * Travis rules do not understand PostgreSQL constants Grrr * Tag controller + UI * Add column attributes to tags * Use only favicon for now, for label * Fix styling for different themes * Constant for maximum InnoDB index length in Unicode https://github.com/FreshRSS/FreshRSS/pull/2027#discussion_r219052200 (I would have personnally prefered keeping the readability of a real value instead of a constant, in this case of many SQL fields) * Use FreshRSS_Factory::createCategoryDao * Add view of all articles containing any tag * Fix search in tags * Mark as read tags * Partial auto-update unread tags * More auto update tag unreads * Add tag deletion * Do not purge tagged articles * Minor comment * Fix SQLite and UI bug * Google Reader API support for user tags Add SQL check that tag names must be distinct from category names * whitespace * Add missing API for EasyRSS * Compatibility SQLite Problematic parentheses * Add SQL DISTINCT for cases with multiple tags * Fix for PostgreSQL PostgreSQL needs some additional type hint to avoid "could not determine data type of parameter $1" http://www.postgresql-archive.org/Could-not-determine-data-type-of-parameter-1-tp2171092p2171094.html
Diffstat (limited to 'p')
-rw-r--r--p/api/fever.php4
-rw-r--r--p/api/greader.php167
-rw-r--r--p/scripts/main.js108
-rw-r--r--p/themes/BlueLagoon/BlueLagoon.css3
-rw-r--r--p/themes/Flat/flat.css1
-rw-r--r--p/themes/Screwdriver/screwdriver.css3
-rw-r--r--p/themes/base-theme/template.css8
7 files changed, 264 insertions, 30 deletions
diff --git a/p/api/fever.php b/p/api/fever.php
index abbade768..bf38dc662 100644
--- a/p/api/fever.php
+++ b/p/api/fever.php
@@ -312,7 +312,7 @@ class FeverAPI
{
$groups = array();
- $categoryDAO = new FreshRSS_CategoryDAO();
+ $categoryDAO = FreshRSS_Factory::createCategoryDao();
$categories = $categoryDAO->listCategories(false, false);
/** @var FreshRSS_Category $category */
@@ -457,7 +457,7 @@ class FeverAPI
}
if (isset($_REQUEST['group_ids'])) {
- $categoryDAO = new FreshRSS_CategoryDAO();
+ $categoryDAO = FreshRSS_Factory::createCategoryDao();
$group_ids = explode(',', $_REQUEST['group_ids']);
foreach ($group_ids as $id) {
/** @var FreshRSS_Category $category */
diff --git a/p/api/greader.php b/p/api/greader.php
index f5b84f7a1..27362082e 100644
--- a/p/api/greader.php
+++ b/p/api/greader.php
@@ -42,6 +42,12 @@ if (PHP_INT_SIZE < 8) { //32-bit
}
}
+if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
+ define('JSON_OPTIONS', JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
+} else {
+ define('JSON_OPTIONS', 0);
+}
+
function headerVariable($headerName, $varName) {
$header = '';
$upName = 'HTTP_' . strtoupper($headerName);
@@ -234,7 +240,7 @@ function userInfo() { //https://github.com/theoldreader/api#user-info
'userName' => $user,
'userProfileId' => $user,
'userEmail' => FreshRSS_Context::$user_conf->mail_login,
- )));
+ ), JSON_OPTIONS));
}
function tagList() {
@@ -254,10 +260,24 @@ function tagList() {
$tags[] = array(
'id' => 'user/-/label/' . $cName,
//'sortid' => $cName,
+ 'type' => 'folder', //Inoreader
);
}
- echo json_encode(array('tags' => $tags)), "\n";
+ unset($res);
+
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ $labels = $tagDAO->listTags(true);
+ foreach ($labels as $label) {
+ $tags[] = array(
+ 'id' => 'user/-/label/' . $label->name(),
+ //'sortid' => $cName,
+ 'type' => 'tag', //Inoreader
+ 'unread_count' => $label->nbUnread(),
+ );
+ }
+
+ echo json_encode(array('tags' => $tags), JSON_OPTIONS), "\n";
exit();
}
@@ -293,7 +313,7 @@ function subscriptionList() {
);
}
- echo json_encode(array('subscriptions' => $subscriptions)), "\n";
+ echo json_encode(array('subscriptions' => $subscriptions), JSON_OPTIONS), "\n";
exit();
}
@@ -310,7 +330,7 @@ function subscriptionEdit($streamNames, $titles, $action, $add = '', $remove = '
$addCatId = 0;
$categoryDAO = null;
if ($add != '' || $remove != '') {
- $categoryDAO = new FreshRSS_CategoryDAO();
+ $categoryDAO = FreshRSS_Factory::createCategoryDao();
}
$c_name = '';
if ($add != '' && strpos($add, 'user/') === 0) { //user/-/label/Example ; user/username/label/Example
@@ -391,13 +411,13 @@ function quickadd($url) {
exit(json_encode(array(
'numResults' => 1,
'streamId' => $feed->id(),
- )));
+ ), JSON_OPTIONS));
} catch (Exception $e) {
Minz_Log::error('quickadd error: ' . $e->getMessage(), API_LOG);
die(json_encode(array(
'numResults' => 0,
'error' => $e->getMessage(),
- )));
+ ), JSON_OPTIONS));
}
}
@@ -407,7 +427,7 @@ function unreadCount() { //http://blog.martindoms.com/2009/10/16/using-the-googl
$totalUnreads = 0;
$totalLastUpdate = 0;
- $categoryDAO = new FreshRSS_CategoryDAO();
+ $categoryDAO = FreshRSS_Factory::createCategoryDao();
foreach ($categoryDAO->listCategories(true, true) as $cat) {
$catLastUpdate = 0;
foreach ($cat->feeds() as $feed) {
@@ -432,6 +452,14 @@ function unreadCount() { //http://blog.martindoms.com/2009/10/16/using-the-googl
}
}
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ foreach ($tagDAO->listTags(true) as $label) {
+ $unreadcounts[] = array(
+ 'id' => 'user/-/label/' . $label->name(),
+ 'count' => $label->nbUnread(),
+ );
+ }
+
$unreadcounts[] = array(
'id' => 'user/-/state/com.google/reading-list',
'count' => $totalUnreads,
@@ -441,13 +469,21 @@ function unreadCount() { //http://blog.martindoms.com/2009/10/16/using-the-googl
echo json_encode(array(
'max' => $totalUnreads,
'unreadcounts' => $unreadcounts,
- )), "\n";
+ ), JSON_OPTIONS), "\n";
exit();
}
function entriesToArray($entries) {
+ if (empty($entries)) {
+ return array();
+ }
$feedDAO = FreshRSS_Factory::createFeedDao();
$arrayFeedCategoryNames = $feedDAO->arrayFeedCategoryNames();
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ $entryIdsTagNames = $tagDAO->getEntryIdsTagNames($entries);
+ if ($entryIdsTagNames == false) {
+ $entryIdsTagNames = array();
+ }
$items = array();
foreach ($entries as $entry) {
@@ -472,7 +508,6 @@ function entriesToArray($entries) {
'categories' => array(
'user/-/state/com.google/reading-list',
'user/-/label/' . $c_name,
- //TODO: Add other tags
),
'origin' => array(
'streamId' => 'feed/' . $f_id,
@@ -490,6 +525,10 @@ function entriesToArray($entries) {
if ($entry->isFavorite()) {
$item['categories'][] = 'user/-/state/com.google/starred';
}
+ $tagNames = isset($entryIdsTagNames['e_' . $entry->id()]) ? $entryIdsTagNames['e_' . $entry->id()] : array();
+ foreach ($tagNames as $tagName) {
+ $item['categories'][] = 'user/-/label/' . $tagName;
+ }
$items[] = $item;
}
return $items;
@@ -511,10 +550,22 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex
$type = 'f';
break;
case 'label':
- $type = 'c';
- $categoryDAO = new FreshRSS_CategoryDAO();
+ $categoryDAO = FreshRSS_Factory::createCategoryDao();
$cat = $categoryDAO->searchByName($include_target);
- $include_target = $cat == null ? -1 : $cat->id();
+ if ($cat != null) {
+ $type = 'c';
+ $include_target = $cat->id();
+ } else {
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ $tag = $tagDAO->searchByName($include_target);
+ if ($tag != null) {
+ $type = 't';
+ $include_target = $tag->id();
+ } else {
+ $type = 'A';
+ $include_target = -1;
+ }
+ }
break;
default:
$type = 'A';
@@ -559,7 +610,7 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex
}
}
- echo json_encode($response), "\n";
+ echo json_encode($response, JSON_OPTIONS), "\n";
exit();
}
@@ -579,9 +630,22 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude
} elseif (strpos($streamId, 'user/-/label/') === 0) {
$type = 'c';
$c_name = substr($streamId, 13);
- $categoryDAO = new FreshRSS_CategoryDAO();
+ $categoryDAO = FreshRSS_Factory::createCategoryDao();
$cat = $categoryDAO->searchByName($c_name);
- $id = $cat == null ? -1 : $cat->id();
+ if ($cat != null) {
+ $type = 'c';
+ $id = $cat->id();
+ } else {
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ $tag = $tagDAO->searchByName($c_name);
+ if ($tag != null) {
+ $type = 't';
+ $id = $tag->id();
+ } else {
+ $type = 'A';
+ $id = -1;
+ }
+ }
}
switch ($exclude_target) {
@@ -625,7 +689,7 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude
}
}
- echo json_encode($response), "\n";
+ echo json_encode($response, JSON_OPTIONS), "\n";
exit();
}
@@ -647,7 +711,7 @@ function streamContentsItems($e_ids, $order) {
'items' => $items,
);
- echo json_encode($response), "\n";
+ echo json_encode($response, JSON_OPTIONS), "\n";
exit();
}
@@ -657,6 +721,7 @@ function editTag($e_ids, $a, $r) {
}
$entryDAO = FreshRSS_Factory::createEntryDao();
+ $tagDAO = FreshRSS_Factory::createTagDao();
switch ($a) {
case 'user/-/state/com.google/read':
@@ -671,6 +736,30 @@ function editTag($e_ids, $a, $r) {
break;
case 'user/-/state/com.google/broadcast':
break;*/
+ default:
+ $tagName = '';
+ if (strpos($a, 'user/-/label/') === 0) {
+ $tagName = substr($a, 13);
+ } else {
+ $user = Minz_Session::param('currentUser', '_');
+ $prefix = 'user/' . $user . '/label/';
+ if (strpos($a, $prefix) === 0) {
+ $tagName = substr($a, strlen($prefix));
+ }
+ }
+ if ($tagName != '') {
+ $tag = $tagDAO->searchByName($tagName);
+ if ($tag == null) {
+ $tagDAO->addTag(array('name' => $tagName));
+ $tag = $tagDAO->searchByName($tagName);
+ }
+ if ($tag != null) {
+ foreach ($e_ids as $e_id) {
+ $tagDAO->tagEntry($tag->id(), $e_id, true);
+ }
+ }
+ }
+ break;
}
switch ($r) {
case 'user/-/state/com.google/read':
@@ -679,6 +768,17 @@ function editTag($e_ids, $a, $r) {
case 'user/-/state/com.google/starred':
$entryDAO->markFavorite($e_ids, false);
break;
+ default:
+ if (strpos($r, 'user/-/label/') === 0) {
+ $tagName = substr($r, 13);
+ $tag = $tagDAO->searchByName($tagName);
+ if ($tag != null) {
+ foreach ($e_ids as $e_id) {
+ $tagDAO->tagEntry($tag->id(), $e_id, false);
+ }
+ }
+ }
+ break;
}
exit('OK');
@@ -688,12 +788,20 @@ function renameTag($s, $dest) {
if ($s != '' && strpos($s, 'user/-/label/') === 0 &&
$dest != '' && strpos($dest, 'user/-/label/') === 0) {
$s = substr($s, 13);
- $categoryDAO = new FreshRSS_CategoryDAO();
+ $dest = substr($dest, 13);
+
+ $categoryDAO = FreshRSS_Factory::createCategoryDao();
$cat = $categoryDAO->searchByName($s);
if ($cat != null) {
- $dest = substr($dest, 13);
$categoryDAO->updateCategory($cat->id(), array('name' => $dest));
exit('OK');
+ } else {
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ $tag = $tagDAO->searchByName($s);
+ if ($tag != null) {
+ $tagDAO->updateTag($tag->id(), array('name' => $dest));
+ exit('OK');
+ }
}
}
badRequest();
@@ -702,7 +810,7 @@ function renameTag($s, $dest) {
function disableTag($s) {
if ($s != '' && strpos($s, 'user/-/label/') === 0) {
$s = substr($s, 13);
- $categoryDAO = new FreshRSS_CategoryDAO();
+ $categoryDAO = FreshRSS_Factory::createCategoryDao();
$cat = $categoryDAO->searchByName($s);
if ($cat != null) {
$feedDAO = FreshRSS_Factory::createFeedDao();
@@ -711,6 +819,13 @@ function disableTag($s) {
$categoryDAO->deleteCategory($cat->id());
}
exit('OK');
+ } else {
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ $tag = $tagDAO->searchByName($s);
+ if ($tag != null) {
+ $tagDAO->deleteTag($tag->id());
+ exit('OK');
+ }
}
}
badRequest();
@@ -723,9 +838,17 @@ function markAllAsRead($streamId, $olderThanId) {
$entryDAO->markReadFeed($f_id, $olderThanId);
} elseif (strpos($streamId, 'user/-/label/') === 0) {
$c_name = substr($streamId, 13);
- $categoryDAO = new FreshRSS_CategoryDAO();
+ $categoryDAO = FreshRSS_Factory::createCategoryDao();
$cat = $categoryDAO->searchByName($c_name);
- $entryDAO->markReadCat($cat === null ? -1 : $cat->id(), $olderThanId);
+ if ($cat != null) {
+ $entryDAO->markReadCat($cat->id(), $olderThanId);
+ } else {
+ $tagDAO = FreshRSS_Factory::createTagDao();
+ $tag = $tagDAO->searchByName($c_name);
+ if ($tag != null) {
+ $entryDAO->markReadTag($tag->id(), $olderThanId);
+ }
+ }
} elseif ($streamId === 'user/-/state/com.google/reading-list') {
$entryDAO->markReadEntries($olderThanId, false, -1);
}
diff --git a/p/scripts/main.js b/p/scripts/main.js
index 6ab256065..cac2e8706 100644
--- a/p/scripts/main.js
+++ b/p/scripts/main.js
@@ -114,6 +114,17 @@ function incUnreadsFeed(article, feed_id, nb) {
return isCurrentView;
}
+function incUnreadsTag(tag_id, nb) {
+ var $t = $('#t_' + tag_id);
+ var unreads = str2int($t.attr('data-unread'));
+ $t.attr('data-unread', unreads + nb)
+ .children('.item-title').attr('data-unread', numberFormat(unreads + nb));
+
+ $t = $('.category.tags').find('.title');
+ unreads = str2int($t.attr('data-unread'));
+ $t.attr('data-unread', numberFormat(unreads + nb));
+}
+
var pending_entries = {};
function mark_read(active, only_not_read) {
if ((active.length === 0) || (!active.attr('id')) ||
@@ -157,6 +168,12 @@ function mark_read(active, only_not_read) {
}
faviconNbUnread();
+ if (data.tags) {
+ for (var i = data.tags.length - 1; i >= 0; i--) {
+ incUnreadsTag(data.tags[i], inc);
+ }
+ }
+
delete pending_entries[active.attr('id')];
}).fail(function (data) {
openNotification(i18n.notif_request_failed, 'bad');
@@ -529,12 +546,16 @@ function init_column_categories() {
$(this).parent().next(".tree-folder-items").slideToggle(300 , function() { $(document.body).trigger("sticky_kit:recalc"); });
return false;
});
- $('#aside_feed').on('click', '.tree-folder-items .item .dropdown-toggle', function () {
+ $('#aside_feed').on('click', '.tree-folder-items .feed .dropdown-toggle', function () {
if ($(this).nextAll('.dropdown-menu').length === 0) {
- var feed_id = $(this).closest('.item').attr('id').substr(2),
+ var itemId = $(this).closest('.item').attr('id'),
+ templateId = itemId.substring(0, 2) === 't_' ? 'tag_config_template' : 'feed_config_template',
+ id = itemId.substr(2),
feed_web = $(this).data('fweb'),
- template = $('#feed_config_template').html().replace(/------/g, feed_id).replace('http://example.net/', feed_web);
- $(this).attr('href', '#dropdown-' + feed_id).prev('.dropdown-target').attr('id', 'dropdown-' + feed_id).parent().append(template);
+ template = $('#' + templateId)
+ .html().replace(/------/g, id).replace('http://example.net/', feed_web);
+ $(this).attr('href', '#dropdown-' + id).prev('.dropdown-target').attr('id', 'dropdown-' + id).parent()
+ .append(template).find('button.confirm').removeAttr('disabled');
$('.tree-folder-items .dropdown-close a').click(function(){
$('.tree').removeClass('treepadding');
$(document.body).trigger("sticky_kit:recalc");
@@ -606,7 +627,7 @@ function init_shortcuts() {
auto_share(String.fromCharCode(evt.keyCode));
}
}
- for(var i = 1; i < 10; i++) {
+ for (var i = 1; i < 10; i++) {
shortcut.add(i.toString(), addShortcut, {
'disable_in_input': true
});
@@ -830,6 +851,69 @@ function init_nav_entries() {
});
}
+function loadDynamicTags($div) {
+ $div.removeClass('dynamictags');
+ $div.find('li.item').remove();
+ var entryId = $div.closest('div.flux').attr('id').replace(/^flux_/, '');
+ $.getJSON('./?c=tag&a=getTagsForEntry&id_entry=' + entryId)
+ .done(function (data) {
+ var $ul = $div.find('.dropdown-menu');
+ $ul.append('<li class="item"><label><input class="checkboxTag" name="t_0" type="checkbox" /> <input type="text" name="newTag" /></label></li>');
+ if (data && data.length) {
+ for (var i = 0; i < data.length; i++) {
+ var tag = data[i];
+ $ul.append('<li class="item"><label><input class="checkboxTag" name="t_' + tag.id + '" type="checkbox"' +
+ (tag.checked ? ' checked="checked"' : '') + '> ' + tag.name + '</label></li>');
+ }
+ }
+ })
+ .fail(function () {
+ $div.find('li.item').remove();
+ $div.addClass('dynamictags');
+ });
+}
+
+function init_dynamic_tags() {
+ $stream.on('click', '.dynamictags', function () {
+ loadDynamicTags($(this));
+ });
+
+ $stream.on('change', '.checkboxTag', function (ev) {
+ var $checkbox = $(this);
+ $checkbox.prop('disabled', true);
+ var isChecked = $checkbox.prop('checked');
+ var tagId = $checkbox.attr('name').replace(/^t_/, '');
+ var tagName = $checkbox.siblings('input[name]').val();
+ var $entry = $checkbox.closest('div.flux');
+ var entryId = $entry.attr('id').replace(/^flux_/, '');
+ $.ajax({
+ type: 'POST',
+ url: './?c=tag&a=tagEntry',
+ data: {
+ _csrf: context.csrf,
+ id_tag: tagId,
+ name_tag: tagId == 0 ? tagName : '',
+ id_entry: entryId,
+ checked: isChecked,
+ },
+ })
+ .done(function () {
+ if ($entry.hasClass('not_read')) {
+ incUnreadsTag(tagId, isChecked ? 1 : -1);
+ }
+ })
+ .fail(function () {
+ $checkbox.prop('checked', !isChecked);
+ })
+ .always(function () {
+ $checkbox.prop('disabled', false);
+ if (tagId == 0) {
+ loadDynamicTags($checkbox.closest('div.dropdown'));
+ }
+ });
+ });
+}
+
// <actualize>
var feed_processed = 0;
@@ -1004,7 +1088,7 @@ function refreshUnreads() {
var isAll = $('.category.all.active').length > 0,
new_articles = false;
- $.each(data, function(feed_id, nbUnreads) {
+ $.each(data.feeds, function(feed_id, nbUnreads) {
feed_id = 'f_' + feed_id;
var elem = $('#' + feed_id).get(0),
feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0;
@@ -1016,6 +1100,17 @@ function refreshUnreads() {
}
});
+ var nbUnreadTags = 0;
+
+ $.each(data.tags, function(tag_id, nbUnreads) {
+ nbUnreadTags += nbUnreads;
+ $('#t_' + tag_id).attr('data-unread', nbUnreads)
+ .children('.item-title').attr('data-unread', numberFormat(nbUnreads));
+ });
+
+ $('.category.tags').attr('data-unread', nbUnreadTags)
+ .find('.title').attr('data-unread', numberFormat(nbUnreadTags));
+
var nb_unreads = str2int($('.category.all .title').attr('data-unread'));
if (nb_unreads > 0 && new_articles) {
@@ -1432,6 +1527,7 @@ function init_afterDOM() {
init_load_more($stream);
init_posts();
init_nav_entries();
+ init_dynamic_tags();
init_print_action();
init_post_action();
init_notifs_html5();
diff --git a/p/themes/BlueLagoon/BlueLagoon.css b/p/themes/BlueLagoon/BlueLagoon.css
index 424970501..164088f4b 100644
--- a/p/themes/BlueLagoon/BlueLagoon.css
+++ b/p/themes/BlueLagoon/BlueLagoon.css
@@ -373,6 +373,9 @@ a.btn {
color: #ccc;
font-size: 0.8rem;
}
+.dropdown-menu > .item > label {
+ color: #ccc;
+}
.dropdown-menu > .item:hover {
background: linear-gradient(180deg, #0090FF 0%, #0062BE 100%) #E4992C;
background: -webkit-linear-gradient(top, #0090FF 0%, #0062BE 100%);
diff --git a/p/themes/Flat/flat.css b/p/themes/Flat/flat.css
index 378851299..be047a394 100644
--- a/p/themes/Flat/flat.css
+++ b/p/themes/Flat/flat.css
@@ -300,6 +300,7 @@ a.btn {
/*=== Dropdown */
.dropdown-menu {
+ background: #fafafa;
margin: 5px 0 0;
padding: 5px 0;
font-size: 0.8rem;
diff --git a/p/themes/Screwdriver/screwdriver.css b/p/themes/Screwdriver/screwdriver.css
index a142c3860..1bc49c2db 100644
--- a/p/themes/Screwdriver/screwdriver.css
+++ b/p/themes/Screwdriver/screwdriver.css
@@ -373,6 +373,9 @@ a.btn {
color: #ccc;
font-size: 0.8rem;
}
+.dropdown-menu > .item > label {
+ color: #ccc;
+}
.dropdown-menu > .item:hover {
background: #171717;
color: #fff;
diff --git a/p/themes/base-theme/template.css b/p/themes/base-theme/template.css
index 26143a5d5..e6e9934a4 100644
--- a/p/themes/base-theme/template.css
+++ b/p/themes/base-theme/template.css
@@ -108,6 +108,14 @@ input[type="checkbox"] {
width: 15px !important;
min-height: 15px !important;
}
+.dropdown-menu label > input[type="text"] {
+ with: 150px;
+ width: calc(99% - 5em);
+}
+.dropdown-menu input[type="checkbox"] {
+ margin-left: 1em;
+ margin-right: .5em;
+}
button.as-link,
button.as-link:hover,
button.as-link:active {