Skip to content
Merged
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
8 changes: 0 additions & 8 deletions .github/workflows/check_version.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,25 @@ jobs:
backend_toml=$(grep -Po '(?<=^version = ")[^"]+' backend/pyproject.toml)
backend_app=$(grep -Po "(?<=version=\")[^\"]+" backend/app.py)
frontend_pkg=$(node -p "require('./frontend/package.json').version")
menu_jsx=$(grep -Po "(?<=APP_VERSION = 'v)[^']+" frontend/src/components/Menu.jsx)
app_jsx=$(grep -Po '(?<=Clippy v)[0-9]+\.[0-9]+\.[0-9]+' frontend/src/App.jsx)
readme_en=$(grep -Po '(?<=Version-v)[0-9]+\.[0-9]+\.[0-9]+' README.md)
readme_tw=$(grep -Po '(?<=Version-v)[0-9]+\.[0-9]+\.[0-9]+' README.zh-TW.md)

echo "backend_toml=$backend_toml"
echo "backend_app=$backend_app"
echo "frontend_pkg=$frontend_pkg"
echo "menu_jsx=$menu_jsx"
echo "app_jsx=$app_jsx"
echo "readme_en=$readme_en"
echo "readme_tw=$readme_tw"

echo "current=$backend_toml" >> "$GITHUB_OUTPUT"

if [ "$backend_toml" != "$backend_app" ] || \
[ "$backend_toml" != "$frontend_pkg" ] || \
[ "$backend_toml" != "$menu_jsx" ] || \
[ "$backend_toml" != "$app_jsx" ] || \
[ "$backend_toml" != "$readme_en" ] || \
[ "$backend_toml" != "$readme_tw" ]; then
echo "::error::Version mismatch detected!"
echo " backend/pyproject.toml: $backend_toml"
echo " backend/app.py: $backend_app"
echo " frontend/package.json: $frontend_pkg"
echo " Menu.jsx: $menu_jsx"
echo " App.jsx: $app_jsx"
echo " README.md: $readme_en"
echo " README.zh-TW.md: $readme_tw"
exit 1
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# Clippy

<img src="https://img.shields.io/badge/Version-v2.0.0-green">
<img src="https://img.shields.io/badge/Version-v2.0.1-green">

A web-based application that allows users to share text and files in real-time through secure, encrypted sessions.

Expand Down
2 changes: 1 addition & 1 deletion README.zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# Clippy

<img src="https://img.shields.io/badge/Version-v2.0.0-green">
<img src="https://img.shields.io/badge/Version-v2.0.1-green">

一個讓使用者能透過安全、加密的的方式,即時分享文字與檔案的網頁應用程式。

Expand Down
2 changes: 1 addition & 1 deletion backend/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ async def lifespan(_app: FastAPI):
app = FastAPI(
title="Clippy API",
description="Secure collaborative clipboard with real-time file and text sharing",
version="2.0.0",
version="2.0.1",
openapi_url="/api/v2/openapi.json" if ENABLE_DOCS else None,
docs_url="/api/v2/docs" if ENABLE_DOCS else None,
redoc_url=None,
Expand Down
2 changes: 1 addition & 1 deletion backend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "clippy-backend"
version = "2.0.0"
version = "2.0.1"
description = "Clippy - Collaborative clipboard backend"
requires-python = ">=3.12"
dependencies = [
Expand Down
2 changes: 1 addition & 1 deletion backend/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export default [
requestAnimationFrame: 'readonly',
cancelAnimationFrame: 'readonly',
getComputedStyle: 'readonly',
// Injected at build time by vite.config.js `define`.
__APP_VERSION__: 'readonly',
},
},
plugins: {
Expand Down
4 changes: 2 additions & 2 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
"version": "2.0.0",
"version": "2.0.1",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
55 changes: 47 additions & 8 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,61 @@
import React, {useEffect, useState} from 'react';
import {SessionProvider, useSession} from './context/SessionContext';
import {ToastProvider} from './context/ToastContext';
import {ConfirmProvider} from './context/ConfirmContext';
import {ConfirmProvider, useConfirm} from './context/ConfirmContext';
import {SessionEntry} from './components/SessionEntry';
import {ClipboardInterface} from './components/ClipboardInterface';
import {getSession} from './utils/api';
import {initConfig} from './utils/config';

// The id in the URL, if the path looks like one. `/r/<id>/<code>` raw links and
// anything else with a slash are excluded by the character class.
function urlSessionId() {
const id = window.location.pathname.replace(/^\//, '').trim().toLowerCase();
return /^[a-z0-9]+$/.test(id) ? id : null;
}

function AppContent() {
const {sessionData} = useSession();
const {sessionData, clearSession} = useSession();
const confirm = useConfirm();
const [isReady, setIsReady] = useState(false);

useEffect(() => {
initConfig()
.then(() => setIsReady(true))
.catch((err) => {
(async () => {
try {
await initConfig();
} catch (err) {
console.error('Failed to initialize:', err);
setIsReady(true);
});
}
// A saved session plus a *different* id in the URL — scanning a new
// QR while an old session is still stored — is ambiguous. Without
// this the stored session always won, the URL was ignored, and the
// only thing the user saw was the old session reported as expired.
const newId = urlSessionId();
if (sessionData && newId && newId !== sessionData.connection_id) {
const oldId = sessionData.connection_id;
const oldAlive = await getSession(oldId, sessionData.user_id).then(() => true, () => false);
const shown = oldAlive ? <code>{oldId}</code> : <s><code>{oldId}</code></s>;
const goNew = await confirm({
title: 'Open the new connection?',
message: (
<>
You already have connection {shown}{oldAlive ? '' : ' open but expired'}.
{' '}Open <code>{newId}</code> instead?
</>
),
confirmText: 'Open new',
cancelText: 'Stay',
confirmStyle: 'primary',
});
if (goNew) {
clearSession(); // SessionEntry joins from the URL on mount
} else {
window.history.replaceState({}, '', `/${oldId}`);
}
}
setIsReady(true);
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

if (!isReady) {
Expand All @@ -34,7 +73,7 @@ function AppContent() {
</main>
{!sessionData && (
<footer className="app-footer">
<span className="app-footer-version">Clippy v2.0.0</span>
<span className="app-footer-version">Clippy v{__APP_VERSION__}</span>
<a
className="app-footer-repo"
href="https://github.com/SamWang8891/clippy"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Menu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {useTheme} from '../hooks/useTheme';
import {destroySession, toggleCurl, toggleJoin, transferHost} from '../utils/api';
import './Menu.css';

const APP_VERSION = 'v2.0.0';
const APP_VERSION = `v${__APP_VERSION__}`;
const GITHUB_URL = 'https://github.com/SamWang8891/clippy';

const THEME_OPTIONS = [
Expand Down
6 changes: 6 additions & 0 deletions frontend/vite.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import {readFileSync} from 'node:fs'
import {defineConfig, loadEnv} from 'vite'
import react from '@vitejs/plugin-react'

// Single source of truth for the version the UI displays. Resolved relative to
// this file rather than cwd, so it survives being built from anywhere.
const {version} = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8'))

export default defineConfig(({mode}) => {
const env = loadEnv(mode, process.cwd(), '')
const apiUrl = env.VITE_API_URL || 'http://localhost:8123'
Expand All @@ -10,6 +15,7 @@ export default defineConfig(({mode}) => {

return {
plugins: [react()],
define: {__APP_VERSION__: JSON.stringify(version)},
server: {
port: 3000,
proxy: {
Expand Down
Loading