mirror of
https://github.com/HeyPuter/puter.git
synced 2026-05-04 00:20:45 +00:00
Implement link shortcut feature (refs #682)
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* Web Shortcuts Mod for Puter
|
||||
*
|
||||
* This mod adds a "Create Web Shortcut" option to the context menu
|
||||
* that allows users to create .weblink files that open websites.
|
||||
*/
|
||||
|
||||
const BaseService = require("../../../src/backend/src/services/BaseService");
|
||||
|
||||
class WebShortcutsService extends BaseService {
|
||||
async _init() {
|
||||
const svc_puterHomepage = this.services.get('puter-homepage');
|
||||
svc_puterHomepage.register_script('/web-shortcuts/main.js');
|
||||
}
|
||||
}
|
||||
|
||||
// Function to extract URL from text (handles pasted URLs)
|
||||
function extractURL(text) {
|
||||
// Remove any warning messages that might be in the pasted content
|
||||
const cleanText = text.replace(/⚠️Warning⚠️.*?(?=http)/s, '').trim();
|
||||
|
||||
// Try to find a URL in the text
|
||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||
const matches = cleanText.match(urlRegex);
|
||||
|
||||
if (matches && matches.length > 0) {
|
||||
return matches[0];
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
// Function to validate URL
|
||||
function isValidURL(url) {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to create a web shortcut
|
||||
async function createWebShortcut(targetPath, url = null) {
|
||||
try {
|
||||
// If no URL provided, prompt the user
|
||||
if (!url) {
|
||||
let userInput = prompt('Enter or paste the URL for the web shortcut:', 'https://example.com');
|
||||
if (!userInput) {
|
||||
console.log('User cancelled URL input');
|
||||
return;
|
||||
}
|
||||
url = extractURL(userInput);
|
||||
}
|
||||
|
||||
// Ensure URL has protocol
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||
url = 'https://' + url;
|
||||
}
|
||||
|
||||
// Validate URL
|
||||
if (!isValidURL(url)) {
|
||||
console.error('Invalid URL:', url);
|
||||
alert('Invalid URL. Please enter a valid URL.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the website title (for the shortcut name)
|
||||
const validUrl = new URL(url);
|
||||
const siteName = validUrl.hostname;
|
||||
|
||||
// Get the favicon
|
||||
const faviconUrl = `https://www.google.com/s2/favicons?domain=${validUrl.origin}&sz=64`;
|
||||
|
||||
// Create a JSON file that will store the shortcut data
|
||||
const shortcutData = {
|
||||
url: validUrl.href,
|
||||
icon: faviconUrl,
|
||||
type: 'web_shortcut'
|
||||
};
|
||||
|
||||
// Create the shortcut file
|
||||
const shortcutFileName = `${siteName}.weblink`;
|
||||
|
||||
// Get the target path (default to desktop if not provided)
|
||||
const desktopPath = window.desktop_path || '/Desktop';
|
||||
const targetDirectory = targetPath || desktopPath;
|
||||
|
||||
// Write the file
|
||||
const result = await window.puter.fs.write(
|
||||
targetDirectory + '/' + shortcutFileName,
|
||||
JSON.stringify(shortcutData),
|
||||
{ dedupeName: true }
|
||||
);
|
||||
|
||||
console.log('Web shortcut created:', result);
|
||||
} catch (error) {
|
||||
console.error('Error creating web shortcut:', error);
|
||||
alert('Error creating web shortcut: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle URL drops on desktop
|
||||
window.addEventListener('dragover', function(e) {
|
||||
// Check if we're dragging over the desktop
|
||||
if (!$(e.target).closest('.desktop').length) return;
|
||||
|
||||
// Check if we have text/uri-list or text/plain data
|
||||
if (e.dataTransfer.types.includes('text/uri-list') ||
|
||||
e.dataTransfer.types.includes('text/plain')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('drop', async function(e) {
|
||||
// Check if we're dropping on the desktop
|
||||
if (!$(e.target).closest('.desktop').length) return;
|
||||
|
||||
// Check if we have text/uri-list or text/plain data
|
||||
if (e.dataTransfer.types.includes('text/uri-list')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const url = e.dataTransfer.getData('text/uri-list');
|
||||
if (isValidURL(url)) {
|
||||
await createWebShortcut(window.desktop_path, url);
|
||||
}
|
||||
} else if (e.dataTransfer.types.includes('text/plain')) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const text = e.dataTransfer.getData('text/plain');
|
||||
const url = extractURL(text);
|
||||
if (isValidURL(url)) {
|
||||
await createWebShortcut(window.desktop_path, url);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle URL pastes on desktop
|
||||
window.addEventListener('paste', async function(e) {
|
||||
// Check if we're pasting on the desktop
|
||||
if (!$(e.target).closest('.desktop').length) return;
|
||||
|
||||
const text = e.clipboardData.getData('text/plain');
|
||||
const url = extractURL(text);
|
||||
|
||||
if (isValidURL(url)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
await createWebShortcut(window.desktop_path, url);
|
||||
}
|
||||
});
|
||||
|
||||
// Add "Create Web Shortcut" to the desktop context menu
|
||||
window.addEventListener('ctxmenu-will-open', function(e) {
|
||||
const options = e.detail.options;
|
||||
|
||||
// Only add to desktop context menu or directory context menus
|
||||
if (!options || !options.items) return;
|
||||
|
||||
// Check if this is a desktop or directory context menu
|
||||
const isDesktopOrDirMenu = options.items.some(item =>
|
||||
(item.html === 'New Folder' || item.html === i18n('new_folder')) ||
|
||||
(item.html === 'Paste' || item.html === i18n('paste'))
|
||||
);
|
||||
|
||||
if (isDesktopOrDirMenu) {
|
||||
// Find the position to insert our menu item (after "New Folder")
|
||||
let insertIndex = options.items.findIndex(item =>
|
||||
item.html === 'New Folder' || item.html === i18n('new_folder')
|
||||
);
|
||||
|
||||
if (insertIndex === -1) {
|
||||
// If "New Folder" not found, insert at the beginning
|
||||
insertIndex = 0;
|
||||
} else {
|
||||
// Insert after "New Folder"
|
||||
insertIndex += 1;
|
||||
}
|
||||
|
||||
// Get the target path
|
||||
let targetPath;
|
||||
if (options.parent_element) {
|
||||
const $parentElement = $(options.parent_element);
|
||||
if ($parentElement.hasClass('item-container')) {
|
||||
targetPath = $parentElement.attr('data-path');
|
||||
} else if ($parentElement.hasClass('item') && $parentElement.attr('data-is_dir') === '1') {
|
||||
targetPath = $parentElement.attr('data-path');
|
||||
}
|
||||
}
|
||||
|
||||
// Insert our menu item
|
||||
options.items.splice(insertIndex, 0, {
|
||||
html: 'Create Web Shortcut',
|
||||
icon: '<img src="' + window.icons['link.svg'] + '" style="width:16px; height:16px; margin-bottom: -3px;">',
|
||||
onClick: function() {
|
||||
createWebShortcut(targetPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add "Create Web Shortcut" to the "New" submenu in the desktop context menu
|
||||
const originalUIContextMenu = window.UIContextMenu;
|
||||
window.UIContextMenu = function(options) {
|
||||
if (options && options.items) {
|
||||
// Find the "New" submenu
|
||||
const newItemIndex = options.items.findIndex(item =>
|
||||
(item.html === 'New' || item.html === i18n('new')) &&
|
||||
Array.isArray(item.items)
|
||||
);
|
||||
|
||||
if (newItemIndex !== -1 && options.items[newItemIndex].items) {
|
||||
// Add our item to the "New" submenu
|
||||
options.items[newItemIndex].items.push({
|
||||
html: 'Web Shortcut',
|
||||
icon: '<img src="' + window.icons['link.svg'] + '" style="width:16px; height:16px; margin-bottom: -3px;">',
|
||||
onClick: function() {
|
||||
// Get the target path
|
||||
let targetPath;
|
||||
if (options.parent_element) {
|
||||
const $parentElement = $(options.parent_element);
|
||||
if ($parentElement.hasClass('item-container')) {
|
||||
targetPath = $parentElement.attr('data-path');
|
||||
} else if ($parentElement.hasClass('item') && $parentElement.attr('data-is_dir') === '1') {
|
||||
targetPath = $parentElement.attr('data-path');
|
||||
}
|
||||
}
|
||||
createWebShortcut(targetPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return originalUIContextMenu(options);
|
||||
};
|
||||
|
||||
module.exports = WebShortcutsService;
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "web-shortcuts",
|
||||
"version": "1.0.0",
|
||||
"description": "Create web shortcuts on the desktop",
|
||||
"author": "Puter",
|
||||
"license": "MIT",
|
||||
"dependencies": ["fs", "path"],
|
||||
"client": {
|
||||
"scripts": ["/mods/web-shortcuts"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2024-present Puter Technologies Inc.
|
||||
*
|
||||
* This file is part of Puter.
|
||||
*
|
||||
* Puter is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const WebShortcutsService = require('./index');
|
||||
|
||||
module.exports = {
|
||||
name: 'web-shortcuts',
|
||||
version: '1.0.0',
|
||||
description: 'Create web shortcuts on the desktop',
|
||||
author: 'Puter',
|
||||
license: 'MIT',
|
||||
dependencies: ['fs', 'path'],
|
||||
init: async (puter) => {
|
||||
const service = new WebShortcutsService(puter);
|
||||
await service.init();
|
||||
return service;
|
||||
},
|
||||
routes: {
|
||||
'/mods/web-shortcuts': {
|
||||
GET: (req, res) => {
|
||||
res.sendFile(__dirname + '/public/main.js');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "web-shortcuts",
|
||||
"version": "1.0.0",
|
||||
"description": "Create web shortcuts on the desktop",
|
||||
"author": "Puter",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fs": "*",
|
||||
"path": "*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// Web Shortcuts Mod
|
||||
(function() {
|
||||
console.log('[Web Shortcuts] Mod initializing...');
|
||||
|
||||
// URL validation helper
|
||||
function isValidURL(str) {
|
||||
const pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
|
||||
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain
|
||||
'((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4)
|
||||
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
|
||||
'(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
|
||||
'(\\#[-a-z\\d_]*)?$','i'); // fragment
|
||||
return !!pattern.test(str);
|
||||
}
|
||||
|
||||
// Extract URL from text
|
||||
function extractURL(text) {
|
||||
// Clean the text
|
||||
text = text.trim();
|
||||
|
||||
// If it's already a valid URL, return it
|
||||
if (isValidURL(text)) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// Try to extract a URL
|
||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||
const matches = text.match(urlRegex);
|
||||
return matches ? matches[0] : null;
|
||||
}
|
||||
|
||||
// Create web shortcut
|
||||
async function createWebShortcut(url, name = null) {
|
||||
console.log('[Web Shortcuts] Creating shortcut with URL:', url);
|
||||
try {
|
||||
if (!url) {
|
||||
url = await window.puter.prompt('Enter URL:');
|
||||
if (!url) return;
|
||||
}
|
||||
|
||||
// Add https:// if no protocol specified
|
||||
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||
url = 'https://' + url;
|
||||
}
|
||||
|
||||
if (!isValidURL(url)) {
|
||||
console.log('[Web Shortcuts] Invalid URL:', url);
|
||||
window.puter.alert('Invalid URL');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get domain/hostname and build favicon link
|
||||
const { hostname } = new URL(url);
|
||||
const favicon = `https://www.google.com/s2/favicons?domain=${hostname}&sz=64`;
|
||||
|
||||
// Use hostname as default name if none provided
|
||||
if (!name) {
|
||||
name = await window.puter.prompt('Enter shortcut name:', hostname);
|
||||
if (!name) return;
|
||||
}
|
||||
|
||||
console.log('[Web Shortcuts] Creating shortcut:', { url, name, favicon });
|
||||
|
||||
const shortcutData = {
|
||||
url: url,
|
||||
favicon: favicon,
|
||||
created: new Date().toISOString(),
|
||||
type: 'link'
|
||||
};
|
||||
|
||||
// Build the path for storing the shortcut
|
||||
const filePath = `${window.desktop_path}/${name}.weblink`;
|
||||
|
||||
// Write the file
|
||||
const file = await window.puter.fs.write(
|
||||
filePath,
|
||||
JSON.stringify(shortcutData),
|
||||
{
|
||||
type: 'link',
|
||||
icon: favicon
|
||||
}
|
||||
);
|
||||
|
||||
console.log('[Web Shortcuts] File created:', file);
|
||||
|
||||
// Create the UI icon on the desktop
|
||||
window.UIItem({
|
||||
appendTo: $('.desktop.item-container'),
|
||||
'data-type': 'link',
|
||||
uid: file.uid,
|
||||
path: filePath,
|
||||
icon: favicon,
|
||||
name: name,
|
||||
is_dir: false,
|
||||
metadata: JSON.stringify(shortcutData)
|
||||
});
|
||||
|
||||
window.puter.notify('Web shortcut created successfully');
|
||||
} catch (error) {
|
||||
console.error('[Web Shortcuts] Error creating shortcut:', error);
|
||||
window.puter.alert('Error creating web shortcut: ' + (error.message || 'Please check the URL and try again'));
|
||||
}
|
||||
}
|
||||
|
||||
// Add context menu items
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('[Web Shortcuts] DOM Content Loaded');
|
||||
|
||||
// Get desktop element
|
||||
const el_desktop = document.querySelector('.desktop');
|
||||
console.log('[Web Shortcuts] Desktop element found:', !!el_desktop);
|
||||
|
||||
if (!el_desktop) return;
|
||||
|
||||
// Handle paste events
|
||||
el_desktop.addEventListener('paste', (e) => {
|
||||
console.log('[Web Shortcuts] Paste event on desktop:', e.target === el_desktop);
|
||||
if (e.target !== el_desktop) return;
|
||||
const text = e.clipboardData.getData('text');
|
||||
if (isValidURL(text)) {
|
||||
e.preventDefault();
|
||||
createWebShortcut(text);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle drop events
|
||||
el_desktop.addEventListener('drop', (e) => {
|
||||
console.log('[Web Shortcuts] Drop event on desktop:', e.target === el_desktop);
|
||||
if (e.target !== el_desktop) return;
|
||||
e.preventDefault();
|
||||
const url = e.dataTransfer.getData('text/uri-list') || e.dataTransfer.getData('text/plain');
|
||||
if (url && isValidURL(url)) {
|
||||
createWebShortcut(url);
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for context menu opening
|
||||
window.addEventListener('ctxmenu-will-open', (e) => {
|
||||
console.log('[Web Shortcuts] Context menu will open:', e.detail);
|
||||
const options = e.detail.options;
|
||||
|
||||
// Only modify desktop context menu
|
||||
if (!options || !options.items) {
|
||||
console.log('[Web Shortcuts] No menu options found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a desktop context menu
|
||||
const isDesktopMenu = options.items.some(item =>
|
||||
(item.html === 'New Folder' || item.html === window.i18n('new_folder')) ||
|
||||
(item.html === 'Paste' || item.html === window.i18n('paste'))
|
||||
);
|
||||
console.log('[Web Shortcuts] Is desktop menu:', isDesktopMenu);
|
||||
|
||||
if (isDesktopMenu) {
|
||||
// Find the position to insert our menu item (after "New")
|
||||
const newIndex = options.items.findIndex(item =>
|
||||
item.html === 'New' || item.html === window.i18n('new')
|
||||
);
|
||||
console.log('[Web Shortcuts] New menu index:', newIndex);
|
||||
|
||||
// Insert our menu item after "New" and before the next divider
|
||||
if (newIndex !== -1) {
|
||||
let insertIndex = newIndex + 1;
|
||||
// Find the next divider
|
||||
while (insertIndex < options.items.length && options.items[insertIndex] !== '-') {
|
||||
insertIndex++;
|
||||
}
|
||||
|
||||
console.log('[Web Shortcuts] Inserting at index:', insertIndex);
|
||||
|
||||
// Insert our item before the divider
|
||||
options.items.splice(insertIndex, 0, {
|
||||
html: 'Create Web Shortcut',
|
||||
icon: '<img src="' + window.icons['link.svg'] + '" style="width:16px; height:16px; margin-bottom: -3px;">',
|
||||
onClick: () => createWebShortcut()
|
||||
});
|
||||
|
||||
console.log('[Web Shortcuts] Menu items after insertion:', options.items);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add right-click handler to desktop
|
||||
el_desktop.addEventListener('contextmenu', (e) => {
|
||||
console.log('[Web Shortcuts] Context menu event on desktop:', e.target === el_desktop);
|
||||
// Only handle right-clicks directly on the desktop, not on items
|
||||
if (e.target !== el_desktop) return;
|
||||
|
||||
// The rest of the context menu handling will be done by the ctxmenu-will-open event
|
||||
});
|
||||
|
||||
console.log('[Web Shortcuts] All event listeners attached');
|
||||
});
|
||||
|
||||
console.log('[Web Shortcuts] Mod initialization complete');
|
||||
})();
|
||||
Generated
+237
-68
@@ -19,6 +19,8 @@
|
||||
"javascript-time-ago": "^2.5.11",
|
||||
"json-colorizer": "^3.0.1",
|
||||
"open": "^10.1.0",
|
||||
"openai": "^4.73.1",
|
||||
"qs": "^6.14.0",
|
||||
"sharp": "^0.33.5",
|
||||
"sharp-bmp": "^0.1.5",
|
||||
"sharp-ico": "^0.1.5",
|
||||
@@ -2115,25 +2117,26 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz",
|
||||
"integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==",
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz",
|
||||
"integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.24.7",
|
||||
"@babel/types": "^7.24.7"
|
||||
"@babel/template": "^7.26.9",
|
||||
"@babel/types": "^7.26.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.26.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz",
|
||||
"integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz",
|
||||
"integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.26.0"
|
||||
"@babel/types": "^7.26.10"
|
||||
},
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -2143,9 +2146,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz",
|
||||
"integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==",
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
|
||||
"integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2156,13 +2159,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
|
||||
"integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
|
||||
"version": "7.26.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
|
||||
"integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.25.9",
|
||||
"@babel/parser": "^7.25.9",
|
||||
"@babel/types": "^7.25.9"
|
||||
"@babel/code-frame": "^7.26.2",
|
||||
"@babel/parser": "^7.26.9",
|
||||
"@babel/types": "^7.26.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -2195,9 +2199,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
|
||||
"integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
|
||||
"integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.25.9",
|
||||
"@babel/helper-validator-identifier": "^7.25.9"
|
||||
@@ -8063,9 +8068,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.8",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz",
|
||||
"integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==",
|
||||
"version": "1.8.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
|
||||
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
@@ -8266,6 +8272,21 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/body-parser/node_modules/qs": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
@@ -8506,6 +8527,35 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bound": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"get-intrinsic": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -9820,6 +9870,20 @@
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
@@ -9963,12 +10027,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@@ -9986,6 +10048,18 @@
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.0.tgz",
|
||||
"integrity": "sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw=="
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-error": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
|
||||
@@ -10372,6 +10446,21 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/express/node_modules/qs": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
@@ -10976,15 +11065,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -11002,6 +11097,19 @@
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/getopts": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz",
|
||||
@@ -11162,11 +11270,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -11241,21 +11350,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@@ -13104,6 +13203,15 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.30",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
|
||||
@@ -14002,9 +14110,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
@@ -14074,6 +14186,7 @@
|
||||
"version": "4.73.1",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-4.73.1.tgz",
|
||||
"integrity": "sha512-nWImDJBcUsqrhy7yJScXB4+iqjzbUEgzfA3un/6UnHFdwWhjX24oztj69Ped/njABfOdLcO/F7CeWTI5dt8Xmg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
@@ -14818,11 +14931,12 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.6"
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
@@ -15683,14 +15797,69 @@
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
||||
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"object-inspect": "^1.13.1"
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-list": "^1.0.0",
|
||||
"side-channel-map": "^1.0.1",
|
||||
"side-channel-weakmap": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-list": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-map": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-weakmap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-map": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
"javascript-time-ago": "^2.5.11",
|
||||
"json-colorizer": "^3.0.1",
|
||||
"open": "^10.1.0",
|
||||
"openai": "^4.73.1",
|
||||
"qs": "^6.14.0",
|
||||
"sharp": "^0.33.5",
|
||||
"sharp-bmp": "^0.1.5",
|
||||
"sharp-ico": "^0.1.5",
|
||||
|
||||
@@ -194,6 +194,62 @@ const item_icon = async (fsentry)=>{
|
||||
else if(fsentry.name.toLowerCase().endsWith('.xlsx')){
|
||||
return {image: window.icons['file-xlsx.svg'], type: 'icon'};
|
||||
}
|
||||
// *.weblink
|
||||
else if(fsentry.name.toLowerCase().endsWith('.weblink')){
|
||||
let faviconUrl = null;
|
||||
|
||||
// First try to get icon from data attribute
|
||||
if (fsentry.icon) {
|
||||
faviconUrl = fsentry.icon;
|
||||
}
|
||||
// Then try metadata
|
||||
else if (fsentry.metadata) {
|
||||
try {
|
||||
const metadata = JSON.parse(fsentry.metadata);
|
||||
if (metadata && metadata.faviconUrl) {
|
||||
faviconUrl = metadata.faviconUrl;
|
||||
} else if (metadata && metadata.url) {
|
||||
// If we have the URL but no favicon, generate the Google favicon URL
|
||||
const urlObj = new URL(metadata.url);
|
||||
faviconUrl = `https://www.google.com/s2/favicons?domain=${urlObj.hostname}&sz=64`;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error parsing weblink metadata:", e);
|
||||
}
|
||||
}
|
||||
// Finally try content
|
||||
else if (fsentry.content) {
|
||||
try {
|
||||
const content = JSON.parse(fsentry.content);
|
||||
if (content && content.faviconUrl) {
|
||||
faviconUrl = content.faviconUrl;
|
||||
} else if (content && content.url) {
|
||||
// If we have the URL but no favicon, generate the Google favicon URL
|
||||
const urlObj = new URL(content.url);
|
||||
faviconUrl = `https://www.google.com/s2/favicons?domain=${urlObj.hostname}&sz=64`;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error parsing weblink content:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a favicon URL, use it
|
||||
if (faviconUrl) {
|
||||
return {
|
||||
image: faviconUrl,
|
||||
type: 'icon',
|
||||
onerror: function() {
|
||||
// If favicon fails to load, switch to default icon
|
||||
const $icons = $(`img[data-icon="${faviconUrl}"]`);
|
||||
$icons.attr('src', window.icons['link.svg']);
|
||||
return window.icons['link.svg'];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Fallback to default link icon
|
||||
return {image: window.icons['link.svg'], type: 'icon'};
|
||||
}
|
||||
// --------------------------------------------------
|
||||
// Determine icon by set or derived mime type
|
||||
// --------------------------------------------------
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -49,6 +49,462 @@ const open_item = async function(options){
|
||||
UIAlert(`This shortcut can't be opened because its source has been deleted.`)
|
||||
}
|
||||
//----------------------------------------------------------------
|
||||
// Is this a .weblink file?
|
||||
//----------------------------------------------------------------
|
||||
else if($(el_item).attr('data-name').toLowerCase().endsWith('.weblink')){
|
||||
try {
|
||||
// Log the file information
|
||||
console.log("Opening weblink file:", {
|
||||
name: $(el_item).attr('data-name'),
|
||||
path: item_path,
|
||||
uid: file_uid
|
||||
});
|
||||
|
||||
// First check localStorage using the file's UID
|
||||
let url = null;
|
||||
if (file_uid) {
|
||||
url = localStorage.getItem('weblink_' + file_uid);
|
||||
console.log("Retrieved URL from localStorage:", url);
|
||||
}
|
||||
|
||||
// Try to read the file content directly using the file's path
|
||||
if (!url) {
|
||||
try {
|
||||
// Convert Windows-style path to unix-style path
|
||||
const unixPath = item_path.replace(/\\/g, '/');
|
||||
console.log("Trying to read file content using unix-style path:", unixPath);
|
||||
|
||||
// Try using the simpler puter.fs.read method with unix-style path
|
||||
const content = await puter.fs.read({
|
||||
path: unixPath
|
||||
});
|
||||
|
||||
console.log("File content read successfully:", content);
|
||||
|
||||
// Handle different content types
|
||||
if (content instanceof Blob) {
|
||||
// If content is a Blob, convert it to text
|
||||
console.log("Content is a Blob, converting to text");
|
||||
const text = await content.text();
|
||||
console.log("Blob converted to text:", text);
|
||||
|
||||
// Try to parse the text as JSON
|
||||
try {
|
||||
const jsonData = JSON.parse(text);
|
||||
if (jsonData.url) {
|
||||
url = jsonData.url;
|
||||
console.log("Retrieved URL from Blob content (JSON):", url);
|
||||
|
||||
// Check for icon data in the JSON (support both old and new formats)
|
||||
const iconUrl = jsonData.iconDataUrl || jsonData.faviconUrl;
|
||||
if (iconUrl) {
|
||||
console.log("Found icon URL in JSON:", iconUrl);
|
||||
|
||||
// Use the favicon URL directly without generating a custom icon
|
||||
const customIconUrl = iconUrl;
|
||||
|
||||
// Store the icon URL in a global cache to ensure it persists
|
||||
if (!window.weblink_icon_cache) {
|
||||
window.weblink_icon_cache = {};
|
||||
}
|
||||
|
||||
// Store by both UID and path for maximum reliability
|
||||
if (file_uid) {
|
||||
window.weblink_icon_cache[file_uid] = customIconUrl;
|
||||
}
|
||||
if (item_path) {
|
||||
window.weblink_icon_cache[item_path] = customIconUrl;
|
||||
}
|
||||
|
||||
// Also store in localStorage for persistence across page refreshes
|
||||
try {
|
||||
localStorage.setItem(`weblink_icon_${file_uid}`, customIconUrl);
|
||||
localStorage.setItem(`weblink_icon_${item_path.replace(/[^a-zA-Z0-9]/g, '_')}`, customIconUrl);
|
||||
} catch (e) {
|
||||
console.error("Error storing icon URL in localStorage:", e);
|
||||
}
|
||||
|
||||
// Create a new image element with the custom icon
|
||||
const iconImg = document.createElement('img');
|
||||
iconImg.src = customIconUrl;
|
||||
iconImg.className = 'item-icon persistent-icon';
|
||||
iconImg.style.width = '32px';
|
||||
iconImg.style.height = '32px';
|
||||
// Add important attributes to prevent the icon from being changed
|
||||
iconImg.setAttribute('data-original-icon', customIconUrl);
|
||||
iconImg.setAttribute('data-icon-locked', 'true');
|
||||
|
||||
// Apply multiple approaches to ensure the icon persists
|
||||
console.log("Applying persistent icon using multiple approaches");
|
||||
|
||||
// APPROACH 1: Replace the icon element completely
|
||||
const existingIcon = $(el_item).find('img.item-icon');
|
||||
let iconUpdated = false;
|
||||
if (existingIcon.length > 0) {
|
||||
// Create a completely new element to avoid any references to the old one
|
||||
const newIconElement = document.createElement('img');
|
||||
newIconElement.className = 'item-icon persistent-icon';
|
||||
newIconElement.src = customIconUrl;
|
||||
newIconElement.style.width = '32px';
|
||||
newIconElement.style.height = '32px';
|
||||
newIconElement.setAttribute('data-original-icon', customIconUrl);
|
||||
newIconElement.setAttribute('data-icon-locked', 'true');
|
||||
|
||||
// Replace the existing icon with our new one
|
||||
existingIcon[0].parentNode.replaceChild(newIconElement, existingIcon[0]);
|
||||
console.log("Replaced icon with persistent icon (approach 1)");
|
||||
iconUpdated = true;
|
||||
|
||||
// Add event listener to prevent the src from being changed
|
||||
newIconElement.addEventListener('load', function() {
|
||||
console.log("Icon loaded successfully");
|
||||
});
|
||||
|
||||
// Handle favicon loading error
|
||||
newIconElement.addEventListener('error', function() {
|
||||
console.log("Icon failed to load, using fallback");
|
||||
this.src = window.icons['link.svg'];
|
||||
});
|
||||
}
|
||||
|
||||
// APPROACH 2: Update all img elements inside the item
|
||||
const allImgs = $(el_item).find('img');
|
||||
if (allImgs.length > 0) {
|
||||
allImgs.each(function() {
|
||||
$(this).attr('src', customIconUrl);
|
||||
$(this).attr('data-original-icon', customIconUrl);
|
||||
$(this).attr('data-icon-locked', 'true');
|
||||
// Add !important to the style to prevent it from being overridden
|
||||
$(this).attr('style', `width: 32px !important; height: 32px !important;`);
|
||||
});
|
||||
console.log("Updated all img elements with persistent icon (approach 2)");
|
||||
iconUpdated = true;
|
||||
}
|
||||
|
||||
// APPROACH 3: Add CSS styles to force the icon
|
||||
// Get a unique identifier for this item
|
||||
const itemId = $(el_item).attr('id') || $(el_item).attr('data-uid') || `weblink-${Date.now()}`;
|
||||
if (!$(el_item).attr('id')) {
|
||||
$(el_item).attr('id', itemId);
|
||||
}
|
||||
|
||||
// Add a style tag with !important rules
|
||||
const styleId = `style-${itemId}`;
|
||||
let styleTag = document.getElementById(styleId);
|
||||
if (!styleTag) {
|
||||
styleTag = document.createElement('style');
|
||||
styleTag.id = styleId;
|
||||
document.head.appendChild(styleTag);
|
||||
}
|
||||
|
||||
styleTag.textContent = `
|
||||
#${itemId} img.item-icon,
|
||||
[data-uid="${file_uid}"] img.item-icon {
|
||||
content: url('${customIconUrl}') !important;
|
||||
background-image: url('${customIconUrl}') !important;
|
||||
}
|
||||
`;
|
||||
|
||||
// APPROACH 4: Set data attributes on the item element
|
||||
$(el_item).attr({
|
||||
'data-icon': customIconUrl,
|
||||
'data-original-icon': customIconUrl,
|
||||
'data-icon-locked': 'true',
|
||||
'data-weblink-icon': customIconUrl
|
||||
});
|
||||
|
||||
// APPROACH 5: Add a MutationObserver to ensure the icon persists
|
||||
try {
|
||||
// Create a MutationObserver to watch for changes to the icon
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === 'attributes' &&
|
||||
mutation.attributeName === 'src' &&
|
||||
mutation.target.classList.contains('item-icon')) {
|
||||
// If the src attribute of the icon was changed, set it back
|
||||
if (mutation.target.src !== customIconUrl) {
|
||||
console.log("MutationObserver: Icon src changed, resetting");
|
||||
mutation.target.src = customIconUrl;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Start observing the item element
|
||||
observer.observe(el_item, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributeFilter: ['src']
|
||||
});
|
||||
|
||||
// Store the observer in a global variable to prevent garbage collection
|
||||
if (!window.icon_observers) {
|
||||
window.icon_observers = {};
|
||||
}
|
||||
window.icon_observers[file_uid] = observer;
|
||||
|
||||
console.log("Set up MutationObserver to ensure icon persists");
|
||||
} catch (e) {
|
||||
console.error("Error setting up MutationObserver:", e);
|
||||
}
|
||||
|
||||
// Force a refresh of the item's icon
|
||||
if (!iconUpdated) {
|
||||
console.log("Could not find icon element to replace, forcing refresh");
|
||||
// Try to trigger a refresh of the item
|
||||
setTimeout(() => {
|
||||
$(el_item).trigger('icon_update', { icon: customIconUrl });
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error parsing Blob content as JSON:", e);
|
||||
// Not valid JSON, try using the content directly
|
||||
if (text && (text.startsWith('http://') || text.startsWith('https://'))) {
|
||||
url = text;
|
||||
console.log("Using Blob content as URL (direct):", url);
|
||||
}
|
||||
}
|
||||
} else if (typeof content === 'string') {
|
||||
// If content is a string, try to parse it as JSON
|
||||
try {
|
||||
const jsonData = JSON.parse(content);
|
||||
if (jsonData.url) {
|
||||
url = jsonData.url;
|
||||
console.log("Retrieved URL from string content (JSON):", url);
|
||||
|
||||
// Check for icon data in the JSON (support both old and new formats)
|
||||
const iconUrl = jsonData.iconDataUrl || jsonData.faviconUrl;
|
||||
if (iconUrl) {
|
||||
console.log("Found icon URL in JSON:", iconUrl);
|
||||
|
||||
// Use the favicon URL directly without generating a custom icon
|
||||
const customIconUrl = iconUrl;
|
||||
|
||||
// Store the icon URL in a global cache to ensure it persists
|
||||
if (!window.weblink_icon_cache) {
|
||||
window.weblink_icon_cache = {};
|
||||
}
|
||||
|
||||
// Store by both UID and path for maximum reliability
|
||||
if (file_uid) {
|
||||
window.weblink_icon_cache[file_uid] = customIconUrl;
|
||||
}
|
||||
if (item_path) {
|
||||
window.weblink_icon_cache[item_path] = customIconUrl;
|
||||
}
|
||||
|
||||
// Also store in localStorage for persistence across page refreshes
|
||||
try {
|
||||
localStorage.setItem(`weblink_icon_${file_uid}`, customIconUrl);
|
||||
localStorage.setItem(`weblink_icon_${item_path.replace(/[^a-zA-Z0-9]/g, '_')}`, customIconUrl);
|
||||
} catch (e) {
|
||||
console.error("Error storing icon URL in localStorage:", e);
|
||||
}
|
||||
|
||||
// Apply multiple approaches to ensure the icon persists
|
||||
console.log("Applying persistent icon using multiple approaches");
|
||||
|
||||
// APPROACH 1: Replace the icon element completely
|
||||
const existingIcon = $(el_item).find('img.item-icon');
|
||||
let iconUpdated = false;
|
||||
if (existingIcon.length > 0) {
|
||||
// Create a completely new element to avoid any references to the old one
|
||||
const newIconElement = document.createElement('img');
|
||||
newIconElement.className = 'item-icon persistent-icon';
|
||||
newIconElement.src = customIconUrl;
|
||||
newIconElement.style.width = '32px';
|
||||
newIconElement.style.height = '32px';
|
||||
newIconElement.setAttribute('data-original-icon', customIconUrl);
|
||||
newIconElement.setAttribute('data-icon-locked', 'true');
|
||||
|
||||
// Replace the existing icon with our new one
|
||||
existingIcon[0].parentNode.replaceChild(newIconElement, existingIcon[0]);
|
||||
console.log("Replaced icon with persistent icon (approach 1)");
|
||||
iconUpdated = true;
|
||||
|
||||
// Add event listener to prevent the src from being changed
|
||||
newIconElement.addEventListener('load', function() {
|
||||
console.log("Icon loaded successfully");
|
||||
});
|
||||
|
||||
// Handle favicon loading error
|
||||
newIconElement.addEventListener('error', function() {
|
||||
console.log("Icon failed to load, using fallback");
|
||||
this.src = window.icons['link.svg'];
|
||||
});
|
||||
}
|
||||
|
||||
// APPROACH 2: Update all img elements inside the item
|
||||
const allImgs = $(el_item).find('img');
|
||||
if (allImgs.length > 0) {
|
||||
allImgs.each(function() {
|
||||
$(this).attr('src', customIconUrl);
|
||||
$(this).attr('data-original-icon', customIconUrl);
|
||||
$(this).attr('data-icon-locked', 'true');
|
||||
// Add !important to the style to prevent it from being overridden
|
||||
$(this).attr('style', `width: 32px !important; height: 32px !important;`);
|
||||
});
|
||||
console.log("Updated all img elements with persistent icon (approach 2)");
|
||||
iconUpdated = true;
|
||||
}
|
||||
|
||||
// APPROACH 3: Add CSS styles to force the icon
|
||||
// Get a unique identifier for this item
|
||||
const itemId = $(el_item).attr('id') || $(el_item).attr('data-uid') || `weblink-${Date.now()}`;
|
||||
if (!$(el_item).attr('id')) {
|
||||
$(el_item).attr('id', itemId);
|
||||
}
|
||||
|
||||
// Add a style tag with !important rules
|
||||
const styleId = `style-${itemId}`;
|
||||
let styleTag = document.getElementById(styleId);
|
||||
if (!styleTag) {
|
||||
styleTag = document.createElement('style');
|
||||
styleTag.id = styleId;
|
||||
document.head.appendChild(styleTag);
|
||||
}
|
||||
|
||||
styleTag.textContent = `
|
||||
#${itemId} img.item-icon,
|
||||
[data-uid="${file_uid}"] img.item-icon {
|
||||
content: url('${customIconUrl}') !important;
|
||||
background-image: url('${customIconUrl}') !important;
|
||||
}
|
||||
`;
|
||||
|
||||
// APPROACH 4: Set data attributes on the item element
|
||||
$(el_item).attr({
|
||||
'data-icon': customIconUrl,
|
||||
'data-original-icon': customIconUrl,
|
||||
'data-icon-locked': 'true',
|
||||
'data-weblink-icon': customIconUrl
|
||||
});
|
||||
|
||||
// APPROACH 5: Add a MutationObserver to ensure the icon persists
|
||||
try {
|
||||
// Create a MutationObserver to watch for changes to the icon
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === 'attributes' &&
|
||||
mutation.attributeName === 'src' &&
|
||||
mutation.target.classList.contains('item-icon')) {
|
||||
// If the src attribute of the icon was changed, set it back
|
||||
if (mutation.target.src !== customIconUrl) {
|
||||
console.log("MutationObserver: Icon src changed, resetting");
|
||||
mutation.target.src = customIconUrl;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Start observing the item element
|
||||
observer.observe(el_item, {
|
||||
attributes: true,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributeFilter: ['src']
|
||||
});
|
||||
|
||||
// Store the observer in a global variable to prevent garbage collection
|
||||
if (!window.icon_observers) {
|
||||
window.icon_observers = {};
|
||||
}
|
||||
window.icon_observers[file_uid] = observer;
|
||||
|
||||
console.log("Set up MutationObserver to ensure icon persists");
|
||||
} catch (e) {
|
||||
console.error("Error setting up MutationObserver:", e);
|
||||
}
|
||||
|
||||
// Force a refresh of the item's icon
|
||||
if (!iconUpdated) {
|
||||
console.log("Could not find icon element to replace, forcing refresh");
|
||||
// Try to trigger a refresh of the item
|
||||
setTimeout(() => {
|
||||
$(el_item).trigger('icon_update', { icon: customIconUrl });
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error parsing string content as JSON:", e);
|
||||
// Not valid JSON, try using the content directly
|
||||
if (content && (content.startsWith('http://') || content.startsWith('https://'))) {
|
||||
url = content;
|
||||
console.log("Using string content as URL (direct):", url);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error("Unexpected content type:", typeof content);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error reading file using path:", e);
|
||||
|
||||
// Fallback to using AJAX with the file's UID
|
||||
try {
|
||||
console.log("Trying to read file content using UID:", file_uid);
|
||||
|
||||
const content = await $.ajax({
|
||||
url: window.api_origin + "/fs/read",
|
||||
type: 'POST',
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify({
|
||||
uid: file_uid
|
||||
}),
|
||||
headers: {
|
||||
"Authorization": "Bearer " + window.auth_token
|
||||
}
|
||||
});
|
||||
|
||||
console.log("File content read successfully using AJAX:", content);
|
||||
|
||||
// Try to parse the content as JSON
|
||||
if (content && content.content) {
|
||||
try {
|
||||
const jsonData = JSON.parse(content.content);
|
||||
if (jsonData.url) {
|
||||
url = jsonData.url;
|
||||
console.log("Retrieved URL from file content (AJAX JSON):", url);
|
||||
}
|
||||
} catch (e) {
|
||||
// Not valid JSON, try using the content directly
|
||||
if (content.content && (content.content.startsWith('http://') || content.content.startsWith('https://'))) {
|
||||
url = content.content;
|
||||
console.log("Using file content as URL (AJAX direct):", url);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error reading file using AJAX:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a valid URL, open it
|
||||
if (url && (url.startsWith('http://') || url.startsWith('https://'))) {
|
||||
console.log("Opening URL:", url);
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
} else {
|
||||
// Show a more detailed error message
|
||||
console.error("Failed to retrieve URL from all sources");
|
||||
UIAlert(`Could not determine the URL for this web shortcut.
|
||||
|
||||
Technical details:
|
||||
- File name: ${$(el_item).attr('data-name')}
|
||||
- File path: ${item_path}
|
||||
- File UID: ${file_uid}
|
||||
|
||||
Please try recreating the link.`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error opening web shortcut:', error);
|
||||
UIAlert('Error opening web shortcut: ' + error.message);
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------
|
||||
// Is this a trashed file?
|
||||
//----------------------------------------------------------------
|
||||
else if(item_path.startsWith(window.trash_path + '/')){
|
||||
|
||||
@@ -36,10 +36,22 @@ function Mime() {
|
||||
let ext = last.replace(/^.*\./, "").toLowerCase();
|
||||
let hasPath = last.length < path.length;
|
||||
let hasDot = ext.length < last.length - 1;
|
||||
|
||||
// Special case for .weblink files
|
||||
if (ext === 'weblink') {
|
||||
return 'application/x-weblink';
|
||||
}
|
||||
|
||||
return (hasDot || !hasPath) && this._types[ext] || null;
|
||||
};
|
||||
Mime.prototype.getExtension = function(type) {
|
||||
type = /^\s*([^;\s]*)/.test(type) && RegExp.$1;
|
||||
|
||||
// Special case for .weblink files
|
||||
if (type === 'application/x-weblink') {
|
||||
return 'weblink';
|
||||
}
|
||||
|
||||
return type && this._extensions[type.toLowerCase()] || null;
|
||||
};
|
||||
var Mime_1 = Mime;
|
||||
|
||||
Reference in New Issue
Block a user