labnotes 0.0.62__tar.gz → 0.0.63__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {labnotes-0.0.62 → labnotes-0.0.63}/PKG-INFO +2 -2
- {labnotes-0.0.62 → labnotes-0.0.63}/README.md +1 -1
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/context.md +1 -1
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/create_app.py +15 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/experiments.py +1 -7
- labnotes-0.0.63/src/labnotes/save_conflict_copy.py +16 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/static/note-editor.js +36 -8
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/templates/experiment.html +1 -1
- {labnotes-0.0.62 → labnotes-0.0.63}/.gitignore +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/CHANGELOG.md +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/MIGRATIONS.md +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/pyproject.toml +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/__init__.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/asset_url.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/cell_progress.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/check_counts.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/cli.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/context_guide.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/count_runs.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/create_experiment.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/create_note.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/data_html.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/default_config_text.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/default_counts.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/drain_watch.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/experiment_notes.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/experiment_progress.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/experiment_runs.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/file_changes.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/format_duration.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/format_value.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/group_runs.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/init.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/invocation_started.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/is_positive_int.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/list_experiments.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/list_runs.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/load_config.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/load_experiment.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/models.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/modes.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/nests_runs.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/notebook_kernel.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/output_html.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/parse_run_view.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/print_rows.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/python_blocks.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/read_config.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/read_path.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/render_markdown.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/__init__.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/describe_event.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/execute_run.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/hold_folder.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/new_param_run_id.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/new_run_id.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/new_stamped_run_id.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/param_args.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/param_grid.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/param_run_name.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/plan_runs.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/read_experiment_config.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/run_experiment.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/run_folder.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/run_headless.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/runs_parent.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/shell_command.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/start_experiment.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/stop_then_kill.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/wait_for_group.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/runner/write_run_json.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/save_note.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/saved_outputs.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/serve_app.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/server.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/sort_href.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/sql_quote.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/static/experiment.js +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/static/labnotes.css +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/static/new-note.js +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/static/note-edit.js +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/static/row-link.js +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/static/run-cell.js +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/static/view-toggle.js +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/summarize_config.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tables/__init__.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tables/experiment_rows.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tables/file_prefix.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tables/iso_time.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tables/list_experiment_runs.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tables/open_tables.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tables/path_glob.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tables/path_pattern.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tables/read_experiment.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tables/read_file.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tables/read_run.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tables/summarize_experiments.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/templates/base.html +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/templates/editor_imports.html +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/templates/index.html +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/templates/macros.html +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tui/__init__.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tui/dim.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tui/format_elapsed.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tui/run_app.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tui/run_tui.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/tui/runs_table.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/src/labnotes/wait_for_note.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/testing-conventions.toml +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/tests/integration/conftest.py +0 -0
- {labnotes-0.0.62 → labnotes-0.0.63}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: labnotes
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.63
|
|
4
4
|
Summary: Browse experiment designs, notes, and run data from a folder.
|
|
5
5
|
Project-URL: Repository, https://github.com/thekevinscott/labnotes
|
|
6
6
|
Author-email: Kevin Scott <me@thekevinscott.com>
|
|
@@ -101,7 +101,7 @@ To exclude a run, add `"excluded": true` and an optional `"reason"` to its
|
|
|
101
101
|
replaces it; both show as columns in `runs`.
|
|
102
102
|
|
|
103
103
|
In a terminal, `run` shows a live table of runs (status and time) and stays
|
|
104
|
-
open when they finish; `q` quits. Elsewhere,
|
|
104
|
+
open when they finish; `q` quits. Elsewhere, it prints a
|
|
105
105
|
line per event instead.
|
|
106
106
|
|
|
107
107
|
- `--concurrency N` runs N at a time (default 1).
|
|
@@ -80,7 +80,7 @@ To exclude a run, add `"excluded": true` and an optional `"reason"` to its
|
|
|
80
80
|
replaces it; both show as columns in `runs`.
|
|
81
81
|
|
|
82
82
|
In a terminal, `run` shows a live table of runs (status and time) and stays
|
|
83
|
-
open when they finish; `q` quits. Elsewhere,
|
|
83
|
+
open when they finish; `q` quits. Elsewhere, it prints a
|
|
84
84
|
line per event instead.
|
|
85
85
|
|
|
86
86
|
- `--concurrency N` runs N at a time (default 1).
|
|
@@ -105,7 +105,7 @@ running run under the cursor; `x` again kills it. `+` and
|
|
|
105
105
|
`-` change how many run at once; lowering it lets running runs
|
|
106
106
|
finish. Enter on a run follows its `stdout.log` and `stderr.log` together, as
|
|
107
107
|
they're written, and Escape returns to the table. An `adhoc` experiment that
|
|
108
|
-
plans a single run opens straight on its output. `q` quits. Elsewhere,
|
|
108
|
+
plans a single run opens straight on its output. `q` quits. Elsewhere, it prints a
|
|
109
109
|
line per event instead.
|
|
110
110
|
|
|
111
111
|
- `--concurrency N` runs N at a time to start with. Without it, `concurrency`
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from collections.abc import AsyncIterator
|
|
3
3
|
from contextlib import asynccontextmanager
|
|
4
|
+
from datetime import datetime
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
from starlette.applications import Starlette
|
|
@@ -29,6 +30,7 @@ from labnotes.load_experiment import load_experiment
|
|
|
29
30
|
from labnotes.models import LabnotesConfig
|
|
30
31
|
from labnotes.notebook_kernel import NotebookKernel
|
|
31
32
|
from labnotes.python_blocks import python_blocks
|
|
33
|
+
from labnotes.save_conflict_copy import save_conflict_copy
|
|
32
34
|
from labnotes.save_note import save_note
|
|
33
35
|
from labnotes.saved_outputs import saved_outputs
|
|
34
36
|
from labnotes.tables.open_tables import open_tables
|
|
@@ -127,6 +129,14 @@ def create_app(config: LabnotesConfig) -> Starlette:
|
|
|
127
129
|
return JSONResponse({"content": current}, status_code=409)
|
|
128
130
|
return Response(status_code=204)
|
|
129
131
|
|
|
132
|
+
async def post_conflict(request: Request) -> Response:
|
|
133
|
+
note = await find_note(request)
|
|
134
|
+
content = (await request.json()).get("content")
|
|
135
|
+
if not isinstance(content, str):
|
|
136
|
+
raise HTTPException(400, "Send content as a string")
|
|
137
|
+
copy = save_conflict_copy(Path(note["path"]), content, datetime.now())
|
|
138
|
+
return JSONResponse({"name": copy.name}, status_code=201)
|
|
139
|
+
|
|
130
140
|
async def run_block(request: Request) -> Response:
|
|
131
141
|
blocks = python_blocks((await find_note(request))["content"])
|
|
132
142
|
key = (request.path_params["name"], request.path_params["note"])
|
|
@@ -152,6 +162,11 @@ def create_app(config: LabnotesConfig) -> Starlette:
|
|
|
152
162
|
Route("/experiments/{name}/notes", post_note, methods=["POST"]),
|
|
153
163
|
Route("/experiments/{name}/notes/{note:path}", get_note),
|
|
154
164
|
Route("/experiments/{name}/notes/{note:path}", put_note, methods=["PUT"]),
|
|
165
|
+
Route(
|
|
166
|
+
"/experiments/{name}/notes/{note:path}/conflict",
|
|
167
|
+
post_conflict,
|
|
168
|
+
methods=["POST"],
|
|
169
|
+
),
|
|
155
170
|
Route(
|
|
156
171
|
"/experiments/{name}/notes/{note:path}/run",
|
|
157
172
|
run_block,
|
|
@@ -39,11 +39,6 @@ from labnotes.tui.run_tui import run_tui
|
|
|
39
39
|
type=click.Path(exists=True, dir_okay=False, path_type=Path),
|
|
40
40
|
help="With run: read this file instead of the experiment's experiment.toml.",
|
|
41
41
|
)
|
|
42
|
-
@click.option(
|
|
43
|
-
"--no-tui",
|
|
44
|
-
is_flag=True,
|
|
45
|
-
help="With run: print a line per event instead of the live view.",
|
|
46
|
-
)
|
|
47
42
|
@click.pass_context
|
|
48
43
|
def experiments(
|
|
49
44
|
ctx: click.Context,
|
|
@@ -52,7 +47,6 @@ def experiments(
|
|
|
52
47
|
output_format: str,
|
|
53
48
|
concurrency: int | None,
|
|
54
49
|
experiment_config: Path | None,
|
|
55
|
-
no_tui: bool,
|
|
56
50
|
) -> None:
|
|
57
51
|
config = load_config(ctx)
|
|
58
52
|
if config is None:
|
|
@@ -68,7 +62,7 @@ def experiments(
|
|
|
68
62
|
click.echo(f"Created {path / 'experiment.toml'}")
|
|
69
63
|
return
|
|
70
64
|
case str(), "run":
|
|
71
|
-
live = sys.stdout.isatty() and sys.stderr.isatty()
|
|
65
|
+
live = sys.stdout.isatty() and sys.stderr.isatty()
|
|
72
66
|
drive = run_tui if live else lambda start, title: run_headless(start)
|
|
73
67
|
try:
|
|
74
68
|
results = start_experiment(
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from itertools import chain, count
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def save_conflict_copy(path: Path, content: str, now: datetime) -> Path:
|
|
7
|
+
stamp = f"Conflicted copy {now:%Y%m%d%H%M}"
|
|
8
|
+
tags = chain([stamp], (f"{stamp} {n}" for n in count(2)))
|
|
9
|
+
while True:
|
|
10
|
+
copy = path.with_name(f"{path.stem} ({next(tags)}){path.suffix}")
|
|
11
|
+
try:
|
|
12
|
+
with copy.open("x", encoding="utf-8") as file:
|
|
13
|
+
file.write(content)
|
|
14
|
+
return copy
|
|
15
|
+
except FileExistsError:
|
|
16
|
+
pass
|
|
@@ -105,9 +105,6 @@ function show(status, message) {
|
|
|
105
105
|
|
|
106
106
|
const dmp = new diff_match_patch();
|
|
107
107
|
|
|
108
|
-
// Three-way: what changed on disk since base, applied onto the editor's text.
|
|
109
|
-
const merge = (base, ours, theirs) => dmp.patch_apply(dmp.patch_make(base, theirs), ours)[0];
|
|
110
|
-
|
|
111
108
|
// Minimal changes, so the cursor and undo history survive.
|
|
112
109
|
function setText(view, text) {
|
|
113
110
|
const changes = [];
|
|
@@ -125,22 +122,52 @@ function setText(view, text) {
|
|
|
125
122
|
export function edit(note, prose, status) {
|
|
126
123
|
let base = JSON.parse(note.querySelector(".note-source").textContent);
|
|
127
124
|
let timer;
|
|
125
|
+
let deadline;
|
|
128
126
|
let queue = Promise.resolve();
|
|
129
127
|
|
|
130
128
|
const host = document.createElement("div");
|
|
131
129
|
host.className = "editor";
|
|
132
130
|
prose.replaceWith(host);
|
|
133
131
|
|
|
132
|
+
const notice = (message) => {
|
|
133
|
+
show(status, message);
|
|
134
|
+
setTimeout(() => {
|
|
135
|
+
if (status.textContent === message) status.hidden = true;
|
|
136
|
+
}, 4000);
|
|
137
|
+
};
|
|
138
|
+
|
|
134
139
|
const run = (task) =>
|
|
135
140
|
(queue = queue.then(task).catch((error) => show(status, `Not saved: ${error.message}`)));
|
|
136
141
|
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
// Three-way: what changed on disk since base, applied onto the editor's text.
|
|
143
|
+
// A patch that will not apply leaves the editor alone and keeps the disk
|
|
144
|
+
// version in a sibling file, as Obsidian Sync does.
|
|
145
|
+
const absorb = async (theirs) => {
|
|
146
|
+
const ours = view.state.doc.toString();
|
|
147
|
+
const [merged, applied] = dmp.patch_apply(dmp.patch_make(base, theirs), ours);
|
|
148
|
+
const name = note.dataset.note;
|
|
149
|
+
if (applied.every(Boolean)) {
|
|
150
|
+
setText(view, merged);
|
|
151
|
+
if (ours !== base) notice(`${name} was modified externally, merging changes automatically`);
|
|
152
|
+
} else {
|
|
153
|
+
const response = await fetch(note.dataset.conflictUrl, {
|
|
154
|
+
method: "POST",
|
|
155
|
+
headers: { "Content-Type": "application/json" },
|
|
156
|
+
body: JSON.stringify({ content: theirs }),
|
|
157
|
+
});
|
|
158
|
+
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
|
|
159
|
+
const copy = (await response.json()).name;
|
|
160
|
+
show(
|
|
161
|
+
status,
|
|
162
|
+
`${name} was modified externally and could not be merged. The outside version is saved as ${copy}.`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
139
165
|
base = theirs;
|
|
140
166
|
};
|
|
141
167
|
|
|
142
168
|
const push = async () => {
|
|
143
169
|
clearTimeout(timer);
|
|
170
|
+
deadline = undefined;
|
|
144
171
|
for (;;) {
|
|
145
172
|
const content = view.state.doc.toString();
|
|
146
173
|
if (content === base) return;
|
|
@@ -154,7 +181,7 @@ export function edit(note, prose, status) {
|
|
|
154
181
|
else show(status, `Not saved: ${response.status} ${response.statusText}`);
|
|
155
182
|
return;
|
|
156
183
|
}
|
|
157
|
-
absorb((await response.json()).content);
|
|
184
|
+
await absorb((await response.json()).content);
|
|
158
185
|
}
|
|
159
186
|
};
|
|
160
187
|
|
|
@@ -163,7 +190,7 @@ export function edit(note, prose, status) {
|
|
|
163
190
|
if (!response.ok) return show(status, `Not synced: ${response.status} ${response.statusText}`);
|
|
164
191
|
const theirs = await response.text();
|
|
165
192
|
if (theirs === base) return;
|
|
166
|
-
absorb(theirs);
|
|
193
|
+
await absorb(theirs);
|
|
167
194
|
await push();
|
|
168
195
|
};
|
|
169
196
|
|
|
@@ -194,7 +221,8 @@ export function edit(note, prose, status) {
|
|
|
194
221
|
EditorView.updateListener.of((update) => {
|
|
195
222
|
if (!update.docChanged) return;
|
|
196
223
|
clearTimeout(timer);
|
|
197
|
-
|
|
224
|
+
deadline ??= Date.now() + 2000;
|
|
225
|
+
timer = setTimeout(save, Math.min(500, deadline - Date.now()));
|
|
198
226
|
}),
|
|
199
227
|
],
|
|
200
228
|
}),
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<nav class="tabs sub">
|
|
38
38
|
{% for each in e.notes %}<a href="?tab=notebooks&note={{ each.name | urlencode }}"{% if each.name == note.name %} aria-current="page"{% endif %}>{{ each.name }}</a>{% endfor %}<button type="button" class="new-note" data-url="{{ url_for('post_note', name=e.name) }}" aria-label="New note" title="New note">+</button>
|
|
39
39
|
</nav>
|
|
40
|
-
<article class="note" data-run-url="{{ url_for('run_block', name=e.name, note=note.name) }}" data-restart-url="{{ url_for('restart_kernel', name=e.name, note=note.name) }}" data-save-url="{{ url_for('put_note', name=e.name, note=note.name) }}" data-editor="{{ asset('note-editor.js') }}" data-changes-url="{{ url_for('changes', name=e.name) }}" data-note="{{ note.name }}">
|
|
40
|
+
<article class="note" data-run-url="{{ url_for('run_block', name=e.name, note=note.name) }}" data-restart-url="{{ url_for('restart_kernel', name=e.name, note=note.name) }}" data-save-url="{{ url_for('put_note', name=e.name, note=note.name) }}" data-conflict-url="{{ url_for('post_conflict', name=e.name, note=note.name) }}" data-editor="{{ asset('note-editor.js') }}" data-changes-url="{{ url_for('changes', name=e.name) }}" data-note="{{ note.name }}">
|
|
41
41
|
{% if note.runnable %}<header class="note-name"><button type="button" class="restart" title="Stop this note's Python kernel. The next Run starts a fresh one.">Restart</button></header>{% endif %}
|
|
42
42
|
<p class="edit-status" role="status" hidden></p>
|
|
43
43
|
<div class="prose">{{ note.html | safe }}</div>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|