diff --git a/frontends/bringrss_flask/static/js/api.js b/frontends/bringrss_flask/static/js/api.js
index a520dd2..07ea2c2 100644
--- a/frontends/bringrss_flask/static/js/api.js
+++ b/frontends/bringrss_flask/static/js/api.js
@@ -6,129 +6,161 @@ api.feeds = {};
api.feeds.add_feed =
function add_feed(rss_url, title, isolate_guids, callback)
{
- const url = "/feeds/add";
- const data = {"rss_url": rss_url, "title": title, "isolate_guids": isolate_guids};
- return common.post(url, data, callback);
+ return http.post({
+ url: "/feeds/add",
+ data: {"rss_url": rss_url, "title": title, "isolate_guids": isolate_guids},
+ callback: callback,
+ });
}
api.feeds.delete =
function delete_feed(feed_id, callback)
{
- const url = `/feed/${feed_id}/delete`;
- return common.post(url, null, callback);
+ return http.post({
+ url: `/feed/${feed_id}/delete`,
+ callback: callback,
+ });
}
api.feeds.get_feeds =
function get_feeds(callback)
{
- const url = "/feeds.json";
- return common.get(url, callback);
+ return http.get({
+ url: "/feeds.json",
+ callback: callback,
+ });
}
api.feeds.refresh =
function refresh(feed_id, callback)
{
- const url = `/feed/${feed_id}/refresh`;
- return common.post(url, null, callback);
+ return http.post({
+ url: `/feed/${feed_id}/refresh`,
+ callback: callback,
+ });
}
api.feeds.refresh_all =
function refresh_all(callback)
{
- const url = "/feeds/refresh_all";
- return common.post(url, null, callback);
+ return http.post({
+ url: "/feeds/refresh_all",
+ callback: callback,
+ });
}
api.feeds.set_autorefresh_interval =
function set_autorefresh_interval(feed_id, interval, callback)
{
- const url = `/feed/${feed_id}/set_autorefresh_interval`;
- const data = {"autorefresh_interval": interval};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/feed/${feed_id}/set_autorefresh_interval`,
+ data: {"autorefresh_interval": interval},
+ callback: callback,
+ });
}
api.feeds.set_filters =
function set_filters(feed_id, filter_ids, callback)
{
- const url = `/feed/${feed_id}/set_filters`;
- const data = {"filter_ids": filter_ids.join(",")};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/feed/${feed_id}/set_filters`,
+ data: {"filter_ids": filter_ids.join(",")},
+ callback: callback,
+ });
}
api.feeds.set_http_headers =
function set_http_headers(feed_id, http_headers, callback)
{
- const url = `/feed/${feed_id}/set_http_headers`;
- const data = {"http_headers": http_headers};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/feed/${feed_id}/set_http_headers`,
+ data: {"http_headers": http_headers},
+ callback: callback,
+ });
}
api.feeds.set_icon =
function set_icon(feed_id, image_base64, callback)
{
- const url = `/feed/${feed_id}/set_icon`;
- const data = {"image_base64": image_base64};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/feed/${feed_id}/set_icon`,
+ data: {"image_base64": image_base64},
+ callback: callback,
+ });
}
api.feeds.set_isolate_guids =
function set_isolate_guids(feed_id, isolate_guids, callback)
{
- const url = `/feed/${feed_id}/set_isolate_guids`;
- const data = {"isolate_guids": isolate_guids};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/feed/${feed_id}/set_isolate_guids`,
+ data: {"isolate_guids": isolate_guids},
+ callback: callback,
+ });
}
api.feeds.set_parent =
function set_parent(feed_id, parent_id, ui_order_rank, callback)
{
- const url = `/feed/${feed_id}/set_parent`;
- const data = {"parent_id": parent_id};
if (ui_order_rank !== null)
{
data["ui_order_rank"] = ui_order_rank;
}
- return common.post(url, data, callback);
+ return http.post({
+ url: `/feed/${feed_id}/set_parent`,
+ data: {"parent_id": parent_id},
+ callback: callback,
+ });
}
api.feeds.set_refresh_with_others =
function set_refresh_with_others(feed_id, refresh_with_others, callback)
{
- const url = `/feed/${feed_id}/set_refresh_with_others`;
- const data = {"refresh_with_others": refresh_with_others};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/feed/${feed_id}/set_refresh_with_others`,
+ data: {"refresh_with_others": refresh_with_others},
+ callback: callback,
+ });
}
api.feeds.set_rss_url =
function set_rss_url(feed_id, rss_url, callback)
{
- const url = `/feed/${feed_id}/set_rss_url`;
- const data = {"rss_url": rss_url};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/feed/${feed_id}/set_rss_url`,
+ data: {"rss_url": rss_url},
+ callback: callback,
+ });
}
api.feeds.set_web_url =
function set_web_url(feed_id, web_url, callback)
{
- const url = `/feed/${feed_id}/set_web_url`;
- const data = {"web_url": web_url};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/feed/${feed_id}/set_web_url`,
+ data: {"web_url": web_url},
+ callback: callback,
+ });
}
api.feeds.set_title =
function set_title(feed_id, title, callback)
{
- const url = `/feed/${feed_id}/set_title`;
- const data = {"title": title};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/feed/${feed_id}/set_title`,
+ data: {"title": title},
+ callback: callback,
+ });
}
api.feeds.set_ui_order_rank =
function set_ui_order_rank(feed_id, ui_order_rank, callback)
{
- const url = `/feed/${feed_id}/set_ui_order_rank`;
- const data = {"ui_order_rank": ui_order_rank};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/feed/${feed_id}/set_ui_order_rank`,
+ data: {"ui_order_rank": ui_order_rank},
+ callback: callback,
+ });
}
/**************************************************************************************************/
@@ -137,67 +169,84 @@ api.filters = {};
api.filters.add_filter =
function add_filter(name, conditions, actions, callback)
{
- const url = "/filters/add";
- const data = {"name": name, "conditions": conditions, "actions": actions};
- return common.post(url, data, callback);
+ return http.post({
+ url: "/filters/add",
+ data: {"name": name, "conditions": conditions, "actions": actions},
+ callback: callback,
+ });
}
api.filters.delete_filter =
function delete_filter(filter_id, callback)
{
- const url = `/filter/${filter_id}/delete`;
- return common.post(url, null, callback);
+ return http.post({
+ url: `/filter/${filter_id}/delete`,
+ callback: callback,
+ });
}
api.filters.get_filters =
function get_filters(callback)
{
- const url = "/filters.json";
- return common.get(url, callback);
+ return http.get({
+ url: "/filters.json",
+ callback: callback,
+ });
}
api.filters.run_filter_now =
function run_filter_now(filter_id, feed_id, callback)
{
- const url = `/filter/${filter_id}/run_filter`;
const data = {};
if (feed_id !== null)
{
data['feed_id'] = feed_id;
}
- return common.post(url, data, callback);
+ return http.post({
+ url: `/filter/${filter_id}/run_filter`,
+ data: data,
+ callback: callback,
+ });
}
api.filters.set_actions =
function set_actions(filter_id, actions, callback)
{
- const url = `/filter/${filter_id}/set_actions`;
- const data = {"actions": actions};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/filter/${filter_id}/set_actions`,
+ data: {"actions": actions},
+ callback: callback,
+ });
}
api.filters.set_conditions =
function set_conditions(filter_id, conditions, callback)
{
- const url = `/filter/${filter_id}/set_conditions`;
- const data = {"conditions": conditions};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/filter/${filter_id}/set_conditions`,
+ data: {"conditions": conditions},
+ callback: callback,
+ });
}
api.filters.set_name =
function set_name(filter_id, name, callback)
{
- const url = `/filter/${filter_id}/set_name`;
- const data = {"name": name};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/filter/${filter_id}/set_name`,
+ data: {"name": name},
+ callback: callback,
+ });
}
api.filters.update_filter =
function update_filter(filter_id, name, conditions, actions, callback)
{
- const url = `/filter/${filter_id}/update`;
- const data = {"name": name, "conditions": conditions, "actions": actions};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/filter/${filter_id}/update`,
+ data: {"name": name, "conditions": conditions, "actions": actions},
+ callback: callback,
+ });
}
/**************************************************************************************************/
@@ -206,9 +255,11 @@ api.news = {};
api.news.get_and_set_read =
function get_and_set_read(news_id, callback)
{
- const url = `/news/${news_id}.json`;
- const data = {"set_read": true};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/news/${news_id}.json`,
+ data: {"set_read": true},
+ callback: callback,
+ });
}
api.news.get_newss =
@@ -230,38 +281,49 @@ function get_newss(feed_id, read, recycled, callback)
}
let url = (feed_id === null) ? "/news.json" : `/feed/${feed_id}/news.json`;
url += parameters;
- return common.get(url, callback);
+ return http.get({
+ url: url,
+ callback: callback, callback,
+ });
}
api.news.set_read =
function set_read(news_id, read, callback)
{
- const url = `/news/${news_id}/set_read`;
- const data = {"read": read};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/news/${news_id}/set_read`,
+ data: {"read": read},
+ callback: callback,
+ });
}
api.news.set_recycled =
function set_recycled(news_id, recycled, callback)
{
- const url = `/news/${news_id}/set_recycled`;
- const data = {"recycled": recycled};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/news/${news_id}/set_recycled`,
+ data: {"recycled": recycled},
+ callback: callback,
+ });
}
api.news.batch_set_read =
function batch_set_read(news_ids, read, callback)
{
- const url = `/batch/news/set_read`;
- const data = {"news_ids": news_ids.join(","), "read": read};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/batch/news/set_read`,
+ data: {"news_ids": news_ids.join(","), "read": read},
+ callback: callback,
+ });
}
api.news.batch_set_recycled =
function batch_set_recycled(news_ids, recycled, callback)
{
- const url = `/batch/news/set_recycled`;
- const data = {"news_ids": news_ids.join(","), "recycled": recycled};
- return common.post(url, data, callback);
+ return http.post({
+ url: `/batch/news/set_recycled`,
+ data: {"news_ids": news_ids.join(","), "recycled": recycled},
+ callback: callback,
+ });
}
diff --git a/frontends/bringrss_flask/static/js/common.js b/frontends/bringrss_flask/static/js/common.js
index 3dbaf83..20b1c90 100644
--- a/frontends/bringrss_flask/static/js/common.js
+++ b/frontends/bringrss_flask/static/js/common.js
@@ -34,6 +34,12 @@ function is_wide_mode()
return getComputedStyle(document.documentElement).getPropertyValue("--wide").trim() === "1";
}
+common.go_to_root =
+function go_to_root()
+{
+ window.location.href = "/";
+}
+
common.refresh =
function refresh()
{
@@ -51,146 +57,18 @@ function refresh_or_alert(response)
window.location.reload();
}
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// HTTP ////////////////////////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////////////////////////////////
-
-common.formdata =
-function formdata(data)
-{
- fd = new FormData();
- for (let [key, value] of Object.entries(data))
- {
- if (value === undefined)
- {
- continue;
- }
- if (value === null)
- {
- value = '';
- }
- fd.append(key, value);
- }
- return fd;
-}
-
-common._request =
-function _request(method, url, callback)
-{
- /*
- Perform an HTTP request and call the `callback` with the response.
-
- The response will have the following structure:
- {
- "meta": {
- "completed": true / false,
- "status": If the connection failed or request otherwise could not
- complete, `status` will be 0. If the request completed,
- `status` will be the HTTP response code.
- "json_ok": If the server responded with parseable json, `json_ok`
- will be true, and that data will be in `response.data`. If the
- server response was not parseable json, `json_ok` will be false
- and `response.data` will be undefined.
- "request_url": The URL exactly as given to this call.
- }
- "data": {JSON parsed from server response if json_ok}.
- }
-
- So, from most lenient to most strict, error catching might look like:
- if response.meta.completed
- if response.meta.json_ok
- if response.meta.status === 200
- if response.meta.status === 200 and response.meta.json_ok
- */
- const request = new XMLHttpRequest();
- const response = {
- "meta": {
- "completed": false,
- "status": 0,
- "json_ok": false,
- "request_url": url,
- },
- };
-
- request.onreadystatechange = function()
- {
- /*
- readystate values:
- 0 UNSENT / ABORTED
- 1 OPENED
- 2 HEADERS_RECEIVED
- 3 LOADING
- 4 DONE
- */
- if (request.readyState != 4)
- {return;}
-
- if (callback == null)
- {return;}
-
- response.meta.status = request.status;
-
- if (request.status != 0)
- {
- response.meta.completed = true;
- try
- {
- response.data = JSON.parse(request.responseText);
- response.meta.json_ok = true;
- }
- catch (exc)
- {
- response.meta.json_ok = false;
- }
- }
- callback(response);
- };
- const asynchronous = true;
- request.open(method, url, asynchronous);
- return request;
-}
-
-common.get =
-function get(url, callback)
-{
- request = common._request("GET", url, callback);
- request.send();
- return request;
-}
-
-common.post =
-function post(url, data, callback)
-{
- /*
- `data`:
- a FormData object which you have already filled with values, or a
- dictionary from which a FormData will be made, using common.formdata.
- */
- if (data instanceof FormData || data === null)
- {
- ;
- }
- else
- {
- data = common.formdata(data);
- }
- request = common._request("POST", url, callback);
- request.send(data);
- return request;
-}
-
////////////////////////////////////////////////////////////////////////////////////////////////////
// STRING TOOLS ////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
common.join_and_trail =
-function join_and_trail(l, s)
+function join_and_trail(list, separator)
{
- if (l.length === 0)
+ if (list.length === 0)
{
return "";
}
- return l.join(s) + s
+ return list.join(separator) + separator
}
////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/frontends/bringrss_flask/static/js/http.js b/frontends/bringrss_flask/static/js/http.js
new file mode 100644
index 0000000..42547cf
--- /dev/null
+++ b/frontends/bringrss_flask/static/js/http.js
@@ -0,0 +1,210 @@
+const http = {};
+
+http.HEADERS = {};
+
+http.requests_in_flight = 0;
+
+http.request_queue = {};
+http.request_queue.array = [];
+
+http.request_queue.push =
+function request_queue_push(func)
+{
+ http.request_queue.array.push(func)
+ if (http.requests_in_flight == 0)
+ {
+ http.request_queue_next();
+ }
+}
+
+http.request_queue.unshift =
+function request_queue_unshift(func)
+{
+ http.request_queue.array.unshift(func)
+ if (http.requests_in_flight == 0)
+ {
+ http.request_queue_next();
+ }
+}
+http.request_queue.pushleft = http.request_queue.unshift;
+
+http.request_queue_next =
+function request_queue_next()
+{
+ if (http.requests_in_flight > 0)
+ {
+ return;
+ }
+ if (http.request_queue.array.length === 0)
+ {
+ return;
+ }
+ const func = http.request_queue.array.shift();
+ func();
+}
+
+http.formdata =
+function formdata(data)
+{
+ const fd = new FormData();
+ for (let [key, value] of Object.entries(data))
+ {
+ if (value === undefined)
+ {
+ continue;
+ }
+ if (value === null)
+ {
+ value = '';
+ }
+ fd.append(key, value);
+ }
+ return fd;
+}
+
+http._request =
+function _request(kwargs)
+{
+ /*
+ Perform an HTTP request and call the `callback` with the response.
+
+ The response will have the following structure:
+ {
+ "meta": {
+ "request": the XMLHttpRequest object,
+ "completed": true / false,
+ "status": If the connection failed or request otherwise could not
+ complete, `status` will be 0. If the request completed,
+ `status` will be the HTTP response code.
+ "json_ok": If the server responded with parseable json, `json_ok`
+ will be true, and that data will be in `response.data`. If the
+ server response was not parseable json, `json_ok` will be false
+ and `response.data` will be undefined.
+ "kwargs": The kwargs exactly as given to this call.
+ }
+ "data": {JSON parsed from server response if json_ok},
+ "retry": function you can call to retry the request.
+ }
+
+ So, from most lenient to most strict, error catching might look like:
+ if response.meta.completed
+ if response.meta.json_ok
+ if response.meta.status === 200
+ if response.meta.status === 200 and response.meta.json_ok
+ */
+ const request = new XMLHttpRequest();
+ const response = {
+ "meta": {
+ "request": request,
+ "completed": false,
+ "status": 0,
+ "json_ok": false,
+ "kwargs": kwargs,
+ },
+ "retry": function(){http._request(kwargs)},
+ };
+
+ request.onreadystatechange = function()
+ {
+ /*
+ readystate values:
+ 0 UNSENT / ABORTED
+ 1 OPENED
+ 2 HEADERS_RECEIVED
+ 3 LOADING
+ 4 DONE
+ */
+ if (request.readyState != 4)
+ {
+ return;
+ }
+
+ http.requests_in_flight -= 1;
+ setTimeout(http.request_queue_next, 0);
+
+ if (kwargs["callback"] == null)
+ {
+ return;
+ }
+
+ response.meta.status = request.status;
+
+ if (request.status != 0)
+ {
+ response.meta.completed = true;
+ try
+ {
+ response.data = JSON.parse(request.responseText);
+ response.meta.json_ok = true;
+ }
+ catch (exc)
+ {
+ response.meta.json_ok = false;
+ }
+ }
+ kwargs["callback"](response);
+ };
+
+ // Headers
+
+ const asynchronous = "asynchronous" in kwargs ? kwargs["asynchronous"] : true;
+ request.open(kwargs["method"], kwargs["url"], asynchronous);
+
+ for (const [header, value] of Object.entries(http.HEADERS))
+ {
+ request.setRequestHeader(header, value);
+ }
+
+ const more_headers = kwargs["headers"] || {};
+ for (const [header, value] of Object.entries(more_headers))
+ {
+ request.setRequestHeader(header, value);
+ }
+
+ if (kwargs["with_credentials"])
+ {
+ request.withCredentials = true;
+ }
+
+ // Send
+
+ let data = kwargs["data"];
+ if (data === undefined || data === null)
+ {
+ request.send();
+ }
+ else if (data instanceof FormData)
+ {
+ request.send(data);
+ }
+ else if (typeof(data) === "string" || data instanceof String)
+ {
+ request.send(data);
+ }
+ else
+ {
+ request.send(http.formdata(data));
+ }
+ http.requests_in_flight += 1;
+
+ return request;
+}
+
+http.get =
+function get(kwargs)
+{
+ kwargs["method"] = "GET";
+ return http._request(kwargs);
+}
+
+http.post =
+function post(kwargs)
+{
+ /*
+ `data`:
+ a FormData object which you have already filled with values, or a
+ dictionary from which a FormData will be made, using http.formdata.
+ */
+ kwargs["method"] = "POST";
+ return http._request(kwargs);
+}
diff --git a/frontends/bringrss_flask/templates/feed_settings.html b/frontends/bringrss_flask/templates/feed_settings.html
index cab38df..a0c10ad 100644
--- a/frontends/bringrss_flask/templates/feed_settings.html
+++ b/frontends/bringrss_flask/templates/feed_settings.html
@@ -11,6 +11,7 @@
{% if theme %}{% endif %}
+