Skip to content

Commit 2ca4b1f

Browse files
authored
Organization: auto populate slug on creation (#623)
Show the `slug` field when creating the organization and auto-populate it based on the name of the organization. [Peek 2025-07-03 17-43.webm](https://github.com/user-attachments/assets/1addb2d3-056f-48db-89d0-19dbe6a4510b) Requires readthedocs/readthedocs.org#12297 Closes #25
1 parent 2dbdac7 commit 2ca4b1f

File tree

3 files changed

+62
-21
lines changed

3 files changed

+62
-21
lines changed

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/templates/organizations/organization_form.html

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
{% extends "organizations/base.html" %}
22

3-
{% load i18n %}
4-
{% load crispy_forms_tags %}
3+
{% load trans blocktrans from i18n %}
4+
{% load crispy from crispy_forms_tags %}
5+
{% load alter_field from ext_theme_tags %}
56

67
{% block title %}
78
{% blocktrans %}Create a new organization{% endblocktrans %}
8-
{% endblock %}
9+
{% endblock title %}
910

1011
{% block content %}
1112
<div class="ui very padded container">
1213
<div class="ui stackable centered grid">
1314
<div class="eight wide computer sixteen wide tablet left aligned column">
1415

15-
<h2 class="ui medium heading">
16-
{% trans "Join or create an organization" %}
17-
</h2>
16+
<h2 class="ui medium heading">{% trans "Join or create an organization" %}</h2>
1817

1918
<div class="ui segment">
2019
<div class="ui link list" data-bind="semanticui: { tabmenu: {} }">
@@ -28,9 +27,7 @@ <h2 class="ui medium heading">
2827
<a class="item" data-tab="registered">
2928
<i class="fad fa-building big icon"></i>
3029
<div class="content">
31-
<div class="header">
32-
{% trans "Join an existing organization" %}
33-
</div>
30+
<div class="header">{% trans "Join an existing organization" %}</div>
3431
<div class="description">
3532
<p>
3633
{% blocktrans trimmed %}
@@ -44,9 +41,7 @@ <h2 class="ui medium heading">
4441
<a class="item" data-tab="new">
4542
<i class="fad fa-plus big icon"></i>
4643
<div class="content">
47-
<div class="header">
48-
{% trans "Create a new organization" %}
49-
</div>
44+
<div class="header">{% trans "Create a new organization" %}</div>
5045
<div class="description">
5146
<p>
5247
{% blocktrans trimmed %}
@@ -60,9 +55,7 @@ <h2 class="ui medium heading">
6055
<a class="item" data-tab="not-sure">
6156
<i class="fad fa-question big icon"></i>
6257
<div class="content">
63-
<div class="header">
64-
{% trans "I'm not sure" %}
65-
</div>
58+
<div class="header">{% trans "I'm not sure" %}</div>
6659
<div class="description">
6760
<p>
6861
{% blocktrans trimmed %}
@@ -90,11 +83,24 @@ <h2 class="ui medium heading">
9083
{% endblocktrans %}
9184
</div>
9285

93-
<div class="ui tab" data-tab="new">
86+
<div class="ui tab"
87+
data-tab="new"
88+
data-bind="using: new OrganizationCreateView()">
9489
<form class="ui form" action="" method="post">
9590
{% csrf_token %}
91+
{% alter_field form.name data_bind="valueInit: name, textInput: name" %}
92+
{% alter_field form.slug data_bind="valueInit:slug, text: slug, textInput: slug" %}
9693
{{ form|crispy }}
9794

95+
<div class="ui segment">
96+
<div class="ui list">
97+
<div class="item">
98+
<div class="sub header">{% trans "Example URL for a project" %}</div>
99+
<code data-bind="text: example"></code>
100+
</div>
101+
</div>
102+
</div>
103+
98104
<button class="ui primary button" type="submit">
99105
{% trans "Create organization" %}
100106
</button>
@@ -118,9 +124,7 @@ <h2 class="ui medium heading">
118124

119125
<div class="basic card">
120126
<div class="content">
121-
<div class="header">
122-
{% trans "30 day trial" %}
123-
</div>
127+
<div class="header">{% trans "30 day trial" %}</div>
124128
<div class="description">
125129
{% blocktrans %}
126130
No need to decide now. You can pick a plan that fits your
@@ -134,4 +138,4 @@ <h2 class="ui medium heading">
134138
</div>
135139
</div>
136140
</div>
137-
{% endblock %}
141+
{% endblock content %}

src/js/organization/index.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,40 @@ export class OrganizationSettingsAuthorizationView {
3939
}
4040
}
4141
Registry.add_view(OrganizationSettingsAuthorizationView);
42+
43+
/**
44+
* Organization creation
45+
*/
46+
export class OrganizationCreateView {
47+
static view_name = "OrganizationCreateView";
48+
49+
constructor() {
50+
this.name = ko.observable();
51+
this.slug = ko.observable();
52+
this.name.subscribe((name) => {
53+
const slugified = this.slugify(name);
54+
this.slug(slugified);
55+
});
56+
this.example = ko.computed(() => {
57+
const slug = this.slug() || "organization";
58+
const example = "https://" + slug + "-project.readthedocs-hosted.com";
59+
return example;
60+
});
61+
}
62+
63+
slugify(val) {
64+
if (!val) return "";
65+
return (
66+
val
67+
.toString()
68+
.toLowerCase()
69+
.trim()
70+
// Replace spaces, non-word chars, underscores and dashes with a single '-'
71+
// Copied from Django, which is what we are using under the hood
72+
// https://github.com/django/django/blob/1e9db35/django/utils/text.py#L469-L470
73+
.replace(/[^\w\s-_]+/g, "-")
74+
.replace(/[-\s]+/g, "-")
75+
);
76+
}
77+
}
78+
Registry.add_view(OrganizationCreateView);

0 commit comments

Comments
 (0)