OPML logic
This commit is contained in:
parent
2041e39ab8
commit
98ff8be9d8
52
src/import.rs
Normal file
52
src/import.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Module for handling OPML feed list imports
|
||||||
|
|
||||||
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
use rocket::data::ToByteUnit;
|
||||||
|
use rocket::http::Status;
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use rocket::serde::{Deserialize, Serialize};
|
||||||
|
use rocket::{post, Data};
|
||||||
|
use rocket_db_pools::Connection;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use crate::user::AuthenticatedUser;
|
||||||
|
use crate::Db;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
pub struct ImportResponse {
|
||||||
|
success: bool,
|
||||||
|
message: String,
|
||||||
|
feeds_imported: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Import feeds from an OPML file
|
||||||
|
#[post("/import/opml", data = "<file>")]
|
||||||
|
pub async fn import_opml(
|
||||||
|
mut _db: Connection<Db>,
|
||||||
|
_user: AuthenticatedUser,
|
||||||
|
file: Data<'_>,
|
||||||
|
) -> Result<Json<ImportResponse>, Status> {
|
||||||
|
// Limit file size to 1MB
|
||||||
|
let file_data = file.open(1.mebibytes()).into_bytes().await.map_err(|e| {
|
||||||
|
error!("Failed to read OPML file: {}", e);
|
||||||
|
Status::BadRequest
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if !file_data.is_complete() {
|
||||||
|
error!("OPML file too large");
|
||||||
|
return Err(Status::PayloadTooLarge);
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = file_data.value;
|
||||||
|
let _cursor = Cursor::new(bytes);
|
||||||
|
|
||||||
|
// TODO: Parse OPML and import feeds
|
||||||
|
// For now just return a placeholder response
|
||||||
|
Ok(Json(ImportResponse {
|
||||||
|
success: true,
|
||||||
|
message: "OPML file received successfully. Import not yet implemented.".to_string(),
|
||||||
|
feeds_imported: None,
|
||||||
|
}))
|
||||||
|
}
|
@ -8,6 +8,7 @@ use tracing_subscriber::FmtSubscriber;
|
|||||||
mod demo;
|
mod demo;
|
||||||
mod feed_utils;
|
mod feed_utils;
|
||||||
mod feeds;
|
mod feeds;
|
||||||
|
mod import;
|
||||||
mod poll;
|
mod poll;
|
||||||
mod poll_utils;
|
mod poll_utils;
|
||||||
mod session_store;
|
mod session_store;
|
||||||
@ -162,6 +163,7 @@ fn rocket() -> _ {
|
|||||||
feeds::delete_feed,
|
feeds::delete_feed,
|
||||||
poll::poll_feed,
|
poll::poll_feed,
|
||||||
poll::update_entry_state,
|
poll::update_entry_state,
|
||||||
|
import::import_opml,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.mount("/static", FileServer::from("static"))
|
.mount("/static", FileServer::from("static"))
|
||||||
|
129
static/js/app.js
129
static/js/app.js
@ -1,20 +1,23 @@
|
|||||||
// Add at the beginning of the file
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Mobile menu functionality
|
// Mobile menu functionality
|
||||||
const hamburgerMenu = document.getElementById('hamburgerMenu');
|
const hamburgerMenu = document.getElementById('hamburgerMenu');
|
||||||
const sidebarClose = document.getElementById('sidebarClose');
|
const sidebarClose = document.getElementById('sidebarClose');
|
||||||
const sidebar = document.getElementById('sidebar');
|
const sidebar = document.getElementById('sidebar');
|
||||||
|
const userMenuButton = document.getElementById('userMenuButton');
|
||||||
|
const userMenuDropdown = document.getElementById('userMenuDropdown');
|
||||||
|
|
||||||
// Add OPML import button handler
|
// Create a hidden file input for OPML upload
|
||||||
const importOpmlButton = document.getElementById('importOpmlButton');
|
const opmlFileInput = document.createElement('input');
|
||||||
importOpmlButton.addEventListener('click', function() {
|
opmlFileInput.type = 'file';
|
||||||
console.log('OPML import button clicked - functionality coming soon!');
|
opmlFileInput.accept = '.opml,.xml';
|
||||||
});
|
opmlFileInput.style.display = 'none';
|
||||||
|
document.body.appendChild(opmlFileInput);
|
||||||
|
|
||||||
function toggleSidebar() {
|
function toggleSidebar() {
|
||||||
sidebar.classList.toggle('active');
|
sidebar.classList.toggle('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mobile menu handlers
|
||||||
hamburgerMenu.addEventListener('click', toggleSidebar);
|
hamburgerMenu.addEventListener('click', toggleSidebar);
|
||||||
sidebarClose.addEventListener('click', toggleSidebar);
|
sidebarClose.addEventListener('click', toggleSidebar);
|
||||||
|
|
||||||
@ -28,6 +31,80 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
sidebar.classList.remove('active');
|
sidebar.classList.remove('active');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// OPML import handlers
|
||||||
|
opmlFileInput.addEventListener('change', async (e) => {
|
||||||
|
if (e.target.files.length > 0) {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/import/opml', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.success) {
|
||||||
|
alert('OPML import successful: ' + result.message);
|
||||||
|
// Refresh the feed list if feeds were imported
|
||||||
|
if (result.feeds_imported) {
|
||||||
|
handleFeeds();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert('OPML import failed: ' + result.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert('Failed to import OPML file. Please try again.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('OPML import failed:', error);
|
||||||
|
alert('Failed to import OPML file. Please try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Clear the input so the same file can be selected again
|
||||||
|
e.target.value = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// User menu handlers
|
||||||
|
userMenuButton.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
userMenuDropdown.classList.toggle('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
if (!e.target.closest('.user-menu')) {
|
||||||
|
userMenuDropdown.classList.remove('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
userMenuDropdown.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('importOpmlButton').addEventListener('click', () => {
|
||||||
|
opmlFileInput.click();
|
||||||
|
userMenuDropdown.classList.remove('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('logoutButton').addEventListener('click', async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/logout', {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
window.location.href = '/login';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Logout failed:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize feeds
|
||||||
|
handleFeeds();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch and display feeds
|
// Fetch and display feeds
|
||||||
@ -348,46 +425,6 @@ async function handleFeeds() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load feeds when page loads
|
|
||||||
document.addEventListener('DOMContentLoaded', handleFeeds);
|
|
||||||
|
|
||||||
// User menu functionality
|
|
||||||
const userMenuButton = document.getElementById('userMenuButton');
|
|
||||||
const userMenuDropdown = document.getElementById('userMenuDropdown');
|
|
||||||
|
|
||||||
// Toggle menu on button click
|
|
||||||
userMenuButton.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
userMenuDropdown.classList.toggle('show');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close menu when clicking outside
|
|
||||||
document.addEventListener('click', (e) => {
|
|
||||||
if (!e.target.closest('.user-menu')) {
|
|
||||||
userMenuDropdown.classList.remove('show');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Prevent menu from closing when clicking inside it
|
|
||||||
userMenuDropdown.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Logout functionality
|
|
||||||
document.getElementById('logoutButton').addEventListener('click', async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch('/logout', {
|
|
||||||
method: 'POST',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
window.location.href = '/login';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Logout failed:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Modal functionality
|
// Modal functionality
|
||||||
const modal = document.getElementById('addFeedModal');
|
const modal = document.getElementById('addFeedModal');
|
||||||
const addFeedButton = document.getElementById('addFeedButton');
|
const addFeedButton = document.getElementById('addFeedButton');
|
||||||
|
Loading…
Reference in New Issue
Block a user