diff --git a/Dockerfile b/Dockerfile
index 2d89598..0c49b53 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -25,6 +25,7 @@ COPY --from=builder /install /usr/local
COPY backend ./backend
COPY frontend ./frontend
COPY config.yaml .
+COPY VERSION .
COPY plugins ./plugins
COPY themes ./themes
COPY generate_password.py .
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..26b5dec
--- /dev/null
+++ b/VERSION
@@ -0,0 +1,2 @@
+0.4.0
+
diff --git a/backend/main.py b/backend/main.py
index 17ac833..4b0e2c1 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -43,6 +43,14 @@ config_path = Path(__file__).parent.parent / "config.yaml"
with open(config_path, 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
+# Load version from VERSION file (single source of truth)
+version_path = Path(__file__).parent.parent / "VERSION"
+if not version_path.exists():
+ raise FileNotFoundError("VERSION file not found. Please create it with the current version number.")
+with open(version_path, 'r', encoding='utf-8') as f:
+ version = f.read().strip()
+ config['app']['version'] = version
+
# Initialize app
app = FastAPI(
title=config['app']['name'],
diff --git a/config.yaml b/config.yaml
index 0b5c941..6d0f215 100644
--- a/config.yaml
+++ b/config.yaml
@@ -4,7 +4,6 @@
app:
name: "NoteDiscovery"
tagline: "Your Self-Hosted Knowledge Base"
- version: "1.0.0"
server:
host: "0.0.0.0"
diff --git a/frontend/app.js b/frontend/app.js
index 028d3ef..7041c09 100644
--- a/frontend/app.js
+++ b/frontend/app.js
@@ -35,6 +35,7 @@ function noteApp() {
// App state
appName: 'NoteDiscovery',
appTagline: 'Your Self-Hosted Knowledge Base',
+ appVersion: '0.0.0',
notes: [],
currentNote: '',
currentNoteName: '',
@@ -394,6 +395,7 @@ function noteApp() {
const config = await response.json();
this.appName = config.name;
this.appTagline = config.tagline;
+ this.appVersion = config.version || '0.0.0';
} catch (error) {
console.error('Failed to load config:', error);
}
@@ -1067,16 +1069,9 @@ function noteApp() {
const textarea = event.target;
const cursorPos = textarea.selectionStart || 0;
- // Create a filename based on timestamp (format: YYYYMMDDHHMMSS)
- const now = new Date();
- const timestamp = now.getFullYear() +
- String(now.getMonth() + 1).padStart(2, '0') +
- String(now.getDate()).padStart(2, '0') +
- String(now.getHours()).padStart(2, '0') +
- String(now.getMinutes()).padStart(2, '0') +
- String(now.getSeconds()).padStart(2, '0');
+ // Create a simple filename - backend will add timestamp to prevent collisions
const ext = item.type.split('/')[1] || 'png';
- const filename = `pasted-image-${timestamp}.${ext}`;
+ const filename = `pasted-image.${ext}`;
// Create a File from the blob
const file = new File([blob], filename, { type: item.type });
@@ -2215,7 +2210,7 @@ function noteApp() {
// Parse markdown
let html = marked.parse(content);
- // Post-process: Add target="_blank" to external links
+ // Post-process: Add target="_blank" to external links and title attributes to images
// Parse as DOM to safely manipulate
const tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
@@ -2237,6 +2232,16 @@ function noteApp() {
}
});
+ // Find all images and add title attribute from alt text for hover tooltips
+ const images = tempDiv.querySelectorAll('img');
+ images.forEach(img => {
+ const altText = img.getAttribute('alt');
+ if (altText) {
+ // Set title attribute to show alt text on hover
+ img.setAttribute('title', altText);
+ }
+ });
+
html = tempDiv.innerHTML;
// Trigger MathJax rendering after DOM updates
diff --git a/frontend/index.html b/frontend/index.html
index 49f7bd2..224c607 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -995,6 +995,8 @@
diff --git a/release.ps1 b/release.ps1
new file mode 100644
index 0000000..a9ccf2e
--- /dev/null
+++ b/release.ps1
@@ -0,0 +1,118 @@
+param(
+ [Parameter(Mandatory=$true)]
+ [string]$Version,
+
+ [Parameter(Mandatory=$false)]
+ [switch]$SkipCommit = $false
+)
+
+# Validate version format (semantic versioning)
+if ($Version -notmatch '^\d+\.\d+\.\d+$') {
+ Write-Host "Error: Version must be in format X.Y.Z (e.g., 0.4.0)" -ForegroundColor Red
+ exit 1
+}
+
+# Check if we're on the main branch
+$currentBranch = git rev-parse --abbrev-ref HEAD
+if ($currentBranch -ne "main" -and $currentBranch -ne "master") {
+ Write-Host "Error: Releases can only be created from the main branch." -ForegroundColor Red
+ Write-Host "Current branch: $currentBranch" -ForegroundColor Yellow
+ Write-Host "Please switch to main branch first: git checkout main" -ForegroundColor Yellow
+ exit 1
+}
+
+Write-Host "Releasing version $Version from branch: $currentBranch" -ForegroundColor Green
+
+# Read current version and validate it's higher
+if (Test-Path "VERSION") {
+ $currentVersion = (Get-Content "VERSION" -Raw).Trim()
+
+ if ($currentVersion -match '^\d+\.\d+\.\d+$') {
+ # Parse versions into components
+ $currentParts = $currentVersion -split '\.'
+ $newParts = $Version -split '\.'
+
+ $currentMajor = [int]$currentParts[0]
+ $currentMinor = [int]$currentParts[1]
+ $currentPatch = [int]$currentParts[2]
+
+ $newMajor = [int]$newParts[0]
+ $newMinor = [int]$newParts[1]
+ $newPatch = [int]$newParts[2]
+
+ # Compare versions
+ $isHigher = $false
+ if ($newMajor -gt $currentMajor) {
+ $isHigher = $true
+ } elseif ($newMajor -eq $currentMajor) {
+ if ($newMinor -gt $currentMinor) {
+ $isHigher = $true
+ } elseif ($newMinor -eq $currentMinor) {
+ if ($newPatch -gt $currentPatch) {
+ $isHigher = $true
+ }
+ }
+ }
+
+ if (-not $isHigher) {
+ Write-Host "Error: New version must be higher than current version." -ForegroundColor Red
+ Write-Host "Current version: $currentVersion" -ForegroundColor Yellow
+ Write-Host "New version: $Version" -ForegroundColor Yellow
+ Write-Host "`nVersion comparison:" -ForegroundColor Yellow
+ Write-Host " Major: $newMajor vs $currentMajor" -ForegroundColor Yellow
+ Write-Host " Minor: $newMinor vs $currentMinor" -ForegroundColor Yellow
+ Write-Host " Patch: $newPatch vs $currentPatch" -ForegroundColor Yellow
+ exit 1
+ }
+
+ Write-Host "Version check passed: $currentVersion -> $Version" -ForegroundColor Green
+ } else {
+ Write-Host "Error: Current VERSION file has invalid format. Must be in X.Y.Z format (e.g., 0.4.0)" -ForegroundColor Red
+ Write-Host "Current VERSION file contains: '$currentVersion'" -ForegroundColor Yellow
+ Write-Host "Please fix the VERSION file before creating a release." -ForegroundColor Yellow
+ exit 1
+ }
+} else {
+ Write-Host "No existing VERSION file found. Creating new one..." -ForegroundColor Yellow
+}
+
+# Check if working directory is clean (unless skipping commit)
+if (-not $SkipCommit) {
+ $status = git status --porcelain
+ if ($status) {
+ Write-Host "Warning: Working directory has uncommitted changes:" -ForegroundColor Yellow
+ Write-Host $status -ForegroundColor Yellow
+ $response = Read-Host "Continue anyway? (y/N)"
+ if ($response -ne 'y' -and $response -ne 'Y') {
+ Write-Host "Aborted." -ForegroundColor Red
+ exit 1
+ }
+ }
+}
+
+# Update VERSION file (single source of truth)
+Write-Host "Updating VERSION file..." -ForegroundColor Yellow
+$Version | Out-File -FilePath "VERSION" -Encoding utf8 -NoNewline
+
+# Commit changes (unless skipped)
+if (-not $SkipCommit) {
+ Write-Host "Committing version changes..." -ForegroundColor Yellow
+ git add VERSION
+ git commit -m "Bump version to $Version"
+
+ # Push commits first
+ Write-Host "Pushing commits..." -ForegroundColor Yellow
+ git push
+}
+
+# Create git tag
+Write-Host "Creating git tag v$Version..." -ForegroundColor Yellow
+git tag -a "v$Version" -m "Release version $Version"
+
+# Push tag to remote
+Write-Host "Pushing tag to remote..." -ForegroundColor Yellow
+git push origin "v$Version"
+
+Write-Host "`nRelease $Version completed successfully!" -ForegroundColor Green
+Write-Host "Tag: v$Version" -ForegroundColor Cyan
+