Split HTML5 authoring renderer
CI / python (push) Has been cancelled
CI / rust (push) Has been cancelled

This commit is contained in:
2026-05-17 11:13:46 +03:00
parent 624dc5d7f0
commit 53e983af4e
3 changed files with 611 additions and 600 deletions
+5 -592
View File
@@ -5,6 +5,11 @@ from html import escape
from typing import Iterable from typing import Iterable
from urllib.parse import quote from urllib.parse import quote
from api_server.html5_authoring import (
render_html5_authoring_changes,
render_html5_authoring_preview,
render_html5_metadata_authoring,
)
from sir import NodeKind, SirSnapshot from sir import NodeKind, SirSnapshot
_HTML5_OBJECT_CONTEXT_KINDS = { _HTML5_OBJECT_CONTEXT_KINDS = {
@@ -580,388 +585,6 @@ def render_html5_object_context(
""" """
def render_html5_authoring_changes(project_id: str, changes: Iterable[object] | None) -> str:
if changes is None:
return f"""
<div
class="authoring-panel"
data-html5-authoring-changes
hx-get="/html5/projects/{quote(project_id)}/authoring/changes"
hx-trigger="load"
sse-swap="authoring-changes"
hx-swap="outerHTML"
>
<div class="panel-title">Authoring</div>
<p class="muted padded">Сервер загружает историю рабочих изменений.</p>
</div>
"""
change_list = list(changes)
if not change_list:
body = '<p class="muted padded">Изменений пока нет</p>'
else:
body = "".join(_authoring_change_item(change) for change in change_list[:12])
return f"""
<div
class="authoring-panel"
data-html5-authoring-changes
hx-get="/html5/projects/{quote(project_id)}/authoring/changes"
sse-swap="authoring-changes"
hx-swap="outerHTML"
>
<div class="panel-title">Authoring · {len(change_list)}</div>
{_authoring_changes_summary(change_list)}
{_authoring_recent_change(change_list)}
<div class="review-list">{body}</div>
{render_html5_authoring_change_detail(project_id, None)}
</div>
"""
def render_html5_authoring_preview(project_id: str, preview: object | None, error: str | None = None) -> str:
if preview is None and error is None:
return f"""
<div class="authoring-preview" data-html5-authoring-preview>
<div class="panel-title">Authoring preview</div>
<form
class="authoring-preview-form"
data-html5-authoring-preview-form
method="post"
action="/html5/projects/{quote(project_id)}/authoring/completion-preview"
hx-post="/html5/projects/{quote(project_id)}/authoring/completion-preview"
hx-target="[data-html5-authoring-preview-result]"
hx-swap="outerHTML"
>
<input name="object_name" placeholder="object_name" />
<input name="routine_name" placeholder="routine_name" />
<input name="cursor_line" placeholder="line" />
<input name="intent" value="fill-check" />
<input name="user_id" placeholder="user_id" />
<textarea name="source_text" placeholder="BSL source"></textarea>
<button type="submit">Preview</button>
</form>
{render_html5_authoring_preview_result(project_id)}
<form
class="authoring-preview-form"
data-html5-authoring-diff-form
method="post"
action="/html5/projects/{quote(project_id)}/authoring/semantic-diff-preview"
hx-post="/html5/projects/{quote(project_id)}/authoring/semantic-diff-preview"
hx-target="[data-html5-authoring-diff-result]"
hx-swap="outerHTML"
>
<input name="routine_name" placeholder="routine_name" />
<input name="source_path" placeholder="source_path" />
<input name="task_id" placeholder="task_id" />
<input name="session_id" placeholder="session_id" />
<input name="user_id" placeholder="user_id" />
<textarea name="original_text" placeholder="Original BSL"></textarea>
<textarea name="proposed_text" placeholder="Proposed BSL"></textarea>
<button type="submit">Diff preview</button>
</form>
{render_html5_authoring_diff_result(project_id)}
</div>
"""
return render_html5_authoring_preview_result(project_id, preview, error)
def render_html5_authoring_preview_result(project_id: str, preview: object | None = None, error: str | None = None) -> str:
if preview is None and error is None:
return '<div class="authoring-preview-result" data-html5-authoring-preview-result></div>'
if error:
return f"""
<div class="authoring-preview-result" data-html5-authoring-preview-result>
<div class="panel-title">Preview result</div>
<p class="muted padded">{escape(error)}</p>
</div>
"""
allowed = bool(getattr(preview, "allowed", False))
insert_text = str(getattr(preview, "insert_text", ""))
checks = getattr(preview, "checks", []) or []
diff = getattr(preview, "semantic_diff", []) or []
context = getattr(preview, "context", None)
object_node = getattr(context, "object", None)
routine_node = getattr(context, "routine", None)
object_name = getattr(object_node, "qualified_name", None) or getattr(object_node, "name", None) or "object unavailable"
routine_name = getattr(routine_node, "name", None) or "routine unavailable"
check_rows = "".join(_authoring_check_item(check) for check in checks[:8])
diff_rows = "".join(_authoring_diff_item(line) for line in diff[:8]) or '<p class="muted padded">Diff пустой</p>'
return f"""
<div class="authoring-preview-result" data-html5-authoring-preview-result data-html5-project-id="{escape(project_id)}">
<div class="panel-title">Preview result · {'allowed' if allowed else 'blocked'}</div>
<article class="authoring-change">
<strong>{escape(str(object_name))}</strong>
<span>{escape(str(routine_name))}</span>
<small>{escape(insert_text[:180] or "insert text unavailable")}</small>
</article>
{_authoring_result_summary("allowed" if allowed else "blocked", diff, checks)}
<div class="check-list">{check_rows}</div>
<div class="diff-list">{diff_rows}</div>
</div>
"""
def render_html5_authoring_diff_result(
project_id: str,
preview: object | None = None,
error: str | None = None,
request_payload: dict | None = None,
) -> str:
if preview is None and error is None:
return '<div class="authoring-diff-result" data-html5-authoring-diff-result></div>'
if error:
return f"""
<div class="authoring-diff-result" data-html5-authoring-diff-result>
<div class="panel-title">Diff preview</div>
<p class="muted padded">{escape(error)}</p>
</div>
"""
changed = bool(getattr(preview, "changed", False))
added = getattr(preview, "added_lines", 0)
removed = getattr(preview, "removed_lines", 0)
target = getattr(preview, "target", None)
target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable"
checks = getattr(preview, "checks", []) or []
diff = getattr(preview, "semantic_diff", []) or []
version_preview = getattr(preview, "version_preview", None)
next_version_id = str(getattr(version_preview, "next_version_id", ""))
check_rows = "".join(_authoring_check_item(check) for check in checks[:8])
diff_rows = "".join(_authoring_diff_item(line) for line in diff[:12]) or '<p class="muted padded">Diff пустой</p>'
apply_form = (
_authoring_apply_change_set_form(project_id, request_payload or {}, next_version_id)
if changed and next_version_id
else ""
)
return f"""
<div class="authoring-diff-result" data-html5-authoring-diff-result data-html5-project-id="{escape(project_id)}">
<div class="panel-title">Diff preview · {'changed' if changed else 'unchanged'}</div>
<article class="authoring-change">
<strong>{escape(str(target_name))}</strong>
<span>+{escape(str(added))} / -{escape(str(removed))}</span>
<small>{escape(next_version_id or "version preview unavailable")}</small>
</article>
{_authoring_result_summary("changed" if changed else "unchanged", diff, checks)}
<div class="check-list">{check_rows}</div>
<div class="diff-list">{diff_rows}</div>
{apply_form}
</div>
"""
def render_html5_authoring_apply_result(project_id: str, result: object | None = None, error: str | None = None) -> str:
if result is None and error is None:
return '<div class="authoring-apply-result" data-html5-authoring-apply-result></div>'
if error:
return f"""
<div class="authoring-apply-result" data-html5-authoring-apply-result>
<div class="panel-title">Apply change-set</div>
<p class="muted padded">{escape(error)}</p>
</div>
"""
status = str(getattr(result, "status", "UNKNOWN"))
change_id = str(getattr(result, "change_id", ""))
version = getattr(result, "version", None)
version_id = str(getattr(version, "version_id", ""))
return f"""
<div
class="authoring-apply-result"
data-html5-authoring-apply-result
data-html5-authoring-change="{escape(change_id)}"
data-html5-version-id="{escape(version_id)}"
>
<div class="panel-title">Apply change-set</div>
<article class="authoring-change">
<strong>{escape(status)}</strong>
<span>{escape(change_id)}</span>
<small>{escape(version_id)}</small>
</article>
{_authoring_apply_summary("change-set", status, change_id, version_id)}
<p class="muted padded">Change-set применен в workspace для проекта {escape(project_id)}.</p>
</div>
"""
def render_html5_metadata_authoring(project_id: str) -> str:
return f"""
<div class="authoring-preview" data-html5-metadata-authoring>
<div class="panel-title">Metadata draft</div>
<form
class="authoring-preview-form"
data-html5-metadata-preview-form
method="post"
action="/html5/projects/{quote(project_id)}/authoring/metadata-object-preview"
hx-post="/html5/projects/{quote(project_id)}/authoring/metadata-object-preview"
hx-target="[data-html5-metadata-preview-result]"
hx-swap="outerHTML"
>
<input name="object_kind" value="DOCUMENT" />
<input name="name" placeholder="Имя объекта" required />
<input name="synonym" placeholder="Синоним" />
<input name="attributes" placeholder="Реквизиты: Имя:Тип, ..." />
<input name="tabular_sections" placeholder="ТЧ: Товары[Номенклатура:Строка;Количество:Число]" />
<input name="forms" placeholder="Формы через запятую" />
<input name="commands" placeholder="Команды: Имя:Обработчик" />
<input name="task_id" placeholder="task_id" />
<input name="session_id" placeholder="session_id" />
<input name="user_id" placeholder="user_id" />
<button type="submit">Metadata preview</button>
</form>
{render_html5_metadata_preview_result(project_id)}
</div>
"""
def render_html5_metadata_preview_result(
project_id: str,
preview: object | None = None,
error: str | None = None,
request_payload: dict | None = None,
) -> str:
if preview is None and error is None:
return '<div class="metadata-preview-result" data-html5-metadata-preview-result></div>'
if error:
return f"""
<div class="metadata-preview-result" data-html5-metadata-preview-result>
<div class="panel-title">Metadata preview</div>
<p class="muted padded">{escape(error)}</p>
</div>
"""
changed = bool(getattr(preview, "changed", False))
added = getattr(preview, "added_lines", 0)
target = getattr(preview, "target", None)
target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable"
checks = getattr(preview, "checks", []) or []
diff = getattr(preview, "semantic_diff", []) or []
version_preview = getattr(preview, "version_preview", None)
next_version_id = str(getattr(version_preview, "next_version_id", ""))
check_rows = "".join(_authoring_check_item(check) for check in checks[:8])
diff_rows = "".join(_authoring_diff_item(line) for line in diff[:12]) or '<p class="muted padded">Diff пустой</p>'
apply_form = (
_metadata_apply_form(project_id, request_payload or {}, next_version_id)
if changed and next_version_id
else ""
)
return f"""
<div class="metadata-preview-result" data-html5-metadata-preview-result data-html5-project-id="{escape(project_id)}">
<div class="panel-title">Metadata preview · {'changed' if changed else 'unchanged'}</div>
<article class="authoring-change">
<strong>{escape(str(target_name))}</strong>
<span>+{escape(str(added))} / -0</span>
<small>{escape(next_version_id or "version preview unavailable")}</small>
</article>
{_authoring_result_summary("changed" if changed else "unchanged", diff, checks)}
<div class="check-list">{check_rows}</div>
<div class="diff-list">{diff_rows}</div>
{apply_form}
</div>
"""
def render_html5_metadata_apply_result(project_id: str, result: object | None = None, error: str | None = None) -> str:
if result is None and error is None:
return '<div class="metadata-apply-result" data-html5-metadata-apply-result></div>'
if error:
return f"""
<div class="metadata-apply-result" data-html5-metadata-apply-result>
<div class="panel-title">Metadata apply</div>
<p class="muted padded">{escape(error)}</p>
</div>
"""
status = str(getattr(result, "status", "UNKNOWN"))
change_id = str(getattr(result, "change_id", ""))
version = getattr(result, "version", None)
version_id = str(getattr(version, "version_id", ""))
return f"""
<div
class="metadata-apply-result"
data-html5-metadata-apply-result
data-html5-authoring-change="{escape(change_id)}"
data-html5-version-id="{escape(version_id)}"
>
<div class="panel-title">Metadata apply</div>
<article class="authoring-change">
<strong>{escape(status)}</strong>
<span>{escape(change_id)}</span>
<small>{escape(version_id)}</small>
</article>
{_authoring_apply_summary("metadata", status, change_id, version_id)}
<p class="muted padded">Metadata draft применен в workspace для проекта {escape(project_id)}.</p>
</div>
"""
def render_html5_authoring_change_detail(project_id: str, preview: object | None) -> str:
if preview is None:
return f"""
<div class="authoring-detail" data-html5-authoring-detail>
<div class="panel-title">Rollback preview</div>
<p class="muted padded">Выберите изменение, чтобы сервер рассчитал rollback diff для проекта {escape(project_id)}.</p>
</div>
"""
change_id = str(getattr(preview, "change_id", ""))
original_version_id = str(getattr(preview, "original_version_id", ""))
rollback_version_id = str(getattr(preview, "rollback_version_id", ""))
target = getattr(preview, "target", None)
target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable"
diff = getattr(preview, "semantic_diff", []) or []
checks = getattr(preview, "checks", []) or []
apply_available = bool(getattr(preview, "apply_available", False))
diff_rows = "".join(_authoring_diff_item(line) for line in diff[:12]) or '<p class="muted padded">Diff пустой</p>'
check_rows = "".join(_authoring_check_item(check) for check in checks[:8])
apply_form = _authoring_rollback_form(project_id, change_id, rollback_version_id) if apply_available else ""
return f"""
<div
class="authoring-detail"
data-html5-authoring-detail
data-html5-authoring-change="{escape(change_id)}"
>
<div class="panel-title">Rollback preview · {'ready' if apply_available else 'blocked'}</div>
<article class="authoring-change">
<strong>{escape(str(target_name))}</strong>
<span>{escape(original_version_id)} -> {escape(rollback_version_id)}</span>
<small>{escape(change_id)}</small>
</article>
{_authoring_detail_summary(diff, checks, apply_available)}
<div class="check-list">{check_rows}</div>
<div class="diff-list">{diff_rows}</div>
{apply_form}
</div>
"""
def render_html5_authoring_rollback_result(project_id: str, result: object | None = None, error: str | None = None) -> str:
if result is None and error is None:
return '<div class="authoring-result" data-html5-authoring-result></div>'
if error:
return f"""
<div class="authoring-result" data-html5-authoring-result>
<div class="panel-title">Rollback apply</div>
<p class="muted padded">{escape(error)}</p>
</div>
"""
status = str(getattr(result, "status", "UNKNOWN"))
change_id = str(getattr(result, "change_id", ""))
rollback_change_id = str(getattr(result, "rollback_change_id", ""))
version = getattr(result, "version", None)
version_id = str(getattr(version, "version_id", ""))
return f"""
<div
class="authoring-result"
data-html5-authoring-result
data-html5-authoring-change="{escape(change_id)}"
data-html5-version-id="{escape(version_id)}"
>
<div class="panel-title">Rollback apply</div>
<article class="authoring-change">
<strong>{escape(status)}</strong>
<span>{escape(rollback_change_id)}</span>
<small>{escape(version_id)}</small>
</article>
{_authoring_apply_summary("rollback", status, rollback_change_id or change_id, version_id)}
<p class="muted padded">Rollback применен в workspace для проекта {escape(project_id)}.</p>
</div>
"""
def render_html5_status(project_id: str, snapshot: SirSnapshot) -> str: def render_html5_status(project_id: str, snapshot: SirSnapshot) -> str:
return ( return (
f'<span>project: {escape(project_id)}</span>' f'<span>project: {escape(project_id)}</span>'
@@ -1297,113 +920,6 @@ def _symbol_reference_item(project_id: str, reference: object) -> str:
""" """
def _authoring_change_item(change: object) -> str:
change_id = str(getattr(change, "change_id", ""))
status = str(getattr(change, "status", ""))
target = getattr(change, "target", None)
target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable"
approved_by = str(getattr(change, "approved_by", "") or "not approved")
task_id = str(getattr(change, "task_id", "") or "no task")
added = getattr(change, "added_lines", 0)
removed = getattr(change, "removed_lines", 0)
production = "production" if bool(getattr(change, "production_applied", False)) else "workspace"
project_id = str(getattr(change, "project_id", ""))
detail_attrs = (
f'hx-get="/html5/projects/{quote(project_id)}/authoring/changes/{quote(change_id, safe="")}" '
'hx-target="[data-html5-authoring-detail]" hx-swap="outerHTML"'
if change_id and project_id
else ""
)
return f"""
<article class="authoring-change" data-html5-authoring-change="{escape(change_id)}" {detail_attrs}>
<strong>{escape(str(target_name))}</strong>
<span>{escape(status)} · +{escape(str(added))} / -{escape(str(removed))} · {escape(production)}</span>
<small>{escape(task_id)} · {escape(approved_by)} · {escape(change_id)}</small>
</article>
"""
def _authoring_changes_summary(changes: Iterable[object]) -> str:
change_list = list(changes)
statuses = Counter(str(getattr(change, "status", "") or "UNKNOWN") for change in change_list)
production = sum(1 for change in change_list if bool(getattr(change, "production_applied", False)))
workspace = len(change_list) - production
added = sum(int(getattr(change, "added_lines", 0) or 0) for change in change_list)
removed = sum(int(getattr(change, "removed_lines", 0) or 0) for change in change_list)
status_text = ", ".join(f"{name}: {count}" for name, count in sorted(statuses.items())) or "no changes"
return f"""
<p class="authoring-summary" data-html5-authoring-summary>
{escape(str(len(change_list)))} changes · {escape(status_text)} · {escape(str(workspace))} workspace · {escape(str(production))} production · +{escape(str(added))} / -{escape(str(removed))}
</p>
"""
def _authoring_recent_change(changes: Iterable[object]) -> str:
change_list = list(changes)
if not change_list:
return ""
latest = change_list[0]
change_id = str(getattr(latest, "change_id", ""))
status = str(getattr(latest, "status", "") or "UNKNOWN")
target = getattr(latest, "target", None)
target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable"
version = getattr(latest, "version", None)
version_id = str(getattr(latest, "version_id", "") or getattr(version, "version_id", "") or "version unavailable")
approved_by = str(getattr(latest, "approved_by", "") or "not approved")
return f"""
<article class="authoring-change" data-html5-authoring-recent-change="{escape(change_id)}">
<strong>{escape(status)} · {escape(str(target_name))}</strong>
<span>{escape(version_id)}</span>
<small>{escape(approved_by)} · {escape(change_id)}</small>
</article>
"""
def _authoring_result_summary(state: str, diff: Iterable[object], checks: Iterable[object]) -> str:
diff_list = list(diff)
check_list = list(checks)
diff_kinds = Counter(str(getattr(line, "kind", "") or "CHANGE") for line in diff_list)
check_statuses = Counter(str(getattr(check, "status", "") or "UNKNOWN") for check in check_list)
added = sum(1 for line in diff_list if str(getattr(line, "kind", "")) == "ADD")
removed = sum(1 for line in diff_list if str(getattr(line, "kind", "")) == "REMOVE")
diff_text = ", ".join(f"{name}: {count}" for name, count in sorted(diff_kinds.items())) or "empty diff"
check_text = ", ".join(f"{name}: {count}" for name, count in sorted(check_statuses.items())) or "no checks"
return f"""
<p class="authoring-summary" data-html5-authoring-result-summary>
{escape(state)} · {escape(str(len(diff_list)))} diff lines · +{escape(str(added))} / -{escape(str(removed))} · {escape(diff_text)} · {escape(check_text)}
</p>
"""
def _authoring_detail_summary(diff: Iterable[object], checks: Iterable[object], apply_available: bool) -> str:
diff_list = list(diff)
check_list = list(checks)
diff_kinds = Counter(str(getattr(line, "kind", "") or "CHANGE") for line in diff_list)
check_statuses = Counter(str(getattr(check, "status", "") or "UNKNOWN") for check in check_list)
added = sum(1 for line in diff_list if str(getattr(line, "kind", "")) == "ADD")
removed = sum(1 for line in diff_list if str(getattr(line, "kind", "")) == "REMOVE")
diff_text = ", ".join(f"{name}: {count}" for name, count in sorted(diff_kinds.items())) or "empty diff"
check_text = ", ".join(f"{name}: {count}" for name, count in sorted(check_statuses.items())) or "no checks"
state = "rollback ready" if apply_available else "rollback blocked"
return f"""
<p class="authoring-summary" data-html5-authoring-detail-summary>
{escape(state)} · {escape(str(len(diff_list)))} diff lines · +{escape(str(added))} / -{escape(str(removed))} · {escape(diff_text)} · {escape(check_text)}
</p>
"""
def _authoring_apply_summary(kind: str, status: str, change_id: str, version_id: str) -> str:
return f"""
<p
class="authoring-summary"
data-html5-authoring-apply-summary
data-html5-authoring-apply-kind="{escape(kind)}"
>
{escape(kind)} · {escape(status or "UNKNOWN")} · {escape(change_id or "change unavailable")} · {escape(version_id or "version unavailable")}
</p>
"""
def _named_node_item(label: str, node: object) -> str: def _named_node_item(label: str, node: object) -> str:
name = getattr(node, "qualified_name", None) or getattr(node, "name", "") name = getattr(node, "qualified_name", None) or getattr(node, "name", "")
kind = getattr(node, "kind", label) kind = getattr(node, "kind", label)
@@ -1759,109 +1275,6 @@ def _flowchart_node_item(project_id: str, node: object, depth: int) -> str:
""" """
def _authoring_diff_item(line: object) -> str:
kind = str(getattr(line, "kind", ""))
text = str(getattr(line, "text", ""))
return f"""
<article class="diff-item" data-html5-authoring-diff="{escape(kind)}">
<span>{escape(kind)}</span>
<code>{escape(text)}</code>
</article>
"""
def _authoring_check_item(check: object) -> str:
name = str(getattr(check, "name", "check"))
status = str(getattr(check, "status", "UNKNOWN"))
message = str(getattr(check, "message", ""))
return f"""
<article class="check-item" data-html5-authoring-check="{escape(status)}">
<strong>{escape(name)}</strong>
<span>{escape(status)}</span>
<small>{escape(message)}</small>
</article>
"""
def _authoring_rollback_form(project_id: str, change_id: str, rollback_version_id: str) -> str:
return f"""
<form
class="rollback-form"
data-html5-authoring-rollback-form
method="post"
action="/html5/projects/{quote(project_id)}/authoring/changes/{quote(change_id, safe="")}/apply-rollback"
hx-post="/html5/projects/{quote(project_id)}/authoring/changes/{quote(change_id, safe="")}/apply-rollback"
hx-target="[data-html5-authoring-result]"
hx-swap="outerHTML"
>
<input type="hidden" name="expected_rollback_version_id" value="{escape(rollback_version_id)}" />
<input name="approved_by" placeholder="approved_by" required />
<input name="task_id" placeholder="task_id" />
<input name="session_id" placeholder="session_id" />
<input name="approval_note" placeholder="Комментарий" />
<button type="submit">Apply rollback</button>
</form>
{render_html5_authoring_rollback_result(project_id)}
"""
def _authoring_apply_change_set_form(project_id: str, payload: dict, next_version_id: str) -> str:
return f"""
<form
class="authoring-preview-form"
data-html5-authoring-apply-form
method="post"
action="/html5/projects/{quote(project_id)}/authoring/apply-change-set"
hx-post="/html5/projects/{quote(project_id)}/authoring/apply-change-set"
hx-target="[data-html5-authoring-apply-result]"
hx-swap="outerHTML"
>
<input type="hidden" name="routine_name" value="{escape(str(payload.get("routine_name") or ""))}" />
<input type="hidden" name="source_path" value="{escape(str(payload.get("source_path") or ""))}" />
<textarea hidden name="original_text">{escape(str(payload.get("original_text") or ""))}</textarea>
<textarea hidden name="proposed_text">{escape(str(payload.get("proposed_text") or ""))}</textarea>
<input type="hidden" name="task_id" value="{escape(str(payload.get("task_id") or ""))}" />
<input type="hidden" name="session_id" value="{escape(str(payload.get("session_id") or ""))}" />
<input type="hidden" name="user_id" value="{escape(str(payload.get("user_id") or ""))}" />
<input type="hidden" name="expected_next_version_id" value="{escape(next_version_id)}" />
<input name="approved_by" placeholder="approved_by" required />
<input name="approval_note" placeholder="Комментарий" />
<button type="submit">Apply change-set</button>
</form>
{render_html5_authoring_apply_result(project_id)}
"""
def _metadata_apply_form(project_id: str, payload: dict, next_version_id: str) -> str:
return f"""
<form
class="authoring-preview-form"
data-html5-metadata-apply-form
method="post"
action="/html5/projects/{quote(project_id)}/authoring/apply-metadata-object"
hx-post="/html5/projects/{quote(project_id)}/authoring/apply-metadata-object"
hx-target="[data-html5-metadata-apply-result]"
hx-swap="outerHTML"
>
<input type="hidden" name="object_kind" value="{escape(str(payload.get("object_kind") or ""))}" />
<input type="hidden" name="name" value="{escape(str(payload.get("name") or ""))}" />
<input type="hidden" name="synonym" value="{escape(str(payload.get("synonym") or ""))}" />
<input type="hidden" name="attributes" value="{escape(str(payload.get("_raw_attributes") or ""))}" />
<input type="hidden" name="tabular_sections" value="{escape(str(payload.get("_raw_tabular_sections") or ""))}" />
<input type="hidden" name="forms" value="{escape(str(payload.get("_raw_forms") or ""))}" />
<input type="hidden" name="commands" value="{escape(str(payload.get("_raw_commands") or ""))}" />
<input type="hidden" name="task_id" value="{escape(str(payload.get("task_id") or ""))}" />
<input type="hidden" name="session_id" value="{escape(str(payload.get("session_id") or ""))}" />
<input type="hidden" name="user_id" value="{escape(str(payload.get("user_id") or ""))}" />
<input type="hidden" name="expected_next_version_id" value="{escape(next_version_id)}" />
<input name="approved_by" placeholder="approved_by" required />
<input name="approval_note" placeholder="Комментарий" />
<button type="submit">Apply metadata draft</button>
</form>
{render_html5_metadata_apply_result(project_id)}
"""
def _node_source_text(node: object | None) -> str: def _node_source_text(node: object | None) -> str:
if node is None: if node is None:
return "// Модуль не найден в snapshot.\n// HTML5 IDE показывает серверный fallback без клиентского JS." return "// Модуль не найден в snapshot.\n// HTML5 IDE показывает серверный fallback без клиентского JS."
@@ -0,0 +1,596 @@
from __future__ import annotations
from collections import Counter
from html import escape
from typing import Iterable
from urllib.parse import quote
def render_html5_authoring_changes(project_id: str, changes: Iterable[object] | None) -> str:
if changes is None:
return f"""
<div
class="authoring-panel"
data-html5-authoring-changes
hx-get="/html5/projects/{quote(project_id)}/authoring/changes"
hx-trigger="load"
sse-swap="authoring-changes"
hx-swap="outerHTML"
>
<div class="panel-title">Authoring</div>
<p class="muted padded">Сервер загружает историю рабочих изменений.</p>
</div>
"""
change_list = list(changes)
if not change_list:
body = '<p class="muted padded">Изменений пока нет</p>'
else:
body = "".join(_authoring_change_item(change) for change in change_list[:12])
return f"""
<div
class="authoring-panel"
data-html5-authoring-changes
hx-get="/html5/projects/{quote(project_id)}/authoring/changes"
sse-swap="authoring-changes"
hx-swap="outerHTML"
>
<div class="panel-title">Authoring · {len(change_list)}</div>
{_authoring_changes_summary(change_list)}
{_authoring_recent_change(change_list)}
<div class="review-list">{body}</div>
{render_html5_authoring_change_detail(project_id, None)}
</div>
"""
def render_html5_authoring_preview(project_id: str, preview: object | None, error: str | None = None) -> str:
if preview is None and error is None:
return f"""
<div class="authoring-preview" data-html5-authoring-preview>
<div class="panel-title">Authoring preview</div>
<form
class="authoring-preview-form"
data-html5-authoring-preview-form
method="post"
action="/html5/projects/{quote(project_id)}/authoring/completion-preview"
hx-post="/html5/projects/{quote(project_id)}/authoring/completion-preview"
hx-target="[data-html5-authoring-preview-result]"
hx-swap="outerHTML"
>
<input name="object_name" placeholder="object_name" />
<input name="routine_name" placeholder="routine_name" />
<input name="cursor_line" placeholder="line" />
<input name="intent" value="fill-check" />
<input name="user_id" placeholder="user_id" />
<textarea name="source_text" placeholder="BSL source"></textarea>
<button type="submit">Preview</button>
</form>
{render_html5_authoring_preview_result(project_id)}
<form
class="authoring-preview-form"
data-html5-authoring-diff-form
method="post"
action="/html5/projects/{quote(project_id)}/authoring/semantic-diff-preview"
hx-post="/html5/projects/{quote(project_id)}/authoring/semantic-diff-preview"
hx-target="[data-html5-authoring-diff-result]"
hx-swap="outerHTML"
>
<input name="routine_name" placeholder="routine_name" />
<input name="source_path" placeholder="source_path" />
<input name="task_id" placeholder="task_id" />
<input name="session_id" placeholder="session_id" />
<input name="user_id" placeholder="user_id" />
<textarea name="original_text" placeholder="Original BSL"></textarea>
<textarea name="proposed_text" placeholder="Proposed BSL"></textarea>
<button type="submit">Diff preview</button>
</form>
{render_html5_authoring_diff_result(project_id)}
</div>
"""
return render_html5_authoring_preview_result(project_id, preview, error)
def render_html5_authoring_preview_result(project_id: str, preview: object | None = None, error: str | None = None) -> str:
if preview is None and error is None:
return '<div class="authoring-preview-result" data-html5-authoring-preview-result></div>'
if error:
return f"""
<div class="authoring-preview-result" data-html5-authoring-preview-result>
<div class="panel-title">Preview result</div>
<p class="muted padded">{escape(error)}</p>
</div>
"""
allowed = bool(getattr(preview, "allowed", False))
insert_text = str(getattr(preview, "insert_text", ""))
checks = getattr(preview, "checks", []) or []
diff = getattr(preview, "semantic_diff", []) or []
context = getattr(preview, "context", None)
object_node = getattr(context, "object", None)
routine_node = getattr(context, "routine", None)
object_name = getattr(object_node, "qualified_name", None) or getattr(object_node, "name", None) or "object unavailable"
routine_name = getattr(routine_node, "name", None) or "routine unavailable"
check_rows = "".join(_authoring_check_item(check) for check in checks[:8])
diff_rows = "".join(_authoring_diff_item(line) for line in diff[:8]) or '<p class="muted padded">Diff пустой</p>'
return f"""
<div class="authoring-preview-result" data-html5-authoring-preview-result data-html5-project-id="{escape(project_id)}">
<div class="panel-title">Preview result · {'allowed' if allowed else 'blocked'}</div>
<article class="authoring-change">
<strong>{escape(str(object_name))}</strong>
<span>{escape(str(routine_name))}</span>
<small>{escape(insert_text[:180] or "insert text unavailable")}</small>
</article>
{_authoring_result_summary("allowed" if allowed else "blocked", diff, checks)}
<div class="check-list">{check_rows}</div>
<div class="diff-list">{diff_rows}</div>
</div>
"""
def render_html5_authoring_diff_result(
project_id: str,
preview: object | None = None,
error: str | None = None,
request_payload: dict | None = None,
) -> str:
if preview is None and error is None:
return '<div class="authoring-diff-result" data-html5-authoring-diff-result></div>'
if error:
return f"""
<div class="authoring-diff-result" data-html5-authoring-diff-result>
<div class="panel-title">Diff preview</div>
<p class="muted padded">{escape(error)}</p>
</div>
"""
changed = bool(getattr(preview, "changed", False))
added = getattr(preview, "added_lines", 0)
removed = getattr(preview, "removed_lines", 0)
target = getattr(preview, "target", None)
target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable"
checks = getattr(preview, "checks", []) or []
diff = getattr(preview, "semantic_diff", []) or []
version_preview = getattr(preview, "version_preview", None)
next_version_id = str(getattr(version_preview, "next_version_id", ""))
check_rows = "".join(_authoring_check_item(check) for check in checks[:8])
diff_rows = "".join(_authoring_diff_item(line) for line in diff[:12]) or '<p class="muted padded">Diff пустой</p>'
apply_form = (
_authoring_apply_change_set_form(project_id, request_payload or {}, next_version_id)
if changed and next_version_id
else ""
)
return f"""
<div class="authoring-diff-result" data-html5-authoring-diff-result data-html5-project-id="{escape(project_id)}">
<div class="panel-title">Diff preview · {'changed' if changed else 'unchanged'}</div>
<article class="authoring-change">
<strong>{escape(str(target_name))}</strong>
<span>+{escape(str(added))} / -{escape(str(removed))}</span>
<small>{escape(next_version_id or "version preview unavailable")}</small>
</article>
{_authoring_result_summary("changed" if changed else "unchanged", diff, checks)}
<div class="check-list">{check_rows}</div>
<div class="diff-list">{diff_rows}</div>
{apply_form}
</div>
"""
def render_html5_authoring_apply_result(project_id: str, result: object | None = None, error: str | None = None) -> str:
if result is None and error is None:
return '<div class="authoring-apply-result" data-html5-authoring-apply-result></div>'
if error:
return f"""
<div class="authoring-apply-result" data-html5-authoring-apply-result>
<div class="panel-title">Apply change-set</div>
<p class="muted padded">{escape(error)}</p>
</div>
"""
status = str(getattr(result, "status", "UNKNOWN"))
change_id = str(getattr(result, "change_id", ""))
version = getattr(result, "version", None)
version_id = str(getattr(version, "version_id", ""))
return f"""
<div
class="authoring-apply-result"
data-html5-authoring-apply-result
data-html5-authoring-change="{escape(change_id)}"
data-html5-version-id="{escape(version_id)}"
>
<div class="panel-title">Apply change-set</div>
<article class="authoring-change">
<strong>{escape(status)}</strong>
<span>{escape(change_id)}</span>
<small>{escape(version_id)}</small>
</article>
{_authoring_apply_summary("change-set", status, change_id, version_id)}
<p class="muted padded">Change-set применен в workspace для проекта {escape(project_id)}.</p>
</div>
"""
def render_html5_metadata_authoring(project_id: str) -> str:
return f"""
<div class="authoring-preview" data-html5-metadata-authoring>
<div class="panel-title">Metadata draft</div>
<form
class="authoring-preview-form"
data-html5-metadata-preview-form
method="post"
action="/html5/projects/{quote(project_id)}/authoring/metadata-object-preview"
hx-post="/html5/projects/{quote(project_id)}/authoring/metadata-object-preview"
hx-target="[data-html5-metadata-preview-result]"
hx-swap="outerHTML"
>
<input name="object_kind" value="DOCUMENT" />
<input name="name" placeholder="Имя объекта" required />
<input name="synonym" placeholder="Синоним" />
<input name="attributes" placeholder="Реквизиты: Имя:Тип, ..." />
<input name="tabular_sections" placeholder="ТЧ: Товары[Номенклатура:Строка;Количество:Число]" />
<input name="forms" placeholder="Формы через запятую" />
<input name="commands" placeholder="Команды: Имя:Обработчик" />
<input name="task_id" placeholder="task_id" />
<input name="session_id" placeholder="session_id" />
<input name="user_id" placeholder="user_id" />
<button type="submit">Metadata preview</button>
</form>
{render_html5_metadata_preview_result(project_id)}
</div>
"""
def render_html5_metadata_preview_result(
project_id: str,
preview: object | None = None,
error: str | None = None,
request_payload: dict | None = None,
) -> str:
if preview is None and error is None:
return '<div class="metadata-preview-result" data-html5-metadata-preview-result></div>'
if error:
return f"""
<div class="metadata-preview-result" data-html5-metadata-preview-result>
<div class="panel-title">Metadata preview</div>
<p class="muted padded">{escape(error)}</p>
</div>
"""
changed = bool(getattr(preview, "changed", False))
added = getattr(preview, "added_lines", 0)
target = getattr(preview, "target", None)
target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable"
checks = getattr(preview, "checks", []) or []
diff = getattr(preview, "semantic_diff", []) or []
version_preview = getattr(preview, "version_preview", None)
next_version_id = str(getattr(version_preview, "next_version_id", ""))
check_rows = "".join(_authoring_check_item(check) for check in checks[:8])
diff_rows = "".join(_authoring_diff_item(line) for line in diff[:12]) or '<p class="muted padded">Diff пустой</p>'
apply_form = (
_metadata_apply_form(project_id, request_payload or {}, next_version_id)
if changed and next_version_id
else ""
)
return f"""
<div class="metadata-preview-result" data-html5-metadata-preview-result data-html5-project-id="{escape(project_id)}">
<div class="panel-title">Metadata preview · {'changed' if changed else 'unchanged'}</div>
<article class="authoring-change">
<strong>{escape(str(target_name))}</strong>
<span>+{escape(str(added))} / -0</span>
<small>{escape(next_version_id or "version preview unavailable")}</small>
</article>
{_authoring_result_summary("changed" if changed else "unchanged", diff, checks)}
<div class="check-list">{check_rows}</div>
<div class="diff-list">{diff_rows}</div>
{apply_form}
</div>
"""
def render_html5_metadata_apply_result(project_id: str, result: object | None = None, error: str | None = None) -> str:
if result is None and error is None:
return '<div class="metadata-apply-result" data-html5-metadata-apply-result></div>'
if error:
return f"""
<div class="metadata-apply-result" data-html5-metadata-apply-result>
<div class="panel-title">Metadata apply</div>
<p class="muted padded">{escape(error)}</p>
</div>
"""
status = str(getattr(result, "status", "UNKNOWN"))
change_id = str(getattr(result, "change_id", ""))
version = getattr(result, "version", None)
version_id = str(getattr(version, "version_id", ""))
return f"""
<div
class="metadata-apply-result"
data-html5-metadata-apply-result
data-html5-authoring-change="{escape(change_id)}"
data-html5-version-id="{escape(version_id)}"
>
<div class="panel-title">Metadata apply</div>
<article class="authoring-change">
<strong>{escape(status)}</strong>
<span>{escape(change_id)}</span>
<small>{escape(version_id)}</small>
</article>
{_authoring_apply_summary("metadata", status, change_id, version_id)}
<p class="muted padded">Metadata draft применен в workspace для проекта {escape(project_id)}.</p>
</div>
"""
def render_html5_authoring_change_detail(project_id: str, preview: object | None) -> str:
if preview is None:
return f"""
<div class="authoring-detail" data-html5-authoring-detail>
<div class="panel-title">Rollback preview</div>
<p class="muted padded">Выберите изменение, чтобы сервер рассчитал rollback diff для проекта {escape(project_id)}.</p>
</div>
"""
change_id = str(getattr(preview, "change_id", ""))
original_version_id = str(getattr(preview, "original_version_id", ""))
rollback_version_id = str(getattr(preview, "rollback_version_id", ""))
target = getattr(preview, "target", None)
target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable"
diff = getattr(preview, "semantic_diff", []) or []
checks = getattr(preview, "checks", []) or []
apply_available = bool(getattr(preview, "apply_available", False))
diff_rows = "".join(_authoring_diff_item(line) for line in diff[:12]) or '<p class="muted padded">Diff пустой</p>'
check_rows = "".join(_authoring_check_item(check) for check in checks[:8])
apply_form = _authoring_rollback_form(project_id, change_id, rollback_version_id) if apply_available else ""
return f"""
<div
class="authoring-detail"
data-html5-authoring-detail
data-html5-authoring-change="{escape(change_id)}"
>
<div class="panel-title">Rollback preview · {'ready' if apply_available else 'blocked'}</div>
<article class="authoring-change">
<strong>{escape(str(target_name))}</strong>
<span>{escape(original_version_id)} -> {escape(rollback_version_id)}</span>
<small>{escape(change_id)}</small>
</article>
{_authoring_detail_summary(diff, checks, apply_available)}
<div class="check-list">{check_rows}</div>
<div class="diff-list">{diff_rows}</div>
{apply_form}
</div>
"""
def render_html5_authoring_rollback_result(project_id: str, result: object | None = None, error: str | None = None) -> str:
if result is None and error is None:
return '<div class="authoring-result" data-html5-authoring-result></div>'
if error:
return f"""
<div class="authoring-result" data-html5-authoring-result>
<div class="panel-title">Rollback apply</div>
<p class="muted padded">{escape(error)}</p>
</div>
"""
status = str(getattr(result, "status", "UNKNOWN"))
change_id = str(getattr(result, "change_id", ""))
rollback_change_id = str(getattr(result, "rollback_change_id", ""))
version = getattr(result, "version", None)
version_id = str(getattr(version, "version_id", ""))
return f"""
<div
class="authoring-result"
data-html5-authoring-result
data-html5-authoring-change="{escape(change_id)}"
data-html5-version-id="{escape(version_id)}"
>
<div class="panel-title">Rollback apply</div>
<article class="authoring-change">
<strong>{escape(status)}</strong>
<span>{escape(rollback_change_id)}</span>
<small>{escape(version_id)}</small>
</article>
{_authoring_apply_summary("rollback", status, rollback_change_id or change_id, version_id)}
<p class="muted padded">Rollback применен в workspace для проекта {escape(project_id)}.</p>
</div>
"""
def _authoring_change_item(change: object) -> str:
change_id = str(getattr(change, "change_id", ""))
status = str(getattr(change, "status", ""))
target = getattr(change, "target", None)
target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable"
approved_by = str(getattr(change, "approved_by", "") or "not approved")
task_id = str(getattr(change, "task_id", "") or "no task")
added = getattr(change, "added_lines", 0)
removed = getattr(change, "removed_lines", 0)
production = "production" if bool(getattr(change, "production_applied", False)) else "workspace"
project_id = str(getattr(change, "project_id", ""))
detail_attrs = (
f'hx-get="/html5/projects/{quote(project_id)}/authoring/changes/{quote(change_id, safe="")}" '
'hx-target="[data-html5-authoring-detail]" hx-swap="outerHTML"'
if change_id and project_id
else ""
)
return f"""
<article class="authoring-change" data-html5-authoring-change="{escape(change_id)}" {detail_attrs}>
<strong>{escape(str(target_name))}</strong>
<span>{escape(status)} · +{escape(str(added))} / -{escape(str(removed))} · {escape(production)}</span>
<small>{escape(task_id)} · {escape(approved_by)} · {escape(change_id)}</small>
</article>
"""
def _authoring_changes_summary(changes: Iterable[object]) -> str:
change_list = list(changes)
statuses = Counter(str(getattr(change, "status", "") or "UNKNOWN") for change in change_list)
production = sum(1 for change in change_list if bool(getattr(change, "production_applied", False)))
workspace = len(change_list) - production
added = sum(int(getattr(change, "added_lines", 0) or 0) for change in change_list)
removed = sum(int(getattr(change, "removed_lines", 0) or 0) for change in change_list)
status_text = ", ".join(f"{name}: {count}" for name, count in sorted(statuses.items())) or "no changes"
return f"""
<p class="authoring-summary" data-html5-authoring-summary>
{escape(str(len(change_list)))} changes · {escape(status_text)} · {escape(str(workspace))} workspace · {escape(str(production))} production · +{escape(str(added))} / -{escape(str(removed))}
</p>
"""
def _authoring_recent_change(changes: Iterable[object]) -> str:
change_list = list(changes)
if not change_list:
return ""
latest = change_list[0]
change_id = str(getattr(latest, "change_id", ""))
status = str(getattr(latest, "status", "") or "UNKNOWN")
target = getattr(latest, "target", None)
target_name = getattr(target, "qualified_name", None) or getattr(target, "name", None) or "target unavailable"
version = getattr(latest, "version", None)
version_id = str(getattr(latest, "version_id", "") or getattr(version, "version_id", "") or "version unavailable")
approved_by = str(getattr(latest, "approved_by", "") or "not approved")
return f"""
<article class="authoring-change" data-html5-authoring-recent-change="{escape(change_id)}">
<strong>{escape(status)} · {escape(str(target_name))}</strong>
<span>{escape(version_id)}</span>
<small>{escape(approved_by)} · {escape(change_id)}</small>
</article>
"""
def _authoring_result_summary(state: str, diff: Iterable[object], checks: Iterable[object]) -> str:
diff_list = list(diff)
check_list = list(checks)
diff_kinds = Counter(str(getattr(line, "kind", "") or "CHANGE") for line in diff_list)
check_statuses = Counter(str(getattr(check, "status", "") or "UNKNOWN") for check in check_list)
added = sum(1 for line in diff_list if str(getattr(line, "kind", "")) == "ADD")
removed = sum(1 for line in diff_list if str(getattr(line, "kind", "")) == "REMOVE")
diff_text = ", ".join(f"{name}: {count}" for name, count in sorted(diff_kinds.items())) or "empty diff"
check_text = ", ".join(f"{name}: {count}" for name, count in sorted(check_statuses.items())) or "no checks"
return f"""
<p class="authoring-summary" data-html5-authoring-result-summary>
{escape(state)} · {escape(str(len(diff_list)))} diff lines · +{escape(str(added))} / -{escape(str(removed))} · {escape(diff_text)} · {escape(check_text)}
</p>
"""
def _authoring_detail_summary(diff: Iterable[object], checks: Iterable[object], apply_available: bool) -> str:
diff_list = list(diff)
check_list = list(checks)
diff_kinds = Counter(str(getattr(line, "kind", "") or "CHANGE") for line in diff_list)
check_statuses = Counter(str(getattr(check, "status", "") or "UNKNOWN") for check in check_list)
added = sum(1 for line in diff_list if str(getattr(line, "kind", "")) == "ADD")
removed = sum(1 for line in diff_list if str(getattr(line, "kind", "")) == "REMOVE")
diff_text = ", ".join(f"{name}: {count}" for name, count in sorted(diff_kinds.items())) or "empty diff"
check_text = ", ".join(f"{name}: {count}" for name, count in sorted(check_statuses.items())) or "no checks"
state = "rollback ready" if apply_available else "rollback blocked"
return f"""
<p class="authoring-summary" data-html5-authoring-detail-summary>
{escape(state)} · {escape(str(len(diff_list)))} diff lines · +{escape(str(added))} / -{escape(str(removed))} · {escape(diff_text)} · {escape(check_text)}
</p>
"""
def _authoring_apply_summary(kind: str, status: str, change_id: str, version_id: str) -> str:
return f"""
<p
class="authoring-summary"
data-html5-authoring-apply-summary
data-html5-authoring-apply-kind="{escape(kind)}"
>
{escape(kind)} · {escape(status or "UNKNOWN")} · {escape(change_id or "change unavailable")} · {escape(version_id or "version unavailable")}
</p>
"""
def _authoring_diff_item(line: object) -> str:
kind = str(getattr(line, "kind", ""))
text = str(getattr(line, "text", ""))
return f"""
<article class="diff-item" data-html5-authoring-diff="{escape(kind)}">
<span>{escape(kind)}</span>
<code>{escape(text)}</code>
</article>
"""
def _authoring_check_item(check: object) -> str:
name = str(getattr(check, "name", "check"))
status = str(getattr(check, "status", "UNKNOWN"))
message = str(getattr(check, "message", ""))
return f"""
<article class="check-item" data-html5-authoring-check="{escape(status)}">
<strong>{escape(name)}</strong>
<span>{escape(status)}</span>
<small>{escape(message)}</small>
</article>
"""
def _authoring_rollback_form(project_id: str, change_id: str, rollback_version_id: str) -> str:
return f"""
<form
class="rollback-form"
data-html5-authoring-rollback-form
method="post"
action="/html5/projects/{quote(project_id)}/authoring/changes/{quote(change_id, safe="")}/apply-rollback"
hx-post="/html5/projects/{quote(project_id)}/authoring/changes/{quote(change_id, safe="")}/apply-rollback"
hx-target="[data-html5-authoring-result]"
hx-swap="outerHTML"
>
<input type="hidden" name="expected_rollback_version_id" value="{escape(rollback_version_id)}" />
<input name="approved_by" placeholder="approved_by" required />
<input name="task_id" placeholder="task_id" />
<input name="session_id" placeholder="session_id" />
<input name="approval_note" placeholder="Комментарий" />
<button type="submit">Apply rollback</button>
</form>
{render_html5_authoring_rollback_result(project_id)}
"""
def _authoring_apply_change_set_form(project_id: str, payload: dict, next_version_id: str) -> str:
return f"""
<form
class="authoring-preview-form"
data-html5-authoring-apply-form
method="post"
action="/html5/projects/{quote(project_id)}/authoring/apply-change-set"
hx-post="/html5/projects/{quote(project_id)}/authoring/apply-change-set"
hx-target="[data-html5-authoring-apply-result]"
hx-swap="outerHTML"
>
<input type="hidden" name="routine_name" value="{escape(str(payload.get("routine_name") or ""))}" />
<input type="hidden" name="source_path" value="{escape(str(payload.get("source_path") or ""))}" />
<textarea hidden name="original_text">{escape(str(payload.get("original_text") or ""))}</textarea>
<textarea hidden name="proposed_text">{escape(str(payload.get("proposed_text") or ""))}</textarea>
<input type="hidden" name="task_id" value="{escape(str(payload.get("task_id") or ""))}" />
<input type="hidden" name="session_id" value="{escape(str(payload.get("session_id") or ""))}" />
<input type="hidden" name="user_id" value="{escape(str(payload.get("user_id") or ""))}" />
<input type="hidden" name="expected_next_version_id" value="{escape(next_version_id)}" />
<input name="approved_by" placeholder="approved_by" required />
<input name="approval_note" placeholder="Комментарий" />
<button type="submit">Apply change-set</button>
</form>
{render_html5_authoring_apply_result(project_id)}
"""
def _metadata_apply_form(project_id: str, payload: dict, next_version_id: str) -> str:
return f"""
<form
class="authoring-preview-form"
data-html5-metadata-apply-form
method="post"
action="/html5/projects/{quote(project_id)}/authoring/apply-metadata-object"
hx-post="/html5/projects/{quote(project_id)}/authoring/apply-metadata-object"
hx-target="[data-html5-metadata-apply-result]"
hx-swap="outerHTML"
>
<input type="hidden" name="object_kind" value="{escape(str(payload.get("object_kind") or ""))}" />
<input type="hidden" name="name" value="{escape(str(payload.get("name") or ""))}" />
<input type="hidden" name="synonym" value="{escape(str(payload.get("synonym") or ""))}" />
<input type="hidden" name="attributes" value="{escape(str(payload.get("_raw_attributes") or ""))}" />
<input type="hidden" name="tabular_sections" value="{escape(str(payload.get("_raw_tabular_sections") or ""))}" />
<input type="hidden" name="forms" value="{escape(str(payload.get("_raw_forms") or ""))}" />
<input type="hidden" name="commands" value="{escape(str(payload.get("_raw_commands") or ""))}" />
<input type="hidden" name="task_id" value="{escape(str(payload.get("task_id") or ""))}" />
<input type="hidden" name="session_id" value="{escape(str(payload.get("session_id") or ""))}" />
<input type="hidden" name="user_id" value="{escape(str(payload.get("user_id") or ""))}" />
<input type="hidden" name="expected_next_version_id" value="{escape(next_version_id)}" />
<input name="approved_by" placeholder="approved_by" required />
<input name="approval_note" placeholder="Комментарий" />
<button type="submit">Apply metadata draft</button>
</form>
{render_html5_metadata_apply_result(project_id)}
"""
+10 -8
View File
@@ -38,17 +38,9 @@ from neo4j import AsyncGraphDatabase
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from api_server.html5 import ( from api_server.html5 import (
render_html5_authoring_apply_result,
render_html5_authoring_changes,
render_html5_authoring_change_detail,
render_html5_authoring_diff_result,
render_html5_authoring_preview_result,
render_html5_authoring_rollback_result,
render_html5_editor, render_html5_editor,
render_html5_flowchart, render_html5_flowchart,
render_html5_index, render_html5_index,
render_html5_metadata_apply_result,
render_html5_metadata_preview_result,
render_html5_object_context, render_html5_object_context,
render_html5_object_report, render_html5_object_report,
render_html5_project_rows, render_html5_project_rows,
@@ -59,6 +51,16 @@ from api_server.html5 import (
render_html5_status, render_html5_status,
render_html5_symbols, render_html5_symbols,
) )
from api_server.html5_authoring import (
render_html5_authoring_apply_result,
render_html5_authoring_changes,
render_html5_authoring_change_detail,
render_html5_authoring_diff_result,
render_html5_authoring_preview_result,
render_html5_authoring_rollback_result,
render_html5_metadata_apply_result,
render_html5_metadata_preview_result,
)
from api_server.html5_operations import ( from api_server.html5_operations import (
render_html5_operation_detail, render_html5_operation_detail,
render_html5_operation_rows, render_html5_operation_rows,