Add filter-posts (unfinished) + UI/UX improvements

This commit is contained in:
Bofh 2022-12-29 03:13:38 +01:00
parent ee5eb1af0e
commit 961808d59c
4 changed files with 329 additions and 45 deletions

View File

@ -75,6 +75,7 @@ body > .toast-container {
*[id*="-item"] { display: none !important }
.flex { display: flex }
.iflex { display: inline-flex }
.inline { display: inline-block }
.center { margin: auto }
.wmax { width: 100vw }
@ -158,7 +159,7 @@ textarea {
}
br.sep {
margin-bottom: 1em;
margin-bottom: .7em;
}
hr { color: #0000005e }
@ -170,7 +171,10 @@ a {
main input[type=text],
main input[type=password],
main textarea {
padding: 1em;
padding: .7em;
border-radius: .5em;
border: 1px solid #00000042;
box-shadow: 0px 0px .2em #0000002b;
}
main input[type=number] {
padding: .5em 1em;
@ -179,8 +183,24 @@ main input[type=number] {
main select {
padding: .5em;
}
@media (min-width: 650px) {
main .input-rows-2 {
display: flex;
}
main .input-rows-2.divide > * {
width: 100%;
}
main .input-rows-2 > .div2 {
width: 50%;
}
}
main .input {
margin-bottom: 1em;
border-bottom: 2px dotted #00000017;
padding: .8em .5em;
}
main .input.last {
padding: 1em;
padding-top: 1.5em;
}
.software .only {
@ -294,6 +314,9 @@ table.fields tr.verified td .fa.fa-check::before {
#window-instance #tabs .item[data-id="settings"] {
border-left: 2px solid #5e5e5e;
}
#window-instance #tabs .item.selected {
border-top: 4px solid #ff7a7a;
}
#window-instance #tabs .tab-content {
padding: 1em;
}
@ -304,7 +327,13 @@ table.fields tr.verified td .fa.fa-check::before {
}
#window-instance #content #container {
margin: 0 auto;
max-width: 50em;
padding: 1em;
width: 100%;
}
#window-instance #content #container h3 {
margin-top: 0;
margin-bottom: .5em;
}
#window-instance #container .card {
margin-bottom: 1em;
@ -313,6 +342,12 @@ table.fields tr.verified td .fa.fa-check::before {
#window-instance #filters-all {
max-width: 50em;
}
#window-instance #filters-all .item .btn.mid {
margin-right: .5em;
margin-bottom: .2em;
font-size: .9em;
padding: .3em !important;
}
#window-instance #container #new-accounts .card {
width: calc(100vw - 4em);
max-width: 30em;

View File

@ -0,0 +1,191 @@
<h3>Filters being applied: <button id="btncollapse-filters-all">hide</button></h3>
<div id="filters-all"></div>
<span id="filters-all-item">
<a href="javascript:window.set_hash_argument('filter_id','{id}')">
<div class="btn mid card inline" style="margin-right: .5em; margin-bottom: .5em">
<i class="fa fa-filter fa-fw"></i>{name}
<button class="btn small red"
onclick="{view.js}.delete_filter('{id}');event.preventDefault()">
<i class="fa fa-close fa-fw"></i>
</button>
</div>
</a>
</span>
<h3>
<span id="filter-current-name">*New filter</span>:
<button id="btncollapse-filters-current">hide</button>
<button class="btn mid green center"
onclick="window.del_hash_argument('filter_id');
{view.js}.set_current_filter({})"
><i class="fa fa-plus fa-fw"></i>
New</button>
</h3>
<div id="filters-current">
<div class="card">
<div class="input">
<input type="hidden" name="preset_id" value="-1"/>
<label for="preset_name">Preset name:</label>
<input type="text" name="preset_name" placeholder="Preset name" value="Undefined"/>
</div>
<div class="input">
<label for="profile">Post content:</label>
<input type="radio" id="post-search-type-simple"
name="post_search_type" value="simple" checked
/><label for="post-search-type-simple">simple</label>
<input type="radio" id="post-search-type-expr"
name="post_search_type" value="expr"
/><label for="post-search-type-expr">expr</label>
<br class="sep"/>
<textarea name="profile" placeholder="Input your text here"
style="width: 100%; min-width: 24em; min-height: 6em"></textarea>
</div>
<div class="input">
<label for="instances">Instances:</label>
<br class="sep"/>
<textarea name="instances" placeholder="instance1.org, server2.com, !not-this-one.com"
style="width: 100%; min-width: 24em; min-height: 4em"></textarea>
</div>
<div class="input">
<label for="languages">Languages:</label>
<br class="sep"/>
<textarea name="languages" placeholder="es, en, de"
style="width: 100%; min-width: 24em; min-height: 4em"></textarea>
</div>
<br>
<div class="input flex">
<button class="btn center"
onclick="{view.js}.toggle_filter('{id}')"
><i class="fa fa-search fa-fw"></i>
{enableOrDisable}</button>
<div style="width:1em"></div>
<button class="btn center"
onclick="{view.js}.save_filter()"
><i class="fa fa-save fa-fw"></i>
Save filter</button>
</div>
</div>
<div id="filter-instance-action"></div>
<div id="filter-instance-action-item">
<div class="card">
<div class="flex">
<span class="center"><i class="fa fa-info-circle fa-fw"></i>
Action for users matching this filter on <b>{instance}</b>
</span>
</div>
<hr><br>
<div class="input">
<label for="instance_action_name">What to do:</label>
<br class="sep"/>
<select name="instance_action_name">
<option value="none">None (do nothing)</option>
<option value="report">Report</option>
<option value="silence">Silence</option>
<option value="sensible">Sensible</option>
<option value="suspend">Suspend</option>
</select>
</div>
<div class="input">
<label for="instance_action_explain">Generic explanation to users:</label>
<br class="sep"/>
<textarea type="text" name="instance_action_explain" style="min-height: 6em" class="w100"
placeholder="Generic explanation that will be sent to users when an action is taken..."></textarea>
</div>
<div class="input">
<label for="instance_action_interval">Job interval (in seconds):</label>
<input type="number" name="instance_action_interval" placeholder="60"/>
</div>
<br>
<div class="input flex">
<button class="btn center" onclick="
{view.js}.save_filter_action('{id}')"
><i class="fa fa-save fa-fw"></i>&nbsp;Save filter action</button>
</div>
</div>
</div>
</div>
<h3>Timeline:
<button id="btncollapse-results-box">hide</button>
<button onclick="{view.js}.execute()" class="btn mid center">
<i class="fa fa-search fa-fw"></i></button>
</h3>
<div id="results-box">
<div id="paging"></div>
<div id="paging-item">
<button class="btn mid" onclick="
{view.js}.execute({prev_page},{items_per_page})"
><i class="fa fa-chevron-left fa-fw"></i></button>
<button class="btn mid" onclick="
{view.js}.execute({next_page},{items_per_page})"
><i class="fa fa-chevron-right fa-fw"></i></button>
<span>{current_page} / {total_pages} ({total_users} users)</span>
</div>
<div id="users-all"></div>
<div id="users-all-item" data-id="{id}">
<div class="card software {software} {silencedClass}">
<div class="flex disable-able">
<img data-src="{avatar}"/>
<div class="w100" style="padding:0 1em">
<b style="font-size: 1.1em">{name}</b>
<br>
<span class="gray">@{acct}</span>
<div class="props flex">
<span>{statuses}<br><span class="gray">statuses</span></span>
<span>{following}<br><span class="gray">following</span></span>
<span>{followers}<br><span class="gray">followers</span></span>
</div>
</div>
</div>
<div class="only mastodon">{mastodon:fields}</div>
<div class="note">{note}</div>
<div style="margin-top: 1em; text-align: right;">
<div style="float:left">
<button onclick="if ({do.js}.users.trust_user('{acct}'))
E.element('.item[data-id=&quot;{id}&quot;]').remove()"
title="Add this user to the trusted_users list"
class="btn mid green"><i class="fa fa-thumbs-up fa-fw"></i>
Trust</button>
<a href="{accountAdminLink}" target="_blank" rel="noreferrer noopener">
<button class="btn mid"
title="Open this account in the AP application manager"
><i class="fa fa-id-card fa-fw"></i>
Manage</button>
</a>
</div>
<button onclick="if ({do.js}.users.hide_on_filters('{acct}', 86400))
E.element('.item[data-id=&quot;{id}&quot;]').remove()"
title="Hide the selected account for 1 day (24 hours)"
class="btn mid"><i class="fa fa-clock-o fa-fw"></i>
Hide 1d</button>
<button onclick="
if ('' === '{disabledClass}') {
if ({do.js}.users.silence('{id}')) {
E.element('.item[data-id=&quot;{id}&quot;]').classList.add('silenced');
this.querySelector('span').innerText = 'Silenced';
this.setAttribute('disabled', 'true');
}
}"
title="Silence this user on the instance"
class="btn mid disable-able" {disabledHTML}>
<i class="fa fa-volume-mute fa-fw"></i>
<span>{silencedText}</span></button>
<button onclick="if ({do.js}.users.suspend('{id}')) {
E.element('.item[data-id=&quot;{id}&quot;]').classList.add('suspended');
this.querySelector('span').innerText = 'Suspended'}"
title="Suspend this user on the instance"
class="btn mid red"><i class="fa fa-ban fa-fw"></i>
<span>Suspend</span></button>
</div>
</div>
</div>
<div id="paging" class="bottom"></div>
</div>
<br>

View File

@ -2,7 +2,7 @@
<div id="filters-all"></div>
<span id="filters-all-item">
<a href="javascript:window.set_hash_argument('filter_id','{id}')">
<div class="btn mid card inline" style="margin-right: .5em; margin-bottom: .5em">
<div class="btn mid card inline">
<i class="fa fa-filter fa-fw"></i>{name}
<button class="btn small red"
onclick="{view.js}.delete_filter('{id}');event.preventDefault()">
@ -12,6 +12,7 @@
</a>
</span>
<br>
<h3>
<span id="filter-current-name">*New filter</span>:
<button id="btncollapse-filters-current">hide</button>
@ -41,45 +42,63 @@
/><label for="profile-search-type-empty">Empty</label>
<br class="sep"/>
<textarea name="profile" placeholder="Input your text here"
style="width: 100%; min-width: 24em; min-height: 6em"></textarea>
</div>
<div class="input">
<label for="instances">Instances:</label>
<br class="sep"/>
<textarea name="instances" placeholder="instance1.org, server2.com"
style="width: 100%; min-width: 24em; min-height: 4em"></textarea>
</div>
<div class="input">
<label for="languages">Languages:</label>
<br class="sep"/>
<textarea name="languages" placeholder="es, en, de"
style="width: 100%; min-width: 24em; min-height: 4em"></textarea>
<div class="input-rows-2 divide">
<div class="input">
<label for="instances">Instances:</label>
<br class="sep"/>
<textarea name="instances" placeholder="instance1.org, server2.com, !not-this-one.com"
style="width: 100%; min-height: 2em"></textarea>
</div>
<div class="input div2">
<label for="languages">Languages:</label>
<br class="sep"/>
<textarea name="languages" placeholder="es, en, de"
style="width: 100%; min-height: 2em"></textarea>
</div>
</div>
<div class="input">
<label for="user_filter">Account type:</label>
<br class="sep"/>
<input type="radio" id="user-filter-local"
name="user_filter" value="local" checked
/><label for="user-filter-local">local</label>
<input type="radio" id="user_filter-remote"
name="user_filter" value="remote"
/><label for="user_filter-remote">remote</label>
</div>
<div class="input">
<label>Account stages:</label>
<br class="sep"/>
<input type="checkbox" id="account-prop-noavatar"
name="no_avatar" value="1"
/><label for="account-prop-noavatar">No avatar</label>
<input type="checkbox" id="account-prop-nostatuses"
name="no_statuses" value="1"
/><label for="account-prop-nostatuses">No statuses</label>
<input type="checkbox" id="account-prop-nofollows"
name="no_follows" value="1"
/><label for="account-prop-nofollows">No follows</label>
<input type="checkbox" id="account-prop-ismoved"
name="is_moved" value="1"
/><label for="account-prop-ismoved">Has moved elsewhere</label>
<div class="input-rows-2">
<div class="input">
<label for="user_filter">Account type:</label>
<br class="sep"/>
<div class="iflex">
&nbsp;<input type="radio" id="user-filter-local"
name="user_filter" value="local" checked
/><label for="user-filter-local">local</label>
</div>
<div class="iflex">
&nbsp;<input type="radio" id="user_filter-remote"
name="user_filter" value="remote"
/><label for="user_filter-remote">remote</label>
</div>
<div><br></div>
</div>
<div class="input">
<label>Account stages:</label>
<br class="sep"/>
<div class="iflex">
&nbsp;<input type="checkbox" id="account-prop-noavatar"
name="no_avatar" value="1"
/><label for="account-prop-noavatar">No avatar</label>
</div>
<div class="iflex">
&nbsp;<input type="checkbox" id="account-prop-nostatuses"
name="no_statuses" value="1"
/><label for="account-prop-nostatuses">No statuses</label>
</div>
<div class="iflex">
&nbsp;<input type="checkbox" id="account-prop-nofollows"
name="no_follows" value="1"
/><label for="account-prop-nofollows">No follows</label>
</div>
<div class="iflex">
&nbsp;<input type="checkbox" id="account-prop-ismoved"
name="is_moved" value="1"
/><label for="account-prop-ismoved">Has moved elsewhere</label>
</div>
<div><br></div>
</div>
</div>
<div class="input">
<label>Last activity (days):</label>
@ -106,8 +125,7 @@
</div>
</div>
</div>
<br>
<div class="input flex">
<div class="input last noborder flex">
<button class="btn center"
onclick="{view.js}.execute()"
><i class="fa fa-search fa-fw"></i>
@ -149,8 +167,7 @@
<label for="instance_action_interval">Job interval (in seconds):</label>
<input type="number" name="instance_action_interval" placeholder="60"/>
</div>
<br>
<div class="input flex">
<div class="input last noborder flex">
<button class="btn center" onclick="
{view.js}.save_filter_action('{id}')"
><i class="fa fa-save fa-fw"></i>&nbsp;Save filter action</button>
@ -159,6 +176,7 @@
</div>
</div>
<br>
<h3>Results:
<button id="btncollapse-results-box">hide</button>
<button onclick="{view.js}.execute()" class="btn mid center">

View File

@ -457,6 +457,44 @@ window.view.instance = {
});
});
},
filter_posts: function() {
const hargs = get_hash_arguments();
E.http_template('instance/filter-posts', function(html) {
html = html.replaceAll('{view.js}', 'window.view.instance.do.filter_posts');
html = html.replaceAll('{do.js}', 'window.view.instance.do');
E.element('#window-instance #container').innerHTML = html;
E.elemid('post-search-type-simple').onchange = function(e) {
if (e.target.checked) {
E.element('#filters-current textarea[name=profile]').setAttribute('placeholder', 'Input your text here');
E.element('#filters-current textarea[name=profile]').removeAttribute('disabled');
}
};
E.elemid('post-search-type-expr').onchange = function(e) {
if (e.target.checked) {
E.element('#filters-current textarea[name=profile]').setAttribute('placeholder',
'Input your expression (click on info button for documentation)');
E.element('#filters-current textarea[name=profile]').removeAttribute('disabled');
}
};
E.custom.btncollapse_render();
http.get('api/v1/config/filters/get?type=posts', {}, function(js) {
js = js.sort((a,b) => a.preset_name.localeCompare(b.preset_name));
window.vars.post_filters = js;
E.template('filters-all', function(TPL) {
var html = '';
for (var i = 0; i < js.length; i++) {
var tpl = TPL;
tpl = tpl.replaceAll('{id}', js[i].id);
tpl = tpl.replaceAll('{name}', js[i].preset_name);
html += tpl;
}
return html;
});
if (hargs.filter_id !== undefined)
window.view.instance.do.filter_posts.set_current_filter(hargs.filter_id);
});
});
},
filter_users: function() {
const hargs = get_hash_arguments();
E.http_template('instance/filter-users', function(html) {
@ -472,7 +510,7 @@ window.view.instance = {
E.elemid('profile-search-type-expr').onchange = function(e) {
if (e.target.checked) {
E.element('#filters-current textarea[name=profile]').setAttribute('placeholder',
'(words "apple,banana" AND contains "i like apples") OR words "pineapple,strawberry"');
'Input your expression (click on info button for documentation)');
E.element('#filters-current textarea[name=profile]').removeAttribute('disabled');
}
};
@ -508,6 +546,8 @@ window.view.instance = {
const hargs = get_hash_arguments();
const _empty_content = function() {
E.element('#window-instance #container').innerHTML = '';
E.elements('#tabs .item').forEach(function(t){t.classList.remove('selected')});
E.element(`#tabs *[data-id=${content}]`).classList.add('selected');
};
if (click) {
delete window.hash_argument_nofirechange;