Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
Tim Van Baak | 40464e9078 | |
Tim Van Baak | 29740d5864 | |
Tim Van Baak | 926a67a05e | |
Tim Van Baak | 8828a4abef | |
Tim Van Baak | b7e83c5059 | |
Tim Van Baak | b480d6edfd | |
Tim Van Baak | 8fd6f3b751 |
|
@ -38,16 +38,16 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1685566663,
|
"lastModified": 1717179513,
|
||||||
"narHash": "sha256-btHN1czJ6rzteeCuE/PNrdssqYD2nIA4w48miQAFloM=",
|
"narHash": "sha256-vboIEwIQojofItm2xGCdZCzW96U85l9nDW3ifMuAIdM=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "4ecab3273592f27479a583fb6d975d4aba3486fe",
|
"rev": "63dacb46bf939521bdc93981b4cbb7ecb58427a0",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "23.05",
|
"ref": "24.05",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
|
12
flake.nix
12
flake.nix
|
@ -2,7 +2,7 @@
|
||||||
description = "A personal feed aggregator";
|
description = "A personal feed aggregator";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/23.05";
|
nixpkgs.url = "github:NixOS/nixpkgs/24.05";
|
||||||
# Included to support default.nix and shell.nix
|
# Included to support default.nix and shell.nix
|
||||||
flake-compat = {
|
flake-compat = {
|
||||||
url = "github:edolstra/flake-compat";
|
url = "github:edolstra/flake-compat";
|
||||||
|
@ -43,16 +43,6 @@
|
||||||
PS1="(develop) $PS1"
|
PS1="(develop) $PS1"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
client = let
|
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
|
||||||
in pkgs.mkShell {
|
|
||||||
packages = [
|
|
||||||
pkgs.nodejs_18
|
|
||||||
];
|
|
||||||
shellHook = ''
|
|
||||||
PS1="(client) $PS1"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
overlays.default = final: prev: {
|
overlays.default = final: prev: {
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,28 +0,0 @@
|
||||||
{
|
|
||||||
"name": "intake-client",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-scripts": "^5.0.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {},
|
|
||||||
"scripts": {
|
|
||||||
"start": "react-scripts start",
|
|
||||||
"build": "react-scripts build"
|
|
||||||
},
|
|
||||||
"proxy": "http://localhost:5000",
|
|
||||||
"browserslist": {
|
|
||||||
"production": [
|
|
||||||
">0.2%",
|
|
||||||
"not dead",
|
|
||||||
"not op_mini all"
|
|
||||||
],
|
|
||||||
"development": [
|
|
||||||
"last 1 chrome version",
|
|
||||||
"last 1 firefox version",
|
|
||||||
"last 1 safari version"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QAzAAAAADVpr6lAAAAOElEQVQ4y2NgoBAwMjAwMNQzMPwnR3MjAwMjC4zjS6LmzVCaBV2A7mDgw4CJUi8MvAGj0TgYwgAADKMLO3k0eaQAAAAASUVORK5CYII=">
|
|
||||||
<style>
|
|
||||||
main {
|
|
||||||
max-width: 700px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
article {
|
|
||||||
border: 1px solid black; border-radius: 6px;
|
|
||||||
padding: 5px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
.item-title {
|
|
||||||
font-size: 1.4em;
|
|
||||||
}
|
|
||||||
.item-button {
|
|
||||||
font-size: 1em;
|
|
||||||
float:right;
|
|
||||||
margin-left: 2px;
|
|
||||||
}
|
|
||||||
.item-link {
|
|
||||||
text-decoration: none;
|
|
||||||
float:right;
|
|
||||||
font-size: 1em;
|
|
||||||
padding: 2px 7px;
|
|
||||||
border: 1px solid;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
.item-info {
|
|
||||||
color: rgba(0, 0, 0, 0.7);
|
|
||||||
}
|
|
||||||
article img {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
button, summary {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
summary {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
summary:focus {
|
|
||||||
outline: 1px dotted gray;
|
|
||||||
}
|
|
||||||
.strikethru span, .strikethru p {
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
|
||||||
.fade span, .fade p {
|
|
||||||
color: rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
table.feed-control td {
|
|
||||||
font-family: monospace; padding: 5px 10px;
|
|
||||||
}
|
|
||||||
article.center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=checkbox]{
|
|
||||||
height: 0;
|
|
||||||
width: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
cursor: pointer;
|
|
||||||
width: 3.8em;
|
|
||||||
padding-inline: 1em 0;
|
|
||||||
height: 1.2em;
|
|
||||||
background: grey;
|
|
||||||
display: inline-block;
|
|
||||||
border-radius: 0.6em;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
label:after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0.1em;
|
|
||||||
left: 0.1em;
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 0.5em;
|
|
||||||
transition: 0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:checked + label {
|
|
||||||
background: #cc0000;
|
|
||||||
padding-inline: 0 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:checked + label:after {
|
|
||||||
left: calc(100% - 0.1em);
|
|
||||||
transform: translateX(-100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
label:active:after {
|
|
||||||
width: 1.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<div id="root"></div>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,144 +0,0 @@
|
||||||
import { StrictMode, useState, useEffect } from 'react';
|
|
||||||
import ReactDOM from 'react-dom/client';
|
|
||||||
|
|
||||||
function Item({ item }) {
|
|
||||||
|
|
||||||
function deactivate(source, itemid) {
|
|
||||||
console.log(`deactivate(${source}, ${itemid})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function punt(source, itemid) {
|
|
||||||
console.log(`punt(${source}, ${itemid})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function doAction(source, itemid, action) {
|
|
||||||
console.log(`doAction(${source}, ${itemid}, ${action})`);
|
|
||||||
};
|
|
||||||
|
|
||||||
let classNames = !item.hidden ? "" : item.active ? "fade" : "strikethru fade";
|
|
||||||
return (
|
|
||||||
<article className={classNames} id={`${item.source}-${item.id}`}>
|
|
||||||
<button
|
|
||||||
className="item-button"
|
|
||||||
title="Deactivate"
|
|
||||||
onClick={() => deactivate(item.source, item.id)}>✕</button>
|
|
||||||
<button
|
|
||||||
className="item-button"
|
|
||||||
title="Punt to tomorrow"
|
|
||||||
onClick={() => punt(item.source, item.id)}>↷</button>
|
|
||||||
{item.link && <a className="item-link" href="#" target="_blank">⇗</a>}
|
|
||||||
{/* The item title is a clickable <details> if there is body content */}
|
|
||||||
{(item.body || item.actions) ? (
|
|
||||||
<details open>
|
|
||||||
<summary><span className="item-title">{item.title || item.id}</span></summary>
|
|
||||||
{item.body && (
|
|
||||||
<p dangerouslySetInnerHTML={{ __html: item.body }}></p>
|
|
||||||
)}
|
|
||||||
{item.actions && item.actions.map((action) => {
|
|
||||||
return (
|
|
||||||
<p key={`${item.source}-${item.id}-action-${action}`}>
|
|
||||||
<button onClick={() => doAction(item.source, item.id, action)}>
|
|
||||||
{action}
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</details>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<span className="item-title">{item.title || item.id}</span>
|
|
||||||
<br/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{/* Author/time footer */}
|
|
||||||
{(item.author || item.time) && (
|
|
||||||
<>
|
|
||||||
<span className="item-info">{item.author} {item.time}</span><br/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{/* Source/id/created footer */}
|
|
||||||
{(item.source || item.id || item.created) && (
|
|
||||||
<span className="item-info" title="Tags: TODO">
|
|
||||||
{item.source} {item.id} {item.created}
|
|
||||||
{item.ttl && "T"} {item.ttd && "D"} {item.tts && "S"}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</article>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
const [items, setItems] = useState([]);
|
|
||||||
const [showHidden, setShowHidden] = useState(false);
|
|
||||||
|
|
||||||
// Initial load
|
|
||||||
useEffect(() => {
|
|
||||||
fetch(`http://localhost:5000/api/v1/items?hidden=${showHidden}`)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(newItems => setItems(newItems.items))
|
|
||||||
.catch(error => console.log(error));
|
|
||||||
}, [showHidden]);
|
|
||||||
|
|
||||||
// Button actions
|
|
||||||
function bulkDeactivate(items) {
|
|
||||||
if (confirm(`Deactivate ${items.length} items?`)) {
|
|
||||||
console.log(`bulkDeactivate(${items})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main>
|
|
||||||
<article className="center">
|
|
||||||
<span className="item-title">
|
|
||||||
<a href="{{url_for('root')}}">Home</a>
|
|
||||||
<input
|
|
||||||
id="showHidden"
|
|
||||||
type="checkbox"
|
|
||||||
checked={showHidden}
|
|
||||||
onChange={(e) => setShowHidden(e.target.checked) }
|
|
||||||
className="toggle-checkbox"/>
|
|
||||||
<label htmlFor="showHidden">{showHidden ? "All" : "Active"}</label>
|
|
||||||
{/* {% if item_count > items|length -%} */}
|
|
||||||
{/* [<a {% if page_num is greaterthan(0) -%} href="{{ set_query(page=page_num - 1) }}" {%- endif %}>Prev</a> */}
|
|
||||||
{/* | */}
|
|
||||||
{/* <a {% if ((page_num + 1) * page_count) is lessthan(item_count) -%} href="{{ set_query(page=page_num + 1) }}" {%- endif %}>Next</a>] */}
|
|
||||||
{/* {%- endif %} */}
|
|
||||||
</span>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
|
|
||||||
{items.map((item) => {
|
|
||||||
return <Item item={item} key={item.id}/>;
|
|
||||||
})}
|
|
||||||
|
|
||||||
{items.length == 0 && (
|
|
||||||
<article className="center">
|
|
||||||
<span className="item-title">Feed is empty</span>
|
|
||||||
</article>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* {% if item_count > items|length %} */}
|
|
||||||
<article className="center">
|
|
||||||
<span className="item-title">
|
|
||||||
{/* <a {% if page_num is greaterthan(0) -%} href="{{ set_query(page=page_num - 1) }}" {%- endif %}>Prev</a> */}
|
|
||||||
|
|
|
||||||
{/* <a {% if ((page_num + 1) * page_count) is lessthan(item_count) -%} href="{{ set_query(page=page_num + 1) }}" {%- endif %}>Next</a> */}
|
|
||||||
</span>
|
|
||||||
</article>
|
|
||||||
{/* {% endif %} */}
|
|
||||||
|
|
||||||
<article className="center">
|
|
||||||
<button onClick={() => bulkDeactivate(items)}>Deactivate All</button>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
|
||||||
root.render(
|
|
||||||
<StrictMode>
|
|
||||||
<App />
|
|
||||||
</StrictMode>
|
|
||||||
);
|
|
119
intake/app.py
119
intake/app.py
|
@ -253,7 +253,7 @@ def action(source_name, item_id, action):
|
||||||
data_path: Path = current_app.config["INTAKE_DATA"]
|
data_path: Path = current_app.config["INTAKE_DATA"]
|
||||||
source = LocalSource(data_path, source_name)
|
source = LocalSource(data_path, source_name)
|
||||||
item = execute_action(source, item_id, action)
|
item = execute_action(source, item_id, action)
|
||||||
return jsonify(item)
|
return jsonify(item._item)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/edit/source/<string:name>", methods=["GET", "POST"])
|
@app.route("/edit/source/<string:name>", methods=["GET", "POST"])
|
||||||
|
@ -415,120 +415,3 @@ def _get_ttx_for_date(dt: datetime) -> int:
|
||||||
def wsgi():
|
def wsgi():
|
||||||
app.config["INTAKE_DATA"] = intake_data_dir()
|
app.config["INTAKE_DATA"] = intake_data_dir()
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Experimental API endpoints for the React frontend
|
|
||||||
#
|
|
||||||
|
|
||||||
|
|
||||||
def to_api_model(item: Item) -> dict:
|
|
||||||
"""
|
|
||||||
Convert an item to a JSON representation for the frontend.
|
|
||||||
"""
|
|
||||||
result = {
|
|
||||||
"source": item.source.source_name,
|
|
||||||
"id": item["id"],
|
|
||||||
"created": item["created"],
|
|
||||||
"active": item["active"],
|
|
||||||
}
|
|
||||||
for unchanged_field in (
|
|
||||||
"title",
|
|
||||||
"author",
|
|
||||||
"body",
|
|
||||||
"link",
|
|
||||||
"time",
|
|
||||||
"tags",
|
|
||||||
"tts",
|
|
||||||
"ttl",
|
|
||||||
"ttd",
|
|
||||||
):
|
|
||||||
if val := item.get(unchanged_field):
|
|
||||||
result[unchanged_field] = val
|
|
||||||
if actions := item.get("action"):
|
|
||||||
result["actions"] = list(actions.keys())
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/v1/items")
|
|
||||||
# @auth_check
|
|
||||||
def items():
|
|
||||||
"""
|
|
||||||
Get multiple items according to a filter.
|
|
||||||
Supported filters:
|
|
||||||
- &channel=<channel>
|
|
||||||
- &source=<source>
|
|
||||||
- &hidden=<true|false>
|
|
||||||
- TODO &tags=<+tag,-tag,...>
|
|
||||||
- &count= and &page=
|
|
||||||
|
|
||||||
Returns a JSON response with
|
|
||||||
- count: number of items
|
|
||||||
- items: items as JSON
|
|
||||||
- prev: if there are previous pages, the previous page number
|
|
||||||
- next: if there are further pages, the next page number
|
|
||||||
"""
|
|
||||||
data_path: Path = current_app.config["INTAKE_DATA"]
|
|
||||||
|
|
||||||
# &channels and &sources may not both be specified
|
|
||||||
filter_channel = request.args.get("channel")
|
|
||||||
filter_source = request.args.get("source")
|
|
||||||
if filter_channel and filter_source:
|
|
||||||
response = jsonify({"error": "One of channel and source may be specified"})
|
|
||||||
response.status_code = 400
|
|
||||||
return response
|
|
||||||
|
|
||||||
source_names = []
|
|
||||||
|
|
||||||
# If the channel was specified, load the channel defs to get the sources
|
|
||||||
if filter_channel:
|
|
||||||
channels_config_path = data_path / "channels.json"
|
|
||||||
if not channels_config_path.exists():
|
|
||||||
abort(404)
|
|
||||||
channels = json.loads(channels_config_path.read_text(encoding="utf8"))
|
|
||||||
if filter_channel not in channels:
|
|
||||||
abort(404)
|
|
||||||
source_names = channels[filter_channel]
|
|
||||||
|
|
||||||
# If a source was specified, use that source
|
|
||||||
elif filter_source:
|
|
||||||
source_names = [filter_source]
|
|
||||||
|
|
||||||
# If neither was specified, use all sources
|
|
||||||
else:
|
|
||||||
source_names = [
|
|
||||||
child.name
|
|
||||||
for child in data_path.iterdir()
|
|
||||||
if (child / "intake.json").exists()
|
|
||||||
]
|
|
||||||
|
|
||||||
sources = [LocalSource(data_path, name) for name in source_names]
|
|
||||||
|
|
||||||
# Get the items, applying the hidden filter
|
|
||||||
show_hidden = request.args.get("hidden") == "true"
|
|
||||||
all_items = sorted(
|
|
||||||
[
|
|
||||||
item
|
|
||||||
for source in sources
|
|
||||||
for item in source.get_all_items()
|
|
||||||
if not item.is_hidden or show_hidden
|
|
||||||
],
|
|
||||||
key=item_sort_key,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Apply paging filters
|
|
||||||
count = int(request.args.get("count", "100"))
|
|
||||||
page = int(request.args.get("page", "0"))
|
|
||||||
paged_items = all_items[count * page : count * page + count]
|
|
||||||
|
|
||||||
# Return the result set
|
|
||||||
response_params = {
|
|
||||||
"count": len(paged_items),
|
|
||||||
"items": list(map(to_api_model, paged_items)),
|
|
||||||
}
|
|
||||||
if page > 0:
|
|
||||||
response_params["prev"] = page - 1
|
|
||||||
if (count * page + count) < len(all_items):
|
|
||||||
response_params["next"] = page + 1
|
|
||||||
response = jsonify(response_params)
|
|
||||||
return response
|
|
||||||
|
|
|
@ -172,7 +172,7 @@ def cmd_action(cmd_args):
|
||||||
source = LocalSource(data_path, args.source)
|
source = LocalSource(data_path, args.source)
|
||||||
try:
|
try:
|
||||||
item = execute_action(source, args.item, args.action, 5)
|
item = execute_action(source, args.item, args.action, 5)
|
||||||
print("Item:", item, file=sys.stderr)
|
print("Item:", item._item, file=sys.stderr)
|
||||||
except InvalidConfigException as ex:
|
except InvalidConfigException as ex:
|
||||||
print("Could not fetch", args.source, file=sys.stderr)
|
print("Could not fetch", args.source, file=sys.stderr)
|
||||||
print(ex, file=sys.stderr)
|
print(ex, file=sys.stderr)
|
||||||
|
|
|
@ -53,6 +53,7 @@ def update_crontab_entries(data_path: Path):
|
||||||
section_found = False
|
section_found = False
|
||||||
in_section = False
|
in_section = False
|
||||||
for i in range(len(crontab_lines)):
|
for i in range(len(crontab_lines)):
|
||||||
|
|
||||||
if not section_found and crontab_lines[i] == INTAKE_CRON_BEGIN:
|
if not section_found and crontab_lines[i] == INTAKE_CRON_BEGIN:
|
||||||
section_found = True
|
section_found = True
|
||||||
in_section = True
|
in_section = True
|
||||||
|
|
|
@ -35,6 +35,7 @@ article {
|
||||||
}
|
}
|
||||||
article img {
|
article img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
article textarea {
|
article textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -35,6 +35,7 @@ article {
|
||||||
}
|
}
|
||||||
article img {
|
article img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
button, summary {
|
button, summary {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -19,6 +19,7 @@ article {
|
||||||
}
|
}
|
||||||
article img {
|
article img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
button, summary {
|
button, summary {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -53,7 +54,7 @@ summary:focus {
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article>
|
<article>
|
||||||
<details open>
|
<details>
|
||||||
<summary><span class="item-title">Sources</span></summary>
|
<summary><span class="item-title">Sources</span></summary>
|
||||||
{% if not sources %}
|
{% if not sources %}
|
||||||
<p>No sources found.</p>
|
<p>No sources found.</p>
|
||||||
|
|
|
@ -57,7 +57,7 @@ in {
|
||||||
let
|
let
|
||||||
# Define the intake package and a python environment to run it from
|
# Define the intake package and a python environment to run it from
|
||||||
intake = intakeCfg.package;
|
intake = intakeCfg.package;
|
||||||
pythonEnv = pkgs.python38.withPackages (pypkgs: [ intake ]);
|
pythonEnv = pkgs.python3.withPackages (pypkgs: [ intake ]);
|
||||||
|
|
||||||
# Assign each user an internal port for their personal intake instance
|
# Assign each user an internal port for their personal intake instance
|
||||||
enabledUsers = filterAttrs (userName: userCfg: userCfg.enable) intakeCfg.users;
|
enabledUsers = filterAttrs (userName: userCfg: userCfg.enable) intakeCfg.users;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
name = "intake"
|
name = "intake"
|
||||||
version = "1.0.4"
|
version = "1.1.0"
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
intake = "intake.cli:main"
|
intake = "intake.cli:main"
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import argparse, json, sys, time
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument("action")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
print("args:", args, file=sys.stderr, flush=True)
|
|
||||||
|
|
||||||
def item(i):
|
|
||||||
print(json.dumps(i))
|
|
||||||
|
|
||||||
if args.action == "fetch":
|
|
||||||
item({
|
|
||||||
"id": "item1-only-id",
|
|
||||||
})
|
|
||||||
item({
|
|
||||||
"id": "item2-title",
|
|
||||||
"title": "This item has a title",
|
|
||||||
})
|
|
||||||
item({
|
|
||||||
"id": "item3-body",
|
|
||||||
"title": "This item has a title and body",
|
|
||||||
"body": "<p>Hello, intake! This is an <b>item</b> body.</p>",
|
|
||||||
})
|
|
||||||
item({
|
|
||||||
"id": "item4-action",
|
|
||||||
"title": "This item has an action but no body",
|
|
||||||
"action": {
|
|
||||||
"action1": None
|
|
||||||
},
|
|
||||||
})
|
|
||||||
item({
|
|
||||||
"id": "item5-actions",
|
|
||||||
"title": "This item has two actions and a body",
|
|
||||||
"body": "<p>This is body text.</p>",
|
|
||||||
"action": {
|
|
||||||
"action1": None,
|
|
||||||
"action2": None,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
item({
|
|
||||||
"id": "item6-footer",
|
|
||||||
"title": "No body text but all footer fields",
|
|
||||||
"author": "Authorname",
|
|
||||||
"time": int(time.time()),
|
|
||||||
})
|
|
||||||
item({
|
|
||||||
"id": "item7-footer",
|
|
||||||
"title": "Body text and all footer fields",
|
|
||||||
"author": "Authorname",
|
|
||||||
"time": int(time.time()),
|
|
||||||
"body": "<p>This is body text.</p>",
|
|
||||||
})
|
|
||||||
item({
|
|
||||||
"id": "item8-link",
|
|
||||||
"title": "Item with a link",
|
|
||||||
"link": "#",
|
|
||||||
})
|
|
||||||
|
|
||||||
if args.action == "action1":
|
|
||||||
item = sys.stdin.readline()
|
|
||||||
item = json.loads(item)
|
|
||||||
print(json.dumps(item))
|
|
||||||
|
|
||||||
if args.action == "action2":
|
|
||||||
item = sys.stdin.readline()
|
|
||||||
item = json.loads(item)
|
|
||||||
print(json.dumps(item))
|
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"action": {
|
|
||||||
"fetch": {
|
|
||||||
"exe": "./command.py",
|
|
||||||
"args": [
|
|
||||||
"fetch"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"action1": {
|
|
||||||
"exe": "./command.py",
|
|
||||||
"args": [
|
|
||||||
"action1"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"action2": {
|
|
||||||
"exe": "./command.py",
|
|
||||||
"args": [
|
|
||||||
"action2"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue