Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ on:
- "**"
paths-ignore:
- "website/**"
- ".github/workflows/website.yml"
- ".github/workflows/*.yml"
- "!.github/workflows/ci.yml"
- "*.md"

jobs:
Expand Down
101 changes: 101 additions & 0 deletions .github/workflows/website-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Website Preview

on:
pull_request:
branches:
- main
paths:
- "website/**"
types:
- opened
- synchronize
- reopened

permissions:
contents: read
pull-requests: write

jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Resolve version
run: |
LATEST=$(curl -s \
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
"https://api.github.com/repos/${{ github.repository }}/releases/latest" \
| jq -r '.tag_name')
echo "VERSION=${LATEST}" >> $GITHUB_ENV

- name: Inject version into HTML
run: |
sed -i "s/{{VERSION}}/${{ env.VERSION }}/g" website/*.html

- name: Minify HTML
run: |
npx html-minifier-terser \
--input-dir website \
--output-dir website \
--file-ext html \
--collapse-whitespace \
--remove-comments \
--remove-redundant-attributes \
--remove-script-type-attributes \
--remove-style-link-type-attributes \
--minify-css true \
--minify-js true

- name: Deploy preview to Cloudflare Pages
id: deploy
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy website --project-name=app-switcher --commit-dirty=true --branch=pr-${{ github.event.pull_request.number }}

- name: Comment preview URL on PR
uses: actions/github-script@v7
with:
script: |
const deploymentUrl = '${{ steps.deploy.outputs.deployment-url }}';
const sha = context.payload.pull_request.head.sha.slice(0, 7);
const body = [
'## 🚀 Website Preview',
'',
'| | |',
'|---|---|',
`| **Preview URL** | ${deploymentUrl} |`,
`| **Commit** | \`${sha}\` |`,
`| **Updated** | ${new Date().toUTCString()} |`,
'',
'_This comment is updated automatically on each push._',
].join('\n');

const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});

const marker = '## 🚀 Website Preview';
const existing = comments.find(
c => c.user.login === 'github-actions[bot]' && c.body.includes(marker)
);

if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
103 changes: 103 additions & 0 deletions website/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,67 @@
color: var(--muted);
}

.install-command {
margin-top: 0.85rem;
display: inline-flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 0.5rem;
max-width: 100%;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 0.45rem 0.75rem;
font-size: 0.85rem;
}

.install-command-label {
color: var(--muted);
font-weight: 500;
}

.install-command-code {
font-family: Consolas, 'Courier New', monospace;
color: var(--text);
max-width: 100%;
overflow-wrap: anywhere;
}

.install-command-copy {
background: none;
border: none;
cursor: pointer;
color: var(--muted);
padding: 0;
display: flex;
align-items: center;
transition: color 0.15s;
}

.install-command-copy:hover,
.install-command-copy:focus-visible {
color: var(--text);
}

.install-command-copy:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 4px;
border-radius: 4px;
}

.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}

/* DEMO GIF */
.demo {
max-width: 860px;
Expand Down Expand Up @@ -680,13 +741,29 @@ <h1>Switch apps in an <em>instant</em>.<br>No more Alt+Tab.</h1>
<a href="https://github.com/ipatalas/app-switcher/releases/latest/download/AppSwitcher-portable.zip">🗜️
Portable (.zip) <span class="dl-sub">No install needed</span></a>
<div class="dl-divider"></div>
<a href="https://community.chocolatey.org/packages/ipatalas-appswitcher" target="_blank" rel="noreferrer">🍫
Chocolatey <span class="dl-sub">choco install</span></a>
<div class="dl-divider"></div>
<a class="muted-link" href="https://github.com/ipatalas/app-switcher/releases">All
releases on GitHub →</a>
</div>
</div>
<a href="#demo" class="btn-secondary">See it in action</a>
</div>
<p class="hero-meta">Windows 10 2004+ / 11 · Free · <strong>{{VERSION}}</strong></p>
<div class="install-command">
<span class="install-command-label">via Chocolatey:</span>
<code class="install-command-code">choco install ipatalas-appswitcher</code>
<button class="install-command-copy" type="button" aria-label="Copy Chocolatey install command"
onclick="copyChoco(this)">
<svg width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"
aria-hidden="true">
<rect x="9" y="9" width="13" height="13" rx="2" />
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
</svg>
</button>
<span class="visually-hidden" aria-live="polite"></span>
</div>
</section>

<!-- DEMO GIF -->
Expand Down Expand Up @@ -989,6 +1066,32 @@ <h2>Ready to ditch Alt+Tab?</h2>
answer.setAttribute('aria-hidden', 'false');
}
}

const copyIconMarkup = '<rect x="9" y="9" width="13" height="13" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>';
const copiedIconMarkup = '<polyline points="20 6 9 17 4 12"></polyline>';

function setCopyFeedback(button, statusText, iconMarkup, ariaLabel) {
const icon = button.querySelector('svg');
const status = button.parentElement.querySelector('.visually-hidden');

icon.innerHTML = iconMarkup;
button.setAttribute('aria-label', ariaLabel);
status.textContent = statusText;

clearTimeout(button._copyResetTimeout);
button._copyResetTimeout = setTimeout(() => {
icon.innerHTML = copyIconMarkup;
button.setAttribute('aria-label', 'Copy Chocolatey install command');
status.textContent = '';
}, 1500);
}

async function copyChoco(button) {
const command = button.parentElement.querySelector('.install-command-code').textContent.trim();

await navigator.clipboard.writeText(command);
setCopyFeedback(button, 'Copied Chocolatey install command', copiedIconMarkup, 'Copied Chocolatey install command');
}
</script>

</body>
Expand Down
Loading