Fire Sale is an only slightly clever play on price markdowns—because it’s a Markdown editor after all. Clipmaster 9000 is a simple UI for the Clipmaster application.
const { app, BrowserWindow, dialog } = require('electron');
const fs = require('fs');
const windows = new Set();
const openFiles = new Map();
app.on('ready', () => {
createWindow();
});
app.on('window-all-closed', () => {
if (process.platform === 'darwin') {
return false;
}
});
app.on('activate', (event, hasVisibleWindows) => {
if (!hasVisibleWindows) { createWindow(); }
});
const createWindow = exports.createWindow = () => {
let x, y;
const currentWindow = BrowserWindow.getFocusedWindow();
if (currentWindow) {
const [ currentWindowX, currentWindowY ] = currentWindow.getPosition();
x = currentWindowX + 10;
y = currentWindowY + 10;
}
let newWindow = new BrowserWindow({ x, y, show: false });
newWindow.loadURL(`file://${__dirname}/index.html`);
newWindow.once('ready-to-show', () => {
newWindow.show();
});
newWindow.on('close', (event) => {
if (newWindow.isDocumentEdited()) {
event.preventDefault();
const result = dialog.showMessageBox(newWindow, {
type: 'warning',
title: 'Quit with Unsaved Changes?',
message: 'Your changes will be lost permanently if you do not save.',
buttons: [
'Quit Anyway',
'Cancel',
],
cancelId: 1,
defaultId: 0
});
if (result === 0) newWindow.destroy();
}
});
newWindow.on('closed', () => {
windows.delete(newWindow);
stopWatchingFile(newWindow);
newWindow = null;
});
windows.add(newWindow);
return newWindow;
};
const getFileFromUser = exports.getFileFromUser = (targetWindow) => {
const files = dialog.showOpenDialog(targetWindow, {
properties: ['openFile'],
filters: [
{ name: 'Text Files', extensions: ['txt'] },
{ name: 'Markdown Files', extensions: ['md', 'markdown'] }
]
});
if (files) { openFile(targetWindow, files[0]); }
};
const openFile = exports.openFile = (targetWindow, file) => {
const content = fs.readFileSync(file).toString();
app.addRecentDocument(file);
targetWindow.setRepresentedFilename(file);
targetWindow.webContents.send('file-opened', file, content);
startWatchingFile(targetWindow, file);
};
const saveMarkdown = exports.saveMarkdown = (targetWindow, file, content) =>
{
if (!file) {
file = dialog.showSaveDialog(targetWindow, {
title: 'Save Markdown',
defaultPath: app.getPath('documents'),
filters: [
{ name: 'Markdown Files', extensions: ['md', 'markdown'] }
]
});
}
if (!file) return;
fs.writeFileSync(file, content);
openFile(targetWindow, file);
};
const saveHtml = exports.saveHtml = (targetWindow, content) => {
const file = dialog.showSaveDialog(targetWindow, {
title: 'Save HTML',
defaultPath: app.getPath('documents'),
filters: [
{ name: 'HTML Files', extensions: ['html', 'htm'] }
]
});
if (!file) return;
fs.writeFileSync(file, content);
};
const startWatchingFile = (targetWindow, file) => {
stopWatchingFile(targetWindow);
const watcher = fs.watchFile(file, () => {
const content = fs.readFileSync(file);
targetWindow.webContents.send('file-changed', file, content);
});
openFiles.set(targetWindow, watcher);
};
const stopWatchingFile = (targetWindow) => {
if (openFiles.has(targetWindow)) {
openFiles.get(targetWindow).stop();
openFiles.delete(targetWindow);
}
};
const { remote, ipcRenderer } = require('electron');
const path = require('path');
const mainProcess = remote.require('./main.js');
const currentWindow = remote.getCurrentWindow();
const marked = require('marked');
const markdownView = document.querySelector('#markdown');
const htmlView = document.querySelector('#html');
const newFileButton = document.querySelector('#new-file');
const openFileButton = document.querySelector('#open-file');
const saveMarkdownButton = document.querySelector('#save-markdown');
const revertButton = document.querySelector('#revert');
const saveHtmlButton = document.querySelector('#save-html');
const showFileButton = document.querySelector('#show-file');
const openInDefaultButton = document.querySelector('#open-in-default');
let filePath = null;
let originalContent = '';
const isDifferentContent = (content) => content !== markdownView.value;
const renderMarkdownToHtml = (markdown) => {
htmlView.innerHTML = marked(markdown, { sanitize: true });
};
const renderFile = (file, content) => {
filePath = file;
originalContent = content;
markdownView.value = content;
renderMarkdownToHtml(content);
updateUserInterface(false);
};
const updateUserInterface = (isEdited) => {
let title = 'Fire Sale';
if (filePath) { title = `${path.basename(filePath)} - ${title}`; }
if (isEdited) { title = `${title} (Edited)`; }
currentWindow.setTitle(title);
currentWindow.setDocumentEdited(isEdited);
saveMarkdownButton.disabled = !isEdited;
revertButton.disabled = !isEdited;
};
markdownView.addEventListener('keyup', (event) => {
const currentContent = event.target.value;
renderMarkdownToHtml(currentContent);
updateUserInterface(currentContent !== originalContent);
});
newFileButton.addEventListener('click', () => {
mainProcess.createWindow();
});
openFileButton.addEventListener('click', () => {
mainProcess.getFileFromUser(currentWindow);
});
saveMarkdownButton.addEventListener('click', () => {
mainProcess.saveMarkdown(currentWindow, filePath, markdownView.value);
});
revertButton.addEventListener('click', () => {
markdownView.value = originalContent;
renderMarkdownToHtml(originalContent);
});
saveHtmlButton.addEventListener('click', () => {
mainProcess.saveHtml(currentWindow, htmlView.innerHTML);
});
ipcRenderer.on('file-opened', (event, file, content) => {
if (currentWindow.isDocumentEdited() && isDifferentContent(content)) {
const result = remote.dialog.showMessageBox(currentWindow, {
type: 'warning',
title: 'Overwrite Current Unsaved Changes?',
message: 'Opening a new file in this window will overwrite your unsaved
changes. Open this file anyway?',
buttons: [
'Yes',
'Cancel',
],
defaultId: 0,
cancelId: 1,
});
if (result === 1) { return; }
}
renderFile(file, content);
});
ipcRenderer.on('file-changed', (event, file, content) => {
if (isDifferentContent(content)) return;
const result = remote.dialog.showMessageBox(currentWindow, {
type: 'warning',
title: 'Overwrite Current Unsaved Changes?',
message: 'Another application has changed this file. Load changes?',
buttons: [
'Yes',
'Cancel',
],
defaultId: 0,
cancelId: 1
});
renderFile(file, content);
});
/* Implement Drag and Drop */
document.addEventListener('dragstart', event => event.preventDefault());
document.addEventListener('dragover', event => event.preventDefault());
document.addEventListener('dragleave', event => event.preventDefault());
document.addEventListener('drop', event => event.preventDefault());
const getDraggedFile = (event) => event.dataTransfer.items[0];
const getDroppedFile = (event) => event.dataTransfer.files[0];
const fileTypeIsSupported = (file) => {
return ['text/plain', 'text/markdown'].includes(file.type);
};
markdownView.addEventListener('dragover', (event) => {
const file = getDraggedFile(event);
if (fileTypeIsSupported(file)) {
markdownView.classList.add('drag-over');
} else {
markdownView.classList.add('drag-error');
}
});
markdownView.addEventListener('dragleave', () => {
markdownView.classList.remove('drag-over');
markdownView.classList.remove('drag-error');
});
markdownView.addEventListener('drop', (event) => {
const file = getDroppedFile(event);
if (fileTypeIsSupported(file)) {
mainProcess.openFile(currentWindow, file.path);
} else {
alert('That file type is not supported');
}
markdownView.classList.remove('drag-over');
markdownView.classList.remove('drag-error');
});
const { app, dialog, Menu } = require('electron');
const mainProcess = require('./main');
const template = [
{
label: 'File',
submenu: [
{
label: 'New File',
accelerator: 'CommandOrControl+N',
click() {
mainProcess.createWindow();
}
},
{
label: 'Open File',
accelerator: 'CommandOrControl+O',
click(item, focusedWindow) {
if (focusedWindow) {
return mainProcess.getFileFromUser(focusedWindow);
}
const newWindow = mainProcess.createWindow();
newWindow.on('show', () => {
mainProcess.getFileFromUser(newWindow);
});
},
},
{
label: 'Save File',
accelerator: 'CommandOrControl+S',
click(item, focusedWindow) {
if (!focusedWindow) {
return dialog.showErrorBox(
'Cannot Save or Export',
'There is currently no active document to save or export.'
);
}
mainProcess.saveMarkdown(focusedWindow);
},
},
{
label: 'Export HTML',
accelerator: 'Shift+CommandOrControl+S',
click(item, focusedWindow) {
if (!focusedWindow) {
return dialog.showErrorBox(
'Cannot Save or Export',
'There is currently no active document to save or export.'
);
}
mainProcess.saveHtml(focusedWindow);
},
},
],
},
{
label: 'Edit',
submenu: [
{
label: 'Undo',
accelerator: 'CommandOrControl+Z',
role: 'undo',
},
{
label: 'Redo',
accelerator: 'Shift+CommandOrControl+Z',
role: 'redo',
},
{ type: 'separator' },
{
label: 'Cut',
accelerator: 'CommandOrControl+X',
role: 'cut',
},
{
label: 'Copy',
accelerator: 'CommandOrControl+C',
role: 'copy',
},
{
label: 'Paste',
accelerator: 'CommandOrControl+V',
role: 'paste',
},
{
label: 'Select All',
accelerator: 'CommandOrControl+A',
role: 'selectall',
},
],
},
{
label: 'Window',
submenu: [
{
label: 'Minimize',
accelerator: 'CommandOrControl+M',
role: 'minimize',
},
{
label: 'Close',
accelerator: 'CommandOrControl+W',
role: 'close',
},
],
},
{
label: 'Help',
role: 'help',
submenu: [
{
label: 'Visit Website',
click() { /* To be implemented */ }
},
{
label: 'Toggle Developer Tools',
click(item, focusedWindow) {
if (focusedWindow) focusedWindow.webContents.toggleDevTools();
}
}
],
}
];
if (process.platform === 'darwin') {
const name = 'Fire Sale';
template.unshift({
label: name,
submenu: [
{
label: `About ${name}`,
role: 'about',
},
{ type: 'separator' },
{
label: 'Services',
role: 'services',
submenu: [],
},
{ type: 'separator' },
{
label: `Hide ${name}`,
accelerator: 'Command+H',
role: 'hide',
},
{
label: 'Hide Others',
accelerator: 'Command+Alt+H',
role: 'hideothers',
},
{
label: 'Show All',
role: 'unhide',
},
{ type: 'separator' },
{
label: `Quit ${name}`,
accelerator: 'Command+Q',
click() { app.quit(); },
},
],
});
const windowMenu = template.find(item => item.label === 'Window');
windowMenu.submenu.push(
{ type: 'separator' },
{
label: 'Bring All to Front',
role: 'front',
}
);
}
module.exports = Menu.buildFromTemplate(template);
const { remote, ipcRenderer } = require('electron');
const { Menu } = remote;
const path = require('path');
const mainProcess = remote.require('./main.js');
const currentWindow = remote.getCurrentWindow();
const marked = require('marked');
const markdownView = document.querySelector('#markdown');
const htmlView = document.querySelector('#html');
const newFileButton = document.querySelector('#new-file');
const openFileButton = document.querySelector('#open-file');
const saveMarkdownButton = document.querySelector('#save-markdown');
const revertButton = document.querySelector('#revert');
const saveHtmlButton = document.querySelector('#save-html');
const showFileButton = document.querySelector('#show-file');
const openInDefaultButton = document.querySelector('#open-in-default');
let filePath = null;
let originalContent = '';
const isDifferentContent = (content) => content !== markdownView.value;
const renderMarkdownToHtml = (markdown) => {
htmlView.innerHTML = marked(markdown, { sanitize: true });
};
const renderFile = (file, content) => {
filePath = file;
originalContent = content;
markdownView.value = content;
renderMarkdownToHtml(content);
updateUserInterface(false);
};
const updateUserInterface = (isEdited) => {
let title = 'Fire Sale';
if (filePath) { title = `${path.basename(filePath)} - ${title}`; }
if (isEdited) { title = `${title} (Edited)`; }
currentWindow.setTitle(title);
currentWindow.setDocumentEdited(isEdited);
saveMarkdownButton.disabled = !isEdited;
revertButton.disabled = !isEdited;
};
markdownView.addEventListener('keyup', (event) => {
const currentContent = event.target.value;
renderMarkdownToHtml(currentContent);
updateUserInterface(currentContent !== originalContent);
});
newFileButton.addEventListener('click', () => {
mainProcess.createWindow();
});
openFileButton.addEventListener('click', () => {
mainProcess.getFileFromUser(currentWindow);
});
saveMarkdownButton.addEventListener('click', () => {
mainProcess.saveMarkdown(currentWindow, filePath, markdownView.value);
});
revertButton.addEventListener('click', () => {
markdownView.value = originalContent;
renderMarkdownToHtml(originalContent);
});
saveHtmlButton.addEventListener('click', () => {
mainProcess.saveHtml(currentWindow, htmlView.innerHTML);
});
ipcRenderer.on('file-opened', (event, file, content) => {
if (currentWindow.isDocumentEdited() && isDifferentContent(content)) {
const result = remote.dialog.showMessageBox(currentWindow, {
type: 'warning',
title: 'Overwrite Current Unsaved Changes?',
message: 'Opening a new file in this window will overwrite your unsaved changes. Open this file anyway?',
buttons: [
'Yes',
'Cancel',
],
defaultId: 0,
cancelId: 1,
});
if (result === 1) { return; }
}
renderFile(file, content);
});
ipcRenderer.on('file-changed', (event, file, content) => {
if (isDifferentContent(content)) return;
const result = remote.dialog.showMessageBox(currentWindow, {
type: 'warning',
title: 'Overwrite Current Unsaved Changes?',
message: 'Another application has changed this file. Load changes?',
buttons: [
'Yes',
'Cancel',
],
defaultId: 0,
cancelId: 1
});
renderFile(file, content);
});
/* Implement Drag and Drop */
document.addEventListener('dragstart', event => event.preventDefault());
document.addEventListener('dragover', event => event.preventDefault());
document.addEventListener('dragleave', event => event.preventDefault());
document.addEventListener('drop', event => event.preventDefault());
const getDraggedFile = (event) => event.dataTransfer.items[0];
const getDroppedFile = (event) => event.dataTransfer.files[0];
const fileTypeIsSupported = (file) => {
return ['text/plain', 'text/markdown'].includes(file.type);
};
markdownView.addEventListener('dragover', (event) => {
const file = getDraggedFile(event);
if (fileTypeIsSupported(file)) {
markdownView.classList.add('drag-over');
} else {
markdownView.classList.add('drag-error');
}
});
markdownView.addEventListener('dragleave', () => {
markdownView.classList.remove('drag-over');
markdownView.classList.remove('drag-error');
});
markdownView.addEventListener('drop', (event) => {
const file = getDroppedFile(event);
if (fileTypeIsSupported(file)) {
mainProcess.openFile(currentWindow, file.path);
} else {
alert('That file type is not supported');
}
markdownView.classList.remove('drag-over');
markdownView.classList.remove('drag-error');
});
ipcRenderer.on('save-markdown', () => {
mainProcess.saveMarkdown(currentWindow, filePath, markdownView.value);
});
ipcRenderer.on('save-html', () => {
mainProcess.saveHtml(currentWindow, htmlView.innerHTML);
});
const markdownContextMenu = Menu.buildFromTemplate([
{ label: 'Open File', click() { mainProcess.getFileFromUser(); } },
{ type: 'separator' },
{ label: 'Cut', role: 'cut' },
{ label: 'Copy', role: 'copy' },
{ label: 'Paste', role: 'paste' },
{ label: 'Select All', role: 'selectall' },
]);
markdownView.addEventListener('contextmenu', (event) => {
event.preventDefault();
markdownContextMenu.popup();
});
const { remote, ipcRenderer, shell } = require('electron');
const { Menu } = remote;
const path = require('path');
const mainProcess = remote.require('./main.js');
const currentWindow = remote.getCurrentWindow();
const marked = require('marked');
const markdownView = document.querySelector('#markdown');
const htmlView = document.querySelector('#html');
const newFileButton = document.querySelector('#new-file');
const openFileButton = document.querySelector('#open-file');
const saveMarkdownButton = document.querySelector('#save-markdown');
const revertButton = document.querySelector('#revert');
const saveHtmlButton = document.querySelector('#save-html');
const showFileButton = document.querySelector('#show-file');
const openInDefaultButton = document.querySelector('#open-in-default');
let filePath = null;
let originalContent = '';
const isDifferentContent = (content) => content !== markdownView.value;
const renderMarkdownToHtml = (markdown) => {
htmlView.innerHTML = marked(markdown, { sanitize: true });
};
const renderFile = (file, content) => {
filePath = file;
originalContent = content;
markdownView.value = content;
renderMarkdownToHtml(content);
showFileButton.disabled = false;
openInDefaultButton.disabled = false;
updateUserInterface(false);
};
const updateUserInterface = (isEdited) => {
let title = 'Fire Sale';
if (filePath) { title = `${path.basename(filePath)} - ${title}`; }
if (isEdited) { title = `${title} (Edited)`; }
currentWindow.setTitle(title);
currentWindow.setDocumentEdited(isEdited);
saveMarkdownButton.disabled = !isEdited;
revertButton.disabled = !isEdited;
};
markdownView.addEventListener('keyup', (event) => {
const currentContent = event.target.value;
renderMarkdownToHtml(currentContent);
updateUserInterface(currentContent !== originalContent);
});
newFileButton.addEventListener('click', () => {
mainProcess.createWindow();
});
openFileButton.addEventListener('click', () => {
mainProcess.getFileFromUser(currentWindow);
});
saveMarkdownButton.addEventListener('click', () => {
mainProcess.saveMarkdown(currentWindow, filePath, markdownView.value);
});
revertButton.addEventListener('click', () => {
markdownView.value = originalContent;
renderMarkdownToHtml(originalContent);
});
saveHtmlButton.addEventListener('click', () => {
mainProcess.saveHtml(currentWindow, htmlView.innerHTML);
});
const showFile = () => {
if (!filePath) { return alert('This file has not been saved to the file
system.'); }
shell.showItemInFolder(filePath);
};
const openInDefaultApplication = () => {
if (!filePath) { return alert('This file has not been saved to the file
system.'); }
shell.openItem(filePath);
};
showFileButton.addEventListener('click', showFile);
openInDefaultButton.addEventListener('click', openInDefaultApplication);
ipcRenderer.on('show-file', showFile);
ipcRenderer.on('open-in-default', openInDefaultApplication);
ipcRenderer.on('file-opened', (event, file, content) => {
if (currentWindow.isDocumentEdited() && isDifferentContent(content)) {
const result = remote.dialog.showMessageBox(currentWindow, {
type: 'warning',
title: 'Overwrite Current Unsaved Changes?',
message: 'Opening a new file in this window will overwrite your unsaved
changes. Open this file anyway?',
buttons: [
'Yes',
'Cancel',
],
defaultId: 0,
cancelId: 1,
});
if (result === 1) { return; }
}
renderFile(file, content);
});
ipcRenderer.on('file-changed', (event, file, content) => {
if (isDifferentContent(content)) return;
const result = remote.dialog.showMessageBox(currentWindow, {
type: 'warning',
title: 'Overwrite Current Unsaved Changes?',
message: 'Another application has changed this file. Load changes?',
buttons: [
'Yes',
'Cancel',
],
defaultId: 0,
cancelId: 1
});
renderFile(file, content);
});
/* Implement Drag and Drop */
document.addEventListener('dragstart', event => event.preventDefault());
document.addEventListener('dragover', event => event.preventDefault());
document.addEventListener('dragleave', event => event.preventDefault());
document.addEventListener('drop', event => event.preventDefault());
const getDraggedFile = (event) => event.dataTransfer.items[0];
const getDroppedFile = (event) => event.dataTransfer.files[0];
const fileTypeIsSupported = (file) => {
return ['text/plain', 'text/markdown'].includes(file.type);
};
markdownView.addEventListener('dragover', (event) => {
const file = getDraggedFile(event);
if (fileTypeIsSupported(file)) {
markdownView.classList.add('drag-over');
} else {
markdownView.classList.add('drag-error');
}
});
markdownView.addEventListener('dragleave', () => {
markdownView.classList.remove('drag-over');
markdownView.classList.remove('drag-error');
});
markdownView.addEventListener('drop', (event) => {
const file = getDroppedFile(event);
if (fileTypeIsSupported(file)) {
mainProcess.openFile(currentWindow, file.path);
} else {
alert('That file type is not supported');
}
markdownView.classList.remove('drag-over');
markdownView.classList.remove('drag-error');
});
const createContextMenu = () => {
return Menu.buildFromTemplate([
{ label: 'Open File', click() { mainProcess.getFileFromUser(); } },
{
label: 'Show File in Folder',
click: showFile,
enabled: !!filePath
},
{
label: 'Open in Default',
click: openInDefaultApplication,
enabled: !!filePath
},
{ type: 'separator' },
{ label: 'Cut', role: 'cut' },
{ label: 'Copy', role: 'copy' },
{ label: 'Paste', role: 'paste' },
{ label: 'Select All', role: 'selectall' },
]);
};
markdownView.addEventListener('contextmenu', (event) => {
event.preventDefault();
createContextMenu().popup();
});
ipcRenderer.on('save-markdown', () => {
mainProcess.saveMarkdown(currentWindow, filePath, markdownView.value);
});
ipcRenderer.on('save-html', () => {
mainProcess.saveHtml(currentWindow, filePath, markdownView.value);
});
const { app, BrowserWindow, dialog, Menu, shell } = require('electron');
const mainProcess = require('./main');
const createApplicationMenu = () => {
const hasOneOrMoreWindows = !!BrowserWindow.getAllWindows().length;
const focusedWindow = BrowserWindow.getFocusedWindow();
const hasFilePath = !!(focusedWindow &&
focusedWindow.getRepresentedFilename());
const template = [
{
label: 'File',
submenu: [
{
label: 'New File',
accelerator: 'CommandOrControl+N',
click() {
mainProcess.createWindow();
}
},
{
label: 'Open File',
accelerator: 'CommandOrControl+O',
click(item, focusedWindow) {
if (focusedWindow) {
return mainProcess.getFileFromUser(focusedWindow);
}
const newWindow = mainProcess.createWindow();
newWindow.on('show', () => {
mainProcess.getFileFromUser(newWindow);
});
},
},
{
label: 'Save File',
accelerator: 'CommandOrControl+S',
enabled: hasOneOrMoreWindows,
click(item, focusedWindow) {
if (!focusedWindow) {
return dialog.showErrorBox(
'Cannot Save or Export',
'There is currently no active document to save or export.'
);
}
mainProcess.saveMarkdown(focusedWindow);
},
},
{
label: 'Export HTML',
accelerator: 'Shift+CommandOrControl+S',
enabled: hasOneOrMoreWindows,
click(item, focusedWindow) {
if (!focusedWindow) {
return dialog.showErrorBox(
'Cannot Save or Export',
'There is currently no active document to save or export.'
);
}
mainProcess.saveHtml(focusedWindow);
},
},
{ type: 'separator' },
{
label: 'Show File',
enabled: hasFilePath,
click(item, focusedWindow) {
if (!focusedWindow) {
return dialog.showErrorBox(
'Cannot Show File\'s Location',
'There is currently no active document show.'
);
}
focusedWindow.webContents.send('show-file');
},
},
{
label: 'Open in Default Application',
enabled: hasFilePath,
click(item, focusedWindow) {
if (!focusedWindow) {
return dialog.showErrorBox(
'Cannot Open File in Default Application',
'There is currently no active document to open.'
);
}
focusedWindow.webContents.send('open-in-default');
},
},
],
},
{
label: 'Edit',
submenu: [
{
label: 'Undo',
accelerator: 'CommandOrControl+Z',
role: 'undo',
},
{
label: 'Redo',
accelerator: 'Shift+CommandOrControl+Z',
role: 'redo',
},
{ type: 'separator' },
{
label: 'Cut',
accelerator: 'CommandOrControl+X',
role: 'cut',
},
{
label: 'Copy',
accelerator: 'CommandOrControl+C',
role: 'copy',
},
{
label: 'Paste',
accelerator: 'CommandOrControl+V',
role: 'paste',
},
{
label: 'Select All',
accelerator: 'CommandOrControl+A',
role: 'selectall',
},
],
},
{
label: 'Window',
submenu: [
{
label: 'Minimize',
accelerator: 'CommandOrControl+M',
role: 'minimize',
},
{
label: 'Close',
accelerator: 'CommandOrControl+W',
role: 'close',
},
],
},
{
label: 'Help',
role: 'help',
submenu: [
{
label: 'Visit Website',
click() { /* To be implemented */ }
},
{
label: 'Toggle Developer Tools',
click(item, focusedWindow) {
if (focusedWindow) focusedWindow.webContents.toggleDevTools();
}
}
],
}
];
if (process.platform === 'darwin') {
const name = 'Fire Sale';
template.unshift({
label: name,
submenu: [
{
label: `About ${name}`,
role: 'about',
},
{ type: 'separator' },
{
label: 'Services',
role: 'services',
submenu: [],
},
{ type: 'separator' },
{
label: `Hide ${name}`,
accelerator: 'Command+H',
role: 'hide',
},
{
label: 'Hide Others',
accelerator: 'Command+Alt+H',
role: 'hideothers',
},
{
label: 'Show All',
role: 'unhide',
},
{ type: 'separator' },
{
label: `Quit ${name}`,
accelerator: 'Command+Q',
click() { app.quit(); },
},
],
});
const windowMenu = template.find(item => item.label === 'Window');
windowMenu.submenu.push(
{ type: 'separator' },
{
label: 'Bring All to Front',
role: 'front',
}
);
}
return Menu.setApplicationMenu(Menu.buildFromTemplate(template));
};
module.exports = createApplicationMenu;
const { app, BrowserWindow, dialog, Menu } = require('electron');
const createApplicationMenu = require('./application-menu');
const fs = require('fs');
const windows = new Set();
const openFiles = new Map();
app.on('ready', () => {
createApplicationMenu();
createWindow();
});
app.on('window-all-closed', () => {
if (process.platform === 'darwin') {
return false;
}
});
app.on('activate', (event, hasVisibleWindows) => {
if (!hasVisibleWindows) { createWindow(); }
});
const createWindow = exports.createWindow = () => {
let x, y;
const currentWindow = BrowserWindow.getFocusedWindow();
if (currentWindow) {
const [ currentWindowX, currentWindowY ] = currentWindow.getPosition();
x = currentWindowX + 10;
y = currentWindowY + 10;
}
let newWindow = new BrowserWindow({ x, y, show: false });
newWindow.loadURL(`file://${__dirname}/index.html`);
newWindow.once('ready-to-show', () => {
newWindow.show();
});
newWindow.on('focus', createApplicationMenu);
newWindow.on('close', (event) => {
if (newWindow.isDocumentEdited()) {
event.preventDefault();
const result = dialog.showMessageBox(newWindow, {
type: 'warning',
title: 'Quit with Unsaved Changes?',
message: 'Your changes will be lost permanently if you do not save.',
buttons: [
'Quit Anyway',
'Cancel',
],
cancelId: 1,
defaultId: 0
});
if (result === 0) newWindow.destroy();
}
});
newWindow.on('closed', () => {
windows.delete(newWindow);
createApplicationMenu();
newWindow = null;
});
windows.add(newWindow);
return newWindow;
};
const getFileFromUser = exports.getFileFromUser = (targetWindow) => {
const files = dialog.showOpenDialog(targetWindow, {
properties: ['openFile'],
filters: [
{ name: 'Text Files', extensions: ['txt'] },
{ name: 'Markdown Files', extensions: ['md', 'markdown'] }
]
});
if (files) { openFile(targetWindow, files[0]); }
};
const openFile = exports.openFile = (targetWindow, file) => {
const content = fs.readFileSync(file).toString();
startWatchingFile(targetWindow, file);
app.addRecentDocument(file);
targetWindow.setRepresentedFilename(file);
targetWindow.webContents.send('file-opened', file, content);
createApplicationMenu();
};
const saveMarkdown = exports.saveMarkdown = (targetWindow, file, content) =>
{
if (!file) {
file = dialog.showSaveDialog(targetWindow, {
title: 'Save Markdown',
defaultPath: app.getPath('documents'),
filters: [
{ name: 'Markdown Files', extensions: ['md', 'markdown'] }
]
});
}
if (!file) return;
fs.writeFileSync(file, content);
openFile(targetWindow, file);
};
const saveHtml = exports.saveHtml = (targetWindow, content) => {
const file = dialog.showSaveDialog(targetWindow, {
title: 'Save HTML',
defaultPath: app.getPath('documents'),
filters: [
{ name: 'HTML Files', extensions: ['html', 'htm'] }
]
});
if (!file) return;
fs.writeFileSync(file, content);
};
const startWatchingFile = (targetWindow, file) => {
stopWatchingFile(targetWindow);
const watcher = fs.watchFile(file, () => {
const content = fs.readFileSync(file);
targetWindow.webContents.send('file-changed', file, content);
});
openFiles.set(targetWindow, watcher);
};
const stopWatchingFile = (targetWindow) => {
if (openFiles.has(targetWindow)) {
openFiles.get(targetWindow).stop();
openFiles.delete(targetWindow);
}
};
const Menubar = require('menubar');
const { globalShortcut, Menu } = require('electron');
const menubar = Menubar({
preloadWindow: true,
index: `file://${__dirname}/index.html`,
});
menubar.on('ready', () => {
const secondaryMenu = Menu.buildFromTemplate([
{
label: 'Quit',
click() { menubar.app.quit(); },
accelerator: 'CommandOrControl+Q'
},
]);
menubar.tray.on('right-click', () => {
menubar.tray.popUpContextMenu(secondaryMenu);
});
const createClipping = globalShortcut.register('CommandOrControl+!', () =>
{
menubar.window.webContents.send('create-new-clipping');
});
const writeClipping = globalShortcut.register('CmdOrCtrl+Alt+@', () => {
menubar.window.webContents.send('write-to-clipboard');
});
const publishClipping = globalShortcut.register('CmdOrCtrl+Alt+#', () => {
menubar.window.webContents.send('publish-clipping');
});
if (!createClipping) { console.error('Registration failed',
'createClipping'); }
if (!writeClipping) { console.error('Registration failed',
'writeClipping'); }
if (!publishClipping) { console.error('Registration failed',
'publishClipping'); }
});
const { clipboard, ipcRenderer, shell } = require('electron');
const request = require('request').defaults({
url: 'https://cliphub.glitch.me/clippings',
headers: { 'User-Agent': 'Clipmaster 9000' },
json: true,
});
const clippingsList = document.getElementById('clippings-list');
const copyFromClipboardButton = document.getElementById('copy-from-clipboard');
ipcRenderer.on('create-new-clipping', () => {
addClippingToList();
new Notification('Clipping Added', {
body: `${clipboard.readText()}`
});
});
ipcRenderer.on('write-to-clipboard', () => {
const clipping = clippingsList.firstChild;
writeToClipboard(getClippingText(clipping));
new Notification('Clipping Copied', {
body: `${clipboard.readText()}`
});
});
ipcRenderer.on('publish-clipping', () => {
const clipping = clippingsList.firstChild;
publishClipping(getClippingText(clipping));
});
const createClippingElement = (clippingText) => {
const clippingElement = document.createElement('article');
clippingElement.classList.add('clippings-list-item');
clippingElement.innerHTML = `
<div class="clipping-text" disabled="true"></div>
<div class="clipping-controls">
<button class="copy-clipping">→ Clipboard</button>
<button class="publish-clipping">Publish</button>
<button class="remove-clipping">Remove</button>
</div>
`;
clippingElement.querySelector('.clipping-text').innerText = clippingText;
return clippingElement;
};
const addClippingToList = () => {
const clippingText = clipboard.readText();
const clippingElement = createClippingElement(clippingText);
clippingsList.prepend(clippingElement);
};
copyFromClipboardButton.addEventListener('click', addClippingToList);
clippingsList.addEventListener('click', (event) => {
const hasClass = className => event.target.classList.contains(className);
const clippingListItem = getButtonParent(event);
if (hasClass('remove-clipping')) removeClipping(clippingListItem);
if (hasClass('copy-clipping'))
writeToClipboard(getClippingText(clippingListItem));
if (hasClass('publish-clipping'))
publishClipping(getClippingText(clippingListItem));
});
const removeClipping = (target) => {
target.remove();
};
const writeToClipboard = (clippingText) => {
clipboard.writeText(clippingText);
};
const publishClipping = (clippingText) => {
request.post({ json: { clipping: clippingText } }, (err, response, body) =>
{
if (err) {
return new Notification('Error Publishing Your Clipping', {
body: JSON.parse(err).message
});
}
const gistUrl = body.url;
const notification = new Notification('Your Clipping Has Been Published',
{
body: `Click to open ${gistUrl} in your browser.`
});
notification.onclick = () => { shell.openExternal(gistUrl); };
clipboard.writeText(gistUrl);
});
};
const getButtonParent = ({ target }) => {
return target.parentNode.parentNode;
};
const getClippingText = (clippingListItem) => {
return clippingListItem.querySelector('.clipping-text').innerText;
};