mirror of
https://github.com/gryf/pentadactyl-pm.git
synced 2025-12-22 17:57:58 +01:00
new args parser, converted :bmarks to it, which can now user -keyword= and -tags=
This commit is contained in:
31
Donators
31
Donators
@@ -1,22 +1,25 @@
|
||||
<pre>
|
||||
<b>Note:</b> If you don't wish to appear on this list when making a donation, please tell me.
|
||||
|
||||
* Andrew Pantyukhin
|
||||
* Ben Klemens
|
||||
* Sjoerd Siebinga
|
||||
* Cillian de Roiste
|
||||
* Miron Tewfik
|
||||
2007 (most recent donators first):
|
||||
|
||||
* Robert Heckel
|
||||
* Stefan Krauth
|
||||
* Giuseppe Guida
|
||||
* Richard Dooling
|
||||
* Nigel McNie
|
||||
* Paulo Tanimoto
|
||||
* Nathan Saper
|
||||
* Albert Menkveld
|
||||
* Ian Taylor
|
||||
* Thomas Svensen
|
||||
* Ramana Kumar
|
||||
* Thomas Svensen
|
||||
* Ian Taylor
|
||||
* Albert Menkveld
|
||||
* Nathan Saper
|
||||
* Paulo Tanimoto
|
||||
* Nigel McNie
|
||||
* Richard Dooling
|
||||
* Giuseppe Guida
|
||||
* Stefan Krauth
|
||||
* Robert Heckel
|
||||
* Miron Tewfik
|
||||
* Cillian de Roiste
|
||||
* Sjoerd Siebinga
|
||||
* Ben Klemens
|
||||
* Andrew Pantyukhin
|
||||
|
||||
I want to say a big <b>THANK YOU</b> for all people which supported this project in this way.
|
||||
</pre>
|
||||
|
||||
1
NEWS
1
NEWS
@@ -2,6 +2,7 @@
|
||||
2007-xx-xx:
|
||||
* version 0.6
|
||||
* THIS VERSION ONLY WORKS WITH FIREFOX 3.0
|
||||
* tags and keyword support for :bmark
|
||||
* added full zoom, and changed keybindings slightly for text zoom
|
||||
* :buffer partial_string works now as in vim, and with ! even better
|
||||
* improvements for scrollable -- more -- prompt
|
||||
|
||||
@@ -38,8 +38,8 @@ function Bookmarks() //{{{
|
||||
.getService(Components.interfaces.nsINavBookmarksService);
|
||||
const tagging_service = Components.classes["@mozilla.org/browser/tagging-service;1"]
|
||||
.getService(Components.interfaces.nsITaggingService);
|
||||
const search_service = Components.classes["@mozilla.org/browser/search-service;1"].
|
||||
getService(Components.interfaces.nsIBrowserSearchService);
|
||||
const search_service = Components.classes["@mozilla.org/browser/search-service;1"]
|
||||
.getService(Components.interfaces.nsIBrowserSearchService);
|
||||
const io_service = Components.classes['@mozilla.org/network/io-service;1']
|
||||
.getService(Components.interfaces.nsIIOService);
|
||||
|
||||
@@ -112,21 +112,39 @@ function Bookmarks() //{{{
|
||||
return bookmarks;
|
||||
}
|
||||
|
||||
this.add = function (title, url, keyword)
|
||||
this.add = function (title, url, keyword, tags)
|
||||
{
|
||||
if (!bookmarks)
|
||||
load();
|
||||
|
||||
// if no protocol specified, default to http://, isn't there a better way?
|
||||
if (/^\w+:/.test(url) == false)
|
||||
url = "http://" + url;
|
||||
|
||||
try
|
||||
{
|
||||
var uri = io_service.newURI(url, null, null);
|
||||
var id = bookmarks_service.insertBookmark(bookmarks_service.bookmarksRoot, uri, -1, title);
|
||||
if (id && keyword)
|
||||
if (!id)
|
||||
return false;
|
||||
|
||||
if (keyword)
|
||||
{
|
||||
bookmarks_service.setKeywordForBookmark(id, keyword);
|
||||
keywords.unshift([keyword, title, url]);
|
||||
}
|
||||
|
||||
if (tags)
|
||||
tagging_service.tagURI(uri, tags);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
vimperator.log(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
//also update bookmark cache
|
||||
bookmarks.unshift([url, title, null, []]);
|
||||
bookmarks.unshift([url, title, keyword, tags || []]);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -136,12 +154,22 @@ function Bookmarks() //{{{
|
||||
if (!url)
|
||||
return 0;
|
||||
|
||||
var i = 0;
|
||||
try
|
||||
{
|
||||
var uri = io_service.newURI(url, null, null);
|
||||
var count = {};
|
||||
var bmarks = bookmarks_service.getBookmarkIdsForURI(uri, count);
|
||||
|
||||
for (var i = 0; i < bmarks.length; i++)
|
||||
for (; i < bmarks.length; i++)
|
||||
bookmarks_service.removeItem(bmarks[i]);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
vimperator.log(e);
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
// also update bookmark cache, if we removed at least one bookmark
|
||||
if (count.value > 0)
|
||||
@@ -298,75 +326,6 @@ function Bookmarks() //{{{
|
||||
vimperator.commandline.echo(list, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE);
|
||||
}
|
||||
}
|
||||
|
||||
// res = parseBookmarkString("-t tag1,tag2 -T title http://www.orf.at");
|
||||
// res.tags is an array of tags
|
||||
// res.title is the title or "" if no one was given
|
||||
// res.url is the url as a string
|
||||
// returns null, if parsing failed
|
||||
Bookmarks.parseBookmarkString = function(str)
|
||||
{
|
||||
var res = {};
|
||||
res.tags = [];
|
||||
res.title = null;
|
||||
res.url = null;
|
||||
|
||||
var re_title = /^\s*((-t|--title)\s+(\w+|\".*\"))(.*)/;
|
||||
var re_tags = /^\s*((-T|--tags)\s+((\w+)(,\w+)*))(.*)/;
|
||||
var re_url = /^\s*(\".+\"|\S+)(.*)/;
|
||||
|
||||
var match_tags = null;
|
||||
var match_title = null;
|
||||
var match_url = null;
|
||||
|
||||
while (!str.match(/^\s*$/))
|
||||
{
|
||||
// first check for --tags
|
||||
match_tags = str.match(re_tags);
|
||||
if (match_tags != null)
|
||||
{
|
||||
str = match_tags[match_tags.length - 1]; // the last captured parenthesis is the rest of the string
|
||||
tags = match_tags[3].split(",");
|
||||
res.tags = res.tags.concat(tags);
|
||||
}
|
||||
else // then for --titles
|
||||
{
|
||||
|
||||
match_title = str.match(re_title);
|
||||
if (match_title != null)
|
||||
{
|
||||
// only one title allowed
|
||||
if (res.title != null)
|
||||
return null;
|
||||
|
||||
str = match_title[match_title.length - 1]; // the last captured parenthesis is the rest of the string
|
||||
var title = match_title[3];
|
||||
if (title.charAt(0) == '"')
|
||||
title = title.substring(1, title.length - 1);
|
||||
res.title = title;
|
||||
}
|
||||
else // at last check for a URL
|
||||
{
|
||||
match_url = str.match(re_url);
|
||||
if (match_url != null)
|
||||
{
|
||||
// only one url allowed
|
||||
if (res.url != null)
|
||||
return null;
|
||||
|
||||
str = match_url[match_url.length - 1]; // the last captured parenthesis is the rest of the string
|
||||
url = match_url[1];
|
||||
if (url.charAt(0) == '"')
|
||||
url = url.substring(1, url.length - 1);
|
||||
res.url = url;
|
||||
}
|
||||
else
|
||||
return null; // no url, tag or title found but still text left, abort
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
//}}}
|
||||
} //}}}
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ function Command(specs, action, extra_info) //{{{
|
||||
this.help = extra_info.help || null;
|
||||
this.short_help = extra_info.short_help || null;
|
||||
this.completer = extra_info.completer || null;
|
||||
this.args = extra_info.args || [];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -127,6 +128,14 @@ function Commands() //{{{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// PRIVATE SECTION /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
const OPTION_ANY = 0; // can be given no argument or an argument of any type, user is responsible
|
||||
// for parsing the return value
|
||||
const OPTION_NOARG = 1;
|
||||
const OPTION_BOOL = 2;
|
||||
const OPTION_STRING = 3;
|
||||
const OPTION_INT = 4;
|
||||
const OPTION_FLOAT = 5;
|
||||
const OPTION_LIST = 6;
|
||||
|
||||
var ex_commands = [];
|
||||
var last_run_command = ""; // updated whenever the users runs a command with :!
|
||||
@@ -140,6 +149,243 @@ function Commands() //{{{
|
||||
}
|
||||
}
|
||||
|
||||
// in '-quoted strings, only ' and \ itself are escaped
|
||||
// in "-quoted strings, also ", \n and \t are translated
|
||||
//
|
||||
// "options" is an array [name, type, validator, completions] and could look like:
|
||||
// options = [[["-force"], OPTION_NOARG],
|
||||
// [["-fullscreen"], OPTION_BOOL],
|
||||
// [["-language"], OPTION_STRING, validateFunc, ["perl", "ruby"]],
|
||||
// [["-speed"], OPTION_INT],
|
||||
// [["-acceleration"], OPTION_FLOAT],
|
||||
// [["-accessories"], OPTION_LIST, null, ["foo", "bar"]],
|
||||
// [["-other"], OPTION_ANY]];
|
||||
// TODO: should it handle comments?
|
||||
// TODO: should it return an error, if it contains arguments which look like options (beginning with -)?
|
||||
function parseArgs(str, options)
|
||||
{
|
||||
// returns [count, parsed_argument]
|
||||
function getNextArg(str)
|
||||
{
|
||||
var in_single_string = false;
|
||||
var in_double_string = false;
|
||||
var in_escape_key = false;
|
||||
|
||||
var arg = "";
|
||||
|
||||
outer:
|
||||
for (var i = 0; i < str.length; i++)
|
||||
{
|
||||
switch(str[i])
|
||||
{
|
||||
case "\"":
|
||||
if (in_escape_key)
|
||||
{
|
||||
in_escape_key = false;
|
||||
break;
|
||||
}
|
||||
if (!in_single_string)
|
||||
{
|
||||
in_double_string = !in_double_string;
|
||||
continue outer;
|
||||
}
|
||||
break;
|
||||
|
||||
case "'":
|
||||
if (in_escape_key)
|
||||
{
|
||||
in_escape_key = false;
|
||||
break;
|
||||
}
|
||||
if (!in_double_string)
|
||||
{
|
||||
in_single_string = !in_single_string;
|
||||
continue outer;
|
||||
}
|
||||
break;
|
||||
|
||||
// \ is an escape key for non quoted or "-quoted strings
|
||||
// for '-quoted strings it is taken literally, apart from \' and \\
|
||||
case "\\":
|
||||
if (in_escape_key)
|
||||
{
|
||||
in_escape_key = false;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
in_escape_key = true;
|
||||
if (in_single_string && str[i+1] != "\\" && str[i+1] != "'")
|
||||
break;
|
||||
else
|
||||
continue outer;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (in_single_string)
|
||||
{
|
||||
in_escape_key = false;
|
||||
break;
|
||||
}
|
||||
else if (in_escape_key)
|
||||
{
|
||||
in_escape_key = false;
|
||||
switch (str[i])
|
||||
{
|
||||
case "n": arg += "\n"; continue outer;
|
||||
case "t": arg += "\t"; continue outer;
|
||||
default:
|
||||
break; // this makes "a\fb" -> afb; wanted or should we return ab? --mst
|
||||
}
|
||||
}
|
||||
else if (!in_double_string && /\s/.test(str[i]))
|
||||
{
|
||||
return [i, arg];
|
||||
}
|
||||
else // a normal charcter
|
||||
break;
|
||||
}
|
||||
arg += str[i];
|
||||
}
|
||||
|
||||
// TODO: add parsing of a " comment here:
|
||||
if (in_double_string || in_single_string)
|
||||
return [-1, "unterminated string literal"];
|
||||
if (in_escape_key)
|
||||
return [-1, "trailing \\"];
|
||||
else
|
||||
return [str.length, arg];
|
||||
}
|
||||
|
||||
|
||||
var args = []; // parsed arguments
|
||||
var opts = []; // parsed options
|
||||
if (!options)
|
||||
options = [];
|
||||
|
||||
var arg = null;
|
||||
var count = 0;
|
||||
var i = 0;
|
||||
outer:
|
||||
while(i < str.length)
|
||||
{
|
||||
// skip whitespace
|
||||
if (/\s/.test(str[i]))
|
||||
{
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var sub = str.substr(i);
|
||||
var optname = "";
|
||||
for (var opt = 0; opt < options.length; opt++)
|
||||
{
|
||||
for (var name = 0; name < options[opt][0].length; name++)
|
||||
{
|
||||
optname = options[opt][0][name];
|
||||
if (sub.indexOf(optname) == 0)
|
||||
{
|
||||
var invalid = false;
|
||||
// no value to the option
|
||||
if (optname.length >= sub.length || /\s/.test(sub[optname.length]))
|
||||
{
|
||||
arg = null;
|
||||
count = 0;
|
||||
}
|
||||
else if (sub[optname.length] == "=")
|
||||
{
|
||||
[count, arg] = getNextArg(sub.substr(optname.length + 1));
|
||||
if (count == -1)
|
||||
return { error: "Invalid argument for option " + optname, opts: [], args: [] }
|
||||
|
||||
count++; // to compensate the "=" character
|
||||
}
|
||||
else
|
||||
{
|
||||
// this isn't really an option as it has trailing characters, parse it as an argument
|
||||
invalid = true;
|
||||
}
|
||||
|
||||
if (!invalid)
|
||||
{
|
||||
switch (options[opt][1]) // type
|
||||
{
|
||||
case OPTION_NOARG:
|
||||
if (arg != null)
|
||||
return { error: "No argument allowed for option: " + optname, opts: [], args: [] }
|
||||
break;
|
||||
case OPTION_BOOL:
|
||||
if (arg == "true" || arg == "1" || arg == "on")
|
||||
arg = true;
|
||||
else if (arg == "false" || arg == "0" || arg == "off")
|
||||
arg = false;
|
||||
else
|
||||
return { error: "Invalid argument for boolean option: " + optname, opts: [], args: [] }
|
||||
break;
|
||||
case OPTION_STRING:
|
||||
if (arg == null)
|
||||
return { error: "Argument required for string option: " + optname, opts: [], args: [] }
|
||||
break;
|
||||
case OPTION_INT:
|
||||
arg = parseInt(arg);
|
||||
if (isNaN(arg))
|
||||
return { error: "Numeric argument required for integer option: " + optname, opts: [], args: [] }
|
||||
break;
|
||||
case OPTION_FLOAT:
|
||||
arg = parseFloat(arg);
|
||||
if (isNaN(arg))
|
||||
return { error: "Numeric argument required for float option: " + optname, opts: [], args: [] }
|
||||
break;
|
||||
case OPTION_LIST:
|
||||
if (arg == null)
|
||||
return { error: "Argument required for list option: " + optname, opts: [], args: [] }
|
||||
arg = arg.split(/\s*,\s*/);
|
||||
break;
|
||||
}
|
||||
|
||||
// we have a validator function
|
||||
if (typeof options[opt][2] == "function")
|
||||
{
|
||||
if (options[opt][2].call(this, arg) == false)
|
||||
return { error: "Invalid argument for option: " + optname, opts: [], args: [] }
|
||||
}
|
||||
|
||||
opts.push([options[opt][0][0], arg]); // always use the first name of the option
|
||||
i += optname.length + count;
|
||||
continue outer;
|
||||
}
|
||||
// if it is invalid, just fall through and try the next argument
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if not an option, treat this token as an argument
|
||||
var [count, arg] = getNextArg(sub);
|
||||
if (count == -1)
|
||||
return { error: "Error parsing arguments: " + arg, opts: [], args: [] }
|
||||
|
||||
if (arg != null)
|
||||
args.push(arg);
|
||||
|
||||
i += count; // hopefully count is always >0, otherwise we get an endless loop
|
||||
}
|
||||
|
||||
return { error: null, opts: opts, args: args }
|
||||
}
|
||||
|
||||
function getOption(opts, option, def)
|
||||
{
|
||||
for (var i = 0; i < opts.length; i++)
|
||||
{
|
||||
if (opts[i][0] == option)
|
||||
return opts[i][1];
|
||||
}
|
||||
|
||||
// no match found, return default
|
||||
return def;
|
||||
}
|
||||
|
||||
function commandsIterator()
|
||||
{
|
||||
for (var i = 0; i < ex_commands.length; i++)
|
||||
@@ -293,43 +539,39 @@ function Commands() //{{{
|
||||
}
|
||||
));
|
||||
addDefaultCommand(new Command(["bma[rk]"],
|
||||
// takes: -t "foo" myurl
|
||||
// converts that string to a useful url and title, and calls addBookmark
|
||||
function(args)
|
||||
{
|
||||
var result = Bookmarks.parseBookmarkString(args);
|
||||
|
||||
if (result)
|
||||
var res = parseArgs(args, this.args);
|
||||
if (res.error)
|
||||
{
|
||||
if (result.url == null)
|
||||
{
|
||||
result.url = vimperator.buffer.URL;
|
||||
// also guess title if the current url is :bmarked
|
||||
if (result.title == null)
|
||||
result.title = vimperator.buffer.title;
|
||||
vimperator.echoerr(res.error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result.title == null) // title could still be null
|
||||
result.title = result.url;
|
||||
var url = res.args.length == 0 ? vimperator.buffer.URL : res.args[0];
|
||||
var title = getOption(res.opts, "-title", res.args.length == 0 ? vimperator.buffer.title : null);
|
||||
if (!title)
|
||||
title = url;
|
||||
var keyword = getOption(res.opts, "-keyword", null);
|
||||
var tags = getOption(res.opts, "-tags", []);
|
||||
|
||||
vimperator.bookmarks.add(result.title, result.url);
|
||||
vimperator.echo("Bookmark `" + result.title + "' added with url `" + result.url + "'", vimperator.commandline.FORCE_SINGLELINE);
|
||||
}
|
||||
if (vimperator.bookmarks.add(title, url, keyword, tags))
|
||||
vimperator.echo("Bookmark `" + title + "' added with url `" + url + "'", vimperator.commandline.FORCE_SINGLELINE);
|
||||
else
|
||||
{
|
||||
vimperator.echoerr("E474: Invalid argument");
|
||||
}
|
||||
vimperator.echoerr("Exxx: Could not add bookmark `" + title + "'", vimperator.commandline.FORCE_SINGLELINE);
|
||||
},
|
||||
{
|
||||
usage: ["bma[rk] [-t {title}] [url]"],
|
||||
usage: ["bma[rk] [-title=title] [-keyword=kw] [-tags=tag1,tag2] [url]"],
|
||||
short_help: "Add a bookmark",
|
||||
help: "If you don't add a custom title, either the title of the web page or the URL will be taken as the title.<br/>" +
|
||||
"You can omit the optional <code class=\"argument\">[url]</code> argument, so just do <code class=\"command\">:bmark</code> to bookmark the currently loaded web page with a default title and without any tags.<br/>" +
|
||||
" -t \"custom title\"<br/>" +
|
||||
"The following options will be interpreted in the future:<br/>" +
|
||||
" -T comma,separated,tag,list<br/>" +
|
||||
" -k keyword<br/>" +
|
||||
"Tags WILL be some mechanism to classify bookmarks. Assume, you tag a URL with the tags \"linux\" and \"computer\" you'll be able to search for bookmarks containing these tags."
|
||||
"The following options are interpreted:<br/>" +
|
||||
" -title=\"custom title\"<br/>" +
|
||||
" -tags=comma,separated,tag,list<br/>" +
|
||||
" -keyword=keyword<br/>",
|
||||
args: [[["-title", "-t"], OPTION_STRING],
|
||||
[["-tags", "-T"], OPTION_LIST],
|
||||
[["-keyword", "-k"], OPTION_STRING, function(arg) { return /\w/.test(arg); } ]]
|
||||
}
|
||||
));
|
||||
addDefaultCommand(new Command(["bmarks"],
|
||||
@@ -405,25 +647,20 @@ function Commands() //{{{
|
||||
addDefaultCommand(new Command(["delbm[arks]"],
|
||||
function(args, special)
|
||||
{
|
||||
var result = Bookmarks.parseBookmarkString(args);
|
||||
var url = args;
|
||||
|
||||
if (result)
|
||||
{
|
||||
if (result.url == null)
|
||||
result.url = vimperator.buffer.URL;
|
||||
if (!url)
|
||||
url = vimperator.buffer.URL;
|
||||
|
||||
var deleted_count = vimperator.bookmarks.remove(result.url);
|
||||
vimperator.echo(deleted_count + " bookmark(s) with url `" + result.url + "' deleted", vimperator.commandline.FORCE_SINGLELINE);
|
||||
}
|
||||
else
|
||||
{
|
||||
vimperator.echoerr("E488: Trailing characters");
|
||||
}
|
||||
var deleted_count = vimperator.bookmarks.remove(url);
|
||||
vimperator.echo(deleted_count + " bookmark(s) with url `" + url + "' deleted", vimperator.commandline.FORCE_SINGLELINE);
|
||||
},
|
||||
{
|
||||
usage: ["delbm[arks] {url}"],
|
||||
usage: ["delbm[arks] [url]"],
|
||||
short_help: "Delete a bookmark",
|
||||
help: "Deletes <b>all</b> bookmarks which match the <code class=\"argument\">{url}</code>. Use <code><Tab></code> key on a string to complete the URL which you want to delete.<br/>" +
|
||||
help: "Deletes <b>all</b> bookmarks which match the <code class=\"argument\">[url]</code>. " +
|
||||
"If ommited, <code class=\"argument\">[url]</code> defaults to the URL of the current buffer. " +
|
||||
"Use <code><Tab></code> key on a string to complete the URL which you want to delete.<br/>" +
|
||||
"The following options WILL be interpreted in the future:<br/>" +
|
||||
" [!] a special version to delete ALL bookmarks <br/>" +
|
||||
" -T comma,separated,tag,list <br/>",
|
||||
@@ -433,11 +670,21 @@ function Commands() //{{{
|
||||
addDefaultCommand(new Command(["com[mand]"],
|
||||
function(args)
|
||||
{
|
||||
vimperator.echo(vimperator.util.colorize(window.argParser(args)));
|
||||
var res = parseArgs(args, this.args);
|
||||
if (res.error)
|
||||
vimperator.echoerr(res.error);
|
||||
else
|
||||
{
|
||||
vimperator.echo(vimperator.util.colorize(res));
|
||||
}
|
||||
},
|
||||
{
|
||||
usage: ["com[mand][!] [{attr}...] {cmd} {rep}"],
|
||||
short_help: "Temporarily used for testing args parser",
|
||||
help: ""
|
||||
help: "",
|
||||
args: [[["-nargs"], OPTION_STRING, function(arg) { return /^(0|1|\*|\?|\+)$/.test(arg); } ],
|
||||
[["-bang"], OPTION_NOARG],
|
||||
[["-bar"], OPTION_NOARG]]
|
||||
}
|
||||
));
|
||||
addDefaultCommand(new Command(["delm[arks]"],
|
||||
|
||||
@@ -733,7 +733,6 @@ function Events() //{{{
|
||||
else
|
||||
{
|
||||
vimperator.input.buffer = "";
|
||||
// vimperator.log("executed: " + candidate_command + " in mode: " + mode, 8);
|
||||
map.execute(null, vimperator.input.count);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user