Merge pull request #1219 from rodrick-mpofu/feature/desktop-shortcut-link

feat: add desktop link shortcuts (refs #682)
This commit is contained in:
Nariman Jelveh
2025-06-27 23:27:51 -07:00
committed by GitHub
6 changed files with 184 additions and 4 deletions
+1 -1
View File
@@ -16325,4 +16325,4 @@
"license": "AGPL-3.0-only"
}
}
}
}
+1 -1
View File
@@ -61,4 +61,4 @@
"string-template": "^1.0.0",
"uuid": "^9.0.1"
}
}
}
+4
View File
@@ -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
// --------------------------------------------------
+87 -2
View File
@@ -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'),
+79
View File
@@ -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 + '/')){
+12
View File
@@ -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;