Files
anonymous_github/public/partials/anonymize.htm
T
Thomas DurieuxandClaude Opus 5.5 5bad2a2597 fix(ui): mobile viewer, code-block redactions and anonymize preview
- Anonymize preview renders redactions as the viewer's numbered slots
- Code-block slots keep the width of XXXX-n and sit on the text line
- README no longer overflows on phones (grid forced two columns)
- Status bar always hides under the file header: no second Files button
  or breadcrumb on repos with a website or for owners; Open website and
  Edit anonymization move to the file menu
- Loading state no longer repeats the path visually
- Phone usage popover opens inside the screen
- Home closing band: no double divider, tighter gap above the footer
- File drawer closes after opening a file on phones

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 20:29:50 +00:00

559 lines
53 KiB
HTML

<div class="anonymize-page h-100" :class="{'mobile-preview-active': mobilePane === 'preview', 'preview-expanded': previewExpanded}">
<!-- ===== STATE 1: No URL — centered input ===== -->
<div class="anonymize-landing" v-show="!workspaceOpen &amp;&amp; !isUpdate">
<div class="anonymize-landing-inner">
<div class="paper-crumbs">My work &nbsp;/&nbsp; <span class="here">New anonymization</span></div>
<h1 class="paper-page-title">New <em>anonymization</em></h1>
<p class="paper-page-lede">
Paste a repository, pull request, or gist URL. You&rsquo;ll choose the terms to redact next.
</p>
<div class="form-group mt-4 mb-2">
<div class="source-url-label-row">
<label class="paper-field-label" for="sourceUrl-landing">Source URL</label>
<aside class="repo-access" aria-label="GitHub access" v-if="!sourceUrl &amp;&amp; githubConnections?.appEnabled &amp;&amp; githubConnections.appConnected &amp;&amp; githubConnections.oauthConnected">
<div class="repo-access-switch" role="group" aria-label="Repository connection" v-if="!isUpdate &amp;&amp; detectedType !== 'gist'">
<button type="button" :aria-pressed="githubConnection === 'github-app'" :disabled="githubConnection === 'github-app'" @click="chooseGitHubConnection('github-app')"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-shield"></use></svg> <span class="sr-only">Read-only </span>GitHub App</button>
<button type="button" :aria-pressed="githubConnection === 'oauth'" :disabled="githubConnection === 'oauth'" @click="chooseGitHubConnection('oauth')">Legacy OAuth</button>
</div>
<span class="connection-badge" v-if="isUpdate || detectedType === 'gist'">{{ (detectedType === 'gist' ? githubConnections?.gistConnection === 'github-app' : githubConnection === 'github-app') ? 'Read-only GitHub App' : 'Legacy OAuth' }}</span>
</aside>
</div>
<div class="landing-url-row"><input id="sourceUrl-landing" onblur="this.scrollLeft=0" type="text" class="form-control form-control-lg" placeholder="https://github.com/owner/repository" v-field="{ state: viewState, set: value =&gt; { sourceUrl = value }, value: sourceUrl, form: null, options: { debounce: {default: 1000, blur: 0, click: 0}, updateOn: &#x27;default blur click&#x27; }, change: () =&gt; { urlSelected() } }"><button type="button" class="btn btn-primary landing-continue" :aria-disabled="!sourceUrl ? 'true' : null" @click="sourceUrl && urlSelected()">Continue</button></div>
</div>
<p class="text-danger" role="alert" v-if="sourceUrlError">{{ sourceUrlError }}</p>
<small class="form-text" style="color: var(--ink-muted);">
Repository, pull request (…/pull/42) and gist (gist.github.com/…) URLs are all accepted.
</small>
<aside class="repo-access-details" aria-label="GitHub access" v-if="!sourceUrl &amp;&amp; githubConnections?.appEnabled &amp;&amp; githubConnections.appConnected">
<div class="repo-access-main">
<div class="repo-access-controls">
<span class="access-q" v-if="detectedType !== 'gist' &amp;&amp; githubConnection !== 'oauth'">Repository not listed?</span><button type="button" class="btn btn-outline-ink" v-if="detectedType !== 'gist' &amp;&amp; githubConnection !== 'oauth'" @click="grantGitHubAccess()">Manage access <svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-external-link"></use></svg></button>
<a class="connection-link" href="/connections" v-if="isUpdate || detectedType === 'gist' || githubConnection === 'oauth'">{{ isUpdate ? 'Change connection' : 'Manage connections' }} &rarr;</a>
</div>
</div>
<div class="repo-access-note"><span v-if="error &amp;&amp; detectedType !== 'gist' &amp;&amp; githubConnection === 'github-app'">Signing in does not grant access to private repositories. Use Manage access to select this repository, then return here. Organization access may require administrator approval.</span><span><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-info"></use></svg> Anonymized content is public, even from private repositories.</span></div>
</aside>
</div>
</div>
<!-- ===== STATE 2: URL provided — form (left) + preview (right) ===== -->
<div class="anonymize-workspace" v-show="workspaceOpen || isUpdate">
<header class="anonymize-topbar">
<div class="anonymize-topbar-inner">
<div class="anonymize-topbar-head">
<h1 class="paper-page-title anonymize-topbar-title">
<span v-if="!isUpdate">New anonymization</span>
<span v-if="isUpdate">Edit anonymization</span>
</h1>
<span class="type-badge" v-show="detectedType" :class="{&#x27;type-repo&#x27;: detectedType === &#x27;repo&#x27;, &#x27;type-pr&#x27;: detectedType === &#x27;pr&#x27;, &#x27;type-gist&#x27;: detectedType === &#x27;gist&#x27;}">
{{ detectedType === 'repo' ? 'Repository' : detectedType === 'pr' ? 'Pull request' : 'Gist' }}
</span>
</div>
</div>
</header>
<aside class="alert alert-info" role="status" v-if="detectedType === 'gist' &amp;&amp; gistAppRequired">
<p>Reconnect your GitHub account to read this gist. Your draft will be restored when you return.</p>
<button type="button" class="btn btn-ink" @click="connectGistApp()">Connect GitHub App to access gists</button>
</aside>
<aside class="alert alert-info" role="status" v-if="detectedType === 'gist' &amp;&amp; gistOAuthRequired">
<p>Connect GitHub OAuth to access gists. Your account and existing anonymizations stay the same, and your draft will be restored when you return.</p>
<p>GitHub's legacy OAuth connection includes write access to private repositories.</p>
<button type="button" class="btn btn-ink" @click="connectGistOAuth()" v-if="githubConnections?.oauthEnabled">Connect GitHub OAuth to access gists</button>
<p v-else>GitHub OAuth is currently unavailable. Please try again later.</p>
</aside>
<nav class="anonymize-mobile-tabs" role="tablist" aria-label="Anonymization workspace">
<button id="workspace-settings-tab" role="tab" type="button" :aria-selected="mobilePane === 'settings'" aria-controls="workspace-settings" :tabindex="mobilePane === 'settings' ? 0 : -1" @keydown="previewTabKeydown($event)" @click="selectMobilePane('settings')">Settings</button>
<button id="workspace-preview-tab" role="tab" type="button" :aria-selected="mobilePane === 'preview'" aria-controls="workspace-preview" :tabindex="mobilePane === 'preview' ? 0 : -1" @keydown="previewTabKeydown($event)" @click="selectMobilePane('preview')">Reviewer preview</button>
</nav>
<div class="anonymize-preview-empty" id="workspace-preview" v-if="detectedType !== 'repo' &amp;&amp; !details" role="status">{{ sourceLoading ? 'Loading source…' : 'Choose an accessible source to see its preview.' }}</div>
<div class="anonymize-split">
<!-- Form column (left) -->
<div id="workspace-settings" :role="mobileWorkspace ? 'tabpanel' : null" :aria-labelledby="mobileWorkspace ? 'workspace-settings-tab' : null" class="anonymize-form-col overflow-auto">
<form class="form needs-validation paper-settings-main" name="anonymize" novalidate v-form="viewState"><div class="anonymize-fields">
<section class="paper-settings-section">
<h2 class="paper-section-eyebrow">Source &amp; access</h2>
<div class="form-group">
<div class="source-url-label-row">
<label class="paper-field-label" for="sourceUrl">Source URL</label>
<aside class="repo-access" aria-label="GitHub access" v-if="sourceUrl &amp;&amp; githubConnections?.appEnabled &amp;&amp; githubConnections.appConnected &amp;&amp; githubConnections.oauthConnected">
<div class="repo-access-switch" role="group" aria-label="Repository connection" v-if="!isUpdate &amp;&amp; detectedType !== 'gist'">
<button type="button" :aria-pressed="githubConnection === 'github-app'" :disabled="githubConnection === 'github-app'" @click="chooseGitHubConnection('github-app')"><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-shield"></use></svg> <span class="sr-only">Read-only </span>GitHub App</button>
<button type="button" :aria-pressed="githubConnection === 'oauth'" :disabled="githubConnection === 'oauth'" @click="chooseGitHubConnection('oauth')">Legacy OAuth</button>
</div>
<span class="connection-badge" v-if="isUpdate || detectedType === 'gist'">{{ (detectedType === 'gist' ? githubConnections?.gistConnection === 'github-app' : githubConnection === 'github-app') ? 'Read-only GitHub App' : 'Legacy OAuth' }}</span>
</aside>
</div>
<input type="text" class="form-control" name="sourceUrl" id="sourceUrl" onblur="this.scrollLeft=0" :disabled="isUpdate &amp;&amp; detectedType !== &#x27;repo&#x27;" placeholder="Paste a GitHub repo or pull request URL" v-field="{ state: viewState, set: value =&gt; { sourceUrl = value }, value: sourceUrl, form: &quot;anonymize&quot;, options: { debounce: {default: 1000, blur: 0, click: 0}, updateOn: &#x27;default blur click&#x27; }, change: () =&gt; { urlSelected() } }" :class="{&#x27;is-invalid&#x27;: anonymize?.sourceUrl?.invalid}">
<div class="invalid-feedback" v-show="anonymize?.sourceUrl?.errors?.github">
Please provide a valid GitHub URL.
</div>
<div class="invalid-feedback" v-show="anonymize?.sourceUrl?.errors?.access">
Not accessible. The organization may restrict access.
</div>
<div class="invalid-feedback" v-show="anonymize?.sourceUrl?.errors?.missing">
Does not exist or is not accessible.
</div>
<div class="invalid-feedback" v-show="anonymize?.sourceUrl?.errors?.used">
Already anonymized.
</div>
</div>
<aside class="repo-access-details" aria-label="Previous repository configuration" v-if="!isUpdate &amp;&amp; previousConfigurations.length">
<p role="status">You have already anonymized this repository. Use the existing configuration to keep its anonymous link and avoid creating another repository.</p>
<div class="form-group" v-if="previousConfigurations.length > 1">
<label class="paper-field-label" for="previousConfiguration">Previous anonymization</label>
<select id="previousConfiguration" class="form-control" v-model="previousConfigurationId">
<option v-for="previous in previousConfigurations" :key="previous.repoId" :value="previous.repoId">{{ previous.repoId }}</option>
</select>
</div>
<p v-else>{{ previousConfigurationId }}</p>
<p>You can review and update its settings before saving.</p>
<div class="repo-access-controls">
<a class="btn btn-ink" :href="existingConfigurationUrl()">Use existing configuration</a>
<button type="button" class="btn btn-link" @click="previousConfigurations = []">Create a separate anonymization</button>
</div>
</aside>
<aside class="repo-access-details" aria-label="GitHub access" v-if="sourceUrl &amp;&amp; githubConnections?.appEnabled &amp;&amp; githubConnections.appConnected">
<div class="repo-access-main">
<div class="repo-access-controls">
<span class="access-q" v-if="detectedType !== 'gist' &amp;&amp; githubConnection !== 'oauth' &amp;&amp; !details">Repository not listed?</span><button type="button" class="btn btn-outline-ink" v-if="detectedType !== 'gist' &amp;&amp; githubConnection !== 'oauth' &amp;&amp; !details" @click="grantGitHubAccess()">Manage access <svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-external-link"></use></svg></button>
<a class="connection-link" href="/connections" v-if="isUpdate || detectedType === 'gist' || githubConnection === 'oauth'">{{ isUpdate ? 'Change connection' : 'Manage connections' }} &rarr;</a>
</div>
</div>
<div class="repo-access-note"><span v-if="error &amp;&amp; detectedType !== 'gist' &amp;&amp; githubConnection === 'github-app'">Signing in does not grant access to private repositories. Use Manage access to select this repository, then return here. Organization access may require administrator approval.</span><span v-if="detectedType !== 'gist' &amp;&amp; githubConnection !== 'oauth' &amp;&amp; !details">Select repositories on GitHub, then return here.</span><span><svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-info"></use></svg> Selected content will be published, including content from private repositories.</span></div>
</aside>
<aside class="repo-access-details" v-if="isUpdate &amp;&amp; detectedType === 'repo' &amp;&amp; role === 'owner' &amp;&amp; accessRepairAllowed &amp;&amp; githubConnections?.appEnabled &amp;&amp; githubConnection === 'github-app' &amp;&amp; sourceUrl === 'https://github.com/' + _originalFullName">
<p>If this repository became private or its App installation changed, repair its access here. This keeps the same anonymous URL and published commit.</p>
<button type="button" class="btn btn-outline-ink" :disabled="repairingGitHubAccess" @click="repairGitHubAccess()">Repair GitHub access</button>
<button type="button" class="btn btn-outline-ink" @click="grantGitHubAccess()">Manage access</button>
<p v-if="accessRepairMessage" role="status">{{ accessRepairMessage }}</p>
</aside>
<aside class="repo-access-details" v-if="isUpdate &amp;&amp; detectedType === 'repo' &amp;&amp; role === 'owner' &amp;&amp; sourceUrl === 'https://github.com/' + _originalFullName">
<p>If you deleted and recreated this repository, reconnect it before choosing a branch.</p>
<button type="button" class="btn btn-outline-ink" @click="reconnectSource()">Reconnect source repository</button>
<p v-if="reconnectRepositoryId" role="status">Source reloaded. Review the branch, commit, and anonymization preview, then save to replace the published snapshot.</p>
</aside>
<div class="form-grid-2" v-if="detectedType === &#x27;repo&#x27;">
<div class="form-group">
<label class="paper-field-label" for="branch">Branch</label>
<div class="input-group">
<select class="form-control" id="branch" name="branch" v-field="{ state: viewState, set: value =&gt; { source.branch = value }, value: source?.branch, form: &quot;anonymize&quot;, options: {} }">
<option v-for="(b, index) in branches" v-text="b?.name" :value="(b?.name)"></option>
</select>
<div class="input-group-append">
<button class="btn btn-icon refresh-branches" type="button" title="Refresh branches" @click="getBranches(true)" aria-label="Refresh branches">
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-refresh"></use></svg>
</button>
</div>
</div>
</div>
<div class="form-group">
<label class="paper-field-label" for="commit">Commit</label>
<input class="form-control" id="commit" :disabled="options.update" name="commit" pattern="[a-fA-Z0-9]{6,}" required v-field="{ state: viewState, set: value =&gt; { source.commit = value }, value: source?.commit, form: &quot;anonymize&quot;, options: {} }" :class="{&#x27;is-invalid&#x27;: anonymize?.commit?.invalid}">
<div class="invalid-feedback" v-show="anonymize?.commit?.errors?.pattern || anonymize?.commit?.errors?.required">
The commit SHA is not valid.
</div>
<div class="invalid-feedback" v-show="anonymize?.commit?.errors?.exists">
This commit no longer exists in the repository. Click refresh to get the latest.
</div>
</div>
</div>
<div class="form-check" v-show="detectedType">
<input class="form-check-input" type="checkbox" id="update" name="update" v-field="{ state: viewState, set: value =&gt; { options.update = value }, value: options?.update, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="update">Auto-update from GitHub</label>
<small class="form-text text-muted">Follows the latest version, at most once an hour.</small>
</div>
</section>
<section class="paper-settings-section" v-show="detectedType">
<h2 class="paper-section-eyebrow">Anonymization</h2>
<div class="form-group" v-show="detectedType === &#x27;repo&#x27;">
<label class="paper-field-label" for="repoId">Anonymized repository ID</label>
<input type="text" class="form-control" name="repoId" id="repoId" :disabled="isUpdate" v-field="{ state: viewState, set: value =&gt; { repoId = value }, value: repoId, form: &quot;anonymize&quot;, options: { debounce: {default: 1000, blur: 0, click: 0}, updateOn: &#x27;default blur click&#x27; } }" :class="{&#x27;is-invalid&#x27;: anonymize?.repoId?.invalid}">
<small class="form-text text-muted">anonymous.4open.science/r/{{ repoId }}</small>
<div class="invalid-feedback" v-show="anonymize?.repoId?.errors?.format">ID can only contain letters and numbers.</div>
<div class="invalid-feedback" v-show="anonymize?.repoId?.errors?.used">{{ repoId }} is already used.</div>
</div>
<div class="form-group" v-show="detectedType === &#x27;pr&#x27;">
<label class="paper-field-label" for="pullRequestId">Anonymized pull request ID</label>
<input type="text" class="form-control" name="pullRequestId" id="pullRequestId" :disabled="isUpdate" v-field="{ state: viewState, set: value =&gt; { pullRequestId = value }, value: pullRequestId, form: &quot;anonymize&quot;, options: { debounce: {default: 1000, blur: 0, click: 0}, updateOn: &#x27;default blur click&#x27; } }" :class="{&#x27;is-invalid&#x27;: anonymize?.pullRequestId?.invalid}">
<small class="form-text text-muted">Your share link will be <code>anonymous.4open.science/pr/{{ pullRequestId }}</code>.</small>
<div class="invalid-feedback" v-show="anonymize?.pullRequestId?.errors?.format">ID can only contain letters and numbers.</div>
<div class="invalid-feedback" v-show="anonymize?.pullRequestId?.errors?.used">{{ pullRequestId }} is already used.</div>
</div>
<div class="form-group" v-show="detectedType === &#x27;gist&#x27;">
<label class="paper-field-label" for="gistId">Anonymized gist ID</label>
<input type="text" class="form-control" name="gistId" id="gistId" :disabled="isUpdate" v-field="{ state: viewState, set: value =&gt; { gistId = value }, value: gistId, form: &quot;anonymize&quot;, options: { debounce: {default: 1000, blur: 0, click: 0}, updateOn: &#x27;default blur click&#x27; } }" :class="{&#x27;is-invalid&#x27;: anonymize?.gistId?.invalid}">
<small class="form-text text-muted">Your share link will be <code>anonymous.4open.science/gist/{{ gistId }}</code>.</small>
<div class="invalid-feedback" v-show="anonymize?.gistId?.errors?.format">ID can only contain letters and numbers.</div>
<div class="invalid-feedback" v-show="anonymize?.gistId?.errors?.used">{{ gistId }} is already used.</div>
</div>
<div class="form-group">
<label class="paper-field-label" for="terms">Terms to redact</label>
<textarea class="form-control" id="terms" name="terms" rows="4" placeholder="Jane Smith&#10;MIT CSAIL&#10;jane.smith@example.org" v-field="{ state: viewState, set: value =&gt; { terms = value }, value: terms, form: &quot;anonymize&quot;, options: { debounce: 250 } }" :class="{&#x27;is-invalid&#x27;: anonymize?.terms?.invalid}"></textarea>
<small class="form-text text-muted">One term per line.</small><details class="redaction-syntax"><summary>Regex &amp; replacement syntax</summary> <small class="form-text text-muted">Regex is supported. Replaced by <code>{{ site_options?.ANONYMIZATION_MASK }}-[N]</code>, or use <code>term=&gt;replacement</code> to pick your own (e.g. <code>Anonymous=&gt;ABC</code>).</small></details>
<div class="warning-feedback" v-show="termsRegexWarning">Regex characters detected. Escape them if unintentional.</div>
<div class="invalid-feedback" v-show="anonymize?.terms?.errors?.format">Terms are in an invalid format.</div>
</div>
</section>
<section class="paper-settings-section" v-show="detectedType">
<h2 class="paper-section-eyebrow">Display</h2>
<div v-show="detectedType === &#x27;repo&#x27;">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="link" name="link" v-field="{ state: viewState, set: value =&gt; { options.link = value }, value: options?.link, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="link">Keep links</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="image" name="image" v-field="{ state: viewState, set: value =&gt; { options.image = value }, value: options?.image, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="image">Display images</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="pdf" name="pdf" v-field="{ state: viewState, set: value =&gt; { options.pdf = value }, value: options?.pdf, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="pdf">Display PDFs</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="notebook" name="notebook" v-field="{ state: viewState, set: value =&gt; { options.notebook = value }, value: options?.notebook, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="notebook">Display Notebooks</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="page" name="page" v-field="{ state: viewState, set: value =&gt; { options.page = value }, value: options?.page, form: &quot;anonymize&quot;, options: {} }" :disabled="!details?.hasPage || (details?.pageSource &amp;&amp; details?.pageSource?.branch !== source?.branch)">
<label class="form-check-label" for="page">GitHub Pages</label>
<small class="form-text text-muted" v-if="!details?.hasPage">
{{ fmt?.translate('WARNINGS.page_not_enabled_on_repo') }}
</small>
<small class="form-text text-muted" v-if="details?.hasPage &amp;&amp; details?.pageSource?.branch &amp;&amp; source?.branch &amp;&amp; details.pageSource.branch !== source.branch">
{{ fmt?.translate('WARNINGS.page_branch_mismatch', { pageBranch: details?.pageSource?.branch, selectedBranch: source?.branch }) }}
</small>
</div>
</div>
<p class="form-text text-muted" v-if="detectedType === 'repo' &amp;&amp; (options.image || options.pdf)">Images and PDFs are served unchanged. Review their contents and metadata before sharing. <a href="/faq#formats">Supported formats</a></p>
<div v-show="detectedType === &#x27;gist&#x27;">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="title-gist" name="title-gist" v-field="{ state: viewState, set: value =&gt; { options.title = value }, value: options?.title, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="title-gist">Gist description</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="content-gist" name="content-gist" v-field="{ state: viewState, set: value =&gt; { options.content = value }, value: options?.content, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="content-gist">File contents</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="comments-gist" name="comments-gist" v-field="{ state: viewState, set: value =&gt; { options.comments = value }, value: options?.comments, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="comments-gist">Comments</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="username-gist" name="username-gist" v-field="{ state: viewState, set: value =&gt; { options.username = value }, value: options?.username, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="username-gist">Usernames</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="date-gist" name="date-gist" v-field="{ state: viewState, set: value =&gt; { options.date = value }, value: options?.date, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="date-gist">Dates</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="origin-gist" name="origin-gist" v-field="{ state: viewState, set: value =&gt; { options.origin = value }, value: options?.origin, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="origin-gist">Source gist ID</label>
</div>
</div>
<div v-show="detectedType === &#x27;pr&#x27;">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="title" name="title" v-field="{ state: viewState, set: value =&gt; { options.title = value }, value: options?.title, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="title">PR title</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="body" name="body" v-field="{ state: viewState, set: value =&gt; { options.body = value }, value: options?.body, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="body">PR body</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="diff" name="diff" v-field="{ state: viewState, set: value =&gt; { options.diff = value }, value: options?.diff, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="diff">Diff</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="comments" name="comments" v-field="{ state: viewState, set: value =&gt; { options.comments = value }, value: options?.comments, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="comments">Comments</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="username" name="username" v-field="{ state: viewState, set: value =&gt; { options.username = value }, value: options?.username, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="username">Usernames</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="date" name="date" v-field="{ state: viewState, set: value =&gt; { options.date = value }, value: options?.date, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="date">Dates</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="origin" name="origin" v-field="{ state: viewState, set: value =&gt; { options.origin = value }, value: options?.origin, form: &quot;anonymize&quot;, options: {} }">
<label class="form-check-label" for="origin">Project name</label>
</div>
</div>
</section>
<section class="paper-settings-section hosting-settings" id="repository-hosting" v-show="detectedType" aria-labelledby="hosting-heading">
<h2 class="paper-section-eyebrow" id="hosting-heading">Hosting &amp; expiration</h2>
<p class="hosting-usage" v-if="detectedType === 'repo' &amp;&amp; billing?.enabled">
<span v-if="isUpdate &amp;&amp; role !== 'owner'">Uses the owner's hosting</span>
<span v-else-if="hasAuthorHosting()">Your author subscription is active.</span>
<span v-else-if="conference">Conference hosting is checked on submission.</span>
<span v-else-if="hostingQuota?.repository">{{ hostingQuota.repository.used }} of {{ hostingQuota.repository.total }} active repositories · Free</span>
</p>
<fieldset class="expiration-choices">
<legend class="sr-only">Expiration</legend>
<label class="expiration-choice"><input type="radio" name="expiration-choice" :checked="options.expirationMode !== 'never'" @change="useDatedHosting()" :disabled="billingPending"><span>Expire on a date</span></label>
<div class="dated-options" v-if="options.expirationMode !== 'never'">
<div class="hosting-date" v-if="options?.expirationMode !== 'never'">
<label class="paper-field-label" for="expirationDate">Expires</label>
<input class="form-control" type="date" name="expirationDate" id="expirationDate" required v-field="{ state: viewState, set: value =&gt; { options.expirationDate = value }, value: options?.expirationDate, form: &quot;anonymize&quot;, options: {} }" :class="{&#x27;is-invalid&#x27;: anonymize?.expirationDate?.invalid}" :min="(minExpirationDate)" :max="(maxExpirationDate)">
<div class="invalid-feedback" v-show="anonymize?.expirationDate?.errors?.min">Pick a date in the future.</div>
<div class="invalid-feedback" v-show="anonymize?.expirationDate?.errors?.max">Pick a date on or before {{ fmt?.date(maxExpirationDate) }}.</div>
<div class="invalid-feedback" v-show="anonymize?.expirationDate?.errors?.required || anonymize?.expirationDate?.errors?.date">Enter a valid expiration date.</div>
</div>
<div class="hosting-property"><label for="expiration">Then</label><select id="expiration" class="form-control" v-model="options.expirationMode"><option value="remove">Remove content</option><option value="redirect">Redirect to GitHub</option></select></div>
</div>
<label class="expiration-choice" v-if="detectedType === 'repo'"><input type="radio" name="expiration-choice" :checked="options.expirationMode === 'never'" @change="options.expirationMode = 'never'" :disabled="billingPending"><span>Never expire<small v-if="options.expirationMode !== 'never' || hasAuthorHosting()">{{ hasAuthorHosting() ? 'Included in your subscription' : 'Author subscription' }}</small></span></label>
</fieldset>
<p class="form-text text-muted" v-if="options.expirationMode === 'redirect'">Visitors will be redirected to the original GitHub source on this date, revealing its identity.</p>
<div class="form-group conference-field">
<label class="paper-field-label" for="conference">Conference code <span class="paper-optional">(optional)</span></label>
<input class="form-control" id="conference" name="conference" v-field="{ state: viewState, set: value =&gt; { conference = value }, value: conference, form: &quot;anonymize&quot;, options: { debounce: { default: 800, blur: 0 }, updateOn: &#x27;default blur&#x27; } }" :class="{&#x27;is-invalid&#x27;: anonymize?.conference?.invalid}">
<small class="form-text text-muted" v-show="conference_data">
<a target="_blank" :href="safeUrl((conference_data?.url))">{{ conference_data?.name }}</a> expires {{ fmt?.date(conference_data?.endDate) }}.
</small>
<div class="invalid-feedback" v-show="anonymize?.conference?.errors?.activated">The conference is not activated.</div>
<small class="form-text text-muted" v-show="!conference_data">Link to a conference to apply its shared defaults.</small>
</div>
<div v-if="detectedType === 'repo' &amp;&amp; billing?.enabled" class="inline-hosting">
<p v-if="taskValidationError" role="alert" class="text-danger">{{ taskValidationError }}</p>
<p v-if="billingError" role="alert" class="text-danger">{{ billingError }} <button type="button" class="task-text-button" @click="loadBilling()" :disabled="billingBusy">Reload billing</button></p>
<div v-if="billingPending" role="status"><p><strong>Confirming your payment…</strong></p><p class="form-text text-muted">Your draft is saved. Please do not pay again.</p></div>
<p v-else-if="billingNotice" role="status" class="form-text text-muted">{{ billingNotice }}</p>
<template v-if="hostingNeedsPayment() &amp;&amp; !billingPending">
<p v-if="options.expirationMode !== 'never'" class="capacity-reason">{{ options.update &amp;&amp; !hostingQuotaBlocked &amp;&amp; hostingQuota?.repository?.used < hostingQuota?.repository?.total ? 'Automatic updates require Author hosting.' : 'Your free hosting allowance is full.' }}</p>
<div class="subscription-choice" v-if="authorPlans().length &amp;&amp; billing.checkoutEnabled !== false">
<h3>Author subscription</h3>
<fieldset class="billing-cycle"><legend class="sr-only">Billing frequency</legend><label class="cycle-option" v-for="plan in authorPlans()" :key="plan.id" :class="{'selected': selectedHostingPlan()?.id === plan.id}"><input type="radio" name="hosting-plan" :value="plan.id" :checked="selectedHostingPlan()?.id === plan.id" @change="hostingPlan = plan.id"><span>{{ billingPrice(plan) }}</span></label></fieldset>
<p>{{ selectedHostingPlan()?.repositories }} repositories, {{ billingBytes(selectedHostingPlan()?.storage) }}. Coauthors included.</p>
<p class="hosting-terms">Renews {{ selectedHostingPlan()?.interval === 'year' ? 'annually' : 'monthly' }} until cancelled. Tax shown at checkout.</p>
<p class="hosting-terms">If paid access ends, never-expiring repositories expire after 90 days.</p>
</div>
<p v-else role="status">New purchases are temporarily unavailable. Your draft is kept.</p>
<details class="capacity-options" v-if="options.expirationMode !== 'never'"><summary>Keep using free hosting</summary><p>Remove a repository you no longer need, or use conference hosting. Removing a repository affects its review link.</p><a href="/dashboard">Review my repositories</a><button type="button" class="task-text-button" @click="anonymizeRepo($event)" :disabled="sourceLoading || billingBusy">Recheck capacity and submit</button></details>
</template>
<p class="hosting-terms" v-else-if="options.expirationMode === 'never' &amp;&amp; !billingPending">No scheduled expiration while subscribed. A 90-day grace period applies if paid access ends.</p>
<details class="repository-pass" v-if="isUpdate &amp;&amp; role === 'owner' &amp;&amp; !hasAuthorHosting()"><summary>Hosting just this repository?</summary><div v-for="plan in (billing?.plans || []).filter(p =&gt; p.id === 'author_pass')" :key="plan.id"><p>{{ billingPrice(plan) }} · {{ billingBytes(plan.storage) }} for 180 days. No renewal. Does not include Never expire.</p><button type="button" class="btn btn-outline-ink" :disabled="billingBusy || hostingSaving || sourceLoading || billing.checkoutEnabled === false || billingPending" @click="checkoutRepository(true)">Review repository pass</button></div></details>
</div>
</section>
<section class="paper-settings-section" v-show="isUpdate &amp;&amp; detectedType === &#x27;repo&#x27;">
<h2 class="paper-section-eyebrow">Coauthors</h2>
<p class="form-text text-muted" style="margin-bottom: 8px;">
Co-authors can view and edit these settings. They cannot delete the anonymization or manage co-authors.
</p>
<div class="form-group" v-show="role === &#x27;owner&#x27; || role === &#x27;admin&#x27;">
<label class="paper-field-label" for="coauthorSearch">Add a GitHub user</label>
<div style="position: relative;">
<input type="text" id="coauthorSearch" class="form-control" placeholder="Search GitHub username…" autocomplete="off" v-field="{ state: viewState, set: value =&gt; { coauthorSearch = value }, value: coauthorSearch, form: &quot;anonymize&quot;, options: { debounce: 300 }, change: () =&gt; { searchCoauthors() } }">
<div class="dropdown-menu show" style="display: block; max-height: 220px; overflow-y: auto; width: 100%;" v-show="coauthorResults?.length &gt; 0">
<a href="#" class="dropdown-item d-flex align-items-center" v-for="(u, index) in coauthorResults" @click.prevent="addCoauthor(u, $event)">
<img alt="" style="width: 22px; height: 22px; border-radius: 50%; margin-right: 8px;" :src="safeUrl((u?.photo))">
<span v-text="u?.username"></span>
</a>
</div>
</div>
<small class="form-text text-muted" v-show="coauthorError" v-text="coauthorError"></small>
</div>
<div class="coauthor-list">
<div class="coauthor-row d-flex align-items-center" style="padding: 6px 0; gap: 8px;" v-for="(c, index) in coauthors">
<img alt="" style="width: 24px; height: 24px; border-radius: 50%;" v-if="c?.photo" :src="safeUrl((c?.photo))">
<a target="_blank" v-text="c?.username" :href="safeUrl(&quot;https://github.com/&quot; + (c?.username))"></a>
<span class="type-badge type-coauthor">Co-author</span>
<button type="button" class="btn btn-sm btn-icon" style="margin-left: auto;" title="Remove co-author" v-show="role === &#x27;owner&#x27; || role === &#x27;admin&#x27;" @click="removeCoauthor(c)" aria-label="Remove co-author">
<svg class="ui-icon" aria-hidden="true"><use href="#project-icon-close"></use></svg>
</button>
</div>
<div class="form-text text-muted" v-show="!coauthors || coauthors?.length === 0">
No co-authors yet.
</div>
</div>
</section>
<div class="alert alert-danger" role="alert" v-if="error" v-text="error"></div>
</div>
<button type="button" class="btn btn-outline-ink mobile-preview-jump" @click="selectMobilePane('preview')">Review preview <span aria-hidden="true">→</span></button>
<div class="anonymize-submit-bar" v-show="detectedType">
<template v-if="detectedType === 'repo'">
<button type="button" class="btn btn-ink" v-if="billingPending" @click="refreshBilling()" :disabled="billingBusy">Check payment status</button>
<button type="submit" class="btn btn-ink" v-else :disabled="sourceLoading || billingBusy || hostingSaving || billingLoading || (hostingNeedsPayment() &amp;&amp; (!authorPlans().length || billing?.checkoutEnabled === false))" @click.prevent="submitForm($event, () =&gt; { submitRepository($event) })">{{ hostingSaving ? 'Saving draft…' : hostingNeedsPayment() ? 'Continue to checkout' : isUpdate ? 'Save changes' : 'Create anonymous link' }}</button>
<p class="checkout-return-note" v-if="hostingNeedsPayment() &amp;&amp; !billingPending">Review payment in Stripe, then return to finish.</p>
</template>
<button type="submit" :disabled="sourceLoading" class="btn btn-ink" v-if="detectedType === &#x27;pr&#x27; &amp;&amp; !isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizePullRequest($event) })">
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-shield"></use></svg> Anonymize Pull Request
</button>
<button type="submit" :disabled="sourceLoading" class="btn btn-ink" v-if="detectedType === &#x27;pr&#x27; &amp;&amp; isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizePullRequest($event) })">
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-save"></use></svg> Update Pull Request
</button>
<button type="submit" :disabled="sourceLoading" class="btn btn-ink" v-if="detectedType === &#x27;gist&#x27; &amp;&amp; !isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizeGist($event) })">
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-shield"></use></svg> Anonymize Gist
</button>
<button type="submit" :disabled="sourceLoading" class="btn btn-ink" v-if="detectedType === &#x27;gist&#x27; &amp;&amp; isUpdate" @click.prevent="submitForm($event, () =&gt; { anonymizeGist($event) })">
<svg class="ui-icon mr-1" aria-hidden="true"><use href="#ui-icon-save"></use></svg> Update Gist
</button>
<div class="draft-status" role="status" v-if="draftRestored">Your unsaved draft was restored. <button type="button" class="draft-discard" @click="discardDraft()">Discard draft</button></div>
<div class="draft-status" v-else-if="unsavedChanges">{{ draftSaved ? "Draft saved ·" : "Unsaved changes ·" }} <button type="button" class="draft-discard" @click="discardDraft()">{{ isUpdate ? "Restore saved" : "Discard" }}</button></div>
</div>
</form>
</div>
<!-- Preview column (right) -->
<div id="workspace-preview" :role="mobileWorkspace ? 'tabpanel' : null" :aria-labelledby="mobileWorkspace ? 'workspace-preview-tab' : null" class="anonymize-preview-col" v-if="detectedType === &#x27;repo&#x27;">
<div class="anonymize-preview-head">
<span class="paper-eyebrow">Live preview</span>
<span class="anonymize-preview-sub">{{ previewStatus !== 'ready' ? 'README preview' : (String(terms || '').trim() ? 'README with redactions applied' : 'No terms yet. Add some to see them redacted here.') }}</span>
<button type="button" class="task-text-button preview-expand" @click="previewExpanded = !previewExpanded">{{ previewExpanded ? 'Restore split view' : 'Expand preview' }}</button>
</div>
<div class="anonymize-preview-status" role="status" v-if="sourceLoading &amp;&amp; previewStatus === 'idle'">Loading source…</div>
<div class="anonymize-preview-status" role="status" v-if="previewStatus === 'loading'">Updating the anonymized preview…</div>
<div class="anonymize-preview-status" role="alert" v-if="previewStatus === 'error'">
<p>Unable to generate the anonymized preview. The latest redactions have not been verified.</p>
<button class="btn btn-outline-ink" type="button" @click="retryPreview()">Retry preview</button>
</div>
<div class="anonymize-preview-status" v-if="!sourceLoading &amp;&amp; !readme &amp;&amp; previewStatus === 'idle'">No README is available to preview. Review the published files before sharing.</div>
<div class="anonymize-preview-body markdown-body body" v-if="previewStatus === 'ready'" v-html="renderRedacted(sanitize(html_readme))"></div>
</div>
<div id="workspace-preview" :role="mobileWorkspace ? 'tabpanel' : null" :aria-labelledby="mobileWorkspace ? 'workspace-preview-tab' : null" class="anonymize-preview-col" v-if="detectedType === &#x27;gist&#x27; &amp;&amp; details">
<div class="anonymize-preview-head">
<strong>Gist preview</strong><button type="button" class="task-text-button preview-expand" @click="previewExpanded = !previewExpanded">{{ previewExpanded ? 'Restore split view' : 'Expand preview' }}</button>
</div>
<div class="anonymize-preview-status" role="status" v-if="sourceLoading &amp;&amp; previewStatus === 'idle'">Loading source…</div>
<div class="anonymize-preview-status" role="status" v-if="previewStatus === 'loading'">Updating the anonymized preview…</div>
<div class="anonymize-preview-status" role="alert" v-if="previewStatus === 'error'">
<p>Unable to generate the anonymized preview. The latest redactions have not been verified.</p>
<button class="btn btn-outline-ink" type="button" @click="retryPreview()">Retry preview</button>
</div>
<div class="anonymize-preview-body" v-if="previewStatus === 'ready'">
<div class="d-flex w-100 justify-content-between align-items-center flex-wrap">
<h2 class="pr-title mb-1">
<span v-if="options?.title">{{ anonymizeGistContent(details?.gist?.description) || 'Untitled gist' }}</span>
<span class="badge" :class="{&#x27;badge-success&#x27;: details?.gist?.isPublic, &#x27;badge-secondary&#x27;: !details?.gist?.isPublic}">
{{ details?.gist?.isPublic ? 'public' : 'secret' }}
</span>
</h2>
<small v-if="options?.date" v-text="fmt?.date(details?.gist?.updatedDate)"></small>
</div>
<small v-if="options?.origin">Gist ID: {{ details?.source?.gistId }}</small>
<small v-if="options?.username &amp;&amp; details?.gist?.ownerLogin">By @{{ anonymizeGistContent(details?.gist?.ownerLogin) }}</small>
<div v-if="options?.content &amp;&amp; previewGistFiles?.length">
<ul class="pr-comments mt-3">
<li class="pr-comment" v-for="(file, index) in previewGistFiles">
<div class="pr-comment-head">
<strong v-text="file?.filename"></strong>
<span class="pr-comment-date" v-if="file?.language">{{ file?.language }}</span>
</div>
<gist-file :file="file" :terms="terms" :options="options"></gist-file>
</li>
</ul>
</div>
<div v-if="options?.comments &amp;&amp; details?.gist?.comments &amp;&amp; details?.gist?.comments?.length">
<h3 class="paper-section-eyebrow mt-3">Comments</h3>
<ul class="pr-comments">
<li class="pr-comment" v-for="(comment, index) in details?.gist?.comments">
<div class="pr-comment-head">
<span class="pr-comment-author" v-if="options?.username">
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-user"></use></svg> @<span v-text="anonymizeGistContent(comment?.author)"></span>
</span>
<span class="pr-comment-date" v-if="options?.date" v-text="fmt?.date(comment?.updatedDate)"></span>
</div>
<div class="pr-comment-body" v-if="options?.body">
<markdown :content="anonymizeGistContent(comment?.body)" :options="options" :terms="terms"></markdown>
</div>
</li>
</ul>
</div>
</div>
</div>
<div id="workspace-preview" :role="mobileWorkspace ? 'tabpanel' : null" :aria-labelledby="mobileWorkspace ? 'workspace-preview-tab' : null" class="anonymize-preview-col" v-if="detectedType === &#x27;pr&#x27; &amp;&amp; details">
<div class="anonymize-preview-head">
<strong>Pull request preview</strong><button type="button" class="task-text-button preview-expand" @click="previewExpanded = !previewExpanded">{{ previewExpanded ? 'Restore split view' : 'Expand preview' }}</button>
</div>
<div class="anonymize-preview-status" role="status" v-if="sourceLoading &amp;&amp; previewStatus === 'idle'">Loading source…</div>
<div class="anonymize-preview-status" role="status" v-if="previewStatus === 'loading'">Updating the anonymized preview…</div>
<div class="anonymize-preview-status" role="alert" v-if="previewStatus === 'error'">
<p>Unable to generate the anonymized preview. The latest redactions have not been verified.</p>
<button class="btn btn-outline-ink" type="button" @click="retryPreview()">Retry preview</button>
</div>
<div class="anonymize-preview-body" v-if="previewStatus === 'ready'">
<div class="d-flex w-100 justify-content-between align-items-center flex-wrap">
<h2 class="pr-title mb-1">
<span v-if="options?.title">{{ anonymizePrContent(details?.pullRequest?.title) }}</span>
<span class="badge" :class="{&#x27;badge-success&#x27;:details?.pullRequest?.merged, &#x27;badge-warning&#x27;:details?.pullRequest?.state==&#x27;open&#x27;, &#x27;badge-danger&#x27;:details?.pullRequest?.state==&#x27;closed&#x27; &amp;&amp; !details?.pullRequest?.merged}">
{{ fmt?.title(details?.pullRequest?.merged ? "merged" : details?.pullRequest?.state) }}
</span>
</h2>
<small v-if="options?.date" v-text="fmt?.date(details?.pullRequest?.updatedDate)"></small>
</div>
<small v-if="options?.origin">Pull Request on {{ details?.pullRequest?.baseRepositoryFullName }}</small>
<div class="pr-body shadow-sm p-3 mb-4 rounded" style="background: var(--paper-bg-alt)" v-if="options?.body">
<markdown :content="anonymizePrContent(details?.pullRequest?.body)" :options="options" :terms="terms"></markdown>
</div>
<nav class="paper-tabs" role="tablist" v-if="options?.diff || options?.comments">
<button class="paper-tab" type="button" role="tab" v-if="options?.diff" :class="{&#x27;active&#x27;: prTabState?.active == &#x27;diff&#x27;}" id="preview-pr-diff-tab" :aria-selected="prTabState?.active === 'diff'" aria-controls="preview-pr-diff-panel" :tabindex="prTabState?.active === 'diff' ? 0 : -1" @keydown="previewTabKeydown($event)" @click="prTabState.active = &#x27;diff&#x27;">
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-code"></use></svg> Diff
</button>
<button class="paper-tab" type="button" role="tab" v-if="options?.comments" :class="{&#x27;active&#x27;: prTabState?.active == &#x27;comments&#x27;}" id="preview-pr-comments-tab" :aria-selected="prTabState?.active === 'comments'" aria-controls="preview-pr-comments-panel" :tabindex="prTabState?.active === 'comments' ? 0 : -1" @keydown="previewTabKeydown($event)" @click="prTabState.active = &#x27;comments&#x27;">
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-comment"></use></svg>
<span v-text="fmt.plural(details?.pullRequest?.comments?.length, {&#x27;0&#x27;: &#x27;No comments&#x27;, &#x27;one&#x27;: &#x27;1 comment&#x27;, &#x27;other&#x27;: &#x27;{} comments&#x27;})"></span>
</button>
</nav>
<div class="anonymize-preview-status" v-if="!options?.diff &amp;&amp; !options?.comments">Diff and comments are hidden by your display settings.</div>
<div class="paper-tab-content">
<div role="tabpanel" id="preview-pr-diff-panel" aria-labelledby="preview-pr-diff-tab" v-if="options?.diff &amp;&amp; prTabState?.active == &#x27;diff&#x27;">
<div class="pr-diff" v-html="sanitize(fmt?.diff(anonymizePrContent(details?.pullRequest?.diff)))"></div>
</div>
<div role="tabpanel" id="preview-pr-comments-panel" aria-labelledby="preview-pr-comments-tab" v-if="options?.comments &amp;&amp; prTabState?.active == &#x27;comments&#x27;">
<ul class="pr-comments">
<li class="pr-comment" v-for="(comment, index) in details?.pullRequest?.comments">
<div class="pr-comment-head">
<span class="pr-comment-author" v-if="options?.username">
<svg class="ui-icon" aria-hidden="true"><use href="#ui-icon-user"></use></svg> @<span v-text="anonymizePrContent(comment?.author)"></span>
</span>
<span class="pr-comment-date" v-if="options?.date" v-text="fmt?.date(comment?.updatedDate)"></span>
</div>
<div class="pr-comment-body" v-if="options?.body">
<markdown :content="anonymizePrContent(comment?.body)" :options="options" :terms="terms"></markdown>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>