Skip to content

Commit f3db30d

Browse files
committed
deploy: b7af29f
0 parents  commit f3db30d

1,665 files changed

Lines changed: 618 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.nojekyll

Whitespace-only changes.

CNAME

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
flatpak.opentubex.org

code-blocks.js

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
const shellCommands = new Set([
2+
'apt',
3+
'curl',
4+
'dnf',
5+
'echo',
6+
'flatpak',
7+
'install',
8+
'rpm',
9+
'sudo',
10+
'tee',
11+
'wget',
12+
'zypper',
13+
]);
14+
15+
const tokenPattern =
16+
/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|https?:\/\/[^\s"'<>]+|\\(?=\n)|--?[a-zA-Z0-9][\w-]*|(?:\/|\.\.?\/)[\w./*-]+|\b\d+\b|[|>]+|\b[\w-]+\b/g;
17+
18+
function escapeHtml(value) {
19+
return value
20+
.replaceAll('&', '&amp;')
21+
.replaceAll('<', '&lt;')
22+
.replaceAll('>', '&gt;');
23+
}
24+
25+
function highlightShell(source) {
26+
let cursor = 0;
27+
let result = '';
28+
29+
for (const match of source.matchAll(tokenPattern)) {
30+
const token = match[0];
31+
let type;
32+
33+
result += escapeHtml(source.slice(cursor, match.index));
34+
35+
if (token.startsWith('"') || token.startsWith("'")) {
36+
type = 'string';
37+
} else if (token.startsWith('http') || token.startsWith('/') || token.startsWith('.')) {
38+
type = 'path';
39+
} else if (token.startsWith('-')) {
40+
type = 'option';
41+
} else if (/^\d+$/.test(token)) {
42+
type = 'number';
43+
} else if (token === '\\' || token.includes('|') || token.includes('>')) {
44+
type = 'operator';
45+
} else if (shellCommands.has(token)) {
46+
type = 'command';
47+
}
48+
49+
result += type
50+
? `<span class="token-${type}">${escapeHtml(token)}</span>`
51+
: escapeHtml(token);
52+
cursor = match.index + token.length;
53+
}
54+
55+
return result + escapeHtml(source.slice(cursor));
56+
}
57+
58+
function copyFallback(value) {
59+
const textarea = document.createElement('textarea');
60+
textarea.value = value;
61+
textarea.style.position = 'fixed';
62+
textarea.style.opacity = '0';
63+
document.body.append(textarea);
64+
textarea.select();
65+
const copied = document.execCommand('copy');
66+
textarea.remove();
67+
68+
if (!copied) {
69+
throw new Error('Could not copy code');
70+
}
71+
}
72+
73+
async function copyText(value) {
74+
if (navigator.clipboard && window.isSecureContext) {
75+
await navigator.clipboard.writeText(value);
76+
} else {
77+
copyFallback(value);
78+
}
79+
}
80+
81+
function enhanceHorizontalScroll(pre, container) {
82+
const track = document.createElement('div');
83+
track.className = 'code-scrollbar';
84+
track.hidden = true;
85+
86+
const thumb = document.createElement('div');
87+
thumb.className = 'code-scrollbar__thumb';
88+
thumb.tabIndex = 0;
89+
thumb.setAttribute('role', 'scrollbar');
90+
thumb.setAttribute('aria-label', 'Code block horizontal scroll');
91+
thumb.setAttribute('aria-orientation', 'horizontal');
92+
thumb.setAttribute('aria-valuemin', '0');
93+
thumb.setAttribute('aria-valuemax', '100');
94+
track.append(thumb);
95+
container.append(track);
96+
97+
function refresh() {
98+
const maxScroll = pre.scrollWidth - pre.clientWidth;
99+
if (maxScroll <= 0) {
100+
track.hidden = true;
101+
return;
102+
}
103+
104+
track.hidden = false;
105+
const thumbWidth = Math.max(36, Math.round(track.clientWidth * (pre.clientWidth / pre.scrollWidth)));
106+
const maxThumbOffset = track.clientWidth - thumbWidth;
107+
const thumbOffset = maxThumbOffset <= 0 ? 0 : (pre.scrollLeft / maxScroll) * maxThumbOffset;
108+
thumb.style.width = `${thumbWidth}px`;
109+
thumb.style.transform = `translateX(${thumbOffset}px)`;
110+
thumb.setAttribute('aria-valuenow', String(Math.round((pre.scrollLeft / maxScroll) * 100)));
111+
}
112+
113+
pre.addEventListener('scroll', refresh, { passive: true });
114+
window.addEventListener('resize', refresh, { passive: true });
115+
if ('ResizeObserver' in window) {
116+
const observer = new ResizeObserver(refresh);
117+
observer.observe(pre);
118+
observer.observe(track);
119+
}
120+
121+
let startX = 0;
122+
let startScroll = 0;
123+
124+
thumb.addEventListener('pointerdown', (event) => {
125+
startX = event.clientX;
126+
startScroll = pre.scrollLeft;
127+
thumb.setPointerCapture(event.pointerId);
128+
track.classList.add('is-dragging');
129+
event.preventDefault();
130+
});
131+
132+
thumb.addEventListener('pointermove', (event) => {
133+
if (!thumb.hasPointerCapture(event.pointerId)) return;
134+
const maxScroll = pre.scrollWidth - pre.clientWidth;
135+
const maxThumbOffset = Math.max(1, track.clientWidth - thumb.offsetWidth);
136+
pre.scrollLeft = startScroll + ((event.clientX - startX) / maxThumbOffset) * maxScroll;
137+
});
138+
139+
function endDrag(event) {
140+
if (thumb.hasPointerCapture(event.pointerId)) thumb.releasePointerCapture(event.pointerId);
141+
track.classList.remove('is-dragging');
142+
}
143+
144+
thumb.addEventListener('pointerup', endDrag);
145+
thumb.addEventListener('pointercancel', endDrag);
146+
147+
track.addEventListener('pointerdown', (event) => {
148+
if (event.target !== track) return;
149+
const maxScroll = pre.scrollWidth - pre.clientWidth;
150+
const maxThumbOffset = Math.max(1, track.clientWidth - thumb.offsetWidth);
151+
const targetOffset = event.clientX - track.getBoundingClientRect().left - thumb.offsetWidth / 2;
152+
pre.scrollLeft = (Math.min(maxThumbOffset, Math.max(0, targetOffset)) / maxThumbOffset) * maxScroll;
153+
});
154+
155+
thumb.addEventListener('keydown', (event) => {
156+
const step = Math.max(48, Math.round(pre.clientWidth * 0.25));
157+
if (event.key === 'ArrowLeft') pre.scrollLeft -= step;
158+
else if (event.key === 'ArrowRight') pre.scrollLeft += step;
159+
else if (event.key === 'Home') pre.scrollLeft = 0;
160+
else if (event.key === 'End') pre.scrollLeft = pre.scrollWidth;
161+
else return;
162+
event.preventDefault();
163+
});
164+
165+
requestAnimationFrame(refresh);
166+
}
167+
168+
for (const code of document.querySelectorAll('code.language-shell')) {
169+
const source = code.textContent;
170+
code.innerHTML = highlightShell(source);
171+
172+
const pre = code.parentElement;
173+
const container = document.createElement('div');
174+
container.className = 'code-block';
175+
pre.before(container);
176+
container.append(pre);
177+
enhanceHorizontalScroll(pre, container);
178+
179+
const button = document.createElement('button');
180+
button.className = 'copy-button';
181+
button.type = 'button';
182+
button.setAttribute('aria-label', 'Copy code');
183+
button.title = 'Copy code';
184+
button.innerHTML = `
185+
<svg class="copy-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
186+
<rect width="14" height="14" x="8" y="8" rx="2"></rect>
187+
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path>
188+
</svg>
189+
<svg class="check-icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
190+
<path d="m5 12 4 4L19 6"></path>
191+
</svg>
192+
`;
193+
194+
let resetTimer;
195+
button.addEventListener('click', async () => {
196+
try {
197+
await copyText(source);
198+
button.dataset.copied = '';
199+
button.setAttribute('aria-label', 'Copied');
200+
button.title = 'Copied';
201+
clearTimeout(resetTimer);
202+
resetTimer = setTimeout(() => {
203+
delete button.dataset.copied;
204+
button.setAttribute('aria-label', 'Copy code');
205+
button.title = 'Copy code';
206+
}, 2000);
207+
} catch {
208+
button.setAttribute('aria-label', 'Copy failed');
209+
button.title = 'Copy failed';
210+
}
211+
});
212+
213+
container.append(button);
214+
}

favicon.ico

364 KB
Binary file not shown.

favicon.svg

Lines changed: 10 additions & 0 deletions
Loading

index.html

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>OpenTubeX Flatpak</title>
7+
<link rel="icon" href="/favicon.ico" sizes="any">
8+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
9+
<link rel="stylesheet" href="/style.css">
10+
<script src="/code-blocks.js" defer></script>
11+
</head>
12+
<body>
13+
<main>
14+
<h1 id="opentubex-flatpak">OpenTubeX Flatpak</h1>
15+
<p>
16+
Flatpak packages for
17+
<a href="https://opentubex.org/">OpenTubeX</a>.
18+
</p>
19+
20+
<h2 id="install">Install</h2>
21+
<p>Add the remote once:</p>
22+
<div class="card">
23+
<pre><code class="language-shell">flatpak remote-add --if-not-exists opentubex \
24+
https://flatpak.opentubex.org/opentubex.flatpakrepo
25+
26+
flatpak install opentubex org.opentubex.OpenTubeX</code></pre>
27+
</div>
28+
29+
<h2 id="nightly-builds">Nightly builds</h2>
30+
<p>Install the opt-in development branch from the same remote:</p>
31+
<div class="card">
32+
<pre><code class="language-shell">flatpak install opentubex org.opentubex.OpenTubeX//nightly</code></pre>
33+
</div>
34+
35+
<h2 id="update">Update</h2>
36+
<div class="card">
37+
<pre><code class="language-shell">flatpak update org.opentubex.OpenTubeX</code></pre>
38+
</div>
39+
40+
<h2 id="offline-bundles">Offline bundles</h2>
41+
<p>
42+
You can also download <code>.flatpak</code> bundle files from
43+
<a href="https://github.com/OpenTubeX/flatpak/releases">GitHub Releases</a>
44+
and run <code>flatpak install ./org.opentubex.OpenTubeX-*.flatpak</code>.
45+
Bundle installs are not tied to this remote, so they do not receive
46+
<code>flatpak update</code> or updates through your software store
47+
(for example Discover on KDE).
48+
</p>
49+
</main>
50+
</body>
51+
</html>

opentubex.flatpakrepo

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[Flatpak Repo]
2+
Version=1
3+
Title=OpenTubeX
4+
Homepage=https://opentubex.org/
5+
Description=OpenTubeX stable and nightly Flatpak packages
6+
Url=https://flatpak.opentubex.org/repo
7+
DefaultBranch=stable
8+
GPGKey=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

repo/.lock

Whitespace-only changes.

repo/config

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[core]
2+
repo_version=1
3+
mode=archive-z2
4+
indexed-deltas=true
5+
6+
[flatpak]
7+
default-branch=stable

0 commit comments

Comments
 (0)