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
10 changes: 8 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,22 @@ test-e2e-ui: test-data-cleanup build
npx playwright test --ui

# Code quality commands
frontend-format:
cd frontend && $(MAKE) format

frontend-format-check:
cd frontend && $(MAKE) format-check

lint:
golangci-lint run

fmt: frontend-format-check
format: frontend-format
go fmt ./...

check-format: frontend-format-check
go fmt ./...

quality: fmt lint test test-e2e
quality: check-format lint test test-e2e

# Release commands for each platform
release-darwin-arm64:
Expand Down
10 changes: 5 additions & 5 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ func New(cfg *config.Config) (*gorm.DB, error) {
// Initialize default settings if they don't exist
var settings models.Settings
if err := db.FirstOrCreate(&settings, models.Settings{
Title: "Captain",
Subtitle: "An AI authored blog engine",
ChromaStyle: "solarized-dark",
Theme: "default",
PostsPerPage: 10,
Title: "Captain",
Subtitle: "An AI authored blog engine",
ChromaStyle: "solarized-dark",
PostsPerPage: 10,
UseLogoAsFavicon: false,
}).Error; err != nil {
return nil, fmt.Errorf("failed to initialize default settings: %w", err)
}
Expand Down
16 changes: 6 additions & 10 deletions e2e/specs/admin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ test.describe('Admin Panel E2E Tests', () => {
const pageSlug = 'test-page';

await page.click('text=Pages');
await page.click('text=Create New Page');
await page.click('text=Create Your First Page');
await page.locator('input[name="title"]').pressSequentially(pageTitle);
const slugValue = await page.locator('input[name="slug"]').inputValue();
expect(slugValue).toBe(pageSlug);
Expand All @@ -137,7 +137,7 @@ test.describe('Admin Panel E2E Tests', () => {
await page.click('text=Menu Items');

// Create custom URL menu item
await page.click('text=Create New Menu Item');
await page.click('text=Create your first menu item!');
await page.fill('input[name="label"]', 'Custom Link');
await page.fill('input[name="url"]', 'https://example.com');
await page.click('button:has-text("Create Menu Item")');
Expand Down Expand Up @@ -181,18 +181,14 @@ test.describe('Admin Panel E2E Tests', () => {

await page.fill('input[name="title"]', 'Updated Title');
await page.fill('input[name="subtitle"]', 'Updated Subtitle');
await page.selectOption('select[name="theme"]', 'dark');
await page.fill('input[name="posts_per_page"]', '1');

const settingsSaveResponse = page.waitForResponse('**/admin/settings');
await page.click('button:has-text("Save Settings")');
await page.fill('input[name="posts-per-page"]', '1');

const response = await settingsSaveResponse;
await expect(response.status()).toBe(302);
await page.click('button:has-text("Save")');
await expect(page.locator('button:has-text("Saved")')).toBeVisible();

// Verify post time updated
await page.goto('/admin/posts');
const thirdPost = page.locator('tr').nth(3);
const thirdPost = page.locator('tbody tr').nth(2);
const publishedAt = await thirdPost.locator('td').nth(2).textContent();
expect(publishedAt).toContain('October 26, 1985 at 11:00 AM');

Expand Down
9 changes: 3 additions & 6 deletions e2e/specs/post-visibility.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,9 @@ test.describe('Post Visibility', () => {
// Go to settings page
await page.goto('/admin/settings');
// Set page per page to 50
await page.fill('input[name="posts_per_page"]', '50');
const settingsSaveResponse = page.waitForResponse('**/admin/settings');
await page.click('button:has-text("Save Settings")');

const response = await settingsSaveResponse;
await expect(response.status()).toBe(302);
await page.fill('input[name="posts-per-page"]', '50');
await page.click('button:has-text("Save")');
await expect(page.locator('button:has-text("Saved")')).toBeVisible();

// Verify posts are visible and properly marked when logged in
await page.goto('/');
Expand Down
46 changes: 0 additions & 46 deletions embedded/admin/static/css/admin.css
Original file line number Diff line number Diff line change
Expand Up @@ -316,37 +316,6 @@ code, pre {
min-width: 200px;
}

.btn {
display: inline-block;
padding: 0.5rem 1rem;
border-radius: 4px;
border: none;
cursor: pointer;
text-decoration: none;
font-size: 0.9rem;
line-height: 1.5;
}

.btn-primary {
background: var(--admin-accent);
color: white;
}

.btn-edit {
background: var(--admin-accent);
color: white;
}

.btn-delete {
background: var(--admin-danger);
color: white;
}

.btn-view {
background: var(--admin-success);
color: white;
}

.tag {
display: inline-block;
padding: 0.2rem 0.5rem;
Expand Down Expand Up @@ -489,21 +458,6 @@ label {
border: 1px solid var(--admin-border);
}

.btn-submit {
background: var(--admin-accent);
color: #000;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
font-weight: bold;
}

.btn-submit:hover {
opacity: 0.9;
}

.toggle-switch {
position: relative;
display: inline-block;
Expand Down
Binary file added embedded/admin/static/img/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
119 changes: 116 additions & 3 deletions embedded/admin/static/js/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,18 @@ function deleteUser(id) {
});
}

function togglePassword(id) {
const input = document.getElementById(id);
const button = input.nextElementSibling;
if (input.type === 'password') {
input.type = 'text';
button.textContent = 'Hide';
} else {
input.type = 'password';
button.textContent = 'Show';
}
}


function initializeMenuItemForm() {
const pageSelect = document.getElementById('page_id');
Expand Down Expand Up @@ -322,6 +334,47 @@ function openLogoMediaSelector() {
});
}

function initializeDarkModeToggle() {
const themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
const themeToggleBtn = document.getElementById('theme-toggle');

// Change the icons inside the button based on previous settings
if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
themeToggleLightIcon.classList.remove('hidden');
} else {
themeToggleDarkIcon.classList.remove('hidden');
}

themeToggleBtn.addEventListener('click', function() {
// toggle icons inside button
themeToggleDarkIcon.classList.toggle('hidden');
themeToggleLightIcon.classList.toggle('hidden');

// if set via local storage previously
if (localStorage.getItem('color-theme')) {
if (localStorage.getItem('color-theme') === 'light') {
document.documentElement.classList.add('dark');
localStorage.setItem('color-theme', 'dark');
} else {
document.documentElement.classList.remove('dark');
localStorage.setItem('color-theme', 'light');
}

// if NOT set via local storage previously
} else {
if (document.documentElement.classList.contains('dark')) {
document.documentElement.classList.remove('dark');
localStorage.setItem('color-theme', 'light');
} else {
document.documentElement.classList.add('dark');
localStorage.setItem('color-theme', 'dark');
}
}

});
}

(function () {

document.querySelectorAll('[x-dynamic-date]').forEach((element) => {
Expand Down Expand Up @@ -367,7 +420,7 @@ function openLogoMediaSelector() {
}
} else {
error(json.error);
document.querySelector('.editor-container').scrollIntoView()
document.querySelector('.app-container').scrollIntoView()
}
}
});
Expand All @@ -377,11 +430,12 @@ function openLogoMediaSelector() {
let method = 'POST';
let url = '/admin/api/pages';

done('saving');

if (props.id) {
method = 'PUT';
url = url + '/' + props.id;
}
done('saving');

const resp = await fetch(url, {
method,
Expand All @@ -399,11 +453,69 @@ function openLogoMediaSelector() {
}
} else {
error(json.error);
document.querySelector('.editor-container').scrollIntoView()
document.querySelector('.app-container').scrollIntoView()
}
}
});

Inity.register('settings', Apps.Settings, {
onSubmit: async (data, done, error, props) => {
let method = 'POST';
let url = '/admin/api/settings';

if (props.id) {
method = 'PUT';
}

done('saving');

const resp = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});

const json = await resp.json();

if (resp.ok) {
done('saved');
} else {
error(json.error);
document.querySelector('.app-container').scrollIntoView()
}
},
uploadLogoHandler: async (files, uploadStarted, uploadFinished) => {
const data = new FormData();
data.append('logo', files[0]);
data.append('filename', files[0].name);

const resp = await fetch('/admin/api/logo', {
method: 'POST',
body: data,
});

if (resp.ok) {
const json = await resp.json();
uploadFinished(null, '/media/' + json.logoUrl);
} else {
uploadFinished(json.error, null);
}
uploadStarted();
},
deleteLogoHandler: async () => {
const resp = await fetch('/admin/api/logo', {
method: 'DELETE',
});

if (!resp.ok) {
const json = await resp.json();
console.error(json.error);
}
},
});

document.addEventListener("DOMContentLoaded", () => Inity.attach());
})();

Expand All @@ -412,4 +524,5 @@ document.addEventListener('DOMContentLoaded', () => {
initializeMenuItemForm();
initializeMenuItems();
initializeMenuToggle();
initializeDarkModeToggle();
});
39 changes: 39 additions & 0 deletions embedded/admin/static/js/menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
document.addEventListener('DOMContentLoaded', function() {
const menuToggle = document.getElementById('menu-toggle');
const sidebar = document.getElementById('sidebar');

function toggleMenu() {
const isOpen = !sidebar.classList.contains('-translate-x-full');

if (isOpen) {
// Close menu
sidebar.classList.add('-translate-x-full');
document.body.classList.remove('overflow-hidden');
} else {
// Open menu
sidebar.classList.remove('-translate-x-full');
document.body.classList.add('overflow-hidden');
}
}

menuToggle.addEventListener('click', toggleMenu);

// Handle clicks outside menu on mobile
document.addEventListener('click', function(event) {
const isMobile = window.innerWidth < 768;
const isOpen = !sidebar.classList.contains('-translate-x-full');
const clickedOutside = !sidebar.contains(event.target) && !menuToggle.contains(event.target);

if (isMobile && isOpen && clickedOutside) {
toggleMenu();
}
});

// Close menu when window is resized to desktop view
window.addEventListener('resize', function() {
if (window.innerWidth >= 768) { // md breakpoint
sidebar.classList.add('-translate-x-full');
document.body.classList.remove('overflow-hidden');
}
});
});
4 changes: 2 additions & 2 deletions embedded/admin/templates/admin_404.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<h1>404 - Page Not Found</h1>
<p>The page you are looking for does not exist.</p>
<div class="error-actions">
<a href="/admin" class="btn btn-primary">
<i class="fas fa-home"></i> Back to Dashboard
<a href="/admin" class="btn-primary">
Back to Dashboard
</a>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions embedded/admin/templates/admin_500.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
</div>
{{end}}
<div class="error-actions">
<a href="/admin" class="btn btn-primary">
<i class="fas fa-home"></i> Back to Dashboard
<a href="/admin" class="btn-primary">
Back to Dashboard
</a>
</div>
</div>
Expand Down
Loading