diff --git a/.erb/configs/webpack.config.renderer.dev.dll.ts b/.erb/configs/webpack.config.renderer.dev.dll.ts
index 614b90f..2e70998 100644
--- a/.erb/configs/webpack.config.renderer.dev.dll.ts
+++ b/.erb/configs/webpack.config.renderer.dev.dll.ts
@@ -31,7 +31,12 @@ const configuration: webpack.Configuration = {
module: require('./webpack.config.renderer.dev').default.module,
entry: {
- renderer: Object.keys(dependencies || {}),
+ // webpack has some issue importing a path from @tiptap/pm
+ // so we're going to exclude it from the entry point bundle
+ // as we don't need it there anyways.
+ renderer: Object.keys(dependencies || {}).filter(
+ (it) => it !== '@tiptap/pm'
+ ),
},
output: {
diff --git a/.yarnclean b/.yarnclean
new file mode 100644
index 0000000..b591611
--- /dev/null
+++ b/.yarnclean
@@ -0,0 +1,45 @@
+# test directories
+__tests__
+test
+tests
+powered-test
+
+# asset directories
+docs
+doc
+website
+images
+assets
+
+# examples
+example
+examples
+
+# code coverage directories
+coverage
+.nyc_output
+
+# build scripts
+Makefile
+Gulpfile.js
+Gruntfile.js
+
+# configs
+appveyor.yml
+circle.yml
+codeship-services.yml
+codeship-steps.yml
+wercker.yml
+.tern-project
+.gitattributes
+.editorconfig
+.*ignore
+.eslintrc
+.jshintrc
+.flowconfig
+.documentup.json
+.yarn-metadata.json
+.travis.yml
+
+# misc
+*.md
diff --git a/assets/garden.jpeg b/assets/garden.jpeg
new file mode 100644
index 0000000..fad5932
Binary files /dev/null and b/assets/garden.jpeg differ
diff --git a/package.json b/package.json
index ccfc5a0..cc41fba 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,7 @@
"build:renderer": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.prod.ts",
"postinstall": "ts-node .erb/scripts/check-native-dep.js && electron-builder install-app-deps && cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts",
"lint": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx",
- "package": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --publish never",
+ "package": "ts-node ./.erb/scripts/clean.js dist && npm run build && electron-builder build --mac --win --publish never",
"rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir release/app",
"start": "ts-node ./.erb/scripts/check-port-in-use.js && npm run start:renderer",
"start:main": "cross-env NODE_ENV=development electronmon -r ts-node/register/transpile-only .",
@@ -91,12 +91,15 @@
"@radix-ui/react-alert-dialog": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5",
- "@tiptap/extension-character-count": "^2.0.3",
+ "@tiptap/extension-character-count": "^2.1.12",
+ "@tiptap/extension-link": "^2.1.12",
"@tiptap/extension-placeholder": "^2.0.3",
"@tiptap/extension-typography": "^2.0.4",
"@tiptap/pm": "^2.0.3",
"@tiptap/react": "^2.0.3",
"@tiptap/starter-kit": "^2.0.3",
+ "axios": "^1.6.0",
+ "cheerio": "^1.0.0-rc.12",
"dotenv": "^16.3.1",
"electron-debug": "^3.2.0",
"electron-log": "^4.4.8",
@@ -105,12 +108,10 @@
"gray-matter": "^4.0.3",
"luxon": "^3.3.0",
"million": "^2.6.4",
- "nanoid": "^4.0.2",
"openai": "4.0.0-beta.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-router-dom": "^6.11.2",
- "use-count-up": "^3.0.1"
+ "react-router-dom": "^6.11.2"
},
"devDependencies": {
"@adobe/css-tools": "^4.3.1",
@@ -140,7 +141,7 @@
"css-minimizer-webpack-plugin": "^5.0.0",
"detect-port": "^1.5.1",
"electron": "^25.8.4",
- "electron-builder": "^24.2.1",
+ "electron-builder": "^24.7.0",
"electron-devtools-installer": "^3.2.0",
"electronmon": "^2.0.2",
"eslint": "^8.42.0",
@@ -226,8 +227,15 @@
]
},
"win": {
+ "publisherName": "Pile",
"target": [
- "portable"
+ {
+ "target": "nsis",
+ "arch": [
+ "x64",
+ "ia32"
+ ]
+ }
]
},
"linux": {
@@ -262,4 +270,4 @@
],
"logLevel": "quiet"
}
-}
+}
\ No newline at end of file
diff --git a/release/app/package-lock.json b/release/app/package-lock.json
index b272627..a43e5ae 100644
--- a/release/app/package-lock.json
+++ b/release/app/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "pile",
- "version": "0.7.50",
+ "version": "0.8.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pile",
- "version": "0.7.50",
+ "version": "0.8.1",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/release/app/package.json b/release/app/package.json
index 53d5787..4996aa2 100644
--- a/release/app/package.json
+++ b/release/app/package.json
@@ -1,6 +1,6 @@
{
"name": "pile",
- "version": "0.7.50",
+ "version": "0.8.1",
"description": "Pile: Everyday journal and thought companion.",
"license": "MIT",
"author": {
diff --git a/src/main/ipc.ts b/src/main/ipc.ts
index 3556ddd..f661e5f 100644
--- a/src/main/ipc.ts
+++ b/src/main/ipc.ts
@@ -1,13 +1,14 @@
import { ipcMain, app, dialog } from 'electron';
import path from 'path';
import fs from 'fs';
-import pileHelper from './utils/PileHelper';
-import pileIndex from './utils/PileIndex';
-import pileTags from './utils/PileTags';
+import pileHelper from './utils/pileHelper';
+import pileIndex from './utils/pileIndex';
+import pileTags from './utils/pileTags';
+import pileLinks from './utils/pileLinks';
import pileHighlights from './utils/pileHighlights';
import keytar from 'keytar';
+import { getLinkPreview, getLinkContent } from './utils/linkPreview';
const os = require('os');
-
const matter = require('gray-matter');
// AI key
@@ -19,6 +20,29 @@ ipcMain.handle('set-ai-key', async (event, secretKey) => {
return await keytar.setPassword('pile', 'aikey', secretKey);
});
+ipcMain.handle('delete-ai-key', async (event) => {
+ return await keytar.deletePassword('pile', 'aikey');
+});
+
+// Link preview
+ipcMain.handle('get-link-preview', async (event, url) => {
+ const preview = await getLinkPreview(url)
+ .then((data) => {
+ return data;
+ })
+ .catch(() => null);
+ return preview;
+});
+
+ipcMain.handle('get-link-content', async (event, url) => {
+ const preview = await getLinkContent(url)
+ .then((data) => {
+ return data;
+ })
+ .catch(() => null);
+ return preview;
+});
+
// Index operations
ipcMain.handle('index-load', (event, pilePath) => {
const index = pileIndex.load(pilePath);
@@ -40,6 +64,17 @@ ipcMain.handle('index-remove', (event, filePath) => {
return index;
});
+// Links operations
+ipcMain.handle('links-get', (event, pilePath, url) => {
+ const data = pileLinks.get(pilePath, url);
+ return data;
+});
+
+ipcMain.handle('links-set', (event, pilePath, url, data) => {
+ const status = pileLinks.set(pilePath, url, data);
+ return status;
+});
+
// Highlight operations
ipcMain.handle('highlights-load', (event, pilePath) => {
const highlights = pileHighlights.load(pilePath);
@@ -92,8 +127,12 @@ ipcMain.on('change-folder', (event, newPath) => {
});
ipcMain.handle('matter-parse', async (event, file) => {
- const post = matter(file);
- return post;
+ try {
+ const post = matter(file);
+ return post;
+ } catch (error) {
+ return null;
+ }
});
ipcMain.handle('matter-stringify', async (event, { content, data }) => {
@@ -107,7 +146,7 @@ ipcMain.handle('get-files', async (event, dirPath) => {
});
ipcMain.handle('get-file', async (event, filePath) => {
- const content = await pileHelper.getFile(filePath);
+ const content = await pileHelper.getFile(filePath).catch(() => null);
return content;
});
diff --git a/src/main/utils/linkPreview.js b/src/main/utils/linkPreview.js
new file mode 100644
index 0000000..4f36007
--- /dev/null
+++ b/src/main/utils/linkPreview.js
@@ -0,0 +1,164 @@
+const axiosBase = require('axios');
+const cheerio = require('cheerio');
+
+const axios = axiosBase.create({
+ timeout: 5000,
+ maxContentLength: 5 * 1024 * 1024, // 5MB
+ maxBodyLength: 10 * 1024 * 1024, // 10MB
+});
+
+// Setting the headers directly on the instance
+axios.defaults.headers.common['User-Agent'] = 'Mozilla/5.0 Pile/1.0';
+axios.defaults.headers.post['Content-Type'] = 'application/json';
+
+export const getLinkPreview = async (url) => {
+ try {
+ const response = await axios
+ .get(url, {
+ headers: {
+ Accept: 'text/html',
+ },
+ })
+ .then((response) => {
+ const contentType = response.headers['content-type'];
+ if (contentType && contentType.includes('text/html')) {
+ return response;
+ }
+ throw new Error('Not an HTML content');
+ });
+
+ const html = response.data;
+ const $ = cheerio.load(html);
+ const parsedUrl = new URL(url);
+
+ let meta = {
+ title: '',
+ images: [],
+ host: parsedUrl.host, // Extract the host domain from the URL
+ favicon: '', // Initialize favicon with an empty string
+ };
+
+ // Extract the title
+ const title = $('title').first().text();
+ meta.title = title;
+
+ // Extract the Open Graph images
+ $('meta').each((index, element) => {
+ const property = $(element).attr('property');
+ const content = $(element).attr('content');
+
+ if (property === 'og:image') {
+ meta.images.push(content);
+ }
+ });
+
+ // Extract the favicon
+ $('link').each((index, element) => {
+ const rel = $(element).attr('rel')?.toLowerCase();
+ const href = $(element).attr('href');
+ if (rel && (rel.includes('icon') || rel === 'shortcut icon') && href) {
+ // Resolve the favicon URL relative to the host URL
+ meta.favicon = new URL(href, parsedUrl.origin).href;
+ return false; // Break out of the loop after finding the favicon
+ }
+ });
+
+ // If no favicon is found in the loop, you can set a default path
+ if (!meta.favicon) {
+ meta.favicon = parsedUrl.origin + '/favicon.ico';
+ }
+
+ return meta;
+ } catch (error) {
+ console.error(error);
+ return null;
+ }
+};
+
+const getContentHeuristics = (html) => {
+ const $ = cheerio.load(html);
+ let maxDensity = 0;
+ let mainContent = '';
+
+ $('*').each(function () {
+ const text = $(this).clone().children().remove().end().text();
+ const wordCount = text.split(/\s+/).length;
+ const density = wordCount / $(this).text().length;
+
+ // Check if the element has a higher text density and contains more words than the current max
+ if (density > maxDensity && wordCount > 200) {
+ // 200 is arbitrary
+ maxDensity = density;
+ mainContent = text;
+ }
+ });
+
+ return mainContent.trim().replace(/\s\s+/g, ' ');
+};
+
+export const getLinkContent = async (url) => {
+ try {
+ const response = await axios.get(url);
+ const $ = cheerio.load(response.data);
+
+ // Some content we want to filter out
+ $(
+ 'script, style, iframe, noscript, nav, header, footer, .nav, .menu, .footer'
+ ).remove();
+
+ let contentSections = [];
+
+ // Targets likely to hold main text content
+ const sectionSelectors = 'div, section, article, main, [role="main"]';
+
+ $(sectionSelectors).each(function () {
+ const sectionText = $(this).text().trim();
+ const textLength = sectionText.replace(/\s+/g, ' ').length;
+
+ // If the section contains a significant amount of text, consider it as content
+ if (textLength > 200) {
+ contentSections.push({
+ html: $(this).html(), // Keep the HTML to preserve images and links
+ textLength: textLength,
+ });
+ }
+ });
+
+ // Sort sections by text length, descending
+ contentSections.sort((a, b) => b.textLength - a.textLength);
+
+ // Concatenate the HTML of the top sections to form the main content
+ let mainContentHtml = contentSections
+ .slice(0, 3)
+ .map((section) => section.html)
+ .join(' '); // Adjust the number of sections as needed
+
+ // Load the concatenated HTML into Cheerio for final cleaning
+ const mainContent = cheerio.load(mainContentHtml);
+
+ // Extract the clean text content
+ const textContent = mainContent.text().replace(/\s+/g, ' ').trim();
+
+ // Extract image sources
+ const imageSources = mainContent('img')
+ .map((i, el) => mainContent(el).attr('src'))
+ .get();
+
+ // Extract links
+ const links = mainContent('a')
+ .map((i, el) => ({
+ href: mainContent(el).attr('href'),
+ text: mainContent(el).text().trim(),
+ }))
+ .get();
+
+ return {
+ text: textContent,
+ images: imageSources.slice(0, 10),
+ links: links.slice(0, 10),
+ };
+ } catch (error) {
+ console.error(error);
+ return null;
+ }
+};
diff --git a/src/main/utils/pileHelper.js b/src/main/utils/pileHelper.js
index b50e99f..ca6219c 100644
--- a/src/main/utils/pileHelper.js
+++ b/src/main/utils/pileHelper.js
@@ -70,7 +70,6 @@ class PileHelper {
const content = await fs.promises.readFile(filePath, 'utf-8');
return content;
} catch (error) {
- console.error(`File unavailable`);
throw error;
}
}
diff --git a/src/main/utils/pileIndex.js b/src/main/utils/pileIndex.js
index ac83b88..99d56d2 100644
--- a/src/main/utils/pileIndex.js
+++ b/src/main/utils/pileIndex.js
@@ -20,8 +20,18 @@ class PileIndex {
return sortedMap;
}
+ resetIndex() {
+ this.index.clear();
+ }
+
load(pilePath) {
if (!pilePath) return;
+
+ // a different pile is being loaded
+ if (pilePath !== this.pilePath) {
+ this.resetIndex();
+ }
+
this.pilePath = pilePath;
const indexFilePath = path.join(this.pilePath, this.fileName);
@@ -34,7 +44,6 @@ class PileIndex {
return sortedIndex;
} else {
- // save to initialize an empty index
this.save();
return this.index;
}
@@ -92,8 +101,8 @@ class PileIndex {
const entries = this.index.entries();
if (!entries) return;
- let strMap = JSON.stringify(Array.from(entries));
+ let strMap = JSON.stringify(Array.from(entries));
fs.writeFileSync(filePath, strMap);
}
}
diff --git a/src/main/utils/pileLinks.js b/src/main/utils/pileLinks.js
new file mode 100644
index 0000000..5cb3a32
--- /dev/null
+++ b/src/main/utils/pileLinks.js
@@ -0,0 +1,77 @@
+const fs = require('fs');
+const path = require('path');
+
+class PileLinks {
+ constructor() {
+ this.fileName = 'links.json';
+ this.pilePath = null;
+ this.links = new Map();
+ }
+
+ resetIndex() {
+ this.links.clear();
+ }
+
+ load(pilePath) {
+ if (!pilePath) return;
+
+ // skip loading
+ if (pilePath === this.pilePath) {
+ return;
+ }
+
+ // a different pile is being loaded
+ if (pilePath !== this.pilePath) {
+ this.resetIndex();
+ }
+
+ this.pilePath = pilePath;
+ const linksFilePath = path.join(this.pilePath, this.fileName);
+
+ if (fs.existsSync(linksFilePath)) {
+ const data = fs.readFileSync(linksFilePath);
+ const loadedIndex = new Map(JSON.parse(data));
+ this.links = loadedIndex;
+
+ return loadedIndex;
+ } else {
+ this.save();
+ return this.links;
+ }
+ }
+
+ get(pilePath, url) {
+ if (pilePath !== this.pilePath) {
+ this.load(pilePath);
+ }
+ return this.links.get(url);
+ }
+
+ set(pilePath, url, data) {
+ if (pilePath !== this.pilePath) {
+ this.load(pilePath);
+ }
+
+ this.links.set(url, data);
+ this.save();
+
+ return this.links;
+ }
+
+ save() {
+ if (!this.pilePath) return;
+ if (!fs.existsSync(this.pilePath)) {
+ fs.mkdirSync(this.pilePath, { recursive: true });
+ }
+
+ const filePath = path.join(this.pilePath, this.fileName);
+ const entries = this.links.entries();
+
+ if (!entries) return;
+
+ let strMap = JSON.stringify(Array.from(entries));
+ fs.writeFileSync(filePath, strMap);
+ }
+}
+
+module.exports = new PileLinks();
diff --git a/src/main/workers/pileIndexWorker.js b/src/main/workers/pileIndexWorker.js
deleted file mode 100644
index 78c1e71..0000000
--- a/src/main/workers/pileIndexWorker.js
+++ /dev/null
@@ -1,25 +0,0 @@
-const { parentPort } = require('worker_threads');
-const PileIndex = require('../utils/pileIndex');
-
-const index = new PileIndex();
-
-parentPort.on('message', (msg) => {
- switch (msg.command) {
- case 'load':
- const loadedIndex = index.load(msg.pilePath);
- parentPort.postMessage({ key: 'loaded', index: loadedIndex });
- break;
- case 'add':
- index.add(msg.filePath);
- break;
- case 'remove':
- index.removeFile(msg.filePath);
- break;
- case 'search':
- const result = index.search(msg.key, msg.value);
- parentPort.postMessage(result);
- break;
- default:
- console.log('Unknown command:', msg.command);
- }
-});
diff --git a/src/renderer/App.css b/src/renderer/App.css
index aa01b71..fccae0d 100644
--- a/src/renderer/App.css
+++ b/src/renderer/App.css
@@ -14,6 +14,7 @@
--secondary: #626262;
--base: #4d80e6;
+ --base-text: rgb(246, 247, 255);
--base-hover: #2b5bbb;
--base-yellow: #e6e04d;
--base-green: #4de64d;
@@ -42,6 +43,7 @@
--secondary: #c1c1c1;
--base: #4d88ff;
+ --base-text: hsl(213, 100%, 93%);
--base-hover: #82acff;
--base-yellow: #776b0e;
--base-green: #128212;
diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx
index 2f38af3..f44cbec 100644
--- a/src/renderer/App.tsx
+++ b/src/renderer/App.tsx
@@ -18,6 +18,8 @@ import { TagsContextProvider } from './context/TagsContext';
import { TimelineContextProvider } from './context/TimelineContext';
import { AIContextProvider } from './context/AIContext';
import { HighlightsContextProvider } from './context/HighlightsContext';
+import { LinksContextProvider } from './context/LinksContext';
+import { ToastsContextProvider } from './context/ToastsContext';
if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual';
@@ -48,62 +50,66 @@ export default function App() {
return (
-
-
-
-
-
-
-
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
- }
- />
-
-
-
-
- }
- />
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+
+ }
+ />
+
+
+
+
+
+
+
+
+
+
);
}
diff --git a/src/renderer/context/AIContext.js b/src/renderer/context/AIContext.js
index 1843e43..d9903aa 100644
--- a/src/renderer/context/AIContext.js
+++ b/src/renderer/context/AIContext.js
@@ -7,16 +7,6 @@ import {
} from 'react';
import OpenAI from 'openai';
-const processKey = (k) => {
- if (k.startsWith('unms-')) {
- k = k.substring(5);
- const reversedStr = k.split('').reverse().join('');
- return 'sk-' + reversedStr;
- }
-
- return k;
-};
-
export const AIContext = createContext();
export const AIContextProvider = ({ children }) => {
@@ -34,10 +24,10 @@ export const AIContextProvider = ({ children }) => {
if (!key) return;
- const processedKey = processKey(key);
const openaiInstance = new OpenAI({
- apiKey: processedKey,
+ apiKey: key,
});
+
setAi(openaiInstance);
};
@@ -49,7 +39,28 @@ export const AIContextProvider = ({ children }) => {
return window.electron.ipc.invoke('set-ai-key', secretKey);
};
- const AIContextValue = { ai, prompt, setKey, getKey };
+ const deleteKey = () => {
+ return window.electron.ipc.invoke('delete-ai-key');
+ };
+
+ const getCompletion = async (model = 'gpt-3', context) => {
+ const response = await ai.chat.completions.create({
+ model: 'gpt-4',
+ max_tokens: 200,
+ messages: context,
+ });
+
+ return response;
+ };
+
+ const AIContextValue = {
+ ai,
+ prompt,
+ setKey,
+ getKey,
+ deleteKey,
+ getCompletion,
+ };
return (
{children}
diff --git a/src/renderer/context/LinksContext.js b/src/renderer/context/LinksContext.js
new file mode 100644
index 0000000..c1fc82a
--- /dev/null
+++ b/src/renderer/context/LinksContext.js
@@ -0,0 +1,167 @@
+import {
+ useState,
+ createContext,
+ useContext,
+ useEffect,
+ useCallback,
+} from 'react';
+import { useLocation } from 'react-router-dom';
+import { usePilesContext } from './PilesContext';
+import { useAIContext } from './AIContext';
+import { useToastsContext } from './ToastsContext';
+
+export const LinksContext = createContext();
+
+export const LinksContextProvider = ({ children }) => {
+ const { currentPile, getCurrentPilePath } = usePilesContext();
+ const { ai, getCompletion } = useAIContext();
+ const { addNotification, updateNotification, removeNotification } =
+ useToastsContext();
+
+ const getLink = useCallback(
+ async (url) => {
+ const pilePath = getCurrentPilePath();
+ const preview = await window.electron.ipc.invoke(
+ 'links-get',
+ pilePath,
+ url
+ );
+
+ // return cached preview if available
+ if (preview) {
+ return preview;
+ }
+
+ addNotification({
+ id: url,
+ type: 'waiting',
+ message: 'Creating link preview',
+ });
+
+ // otherwise generate a new preview
+ const _preview = await getPreview(url);
+ updateNotification(url, 'thinking', 'Generating preview...');
+ const aiCard = await generateMeta(url).catch(() => {
+ console.log(
+ 'Failed to generate AI link preview, a basic preview will be used.'
+ );
+ return null;
+ });
+
+ const linkPreview = {
+ url: url,
+ createdAt: new Date().toISOString(),
+ title: _preview?.title ?? '',
+ images: _preview?.images ?? [],
+ favicon: _preview?.favicon ?? '',
+ host: _preview?.host ?? '',
+ description: aiCard?.summary ?? '',
+ summary: '',
+ aiCard: aiCard ?? null,
+ };
+
+ // cache it
+ setLink(url, linkPreview);
+
+ removeNotification(url);
+
+ return linkPreview;
+ },
+ [currentPile]
+ );
+
+ const setLink = useCallback(
+ async (url, data) => {
+ const pilePath = getCurrentPilePath();
+ window.electron.ipc.invoke('links-set', pilePath, url, data);
+ },
+ [currentPile]
+ );
+
+ const getPreview = async (url) => {
+ const data = await window.electron.ipc.invoke('get-link-preview', url);
+ return data;
+ };
+
+ const getContent = async (url) => {
+ const data = await window.electron.ipc.invoke('get-link-content', url);
+ return data;
+ };
+
+ const trimContent = (string, numWords = 2000) => {
+ const wordsArray = string.split(/\s+/);
+ if (wordsArray.length > numWords) {
+ return wordsArray.slice(0, numWords).join(' ') + '...';
+ }
+ return string;
+ };
+
+ const generateMeta = async (url) => {
+ const { text, images, links } = await getContent(url);
+ const trimmedContent = trimContent(text);
+
+ let context = [];
+
+ context.push({
+ role: 'system',
+ content: `Provided below is some extracted plaintext response of a website. Use it to generate the content for a rich preview card for the webpage.
+ The content is as follows:
+ ${trimmedContent}
+ `,
+ });
+ context.push({
+ role: 'system',
+ content: `These are the links on the page:
+ ${links}
+ `,
+ });
+ context.push({
+ role: 'system',
+ content: `These are the images on the page:
+ ${images}
+ `,
+ });
+ context.push({
+ role: 'system',
+ content: `Provide your response as a JSON object that follows this schema:
+ {
+ "url": ${url},
+ "category": '', // suggest the best category for this page based on the content. eg: video, book, recipie, app, research paper, news, opinion, blog, social media etc.
+ "images": [{src: '', alt: ''}], // key images
+ "summary": string, // tldr summary of this webpage
+ "highlights": [''], // plaintext sentences of 3-8 key insights, facts or quotes. Like an executive summary.
+ "buttons": [{title: '', href: ''}], // use the links to generate a primary and secondary buttons appropriate for this preview. ONLY use relevant links from the page.
+ }`,
+ });
+
+ const response = await ai.chat.completions.create({
+ model: 'gpt-3.5-turbo-1106',
+ max_tokens: 500,
+ messages: context,
+ response_format: {
+ type: 'json_object',
+ },
+ });
+
+ let choice = false;
+
+ try {
+ choice = JSON.parse(response.choices[0].message.content);
+ } catch (error) {}
+
+ return choice;
+ };
+
+ const linksContextValue = {
+ getLink,
+ setLink,
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useLinksContext = () => useContext(LinksContext);
diff --git a/src/renderer/context/ToastsContext.js b/src/renderer/context/ToastsContext.js
new file mode 100644
index 0000000..a071811
--- /dev/null
+++ b/src/renderer/context/ToastsContext.js
@@ -0,0 +1,94 @@
+import {
+ useState,
+ createContext,
+ useContext,
+ useEffect,
+ useCallback,
+ useRef,
+} from 'react';
+
+export const ToastsContext = createContext();
+
+export const ToastsContextProvider = ({ children }) => {
+ const [notificationsQueue, setNotificationsQueue] = useState([]);
+
+ const notificationTimeoutRef = useRef();
+
+ const processQueue = () => {
+ if (notificationsQueue.length > 0) {
+ // Set a timeout to dismiss the first notification
+ notificationTimeoutRef.current = setTimeout(() => {
+ setNotificationsQueue((currentQueue) => currentQueue.slice(1));
+ }, notificationsQueue[0].dismissTime || 5000); // Default 5 seconds
+ }
+ };
+
+ useEffect(() => {
+ // Clear any existing timeouts
+ if (notificationTimeoutRef.current) {
+ clearTimeout(notificationTimeoutRef.current);
+ notificationTimeoutRef.current = null;
+ }
+
+ processQueue();
+
+ // Clean up timeout on unmount
+ return () => {
+ if (notificationTimeoutRef.current) {
+ clearTimeout(notificationTimeoutRef.current);
+ }
+ };
+ }, [notificationsQueue]);
+
+ const addNotification = ({
+ id,
+ type = 'info',
+ message,
+ dismissTime = 5000,
+ }) => {
+ const newNotification = { id, type, message, dismissTime };
+ setNotificationsQueue((currentQueue) => [...currentQueue, newNotification]);
+ };
+
+ const updateNotification = (targetId, newType, newMessage) => {
+ setNotificationsQueue((currentQueue) =>
+ currentQueue.map((notification) =>
+ notification.id === targetId
+ ? { ...notification, type: newType, message: newMessage }
+ : notification
+ )
+ );
+ };
+
+ const removeNotification = (targetId) => {
+ setNotificationsQueue((currentQueue) =>
+ currentQueue.filter((notification) => notification.id !== targetId)
+ );
+
+ // If the notification being removed is the first in the queue, we need to clear the timeout
+ // and process the next notification if available
+ if (
+ notificationsQueue.length > 0 &&
+ notificationsQueue[0].id === targetId
+ ) {
+ clearTimeout(notificationTimeoutRef.current);
+ notificationTimeoutRef.current = null;
+ processQueue();
+ }
+ };
+
+ const ToastsContextValue = {
+ notifications: notificationsQueue,
+ addNotification,
+ updateNotification,
+ removeNotification,
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useToastsContext = () => useContext(ToastsContext);
diff --git a/src/renderer/hooks/usePostHelpers.js b/src/renderer/hooks/usePostHelpers.js
index 0c0c88b..97d8c76 100644
--- a/src/renderer/hooks/usePostHelpers.js
+++ b/src/renderer/hooks/usePostHelpers.js
@@ -18,8 +18,7 @@ export const getPost = async (postPath) => {
const post = { content: parsed.content, data: parsed.data };
return post;
} catch (error) {
- console.error(`Error reading/parsing file: ${postPath}`);
- console.error(error);
+ // TODO: check and cleanup after these files
}
};
diff --git a/src/renderer/icons/img/ChainIcon.js b/src/renderer/icons/img/ChainIcon.js
new file mode 100644
index 0000000..dec97c2
--- /dev/null
+++ b/src/renderer/icons/img/ChainIcon.js
@@ -0,0 +1,23 @@
+export const ChainIcon = (props) => {
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/src/renderer/icons/index.js b/src/renderer/icons/index.js
index fa8a678..5d971b4 100644
--- a/src/renderer/icons/index.js
+++ b/src/renderer/icons/index.js
@@ -37,3 +37,4 @@ export * from './img/NeedleIcon';
export * from './img/EditIcon';
export * from './img/AIIcon';
export * from './img/PileIcon';
+export * from './img/ChainIcon';
diff --git a/src/renderer/pages/Pile/Editor/Editor.module.scss b/src/renderer/pages/Pile/Editor/Editor.module.scss
index a32d7e2..b2d5d6c 100644
--- a/src/renderer/pages/Pile/Editor/Editor.module.scss
+++ b/src/renderer/pages/Pile/Editor/Editor.module.scss
@@ -20,8 +20,6 @@
p {
line-height: 1.45;
-
-
&:first-child {
margin-top: 0;
}
@@ -31,6 +29,14 @@
}
}
+ a {
+ color: var(--secondary);
+
+ &:hover {
+ color: var(--base);
+ }
+ }
+
&.editorBig {
p {
font-size: 1.2em;
@@ -200,8 +206,7 @@
transition: all ease-in-out 120ms;
padding: 0 18px;
line-height: 28px;
- color: var(--base);
- color: var(--active-text);
+ color: var(--base-text);
font-size: 0.9em;
border-radius: 90px;
user-select: none;
diff --git a/src/renderer/pages/Pile/Editor/LinkPreviews/LinkPreview/LinkPreview.module.scss b/src/renderer/pages/Pile/Editor/LinkPreviews/LinkPreview/LinkPreview.module.scss
new file mode 100644
index 0000000..905ed30
--- /dev/null
+++ b/src/renderer/pages/Pile/Editor/LinkPreviews/LinkPreview/LinkPreview.module.scss
@@ -0,0 +1,174 @@
+.card {
+ flex: 1 0 auto;
+ max-width: 400px;
+ background: var(--bg-secondary);
+ border-radius: 17px;
+ margin: 8px;
+ transition: all ease-in-out 220ms;
+
+ // &:hover {
+ // cursor: pointer;
+ // background: var(--bg-tertiary);
+ // }
+
+ .image {
+ padding: 3px 3px 0 3px;
+ border-radius: 17px 17px 0 0;
+
+ img {
+ width: 100%;
+ height: auto;
+ max-height: 280px;
+ border-radius: 14px 14px 3px 3px;
+ margin: 0;
+ object-fit: cover;
+ }
+ }
+
+ .content {
+ padding: 7px 12px 5px 12px;
+
+ .title {
+ font-size: 1.05em;
+ line-height: 1.4;
+ font-weight: 500;
+ color: var(--preview);
+ transition: all ease-in-out 120ms;
+
+ &:hover {
+ cursor: pointer;
+ color: var(--active);
+ }
+ }
+ }
+
+ .aiCard {
+ color: var(--secondary);
+ padding: 0 12px 12px 12px;
+ line-height: 1.35;
+ font-size: 0.9em;
+ transition: max-height ease-in-out 200ms;
+
+ .highlights {
+ display: block;
+ position: relative;
+ margin: 10px 0 0 0;
+ padding: 5px 0 0 1em;
+ height: auto;
+ max-height: 50px;
+ overflow: hidden;
+ border-top: 1px solid var(--border);
+ transition: max-height ease-in-out 200ms;
+
+ &:hover {
+ cursor: pointer;
+ max-height: 100px;
+ }
+
+ &.show {
+ max-height: 100%;
+ }
+
+ .overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+ transition: opacity ease-in-out 200ms;
+ background: linear-gradient(transparent, var(--bg-secondary));
+
+ &.hidden {
+ pointer-events: none;
+ opacity: 0;
+ }
+ }
+
+ li {
+ padding: 4px 0 4px 0;
+ list-style: outside;
+ }
+ }
+
+ .buttons {
+ display: flex;
+ flex-wrap: wrap;
+ margin: 10px 0 5px 0;
+ gap: 15px;
+
+ a {
+ display: flex;
+ align-items: center;
+ color: var(--active-text);
+ background: var(--active);
+ height: 28px;
+ line-height: 28px;
+ padding: 0 12px 0 7px;
+ border-radius: 90px;
+
+ .icon {
+ height: 20px;
+ width: 20px;
+ margin: -2px 3px 0 0;
+ }
+
+ &:hover {
+ background: var(--active-hover);
+ }
+
+ }
+ }
+ }
+
+ .footer {
+ display: flex;
+ align-items: center;
+ padding: 4px 12px 12px 12px;
+ font-weight: 400;
+ font-size: 0.9em;
+ color: var(--secondary);
+
+ .favicon {
+ height: 18px;
+ width: 18px;
+ margin-right: 9px;
+ border-radius: 2px;
+ }
+
+ .category {
+ text-transform: capitalize;
+
+ &::after {
+ content: '·';
+ margin: 0 6px;
+ opacity: 0.7;
+ }
+ }
+ }
+}
+
+.youtube {
+ margin: 8px;
+ padding: 3px 3px 0px 3px;
+ border-radius: 17px;
+ background: var(--bg-secondary);
+ width: 100%;
+ max-width: 560px;
+
+ &:hover {
+ background: var(--bg-tertiary);
+ }
+
+ iframe {
+ width: 100%;
+ height: 340px;
+ border-radius: 14px;
+ margin: 0;
+ padding: 0;
+ border: none;
+
+ &::after {
+ display: none;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/renderer/pages/Pile/Editor/LinkPreviews/LinkPreview/index.jsx b/src/renderer/pages/Pile/Editor/LinkPreviews/LinkPreview/index.jsx
new file mode 100644
index 0000000..07bbb6f
--- /dev/null
+++ b/src/renderer/pages/Pile/Editor/LinkPreviews/LinkPreview/index.jsx
@@ -0,0 +1,173 @@
+import styles from './LinkPreview.module.scss';
+import { useCallback, useState, useEffect } from 'react';
+import {
+ DiscIcon,
+ PhotoIcon,
+ TrashIcon,
+ TagIcon,
+ ChainIcon,
+} from 'renderer/icons';
+import { motion, AnimatePresence } from 'framer-motion';
+import { useLinksContext } from 'renderer/context/LinksContext';
+
+const isUrlYouTubeVideo = (url) => {
+ // Regular expression to check for various forms of YouTube URLs
+ const regExp =
+ /^(https?:\/\/)?(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
+ return regExp.test(url);
+};
+
+export default function LinkPreview({ url }) {
+ const { getLink } = useLinksContext();
+ const [expanded, setExpanded] = useState(false);
+ const [preview, setPreview] = useState(null);
+
+ const toggleExpand = () => setExpanded(!expanded);
+
+ const getPreview = async (url) => {
+ const data = await getLink(url);
+ setPreview(data);
+ };
+
+ const updateSummary = (e) => {
+ const summary = e.target.value;
+ const _preview = { ...preview, aiCard: { ...preview.aiCard, summary } };
+ };
+
+ useEffect(() => {
+ getPreview(url);
+ }, [url]);
+
+ if (!preview) return;
+
+ const createYouTubeEmbed = (url) => {
+ // Extract the video ID from the YouTube URL
+ const regExp = /^.*(youtu.be\/|v\/|e\/|u\/\w+\/|embed\/|v=)([^#\&\?]*).*/;
+ const match = url.match(regExp);
+
+ if (match && match[2].length === 11) {
+ return (
+
+ VIDEO
+
+ );
+ } else {
+ return null;
+ }
+ };
+
+ const renderImage = () => {
+ if (!preview?.images || preview.images.length == 0) return;
+
+ const image = preview.images[0];
+
+ // if (preview.aiCard && preview.aiCard.images.length > 0) {
+ // console.log('preview.aiCard.images', preview.aiCard.images);
+ // const images = preview.aiCard.images;
+
+ // return (
+ //
+ // {images.map((image) => (
+ //
+ // ))}
+ //
+ // );
+ // }
+
+ return (
+
+
+
+ );
+ };
+
+ if (isUrlYouTubeVideo(url)) {
+ return createYouTubeEmbed(url);
+ }
+
+ const renderAICard = () => {
+ // check if AI card is reliable and has enough content.
+ if (!preview.aiCard) return;
+
+ const highlights = () => {};
+
+ return (
+
+
{preview?.aiCard?.summary}
+
+ {/* Highlights */}
+ {preview?.aiCard?.highlights?.length > 0 && (
+
+ {preview.aiCard.highlights.map((highlight, i) => (
+ {highlight}
+ ))}
+
+
+ )}
+
+ {/* Buttons */}
+ {preview?.aiCard?.buttons?.length > 0 && (
+
+ )}
+
+ );
+ };
+
+ return (
+
+
+ {renderImage()}
+
+ {renderAICard()}
+
+
{' '}
+ {preview?.aiCard?.category && (
+
{preview?.aiCard?.category}
+ )}
+ {preview?.host}
+
+
+
+ );
+}
diff --git a/src/renderer/pages/Pile/Editor/LinkPreviews/LinkPreviews.module.scss b/src/renderer/pages/Pile/Editor/LinkPreviews/LinkPreviews.module.scss
new file mode 100644
index 0000000..3e4e9eb
--- /dev/null
+++ b/src/renderer/pages/Pile/Editor/LinkPreviews/LinkPreviews.module.scss
@@ -0,0 +1,7 @@
+.cards {
+ display: flex;
+ flex-wrap: wrap;
+ margin: -8px;
+ margin-top: 10px;
+ margin-bottom: 0px;
+}
\ No newline at end of file
diff --git a/src/renderer/pages/Pile/Editor/LinkPreviews/index.jsx b/src/renderer/pages/Pile/Editor/LinkPreviews/index.jsx
new file mode 100644
index 0000000..02e720a
--- /dev/null
+++ b/src/renderer/pages/Pile/Editor/LinkPreviews/index.jsx
@@ -0,0 +1,41 @@
+import styles from './LinkPreviews.module.scss';
+import { useCallback, useState, useEffect } from 'react';
+import { DiscIcon, PhotoIcon, TrashIcon, TagIcon } from 'renderer/icons';
+import { motion, AnimatePresence } from 'framer-motion';
+import LinkPreview from './LinkPreview';
+
+const extractLinks = (htmlString) => {
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(htmlString, 'text/html');
+ const regex = /https:\/\/[^\s/$.?#].[^\s]*/gi;
+ function extractText(node, urls) {
+ if (node.nodeType === Node.TEXT_NODE) {
+ const matches = node.textContent.match(regex);
+ if (matches) {
+ urls.push(...matches);
+ }
+ } else if (node.nodeType === Node.ELEMENT_NODE) {
+ node.childNodes.forEach((child) => extractText(child, urls));
+ }
+ }
+
+ const urls = [];
+ extractText(doc.body, urls);
+ return urls.filter((value, index, self) => self.indexOf(value) === index);
+};
+
+export default function LinkPreviews({ post, editable = false }) {
+ const getPreview = (url) => {
+ return window.electron.ipc.invoke('get-link-preview', url);
+ };
+ const content = post.content ?? '';
+ const links = extractLinks(content);
+
+ if (links.length == 0) return;
+
+ const renderLinks = () => {
+ return links.map((url, i) => );
+ };
+
+ return {renderLinks()}
;
+}
diff --git a/src/renderer/pages/Pile/Editor/index.jsx b/src/renderer/pages/Pile/Editor/index.jsx
index 6e1b929..256bee3 100644
--- a/src/renderer/pages/Pile/Editor/index.jsx
+++ b/src/renderer/pages/Pile/Editor/index.jsx
@@ -3,10 +3,11 @@ import styles from './Editor.module.scss';
import { useCallback, useState, useEffect, useRef } from 'react';
import { Extension } from '@tiptap/core';
import { useEditor, EditorContent } from '@tiptap/react';
+import Link from '@tiptap/extension-link';
import StarterKit from '@tiptap/starter-kit';
-import CharacterCount from '@tiptap/extension-character-count';
import Typography from '@tiptap/extension-typography';
import Placeholder from '@tiptap/extension-placeholder';
+import CharacterCount from '@tiptap/extension-character-count';
import { DiscIcon, PhotoIcon, TrashIcon, TagIcon } from 'renderer/icons';
import { motion, AnimatePresence } from 'framer-motion';
import { postFormat } from 'renderer/utils/fileOperations';
@@ -18,6 +19,7 @@ import usePost from 'renderer/hooks/usePost';
import ProseMirrorStyles from './ProseMirror.scss';
import { useAIContext } from 'renderer/context/AIContext';
import useThread from 'renderer/hooks/useThread';
+import LinkPreviews from './LinkPreviews';
export default function Editor({
postPath = null,
@@ -74,11 +76,12 @@ export default function Editor({
extensions: [
StarterKit,
Typography,
+ Link,
Placeholder.configure({
placeholder: isAI ? 'AI is thinking...' : 'What are you thinking?',
}),
CharacterCount.configure({
- limit: 100000,
+ limit: 10000,
}),
EnterSubmitExtension,
],
@@ -267,6 +270,8 @@ export default function Editor({
)}
+
+
-
-
- {' '}
- entries
+ {index.size} entries
@@ -65,11 +57,12 @@ export default function PileLayout({ children }) {
{pileName} · {now}
+
-
+ {/* */}
{children}
diff --git a/src/renderer/pages/Pile/PileLayout.module.scss b/src/renderer/pages/Pile/PileLayout.module.scss
index ffa71bb..878f7fd 100644
--- a/src/renderer/pages/Pile/PileLayout.module.scss
+++ b/src/renderer/pages/Pile/PileLayout.module.scss
@@ -12,6 +12,7 @@
--primary: #57168c;
--secondary: #794d92;
--base: #b846ff;
+ --base-text: hsl(288, 75%, 13%);
--base-hover: #c66aff;
--base-yellow: #e6e04d;
--base-green: #4de64d;
@@ -33,14 +34,15 @@
--primary: #ffe9ff;
--secondary: #ab92ae;
--base: #d014e1;
+ --base-text: hsl(295, 100%, 93%);
--base-hover: #df38e5;
--base-yellow: #776b0e;
--base-green: #128212;
--base-red: rgb(255, 116, 85);
--base-red-light: #3d2323;
- --active: #e6ffa3;
- --active-hover: #e6ffa3;
- --active-text: #fff;
+ --active: #ccf263;
+ --active-hover: #ecffb8;
+ --active-text: #251b24;
--border: #5f4263;
--bg: #251b24;
--bg-secondary: #481e51;
@@ -58,6 +60,7 @@
--primary: #412109;
--secondary: #88511e;
--base: #f18e1c;
+ --base-text: hsl(35, 100%, 93%);
--base-hover: #c5751f;
--base-yellow: #e6e04d;
--base-green: #4de64d;
@@ -79,14 +82,15 @@
--primary: #fff7e9;
--secondary: #aea392;
--base: #ff9634;
+ --base-text: hsl(34, 76%, 15%);
--base-hover: #df7000;
--base-yellow: #776b0e;
--base-green: #128212;
--base-red: rgb(255, 71, 71);
--base-red-light: #3d2323;
- --active: #ff9500;
- --active-hover: #df7000;
- --active-text: #fff;
+ --active: #ffa72b;
+ --active-hover: rgb(255, 184, 85);
+ --active-text: #2a2420;
--border: #635342;
--bg: #25201b;
--bg-secondary: #51371e;
@@ -104,6 +108,7 @@
--primary: #1b4109;
--secondary: #40881e;
--base: #22ff00;
+ --base-text: hsl(118, 66%, 22%);
--base-hover: #c5751f;
--base-yellow: #e6e04d;
--base-green: #4de64d;
@@ -125,6 +130,7 @@
--primary: #f5fff0;
--secondary: #95ae92;
--base: #149800;
+ --base-text: hsl(133, 100%, 93%);
--base-hover: #10c500;
--base-yellow: #776b0e;
--base-green: #128212;
@@ -223,14 +229,13 @@
align-items: center;
justify-content: center;
height: 32px;
- width: 36px;
+ min-width: 36px;
border-radius: 8px;
transition: all ease-in-out 120ms;
-webkit-app-region: none;
background: transparent;
margin-right: -2px;
-
&:last-child {
margin-right: 8px;
}
diff --git a/src/renderer/pages/Pile/Posts/Post/Ball/index.jsx b/src/renderer/pages/Pile/Posts/Post/Ball/index.jsx
index bbdf504..60f5e28 100644
--- a/src/renderer/pages/Pile/Posts/Post/Ball/index.jsx
+++ b/src/renderer/pages/Pile/Posts/Post/Ball/index.jsx
@@ -19,7 +19,7 @@ const Ball = ({
return Array.from(highlights, ([highlight, data], index) => {
return (
setHighlight(highlight)}
>
diff --git a/src/renderer/pages/Pile/Posts/Post/Post.module.scss b/src/renderer/pages/Pile/Posts/Post/Post.module.scss
index 635b70c..8ac8d44 100644
--- a/src/renderer/pages/Pile/Posts/Post/Post.module.scss
+++ b/src/renderer/pages/Pile/Posts/Post/Post.module.scss
@@ -213,7 +213,7 @@
&:hover {
cursor: pointer;
- filter: contrast(1.25) brightness(1.1);
+ filter: contrast(1.15);
}
&:active {
diff --git a/src/renderer/pages/Pile/Posts/Posts.module.scss b/src/renderer/pages/Pile/Posts/Posts.module.scss
index d5ddd3d..ac4acc5 100644
--- a/src/renderer/pages/Pile/Posts/Posts.module.scss
+++ b/src/renderer/pages/Pile/Posts/Posts.module.scss
@@ -1,4 +1,44 @@
.posts {
display: block;
scroll-snap-type: x proximity;
+}
+
+.empty {
+ margin: 0 22px;
+ padding: 22px;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+ // background: var(--bg-secondary);
+ // border: 2px solid var(--border);
+ border-radius: 22px;
+ background-image: url('../../../../../assets/garden.jpeg');
+ background-size: cover;
+ background-position: bottom center;
+ height: 380px;
+ color: rgba(0, 0, 0, 0.7);
+ max-width: 800px;
+
+ .wrapper {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ max-width: 200px;
+ text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.1);
+ }
+
+ .none {
+ font-weight: bold;
+ text-transform: uppercase;
+ font-size: 1.35em;
+ font-weight: 600;
+ font-family: "Porpora";
+ }
+
+ .tip {
+ font-size: 0.9em;
+ line-height: 1.3;
+ margin: 5px 0 0 0;
+ }
}
\ No newline at end of file
diff --git a/src/renderer/pages/Pile/Posts/index.jsx b/src/renderer/pages/Pile/Posts/index.jsx
index 6e94d10..21f40c2 100644
--- a/src/renderer/pages/Pile/Posts/index.jsx
+++ b/src/renderer/pages/Pile/Posts/index.jsx
@@ -31,6 +31,20 @@ export default function Posts() {
const { index } = useIndexContext();
const renderPosts = useMemo(() => {
+ if (index.size === 0) {
+ return (
+
+
+
Say Something?
+
+ Pile is ideal for journaling in bursts– type down what you're
+ thinking right now, come back to it over time.
+
+
+
+ );
+ }
+
return Array.from(index, ([postPath, data]) => {
return (
diff --git a/src/renderer/pages/Pile/Settings/Settings.module.scss b/src/renderer/pages/Pile/Settings/Settings.module.scss
index 9051403..58aa8c6 100644
--- a/src/renderer/pages/Pile/Settings/Settings.module.scss
+++ b/src/renderer/pages/Pile/Settings/Settings.module.scss
@@ -3,7 +3,7 @@
align-items: center;
justify-content: center;
height: 32px;
- width: 36px;
+ min-width: 36px;
border-radius: 8px;
transition: all ease-in-out 120ms;
-webkit-app-region: none;
@@ -149,6 +149,22 @@ input {
margin-top: 14px;
}
+.disclaimer {
+ font-size: 0.8em;
+ color: var(--secondary);
+ margin-top: 10px;
+ line-height: 1.4;
+ opacity: 0.75;
+
+ a {
+ color: var(--active);
+
+ &:hover {
+ color: var(--active-hover);
+ }
+ }
+}
+
.Label {
font-size: 12px;
color: var(--secondary);
diff --git a/src/renderer/pages/Pile/Settings/index.jsx b/src/renderer/pages/Pile/Settings/index.jsx
index 38f2630..bb1bd7f 100644
--- a/src/renderer/pages/Pile/Settings/index.jsx
+++ b/src/renderer/pages/Pile/Settings/index.jsx
@@ -9,7 +9,7 @@ import {
} from 'renderer/context/PilesContext';
export default function Settings() {
- const { ai, prompt, getKey, setKey } = useAIContext();
+ const { ai, prompt, getKey, setKey, deleteKey } = useAIContext();
const [key, setCurrentKey] = useState('');
const { currentTheme, setTheme } = usePilesContext();
@@ -27,7 +27,9 @@ export default function Settings() {
};
const handleSaveChanges = () => {
- if (key != '') {
+ if (key == '') {
+ deleteKey();
+ } else {
setKey(key);
}
};
@@ -37,7 +39,7 @@ export default function Settings() {
const colors = availableThemes[theme];
return (
- {/*
*/}
);
});
@@ -81,7 +79,7 @@ export default function Settings() {
- API key (OpenAI / UNMS)
+ API key (OpenAI)
+
Prompt (locked)
diff --git a/src/renderer/pages/Pile/Toasts/Toast/Loaders/Info/index.jsx b/src/renderer/pages/Pile/Toasts/Toast/Loaders/Info/index.jsx
new file mode 100644
index 0000000..4f8076a
--- /dev/null
+++ b/src/renderer/pages/Pile/Toasts/Toast/Loaders/Info/index.jsx
@@ -0,0 +1,59 @@
+export default function Info(props) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/renderer/pages/Pile/Toasts/Toast/Loaders/Readme.md b/src/renderer/pages/Pile/Toasts/Toast/Loaders/Readme.md
new file mode 100644
index 0000000..2a41625
--- /dev/null
+++ b/src/renderer/pages/Pile/Toasts/Toast/Loaders/Readme.md
@@ -0,0 +1,11 @@
+The SVG loading animations used within Pile are from: https://github.com/SamHerbert/SVG-Loaders
+
+The MIT License (MIT)
+
+Copyright (c) 2014 Sam Herbert
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/src/renderer/pages/Pile/Toasts/Toast/Loaders/Thinking/index.jsx b/src/renderer/pages/Pile/Toasts/Toast/Loaders/Thinking/index.jsx
new file mode 100644
index 0000000..73b717b
--- /dev/null
+++ b/src/renderer/pages/Pile/Toasts/Toast/Loaders/Thinking/index.jsx
@@ -0,0 +1,33 @@
+export default function Thinking(props) {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/renderer/pages/Pile/Toasts/Toast/Loaders/Waiting/index.jsx b/src/renderer/pages/Pile/Toasts/Toast/Loaders/Waiting/index.jsx
new file mode 100644
index 0000000..338db7a
--- /dev/null
+++ b/src/renderer/pages/Pile/Toasts/Toast/Loaders/Waiting/index.jsx
@@ -0,0 +1,28 @@
+export default function Waiting(props) {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/renderer/pages/Pile/Toasts/Toast/Toast.module.scss b/src/renderer/pages/Pile/Toasts/Toast/Toast.module.scss
new file mode 100644
index 0000000..cef0295
--- /dev/null
+++ b/src/renderer/pages/Pile/Toasts/Toast/Toast.module.scss
@@ -0,0 +1,32 @@
+.toast {
+ display: inline-flex;
+ align-items: center;
+ text-align: right;
+ font-size: 0.8rem;
+
+ .icon {
+ height: 18px;
+ width: 18px;
+ margin-left: 10px;
+ }
+
+ &.info {
+ color: var(--primary);
+ }
+
+ &.waiting {
+ color: var(--secondary);
+ }
+
+ &.thinking {
+ color: var(--active);
+ }
+
+ &.success {
+ color: var(--base);
+ }
+
+ &.attached {
+ color: var(--base);
+ }
+}
\ No newline at end of file
diff --git a/src/renderer/pages/Pile/Toasts/Toast/index.jsx b/src/renderer/pages/Pile/Toasts/Toast/index.jsx
new file mode 100644
index 0000000..a6b4b07
--- /dev/null
+++ b/src/renderer/pages/Pile/Toasts/Toast/index.jsx
@@ -0,0 +1,39 @@
+import styles from './Toast.module.scss';
+import { motion } from 'framer-motion';
+import { useToastsContext } from 'renderer/context/ToastsContext';
+import Logo from 'renderer/pages/Home/logo';
+import Thinking from './Loaders/Thinking';
+import Waiting from './Loaders/Waiting';
+import Info from './Loaders/Info';
+import { PaperclipIcon, SmileIcon } from 'renderer/icons';
+
+export default function Toast({ notification }) {
+ const renderIcon = (type) => {
+ switch (type) {
+ case 'thinking':
+ return ;
+ case 'waiting':
+ return ;
+ case 'success':
+ return null;
+ case 'attached':
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ return (
+
+
+ {notification.message}
+ {renderIcon(notification.type)}
+
+
+ );
+}
diff --git a/src/renderer/pages/Pile/Toasts/Toasts.module.scss b/src/renderer/pages/Pile/Toasts/Toasts.module.scss
new file mode 100644
index 0000000..5c01fad
--- /dev/null
+++ b/src/renderer/pages/Pile/Toasts/Toasts.module.scss
@@ -0,0 +1,8 @@
+.container {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ align-items: center;
+ padding: 0 6px;
+}
\ No newline at end of file
diff --git a/src/renderer/pages/Pile/Toasts/index.jsx b/src/renderer/pages/Pile/Toasts/index.jsx
new file mode 100644
index 0000000..119ec2f
--- /dev/null
+++ b/src/renderer/pages/Pile/Toasts/index.jsx
@@ -0,0 +1,25 @@
+import styles from './Toasts.module.scss';
+import { useToastsContext } from 'renderer/context/ToastsContext';
+import Logo from 'renderer/pages/Home/logo';
+import Toast from './Toast';
+import { AnimatePresence } from 'framer-motion';
+
+export default function Toasts() {
+ const { notifications, addNotification } = useToastsContext();
+
+ const renderNotifications = () => {
+ if (notifications.length === 0) return;
+
+ return notifications.map((n, i) => {
+ if (i == 0) {
+ return ;
+ }
+ });
+ };
+
+ return (
+
+
{renderNotifications()}
+
+ );
+}
diff --git a/yarn.lock b/yarn.lock
index 6043448..d32d6c4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2154,7 +2154,7 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.1.12.tgz#7c905a577ce30ef2cb335870a23f9d24fd26f6aa"
integrity sha512-vtD8vWtNlmAZX8LYqt2yU9w3mU9rPCiHmbp4hDXJs2kBnI0Ju/qAyXFx6iJ3C3XyuMnMbJdDI9ee0spAvFz7cQ==
-"@tiptap/extension-character-count@^2.0.3":
+"@tiptap/extension-character-count@^2.1.12":
version "2.1.12"
resolved "https://registry.yarnpkg.com/@tiptap/extension-character-count/-/extension-character-count-2.1.12.tgz#241ba33a5cfcae63e159ac468c353deaa9c5bf49"
integrity sha512-+GFbBG13nvF8mFIeisSERG/Q3CuRsTNwVZIRbJTLgGdbHXFqPhJh4Xfm7cv7OaOYevUlVyO+z5pGD7wIl1bLqQ==
@@ -2216,6 +2216,13 @@
resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.1.12.tgz#e99480eb77f8b4e5444fc236add8a831d5aa2343"
integrity sha512-/XYrW4ZEWyqDvnXVKbgTXItpJOp2ycswk+fJ3vuexyolO6NSs0UuYC6X4f+FbHYL5VuWqVBv7EavGa+tB6sl3A==
+"@tiptap/extension-link@^2.1.12":
+ version "2.1.12"
+ resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.1.12.tgz#a18f83a0b54342e6274ff9e5a5907ef7f15aa723"
+ integrity sha512-Sti5hhlkCqi5vzdQjU/gbmr8kb578p+u0J4kWS+SSz3BknNThEm/7Id67qdjBTOQbwuN07lHjDaabJL0hSkzGQ==
+ dependencies:
+ linkifyjs "^4.1.0"
+
"@tiptap/extension-list-item@^2.1.12":
version "2.1.12"
resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.1.12.tgz#3eb28dc998490a98f14765783770b3cf6587d39e"
@@ -2584,9 +2591,9 @@
"@types/node" "*"
"@types/node@*":
- version "20.8.9"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.9.tgz#646390b4fab269abce59c308fc286dcd818a2b08"
- integrity sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==
+ version "20.8.10"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.10.tgz#a5448b895c753ae929c26ce85cab557c6d4a365e"
+ integrity sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==
dependencies:
undici-types "~5.26.4"
@@ -3283,10 +3290,10 @@ app-builder-bin@4.0.0:
resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-4.0.0.tgz#1df8e654bd1395e4a319d82545c98667d7eed2f0"
integrity sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==
-app-builder-lib@24.6.4:
- version "24.6.4"
- resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-24.6.4.tgz#5bf77dd89d3ee557bc615b9ddfaf383f3e51577b"
- integrity sha512-m9931WXb83teb32N0rKg+ulbn6+Hl8NV5SUpVDOVz9MWOXfhV6AQtTdftf51zJJvCQnQugGtSqoLvgw6mdF/Rg==
+app-builder-lib@24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-24.8.0.tgz#b62b58729035865a04d4089d3cc7bc12c05c4e71"
+ integrity sha512-C+IQn8V3D+PJ+c9hkYfcJOchvsWwF8PvrKvLz9D7riPK3mV3gMK2d7Y1/sklf8YcjiqUKhs9ZpKSuVafohyxlA==
dependencies:
"7zip-bin" "~5.1.1"
"@develar/schema-utils" "~2.6.5"
@@ -3297,12 +3304,12 @@ app-builder-lib@24.6.4:
"@types/fs-extra" "9.0.13"
async-exit-hook "^2.0.1"
bluebird-lst "^1.0.9"
- builder-util "24.5.0"
- builder-util-runtime "9.2.1"
+ builder-util "24.8.0"
+ builder-util-runtime "9.2.2"
chromium-pickle-js "^0.2.0"
debug "^4.3.4"
ejs "^3.1.8"
- electron-publish "24.5.0"
+ electron-publish "24.8.0"
form-data "^4.0.0"
fs-extra "^10.1.0"
hosted-git-info "^4.1.0"
@@ -3495,9 +3502,9 @@ async-exit-hook@^2.0.1:
integrity sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==
async@^3.2.3:
- version "3.2.4"
- resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c"
- integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==
+ version "3.2.5"
+ resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
+ integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
asynciterator.prototype@^1.0.0:
version "1.0.0"
@@ -3526,6 +3533,15 @@ axe-core@^4.6.2:
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.8.2.tgz#2f6f3cde40935825cf4465e3c1c9e77b240ff6ae"
integrity sha512-/dlp0fxyM3R8YW7MFzaHWXrf4zzbr0vaYb23VBFCl83R7nWNPg/yaQw2Dc8jzCMmDVLhSdzH8MjrsuIUuvX+6g==
+axios@^1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.0.tgz#f1e5292f26b2fd5c2e66876adc5b06cdbd7d2102"
+ integrity sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==
+ dependencies:
+ follow-redirects "^1.15.0"
+ form-data "^4.0.0"
+ proxy-from-env "^1.1.0"
+
axobject-query@^3.1.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a"
@@ -3800,24 +3816,24 @@ builder-util-runtime@9.1.1:
debug "^4.3.4"
sax "^1.2.4"
-builder-util-runtime@9.2.1:
- version "9.2.1"
- resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz#3184dcdf7ed6c47afb8df733813224ced4f624fd"
- integrity sha512-2rLv/uQD2x+dJ0J3xtsmI12AlRyk7p45TEbE/6o/fbb633e/S3pPgm+ct+JHsoY7r39dKHnGEFk/AASRFdnXmA==
+builder-util-runtime@9.2.2:
+ version "9.2.2"
+ resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.2.tgz#8ba9db2d2f2aa85c280d39edd9a68ee7bde9af92"
+ integrity sha512-Or2/ycVYRGQ876hKMfiz2Ghgzh3WllgPW75jqt1Ta2a5wprpnziFrHpQ9eUq6/ScsVXMnG4PmQqlMsE9NFg8DQ==
dependencies:
debug "^4.3.4"
sax "^1.2.4"
-builder-util@24.5.0:
- version "24.5.0"
- resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-24.5.0.tgz#8683c9a7a1c5c9f9a4c4d2789ecca0e47dddd3f9"
- integrity sha512-STnBmZN/M5vGcv01u/K8l+H+kplTaq4PAIn3yeuufUKSpcdro0DhJWxPI81k5XcNfC//bjM3+n9nr8F9uV4uAQ==
+builder-util@24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-24.8.0.tgz#e9773c65abca10d284858007ab0b6658ee41fa08"
+ integrity sha512-QSDHklYjzsvKybxLTZOvHzXQq8iI167C/ccSKgMxhML53ExCiGOwR+QUM+8VPi0KFR3RBAsiNC/w1KFeOght1A==
dependencies:
"7zip-bin" "~5.1.1"
"@types/debug" "^4.1.6"
app-builder-bin "4.0.0"
bluebird-lst "^1.0.9"
- builder-util-runtime "9.2.1"
+ builder-util-runtime "9.2.2"
chalk "^4.1.2"
cross-spawn "^7.0.3"
debug "^4.3.4"
@@ -3976,6 +3992,31 @@ charenc@0.0.2:
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
integrity sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==
+cheerio-select@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4"
+ integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==
+ dependencies:
+ boolbase "^1.0.0"
+ css-select "^5.1.0"
+ css-what "^6.1.0"
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
+ domutils "^3.0.1"
+
+cheerio@^1.0.0-rc.12:
+ version "1.0.0-rc.12"
+ resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683"
+ integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==
+ dependencies:
+ cheerio-select "^2.1.0"
+ dom-serializer "^2.0.0"
+ domhandler "^5.0.3"
+ domutils "^3.0.1"
+ htmlparser2 "^8.0.1"
+ parse5 "^7.0.0"
+ parse5-htmlparser2-tree-adapter "^7.0.0"
+
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
@@ -4762,14 +4803,14 @@ dir-glob@^3.0.1:
dependencies:
path-type "^4.0.0"
-dmg-builder@24.6.4:
- version "24.6.4"
- resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-24.6.4.tgz#e19b8305f7e1ea0b4faaa30382c81b9d6de39863"
- integrity sha512-BNcHRc9CWEuI9qt0E655bUBU/j/3wUCYBVKGu1kVpbN5lcUdEJJJeiO0NHK3dgKmra6LUUZlo+mWqc+OCbi0zw==
+dmg-builder@24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-24.8.0.tgz#7fcb887de08c7a96036497f8099f5223ab235454"
+ integrity sha512-RVJCdLwog4xM0SVB/54YevwIJjOKVuo5tSVTJplDbKPCsbRRh6CXQ2QIw3i4J+o0upkactzqiNZ5iJtj4TOFHw==
dependencies:
- app-builder-lib "24.6.4"
- builder-util "24.5.0"
- builder-util-runtime "9.2.1"
+ app-builder-lib "24.8.0"
+ builder-util "24.8.0"
+ builder-util-runtime "9.2.2"
fs-extra "^10.1.0"
iconv-lite "^0.6.2"
js-yaml "^4.1.0"
@@ -4935,16 +4976,16 @@ ejs@^3.1.8:
dependencies:
jake "^10.8.5"
-electron-builder@^24.2.1:
- version "24.6.4"
- resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-24.6.4.tgz#c51271e49b9a02c9a3ec444f866b6008c4d98a1d"
- integrity sha512-uNWQoU7pE7qOaIQ6CJHpBi44RJFVG8OHRBIadUxrsDJVwLLo8Nma3K/EEtx5/UyWAQYdcK4nVPYKoRqBb20hbA==
+electron-builder@^24.7.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-24.8.0.tgz#008f52a4ceb39f95df09b7b73b782e8d161a94c0"
+ integrity sha512-UbG+yXDKQFx8nQS7mowDsOkrfAhNjVrSfd5U/ze22dlCa+yfZLhbYlZH5NIH1dOKkrVrNXyvKxsPEIr+85W1XQ==
dependencies:
- app-builder-lib "24.6.4"
- builder-util "24.5.0"
- builder-util-runtime "9.2.1"
+ app-builder-lib "24.8.0"
+ builder-util "24.8.0"
+ builder-util-runtime "9.2.2"
chalk "^4.1.2"
- dmg-builder "24.6.4"
+ dmg-builder "24.8.0"
fs-extra "^10.1.0"
is-ci "^3.0.0"
lazy-val "^1.0.5"
@@ -4995,14 +5036,14 @@ electron-log@^4.4.8:
resolved "https://registry.yarnpkg.com/electron-log/-/electron-log-4.4.8.tgz#fcb9f714dbcaefb6ac7984c4683912c74730248a"
integrity sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA==
-electron-publish@24.5.0:
- version "24.5.0"
- resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-24.5.0.tgz#492a4d7caa232e88ee3c18f5c3b4dc637e5e1b3a"
- integrity sha512-zwo70suH15L15B4ZWNDoEg27HIYoPsGJUF7xevLJLSI7JUPC8l2yLBdLGwqueJ5XkDL7ucYyRZzxJVR8ElV9BA==
+electron-publish@24.8.0:
+ version "24.8.0"
+ resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-24.8.0.tgz#8fcf282a05b63958470c1ca53a8807555089d475"
+ integrity sha512-/qrlJFDs+2xQ6NvHIze2uaRJoQm6nks21dah2QWLRVKlxsfTBnDIYXU9HeGdaPqMJB4rmnYgLky8tEFx049xuA==
dependencies:
"@types/fs-extra" "^9.0.11"
- builder-util "24.5.0"
- builder-util-runtime "9.2.1"
+ builder-util "24.8.0"
+ builder-util-runtime "9.2.2"
chalk "^4.1.2"
fs-extra "^10.1.0"
lazy-val "^1.0.5"
@@ -5884,7 +5925,7 @@ flatted@^3.2.9:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf"
integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==
-follow-redirects@^1.0.0:
+follow-redirects@^1.0.0, follow-redirects@^1.15.0:
version "1.15.3"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a"
integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==
@@ -6372,6 +6413,16 @@ htmlparser2@^6.1.0:
domutils "^2.5.2"
entities "^2.0.0"
+htmlparser2@^8.0.1:
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21"
+ integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==
+ dependencies:
+ domelementtype "^2.3.0"
+ domhandler "^5.0.3"
+ domutils "^3.0.1"
+ entities "^4.4.0"
+
http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
@@ -7638,6 +7689,11 @@ linkify-it@^4.0.1:
dependencies:
uc.micro "^1.0.1"
+linkifyjs@^4.1.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.1.1.tgz#73d427e3bbaaf4ca8e71c589ad4ffda11a9a5fde"
+ integrity sha512-zFN/CTVmbcVef+WaDXT63dNzzkfRBKT1j464NJQkV7iSgJU0sLBus9W0HBwnXK13/hf168pbrx/V/bjEHOXNHA==
+
loader-runner@^4.2.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1"
@@ -8137,11 +8193,6 @@ nanoid@^3.3.6:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
-nanoid@^4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.2.tgz#140b3c5003959adbebf521c170f282c5e7f9fb9e"
- integrity sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==
-
natural-compare-lite@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4"
@@ -8576,6 +8627,14 @@ parse-json@^5.2.0:
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
+parse5-htmlparser2-tree-adapter@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1"
+ integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==
+ dependencies:
+ domhandler "^5.0.2"
+ parse5 "^7.0.0"
+
parse5@^7.0.0, parse5@^7.1.1:
version "7.1.2"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32"
@@ -9176,6 +9235,11 @@ proxy-addr@~2.0.7:
forwarded "0.2.0"
ipaddr.js "1.9.1"
+proxy-from-env@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
+ integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+
psl@^1.1.33:
version "1.9.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
@@ -10736,9 +10800,9 @@ universalify@^0.2.0:
integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
universalify@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
- integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
+ integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
unixify@^1.0.0:
version "1.0.0"
@@ -10815,18 +10879,6 @@ use-callback-ref@^1.3.0:
dependencies:
tslib "^2.0.0"
-use-count-up@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/use-count-up/-/use-count-up-3.0.1.tgz#315edf4e4be91ead94e6dd34444349d9b8c2c6c3"
- integrity sha512-jlVsXJYje6jh+xwQaCEYrwHoB+nRyillNEmr21bhe9kw7tpRzyrSq9jQs9UOlo+8hCFkuOmjUihL3IjEK/piVg==
- dependencies:
- use-elapsed-time "3.0.2"
-
-use-elapsed-time@3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/use-elapsed-time/-/use-elapsed-time-3.0.2.tgz#ef22bf520e60f9873fd102925a2d5cbc5d4faaf5"
- integrity sha512-2EY9lJ5DWbAvT8wWiEp6Ztnl46DjXz2j78uhWbXaz/bg3OfpbgVucCAlcN8Bih6hTJfFTdVYX9L6ySMn5py/wQ==
-
use-sidecar@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2"