Skip to content

Commit 6b356ce

Browse files
authored
Replace project and version KO views (#532)
This solves a few issues and explores a pattern that we need to figure out in order to replace Knockout views with web components: authoring and connection between nested web components. This is a low stakes place to try this. The issues around the buttons were addressed through adding a click wrapper to the link. If you accidentally trigger the link before the API response, the click event is stored and re-emitted after the response finishes. The pattern being explored here is using the web component context API. This is implemented in Lit already. You can read more here: - https://lit.dev/docs/data/context/ - https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md So far, we have been trying out manually using events to pass around data at a global level. The context API functions similar under the hood, but the API is a much cleaner way to pass data around, and it functions only between parent and child elements, not globally. Where this differs from global events is that we can use this pattern between parent and child elements, and we don't need to manually search the DOM for the parent element: ```html <readthedocs-api url="..."> <div> <div> <div> <readthedocs-item-docs> This element will traverse up the tree and listen to only the first readthedocs-api provider </readthedocs-item-docs> </div> </div> </div> </readthedocs-api url="..."> <readthedocs-api url="..."> <readthedocs-item-docs> And this element will only listen for data provided by that second readthedocs-api provider </readthedocs-item-docs> </readthedocs-api> ``` ## Why? We want to author as little as we need to in JS, and as much as we can in templates. Using the context API allows us to author isolated web components that share data _while still using Django templates for as much of our page structure as we can_. The alternative is authoring everything nested in `readthedocs-api` in a big web component, which is not a good pattern for a Django backend. The work here so far this is a very basic example and the provider/consumer elements are close in proximity in the DOM. A more complex example would be a table listing, where the table row is a data provider and elements nested anywhere in that row element can share data. We want to keep as much of that row in Django templates as possible, especially if that row is 95% vanilla HTML elements and 5% dynamic web components. ---- Things I did not do here: - Deprecate the older elements using the same base - Remove old project/version KO views - Split up code This will come next. ---- - Fixes #444 - Fixes #497
1 parent 7560d81 commit 6b356ce

File tree

12 files changed

+550
-174
lines changed

12 files changed

+550
-174
lines changed

.prettierignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Don't do templates at all. Prettier is opinionated and there is extra Django
22
# pieces that would be mangled by Prettier.
33
*.html
4-
!/src/js/tests/**/*.html
4+
/src/js/tests/**/*.html
55
/docs/_build/
66
/docs/out/
77
/build/

package-lock.json

Lines changed: 62 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
"@babel/plugin-proposal-class-properties": "^7.16.7",
2424
"@babel/plugin-transform-modules-commonjs": "^7.23.0",
2525
"@babel/preset-env": "^7.22.20",
26+
"@lit/context": "^1.1.3",
27+
"@lit/localize": "^0.12.2",
2628
"@open-wc/testing": "^3.2.0",
2729
"@readthedocs/sui-common-theme": "github:readthedocs/sui-common-theme#12629bb",
2830
"@rollup/plugin-commonjs": "^25.0.4",

readthedocsext/theme/static/readthedocsext/theme/css/site.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

readthedocsext/theme/static/readthedocsext/theme/js/site.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

readthedocsext/theme/static/readthedocsext/theme/js/vendor.js

Lines changed: 61 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

readthedocsext/theme/templates/builds/partials/version_list.html

Lines changed: 35 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -34,83 +34,43 @@
3434
{% endif %}
3535
{% endblock list_placeholder_text_empty %}
3636

37-
{% block list_item_start %}
38-
{# This confuses the formatter as it's just an opening element #}
39-
{# djlint: off #}
40-
<tr class="middle aligned"
41-
data-bind="using: VersionListItemView({id: {{ object.pk }}, url: '{% url "projects-versions-detail" object.project.slug object.slug %}'})">
42-
{# djlint: on #}
43-
{% endblock list_item_start %}
44-
4537
{% block list_item_right_menu %}
46-
<div class="ui small icon buttons">
47-
48-
{% blocktrans trimmed with version=object.verbose_name asvar label_link %}
49-
View documentation for version {{ version }}
50-
{% endblocktrans %}
51-
<a class="ui {% if not object.built %}disabled{% endif %} button"
52-
data-bind="event: {mouseover: fetch, focusin: fetch}, attr: {href: url_docs}"
53-
target="_blank"
54-
data-content="{{ label_link }}"
55-
aria-label="{{ label_link }}"
56-
tabindex="{% if object.built %}0{% else %}-1{% endif %}">
57-
<i class="fa-solid fa-book icon"></i>
58-
</a>
59-
60-
{% blocktrans trimmed with version=object.verbose_name asvar label_download %}
61-
Offline formats for version {{ version }}
62-
{% endblocktrans %}
63-
<button class="ui {% if not object.has_epub and not object.has_htmlzip and not object.has_pdf %} disabled{% endif %} dropdown button"
64-
data-bind="event: {focusin: fetch, click: fetch}"
65-
data-content="{{ label_download }}"
66-
aria-label="{{ label_download }}"
67-
tabindex="{% if object.has_epub or object.has_htmlzip or object.has_pdf %}0{% else %}-1{% endif %}">
68-
<i class="fa-solid fa-download icon"></i>
69-
<div class="menu">
70-
<div class="header">{% trans "Offline formats" %}</div>
71-
<a class="disabled item"
72-
data-bind="css: {disabled: !url_pdf()}, attr: {href: url_pdf()}">
73-
<i class="fa-duotone fa-file icon"></i>
74-
{% trans "PDF file" %}
75-
</a>
76-
<a class="disabled item"
77-
data-bind="css: {disabled: !url_epub()}, attr: {href: url_epub()}">
78-
<i class="fa-duotone fa-file icon"></i>
79-
{% trans "ePUB file" %}
80-
</a>
81-
<a class="disabled item"
82-
data-bind="css: {disabled: !url_html()}, attr: {href: url_html()}">
83-
<i class="fa-duotone fa-file-zipper icon"></i>
84-
{% trans "HTML archive" %}
85-
</a>
86-
</div>
87-
</button>
88-
89-
{% blocktrans trimmed with version=object.verbose_name asvar label_admin %}
90-
Additional options for version {{ version }}
91-
{% endblocktrans %}
92-
{% if is_project_admin %}
93-
<div class="ui dropdown button"
94-
data-content="{{ label_admin }}"
95-
aria-label="{{ label_admin }}">
96-
<i class="fa-solid fa-ellipsis icon"></i>
97-
<div class="menu">
98-
<div class="header">{% trans "Admin" %}</div>
99-
<readthedocs-menu-build-rebuild class="item"
100-
url="{% url "projects-versions-builds-list" object.project.slug object.slug %}"
101-
csrf-token="{{ csrf_token }}">
102-
<i class="fa-duotone fa-refresh icon"></i>
103-
{% trans "Rebuild version" %}
104-
</readthedocs-menu-build-rebuild>
105-
<a class="item"
106-
href="{% url "project_version_detail" project.slug object.slug %}">
107-
<i class="fa-duotone fa-wrench icon"></i>
108-
{% trans "Configure version" %}
109-
</a>
38+
<readthedocs-api csrf-token="{{ csrf_token }}"
39+
url="{% url "projects-versions-detail" object.project.slug object.slug %}">
40+
<div class="ui small icon buttons">
41+
<readthedocs-item-docs {% if not object.built %}disabled{% endif %}
42+
label="{% blocktrans with name=object.verbose_name %}View documentation for version {{ name }}{% endblocktrans %}">
43+
</readthedocs-item-docs>
44+
<readthedocs-item-downloads {% if not object.has_pdf and not object.has_epub and not object.has_htmlzip %}disabled{% endif %}
45+
label="{% blocktrans with name=object.verbose_name %}Offline formats for version {{ name }}{% endblocktrans %}">
46+
</readthedocs-item-downloads>
47+
48+
{% blocktrans trimmed with name=object.verbose_name asvar label_admin %}
49+
Additional options for version {{ name }}
50+
{% endblocktrans %}
51+
{% if is_project_admin %}
52+
<div class="ui dropdown button"
53+
data-content="{{ label_admin }}"
54+
aria-label="{{ label_admin }}">
55+
<i class="fa-solid fa-ellipsis icon"></i>
56+
<div class="menu">
57+
<div class="header">{% trans "Admin" %}</div>
58+
<readthedocs-menu-build-rebuild class="item"
59+
url="{% url "projects-versions-builds-list" object.project.slug object.slug %}"
60+
csrf-token="{{ csrf_token }}">
61+
<i class="fa-duotone fa-refresh icon"></i>
62+
{% trans "Rebuild version" %}
63+
</readthedocs-menu-build-rebuild>
64+
<a class="item"
65+
href="{% url "project_version_detail" project.slug object.slug %}">
66+
<i class="fa-duotone fa-wrench icon"></i>
67+
{% trans "Configure version" %}
68+
</a>
69+
</div>
11070
</div>
111-
</div>
112-
{% endif %}
113-
</div>
71+
{% endif %}
72+
</div>
73+
</readthedocs-api>
11474
{% endblock list_item_right_menu %}
11575

11676
{% block list_item_icon %}

readthedocsext/theme/templates/profiles/partials/profile_project_list.html

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
{% extends "projects/partials/project_list.html" %}
22

3-
{% load i18n %}
3+
{% load blocktrans from i18n %}
44

55
{# TODO drop this override if we add filtering to the profile page #}
66
{% block top_menu %}
7-
{# Pass Django template tag specific configuration into ``ProjectListView`` #}
8-
<script type="application/json" data-bind="jsonInit: config">
9-
{
10-
"api_url": "{% url "projects-list" %}"
11-
}
12-
</script>
137
{% endblock top_menu %}
148

159
{% block create_button %}

readthedocsext/theme/templates/projects/partials/project_list.html

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -29,41 +29,20 @@
2929
class="ui primary button">{% trans "Learn how to get started" %}</a>
3030
{% endblock list_placeholder_text_empty %}
3131

32-
{% block list_item_start %}
33-
{# djlint:off #}
34-
<tr data-bind="using: ProjectListItemView({id: {{ object.id }}, url: '{% url "projects-detail" object.slug %}'})">
35-
{# djlint:on #}
36-
{% endblock list_item_start %}
37-
3832
{% block list_item_right_menu %}
39-
<div class="ui small icon buttons"
40-
data-bind="event: {mouseover: fetch, focusin: fetch}">
41-
{% blocktrans trimmed with project=object.name asvar label_link %}
42-
View documentation for project {{ project }}
43-
{% endblocktrans %}
44-
<a class="ui {% if not object.has_good_build %}disabled{% endif %} button"
45-
data-content="{{ label_link }}"
46-
aria-label="{{ label_link }}"
47-
data-bind="attr: {href: url_docs}"
48-
target="_blank"
49-
tabindex="{% if object.has_good_build %}0{% else %}-1{% endif %}">
50-
<i class="fa-duotone fa-book icon"></i>
51-
</a>
52-
53-
<button class="ui dropdown button">
54-
<i class="fa-solid fa-ellipsis icon"></i>
55-
<div class="menu">
56-
<div class="header">{% trans "Admin" %}</div>
57-
<a class="disabled item"
58-
href="{% url "projects_edit" object.slug %}"
59-
data-bind="css: { disabled: !is_admin() }">
60-
<i class="fa-duotone fa-wrench icon"></i>
61-
{% trans "Configure project" %}
62-
</a>
63-
</div>
64-
</button>
65-
66-
</div>
33+
<readthedocs-api csrf-token="{{ csrf_token }}"
34+
url="{% url "projects-detail" object.slug %}?expand=permissions">
35+
<div class="ui small icon buttons">
36+
<readthedocs-item-docs {% if not object.has_good_build %}disabled{% endif %}
37+
label="{% blocktrans with name=object.name %}View documentation for project {{ name }}{% endblocktrans %}">
38+
</readthedocs-item-docs>
39+
<button class="ui dropdown button">
40+
<i class="fa-solid fa-ellipsis icon"></i>
41+
<readthedocs-menu-project-admin class="menu" url-settings="{% url "projects_edit" object.slug %}">
42+
</readthedocs-menu-project-admin>
43+
</button>
44+
</div>
45+
</readthedocs-api>
6746
{% endblock list_item_right_menu %}
6847

6948
{% block list_item_image %}

0 commit comments

Comments
 (0)