项目目录结构
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline';connect-src *">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="./style.css" type="text/css">
<title>Bookmarker</title>
</head>
<body>
<h1>Bookmarker</h1>
<div class="error-message"></div>
<section class="add-new-link">
<form class="new-link-form">
<input type="url" class="new-link-url" placeholder="URL" size="100" required>
<input type="submit" class="new-link-submit" value="Submit" disabled>
</form>
</section>
<section class="links"></section>
<section class="controls">
<button class="clear-storage">Clear Storage</button>
</section>
<script src="./renderer.js"></script>
</body>
</html>
main.js
const {app,BrowserWindow} = require('electron');
const path = require('path');
let mainWindow = null;
const createWindow = () => {
const win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
}
})
win.loadFile('app/index.html')
};
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
});
preload.js
const { shell } = require('electron');
window.addEventListener('DOMContentLoaded', () => {
const linksSection = document.querySelector('.links');
linksSection.addEventListener('click',(event)=>{
if(event.target.href){
event.preventDefault();
shell.openExternal(event.target.href);
}
});
})
renderer.js
const parser = new DOMParser();
const parseResponse = (text)=>{
return parser.parseFromString(text, 'text/html');
};
const findTitle = (nodes)=>{
return nodes.querySelector('title').innerText;
};
const linksSection = document.querySelector('.links');
const errorMessage = document.querySelector('.error-message');
const newLinkForm = document.querySelector('.new-link-form');
const newLinkUrl = document.querySelector('.new-link-url');
const newLinkSubmit = document.querySelector('.new-link-submit');
const clearStorageButton = document.querySelector('.clear-storage');
newLinkUrl.addEventListener('keyup',()=>{
newLinkSubmit.disabled = !newLinkUrl.validity.valid;
});
newLinkForm.addEventListener('submit',(event)=>{
event.preventDefault();
const url = newLinkUrl.value;
fetch(url)
.then(validateReponse)
.then(response=>response.text())
.then(parseResponse)
.then(findTitle)
.then(title=>storeLink(title,url))
.then(clearForm)
.then(renderLinks)
.catch(error=>handleError(error, url));
});
const clearForm = ()=>{newLinkUrl.value = null;};
const storeLink = (title, url)=>{
localStorage.setItem(url, JSON.stringify({title: title,url:url}));
};
const getLinks = ()=>{
return Object.keys(localStorage).map(key=>JSON.parse(localStorage.getItem(key)));
};
const convertToElement = (link)=>{
return `
<div class="link">
<h3>${link.title}</h3>
<p>
<a href="${link.url}">${link.url}</a>
</p>
</div>
`;
};
const renderLinks = ()=>{
const linkElements = getLinks().map(convertToElement).join('');
linksSection.innerHTML = linkElements;
};
renderLinks();
clearStorageButton.addEventListener('click',()=>{
localStorage.clear();
linksSection.innerHTML = '';
});
const handleError = (error, url)=>{
errorMessage.innerHTML = `There was an issue adding "${url}": ${error.message}`.trim();
setTimeout(()=>errorMessage.innerText = null, 5000);
};
const validateReponse = (response)=>{
if(response.ok){
return response;
}
throw new Error(`Status code of ${response.status} ${response.statusText}`);
};
style.css
html {
box-sizing: border-box;
}
*, *::before, *::after {
box-sizing: inherit;
}
body, input {
font: menu;
}
package.json
{
"name": "mytest",
"version": "1.0.0",
"description": "",
"main": "./app/main.js",
"scripts": {
"start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Zhou Huang",
"license": "ISC",
"devDependencies": {
"electron": "^19.0.8"
}
}
效果展示
Git
Gitee