Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/gui/src/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ pre {
--select-saturation: 74.22%;
--select-lightness: 55.88%;
--select-color: hsl(var(--select-hue), var(--select-saturation), var(--select-lightness));
--sidebar-header-text-color: hsl(210, 41.18%, 90%); /* Default to light color */
}

html, body {
Expand Down Expand Up @@ -1188,7 +1189,7 @@ span.header-sort-icon img {
height: calc(100% - 30px);
}

.window-cover-page.window-filedialog .window-body {
.window-cover-page .window-body {
height: calc(100% - 109px) !important;
}

Expand Down Expand Up @@ -1218,7 +1219,7 @@ span.header-sort-icon img {
margin: 0;
font-weight: bold;
font-size: 13px;
color: #8f96a3;
color: var(--sidebar-header-text-color);
text-shadow: 1px 1px rgb(247 247 247 / 15%);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
Expand Down
43 changes: 43 additions & 0 deletions src/gui/src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,49 @@ window.create_file = async(options)=>{
}
}

// Create a .weblink file storing a URL
window.create_weblink = async (options) => {
let dirname = options.dirname;
let appendto_element = options.append_to_element;
let urlStr = String(options.url ?? '').trim();

if(!(urlStr.startsWith('http://') || urlStr.startsWith('https://'))){
await UIAlert({ message: i18n('enter_valid_url') ?? 'Please enter a valid URL starting with http:// or https://.' });
return;
}

let hostname = 'Link';
try{
const u = new URL(urlStr);
hostname = u.hostname.replace(/^www\./i, '');
}catch(e){
// fallback
}

const filename = `${hostname}.weblink`;

try{
await puter.fs.upload(new File([urlStr], filename, { type: 'text/plain' }), dirname, {
overwrite: false,
dedupeName: true,
success: async function (data){
const created_file = $(appendto_element).find('.item[data-path="'+html_encode(dirname)+'/'+html_encode(data.name)+'"]');
if(created_file.length > 0){
// Do not immediately rename; keep derived name
// Add action to actions_history for undo ability
window.actions_history.push({
operation: 'create_file',
data: created_file
});
}
}
});
}catch(err){
console.log(err);
await UIAlert({ message: err?.message ?? (i18n('failed_to_create') ?? 'Failed to create link.') });
}
}

window.available_templates = async () => {
const baseRoute = `/${window.user.username}`
const keywords = ["template", "templates", i18n('template')]
Expand Down
5 changes: 5 additions & 0 deletions src/gui/src/helpers/item_icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ 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')){
// Use HTML file icon as a recognizable web link indicator
return {image: window.icons['file-html.svg'], type: 'icon'};
}
// --------------------------------------------------
// Determine icon by set or derived mime type
// --------------------------------------------------
Expand Down
15 changes: 15 additions & 0 deletions src/gui/src/helpers/new_context_menu_item.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,21 @@ 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'});
}
},
// Link Shortcut
{
html: 'New Link',
icon: `<img src="${html_encode(window.icons['file-html.svg'])}" class="ctx-item-icon">`,
onClick: async function(){
const url = prompt('Enter URL (http:// or https://)', 'https://');
if(!url)
return;
if(!(url.startsWith('http://') || url.startsWith('https://'))){
alert('Please enter a valid URL starting with http:// or https://.');
return;
}
await window.create_weblink({ dirname: dirname, append_to_element: append_to_element, url });
}
},
// JPG Image
{
html: i18n('jpeg_image'),
Expand Down
24 changes: 24 additions & 0 deletions src/gui/src/helpers/open_item.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,30 @@ const open_item = async function(options){
}
}
//----------------------------------------------------------------
// Web link shortcut (.weblink): open URL in new tab
//----------------------------------------------------------------
else if(!is_dir && path.extname(item_path).toLowerCase() === '.weblink'){
try{
let urlText = await puter.fs.read(item_path);
if (typeof urlText === 'object' && urlText?.text) {
urlText = await urlText.text();
}
const urlStr = (typeof urlText === 'string') ? urlText.trim() : String(urlText ?? '').trim();
const isHttp = urlStr.startsWith('http://') || urlStr.startsWith('https://');
if(!isHttp){
await UIAlert({ message: i18n('invalid_url') ?? 'Invalid URL in link shortcut.' });
return;
}
const newWin = window.open(urlStr, '_blank', 'noopener,noreferrer');
if(!newWin){
await UIAlert({ message: i18n('popup_blocked') ?? 'Popup was blocked. Please allow popups for this site.' });
}
}catch(e){
console.log(e);
await UIAlert({ message: i18n('failed_to_open') ?? 'Failed to open link.' });
}
}
//----------------------------------------------------------------
// Does the user have a preference for this file type?
//----------------------------------------------------------------
else if(!associated_app_name && !is_dir && window.user_preferences[`default_apps${path.extname(item_path).toLowerCase()}`]) {
Expand Down
32 changes: 23 additions & 9 deletions src/gui/src/keyboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -733,10 +733,6 @@ $(document).bind("keyup keydown", async function(e){
if((e.ctrlKey || e.metaKey) && e.which === 86 && !$(focused_el).is('input') && !$(focused_el).is('textarea')){
let target_path, target_el;

// continue only if there is something in the clipboard
if(window.clipboard.length === 0)
return;

let parent_container = determine_active_container_parent();

if(parent_container){
Expand All @@ -745,11 +741,29 @@ $(document).bind("keyup keydown", async function(e){
// don't allow pasting in Trash
if((target_path === window.trash_path || target_path.startsWith(window.trash_path + '/')) && window.clipboard_op !== 'move')
return;
// execute clipboard operation
if(window.clipboard_op === 'copy')
window.copy_clipboard_items(target_path);
else if(window.clipboard_op === 'move')
window.move_clipboard_items(target_el, target_path);

// If Puter clipboard has items, perform normal paste
if(window.clipboard.length > 0){
if(window.clipboard_op === 'copy')
window.copy_clipboard_items(target_path);
else if(window.clipboard_op === 'move')
window.move_clipboard_items(target_el, target_path);
return false;
}

// Otherwise, try system clipboard text as URL
try{
if(navigator.clipboard && navigator.clipboard.readText){
navigator.clipboard.readText().then(async (clipText)=>{
const text = (clipText ?? '').trim();
if(text && (text.startsWith('http://') || text.startsWith('https://'))){
await window.create_weblink({ dirname: target_path, append_to_element: target_el, url: text });
}
});
}
}catch(err){
// ignored
}
}
return false;
}
Expand Down
11 changes: 11 additions & 0 deletions src/gui/src/services/ThemeService.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,14 @@ export class ThemeService extends Service {
));
}
}

function adjustSidebarHeaderTextColor(hue, saturation, transparency, lightness) {
// Calculate adjusted brightness based on lightness and transparency
const adjustedLightness = lightness * (1 - transparency);
return adjustedLightness > 50 ? `hsl(${hue}, ${saturation}%, 10%)` : `hsl(${hue}, ${saturation}%, 90%)`;
}

export function updateTheme(hue, saturation, transparency, lightness) {
const sidebarHeaderTextColor = adjustSidebarHeaderTextColor(hue, saturation, transparency, lightness);
document.documentElement.style.setProperty('--sidebar-header-text-color', sidebarHeaderTextColor);
}