Make the project page an SEO-optimized overview with a devlog environment
The project detail page becomes a professional project overview: hero with status/type/dates/forked-from, a stats strip (stars, updates, comments, files, forks with #devlog/#comments anchors), an About section, platforms, and the Devlog timeline under a proper h2 - now rendered with feed.css loaded so the post cards are actually styled. The owner posts updates from the page itself: a Post update button opens the shared create-post composer preset to the devlog topic and this project. The composer form is extracted into _post_composer_form.html and reused by feed.html - one form, two surfaces. SEO: software_application_schema is type-aware via project_schema_type (game -> VideoGame with gamePlatform, website -> WebApplication, software/mobile_app -> SoftwareApplication, game_asset -> CreativeWork) and now carries keywords, image, an aggregateRating from stars, and a comment InteractionCounter. project_devlog_schema emits a Blog node with one BlogPosting per devlog entry. The detail route feeds both, adds meta keywords, and exposes the devlog cursor as rel=next; the sitemap's project lastmod follows the newest devlog post via one grouped query. devlog_count/comment_count ride ProjectDetailOut, the docs projects-detail endpoint documents the before cursor, and the routers/projects CLAUDE.md, templates CLAUDE.md and README document the new surface. Unit, api and e2e tests cover the schema mapping, the JSON-LD in the rendered page, the stats strip, and the preset composer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
{# Shared create-post form. Locals: _composer_topic (preselected topic), _composer_project (preselected project uid or ""). #}
|
||||
<form id="create-post-form" method="POST" action="/posts/create" enctype="multipart/form-data">
|
||||
{% set _topics = TOPICS %}{% set _selected = _composer_topic or 'random' %}{% include "_topic_selector.html" %}
|
||||
|
||||
<div class="auth-field auth-field-gap">
|
||||
<label for="post-content">What are you sharing?</label>
|
||||
<textarea id="post-content" name="content" required maxlength="125000" placeholder="What's on your mind?" class="min-h-120" data-mention></textarea>
|
||||
<small class="hint-text"><span id="post-content-count">0/125000</span></small>
|
||||
</div>
|
||||
|
||||
<div class="auth-field auth-field-gap">
|
||||
<label for="post-title">Title (optional)</label>
|
||||
<input type="text" id="post-title" name="title" maxlength="500" placeholder="Post title">
|
||||
<small class="hint-text"><span id="post-title-count">0/500</span></small>
|
||||
</div>
|
||||
|
||||
<div class="auth-field auth-field-gap">
|
||||
<label for="project_uid">Link to Project (Optional)</label>
|
||||
<select id="project_uid" name="project_uid">
|
||||
<option value="">No project</option>
|
||||
{% for p in get_user_projects(user['uid']) %}
|
||||
<option value="{{ p['uid'] }}" {% if _composer_project and p['uid'] == _composer_project %}selected{% endif %}>{{ p['title'] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="auth-field auth-field-gap">
|
||||
<label>Attach files (images, video, audio, documents)</label>
|
||||
{% include "_attachment_form.html" %}
|
||||
</div>
|
||||
|
||||
<div class="auth-field auth-field-gap">
|
||||
<button type="button" class="btn btn-secondary btn-sm" data-poll-toggle><span class="icon">📊</span> <span data-poll-toggle-label>Add poll</span></button>
|
||||
</div>
|
||||
<div class="poll-builder" data-poll-builder hidden>
|
||||
<input type="text" name="poll_question" maxlength="200" placeholder="Poll question" aria-label="Poll question" class="poll-builder-question" disabled>
|
||||
<div class="poll-builder-options" data-poll-options>
|
||||
<div class="poll-builder-row"><input type="text" name="poll_options" maxlength="100" placeholder="Option 1" aria-label="Poll option 1" disabled></div>
|
||||
<div class="poll-builder-row"><input type="text" name="poll_options" maxlength="100" placeholder="Option 2" aria-label="Poll option 2" disabled></div>
|
||||
</div>
|
||||
<button type="button" class="btn-ghost btn-sm" data-poll-add-option>+ Add option</button>
|
||||
<p class="poll-builder-error" data-poll-error hidden></p>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="modal-close btn btn-secondary">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Post</button>
|
||||
</div>
|
||||
</form>
|
||||
Reference in New Issue
Block a user