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
13 changes: 9 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@ on:
branches: [main]
jobs:
ci:
runs-on: ubuntu-latest
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '22'
- run: npm install
- run: npm ci
- run: npm run typecheck
- run: npm run lint
- run: npm run format:check
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: Release
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
jobs:
Expand Down Expand Up @@ -29,7 +28,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: '22'
- run: npm install
- run: npm ci

- name: Sync package.json version
run: npm --no-git-tag-version version ${{ needs.create-tag.outputs.new_version }}
Expand All @@ -52,6 +51,8 @@ jobs:
release/*.exe
release/*.AppImage
release/*.deb
release/*.yml
release/*.blockmap

update-homebrew:
needs: [create-tag, build-and-release]
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ dist-ssr
dist-electron
release
*.env
coverage/
29 changes: 29 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Contributing to PaperCache

First of all, thank you for considering contributing to PaperCache!

## Development Setup

1. **Clone the repository:**
```bash
git clone https://github.com/VariableThe/PaperCache.git
cd PaperCache
```

2. **Install dependencies:**
We strictly use `npm ci` to ensure reproducible builds.
```bash
npm ci
```

3. **Start the development server:**
```bash
npm run dev
```

## Development Guidelines
- **Pull Requests Required**: Never push new features directly to the `main` branch. Always create a new branch and push your changes as a Pull Request (PR) for review.
- **Pre-PR Checks**: Run `npm run lint`, `npm run typecheck`, `npm run format:check`, and `npm run test` before opening any PR — don't open a PR with failing checks.
- **Performance Reporting**: Performance changes require a before/after bundle size comparison in the PR description (just paste the Vite build output).

Thank you for your contributions!
18 changes: 18 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Security Policy

## Supported Versions

Currently, only the latest version of PaperCache receives security updates. Please ensure you are running the most recent version available.

## Reporting a Vulnerability

We take the security of PaperCache seriously. If you discover a security vulnerability, we would appreciate it if you could report it privately so it can be addressed before being disclosed publicly.

Please report any security issues to: **security@papercache.app**

When reporting, please include:
- A description of the vulnerability.
- Steps to reproduce the issue.
- Any potential impact you have identified.

You should receive an acknowledgment of your report within 48 hours, along with an estimated timeline for a fix. Thank you for helping keep PaperCache secure!
141 changes: 115 additions & 26 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import {
dialog,
safeStorage,
powerMonitor,
session,
} from 'electron'
import electronUpdater from 'electron-updater'
const { autoUpdater } = electronUpdater
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import fs from 'node:fs'
Expand All @@ -35,8 +38,15 @@ if (!fs.existsSync(COMMANDS_DIR)) {
fs.mkdirSync(COMMANDS_DIR)
}

fs.writeFileSync(
path.join(COMMANDS_DIR, 'basics.md'),
function writeCommandFile(name: string, content: string) {
const filePath = path.join(COMMANDS_DIR, name)
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, content)
}
}

writeCommandFile(
'basics.md',
`# Basics

- **Zoom**: \`Cmd + +\` to zoom in, \`Cmd + -\` to zoom out, \`Cmd + 0\` to reset.
Expand All @@ -56,11 +66,11 @@ fs.writeFileSync(
*Example use:* Press \`Cmd+K\` right now, select "Settings", and set your global hotkey!

Next: [Folders](/file commands/folders.md)
`,
`
)

fs.writeFileSync(
path.join(COMMANDS_DIR, 'folders.md'),
writeCommandFile(
'folders.md',
`# Folders

Organize your notes by using a \`/\` in the note title.
Expand All @@ -70,11 +80,11 @@ Folders automatically receive a unique color identifier in the Graph View and Se
If you rename this note (click the title at the top left) to \`projects/PaperCache.md\`, it will automatically be placed inside a \`projects\` folder!

Next: [Variables](/file commands/variables.md)
`,
`
)

fs.writeFileSync(
path.join(COMMANDS_DIR, 'variables.md'),
writeCommandFile(
'variables.md',
`# Variables & Math

PaperCache is a smart scratchpad. You can define variables and write math equations that auto-calculate.
Expand All @@ -92,11 +102,11 @@ x * 3 = \u200B30
API_KEY

Next: [Markdown & Code](/file commands/markdown.md)
`,
`
)

fs.writeFileSync(
path.join(COMMANDS_DIR, 'markdown.md'),
writeCommandFile(
'markdown.md',
`# Markdown & Code

PaperCache supports full markdown with seamless inline editing.
Expand Down Expand Up @@ -127,11 +137,11 @@ Type \`/ai <prompt>\` and press enter to summon an AI assistant directly into yo
\`/ai Write a python function to reverse a string\`

Next: [Formats & Colors](/file commands/formats.md)
`,
`
)

fs.writeFileSync(
path.join(COMMANDS_DIR, 'formats.md'),
writeCommandFile(
'formats.md',
`# Formats & Colors

PaperCache automatically recognizes and highlights common formats so you can easily spot them in your notes.
Expand All @@ -146,11 +156,11 @@ Dates and times are also highlighted to help you keep track of your schedule.
Meeting on 31-05-2024 at 14:30.

Next: [Tags](/file commands/tags.md)
`,
`
)

fs.writeFileSync(
path.join(COMMANDS_DIR, 'tags.md'),
writeCommandFile(
'tags.md',
`# Tags

You can tag your notes anywhere by typing an exclamation mark followed by a word (e.g., !important or !work).
Expand All @@ -163,11 +173,11 @@ When you open the search menu (\`Cmd+P\`), you'll see all your unique tags at th
Next: [Tasks](/file commands/tasks.md)

[Back to Welcome](/file Welcome.md)
`,
`
)

fs.writeFileSync(
path.join(COMMANDS_DIR, 'tasks.md'),
writeCommandFile(
'tasks.md',
`# Tasks & Reminders

Stay on top of your work by using tasks!
Expand All @@ -184,17 +194,17 @@ Overdue tasks will automatically highlight in red.
Next: [Ready](/file commands/ready.md)

[Back to Welcome](/file Welcome.md)
`,
`
)

fs.writeFileSync(
path.join(COMMANDS_DIR, 'ready.md'),
writeCommandFile(
'ready.md',
`# Ready to get started?

You're all set to use PaperCache! Start jotting down your thoughts, creating folders, and exploring the capabilities.

[Back to Welcome](/file Welcome.md)
`,
`
)

const welcomePath = path.join(NOTES_DIR, 'Welcome.md')
Expand Down Expand Up @@ -346,6 +356,39 @@ app.on('web-contents-created', (event, contents) => {
})

app.whenReady().then(() => {
// Content Security Policy
const isDev = !!process.env.VITE_DEV_SERVER_URL;
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
const isDevCSP =
"default-src 'none'; " +
"script-src 'self' 'unsafe-eval' 'unsafe-inline'; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https:; " +
"connect-src 'self' https: wss:; " +
"font-src 'self' data: https:; " +
"object-src 'none'; " +
"base-uri 'none';"
const isProdCSP =
"default-src 'none'; " +
"script-src 'self' 'unsafe-eval'; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https:; " +
"connect-src 'self' https:; " +
"font-src 'self' data: https:; " +
"object-src 'none'; " +
"base-uri 'none';"

callback({
responseHeaders: {
...details.responseHeaders,
// unsafe-eval is required for mathjs dynamic compilation
'Content-Security-Policy': [isDev ? isDevCSP : isProdCSP],
},
})
})

autoUpdater.checkForUpdatesAndNotify()

Comment thread
coderabbitai[bot] marked this conversation as resolved.
createWindow()

powerMonitor.on('suspend', () => {
Expand Down Expand Up @@ -379,6 +422,7 @@ app.whenReady().then(() => {

const contextMenu = Menu.buildFromTemplate([
{ label: 'Show/Hide PaperCache', click: toggleWindow },
{ label: 'Check for Updates', click: () => autoUpdater.checkForUpdatesAndNotify() },
{ type: 'separator' },
{
label: 'Quit',
Expand Down Expand Up @@ -615,11 +659,56 @@ ipcMain.on('open-settings', () => {
})


ipcMain.handle('openai-chat', async (_, { model, messages, apiKey, baseURL }) => {
let memoryApiKey = ''
try {
const file = fs.readFileSync(path.join(NOTES_DIR, 'config.enc'), 'utf-8')
if (safeStorage.isEncryptionAvailable()) {
memoryApiKey = safeStorage.decryptString(Buffer.from(file, 'base64'))
} else {
memoryApiKey = file
}
} catch {
// Empty
}

ipcMain.handle('set-api-key', (_, key: string) => {
memoryApiKey = key;
try {
const dataToSave = safeStorage.isEncryptionAvailable()
? safeStorage.encryptString(key).toString('base64')
: key
fs.writeFileSync(path.join(NOTES_DIR, 'config.enc'), dataToSave)
return true
} catch (err) {
console.error('Failed to set API key:', err)
return false
}
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

ipcMain.handle('get-api-key-status', () => {
return !!memoryApiKey && memoryApiKey.length > 0
})

ipcMain.on('check-for-updates', () => {
autoUpdater.checkForUpdatesAndNotify()
})

ipcMain.handle('openai-chat', async (_, { model, messages, baseURL }) => {
// Input Validation
if (typeof model !== 'string' || model.trim() === '') {
throw new Error('Invalid model provided')
}
if (!Array.isArray(messages)) {
throw new Error('Messages must be an array')
}
if (baseURL && typeof baseURL !== 'string') {
throw new Error('Invalid baseURL provided')
}

try {
const OpenAI = (await import('openai')).default
const openai = new OpenAI({
apiKey: apiKey || 'dummy',
apiKey: memoryApiKey || 'dummy',
Comment thread
coderabbitai[bot] marked this conversation as resolved.
baseURL: baseURL || undefined,
})
const completion = await openai.chat.completions.create({
Expand Down
5 changes: 4 additions & 1 deletion electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
saveNote: (id: string, content: string) => ipcRenderer.invoke('save-note', { id, content }),
deleteNote: (id: string) => ipcRenderer.invoke('delete-note', id),
renameNote: (oldId: string, newId: string) => ipcRenderer.invoke('rename-note', { oldId, newId }),
openAIChat: (args: { model: string, messages: { role: string; content: string }[], apiKey: string, baseURL: string }) => ipcRenderer.invoke('openai-chat', args),
openAIChat: (args: { model: string, messages: { role: string; content: string }[], baseURL: string }) => ipcRenderer.invoke('openai-chat', args),
setApiKey: (key: string) => ipcRenderer.invoke('set-api-key', key),
getApiKeyStatus: () => ipcRenderer.invoke('get-api-key-status'),
checkForUpdates: () => ipcRenderer.send('check-for-updates'),
readNote: (id: string) => ipcRenderer.invoke('read-note', id),
exportNote: (filename: string, content: string) =>
ipcRenderer.invoke('export-note', filename, content),
Expand Down
Loading
Loading