mirror of
https://github.com/HeyPuter/puter.git
synced 2026-05-01 20:50:14 -05:00
Merge pull request #1219 from rodrick-mpofu/feature/desktop-shortcut-link
feat: add desktop link shortcuts (refs #682)
This commit is contained in:
Generated
+1
-1
@@ -16325,4 +16325,4 @@
|
||||
"license": "AGPL-3.0-only"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -61,4 +61,4 @@
|
||||
"string-template": "^1.0.0",
|
||||
"uuid": "^9.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,6 +194,10 @@ 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')){
|
||||
return {image: window.icons['link.svg'], type: 'icon'};
|
||||
}
|
||||
// --------------------------------------------------
|
||||
// Determine icon by set or derived mime type
|
||||
// --------------------------------------------------
|
||||
|
||||
@@ -7,16 +7,18 @@
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import UIPrompt from '../UI/UIPrompt.js';
|
||||
import UIAlert from '../UI/UIAlert.js';
|
||||
|
||||
/**
|
||||
* Returns a context menu item to create a new folder and a variety of file types.
|
||||
@@ -55,6 +57,89 @@ const new_context_menu_item = function(dirname, append_to_element){
|
||||
window.create_file({dirname: dirname, append_to_element: append_to_element, name: 'New File.html'});
|
||||
}
|
||||
},
|
||||
// Web Link
|
||||
{
|
||||
html: 'Web Link',
|
||||
icon: `<img src="${html_encode(window.icons['link.svg'])}" class="ctx-item-icon">`,
|
||||
onClick: async function() {
|
||||
// Prompt user for URL
|
||||
const url = await UIPrompt({
|
||||
message: 'Enter the URL for the web link:',
|
||||
placeholder: 'https://example.com',
|
||||
defaultValue: 'https://',
|
||||
validator: (value) => {
|
||||
// Simple URL validation
|
||||
return value.startsWith('http://') || value.startsWith('https://') ?
|
||||
true : 'Please enter a valid URL starting with http:// or https://';
|
||||
}
|
||||
});
|
||||
|
||||
if (url) {
|
||||
// Extract domain for naming
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
const domain = urlObj.hostname;
|
||||
|
||||
// Extract a simple name from the domain (e.g., "google" from "google.com")
|
||||
let siteName = domain.replace(/^www\./, '');
|
||||
|
||||
// Further simplify by removing the TLD (.com, .org, etc.)
|
||||
siteName = siteName.split('.')[0];
|
||||
|
||||
// Capitalize the first letter
|
||||
siteName = siteName.charAt(0).toUpperCase() + siteName.slice(1);
|
||||
|
||||
// Use simple name but keep .weblink extension for the file system
|
||||
let linkName = siteName;
|
||||
let fileName = linkName + '.weblink';
|
||||
|
||||
// Store the URL in a simple JSON object
|
||||
const weblink_content = JSON.stringify({
|
||||
url: url,
|
||||
type: 'weblink',
|
||||
domain: domain,
|
||||
created: Date.now(),
|
||||
modified: Date.now(),
|
||||
version: '2.0',
|
||||
metadata: {
|
||||
originalUrl: url,
|
||||
linkName: linkName,
|
||||
simpleName: siteName
|
||||
}
|
||||
});
|
||||
|
||||
// Create the file with standard link icon
|
||||
const item = await window.create_file({
|
||||
dirname: dirname,
|
||||
append_to_element: append_to_element,
|
||||
name: fileName,
|
||||
content: weblink_content,
|
||||
icon: window.icons['link.svg'],
|
||||
type: 'weblink',
|
||||
metadata: JSON.stringify({
|
||||
url: url,
|
||||
domain: domain,
|
||||
timestamp: Date.now(),
|
||||
version: '2.0'
|
||||
}),
|
||||
html_attributes: {
|
||||
'data-weblink': 'true',
|
||||
'data-icon': window.icons['link.svg'],
|
||||
'data-url': url,
|
||||
'data-domain': domain,
|
||||
'data-display-name': linkName,
|
||||
'data-hide-extension': 'true'
|
||||
},
|
||||
force_refresh: true,
|
||||
class: 'weblink-item'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error creating web link:", error);
|
||||
UIAlert("Error creating web link: " + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// JPG Image
|
||||
{
|
||||
html: i18n('jpeg_image'),
|
||||
|
||||
@@ -49,6 +49,85 @@ 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 {
|
||||
// First check localStorage using the file's UID
|
||||
let url = null;
|
||||
if (file_uid) {
|
||||
url = localStorage.getItem('weblink_' + file_uid);
|
||||
}
|
||||
|
||||
// Try to read the file content directly using the file's path
|
||||
if (!url) {
|
||||
try {
|
||||
const content = await puter.fs.read({
|
||||
path: item_path
|
||||
});
|
||||
|
||||
// Handle different content types
|
||||
if (content instanceof Blob) {
|
||||
// If content is a Blob, convert it to text
|
||||
const text = await content.text();
|
||||
|
||||
// Try to parse the text as JSON
|
||||
try {
|
||||
const jsonData = JSON.parse(text);
|
||||
if (jsonData.url) {
|
||||
url = jsonData.url;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a valid URL, open it
|
||||
if (url && (url.startsWith('http://') || url.startsWith('https://'))) {
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
} else {
|
||||
// Show a more detailed error message
|
||||
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