Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cockpit location ts #21276

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions pkg/base1/test-location.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ QUnit.test("test", function (assert) {
assert.deepEqual(cockpit.location.path, ["hello"], "path is right");

window.setTimeout(function() {
/* window.location.hash has changed so the old `location` object is no longer valid and .go/replace are ignored */
location.go(["not-gonna-happen"]);
assert.strictEqual(window.location.hash, "#/other", "hash is correct");

Expand Down
4 changes: 2 additions & 2 deletions pkg/lib/cockpit.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ declare module 'cockpit' {
options: { [name: string]: string | Array<string> };
path: Array<string>;
href: string;
go(path: Location | string, options?: { [key: string]: string }): void;
replace(path: Location | string, options?: { [key: string]: string }): void;
go(path: Location | string[] | string, options?: { [key: string]: string }): void;
replace(path: Location | string[] | string, options?: { [key: string]: string }): void;
}

export const location: Location;
Expand Down
183 changes: 4 additions & 179 deletions pkg/lib/cockpit.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import {
} from './cockpit/_internal/common';
import { Deferred, later_invoke } from './cockpit/_internal/deferred';
import { event_mixin } from './cockpit/_internal/event-mixin';
import { url_root, transport_origin, calculate_application, calculate_url } from './cockpit/_internal/location';
import { transport_origin, calculate_application, calculate_url } from './cockpit/_internal/location-utils';
import { get_window_location_hash, Location } from 'cockpit/location';
import { ensure_transport, transport_globals } from './cockpit/_internal/transport';
import { FsInfoClient } from "./cockpit/fsinfo";

Expand Down Expand Up @@ -1127,184 +1128,6 @@ function factory() {

let last_loc = null;

function get_window_location_hash() {
return (window.location.href.split('#')[1] || '');
}

function Location() {
const self = this;
const application = cockpit.transport.application();
self.url_root = url_root || "";

if (window.mock?.url_root)
self.url_root = window.mock.url_root;

if (application.indexOf("cockpit+=") === 0) {
if (self.url_root)
self.url_root += '/';
self.url_root = self.url_root + application.replace("cockpit+", '');
}

const href = get_window_location_hash();
const options = { };
self.path = decode(href, options);

/* Resolve dots and double dots */
function resolve_path_dots(parts) {
const out = [];
const length = parts.length;
for (let i = 0; i < length; i++) {
const part = parts[i];
if (part === "" || part == ".") {
continue;
} else if (part == "..") {
if (out.length === 0)
return [];
out.pop();
} else {
out.push(part);
}
}
return out;
}

function decode_path(input) {
const parts = input.split('/').map(decodeURIComponent);
let result, i;
let pre_parts = [];

if (self.url_root)
pre_parts = self.url_root.split('/').map(decodeURIComponent);

if (input && input[0] !== "/" && self.path !== undefined) {
result = [...self.path];
result.pop();
result = result.concat(parts);
} else {
result = parts;
}

result = resolve_path_dots(result);
for (i = 0; i < pre_parts.length; i++) {
if (pre_parts[i] !== result[i])
break;
}
if (i == pre_parts.length)
result.splice(0, pre_parts.length);

return result;
}

function encode(path, options, with_root) {
if (typeof path == "string")
path = decode_path(path);

let href = "/" + path.map(encodeURIComponent).join("/");
if (with_root && self.url_root && href.indexOf("/" + self.url_root + "/") !== 0)
href = "/" + self.url_root + href;

/* Undo unnecessary encoding of these */
href = href.replaceAll("%40", "@");
href = href.replaceAll("%3D", "=");
href = href.replaceAll("%2B", "+");
href = href.replaceAll("%23", "#");

let opt;
const query = [];
function push_option(v) {
query.push(encodeURIComponent(opt) + "=" + encodeURIComponent(v));
}

if (options) {
for (opt in options) {
let value = options[opt];
if (!Array.isArray(value))
value = [value];
value.forEach(push_option);
}
if (query.length > 0)
href += "?" + query.join("&");
}
return href;
}

function decode(href, options) {
if (href[0] == '#')
href = href.substring(1);

const pos = href.indexOf('?');
const first = (pos === -1) ? href : href.substring(0, pos);
const path = decode_path(first);
if (pos !== -1 && options) {
href.substring(pos + 1).split("&")
.forEach(function(opt) {
const parts = opt.split('=');
const name = decodeURIComponent(parts[0]);
const value = decodeURIComponent(parts[1]);
if (options[name]) {
let last = options[name];
if (!Array.isArray(last))
last = options[name] = [last];
last.push(value);
} else {
options[name] = value;
}
});
}

return path;
}

function href_for_go_or_replace(/* ... */) {
let href;
if (arguments.length == 1 && arguments[0] instanceof Location) {
href = String(arguments[0]);
} else if (typeof arguments[0] == "string") {
const options = arguments[1] || { };
href = encode(decode(arguments[0], options), options);
} else {
href = encode.apply(self, arguments);
}
return href;
}

function replace(/* ... */) {
if (self !== last_loc)
return;
const href = href_for_go_or_replace.apply(self, arguments);
window.location.replace(window.location.pathname + '#' + href);
}

function go(/* ... */) {
if (self !== last_loc)
return;
const href = href_for_go_or_replace.apply(self, arguments);
window.location.hash = '#' + href;
}

Object.defineProperties(self, {
path: {
enumerable: true,
writable: false,
value: self.path
},
options: {
enumerable: true,
writable: false,
value: options
},
href: {
enumerable: true,
value: href
},
go: { value: go },
replace: { value: replace },
encode: { value: encode },
decode: { value: decode },
toString: { value: function() { return href } }
});
}

Object.defineProperty(cockpit, "location", {
enumerable: true,
get: function() {
Expand All @@ -1318,6 +1141,8 @@ function factory() {
});

window.addEventListener("hashchange", function() {
if (last_loc)
last_loc.invalidate();
last_loc = null;
let hash = window.location.hash;
if (hash.indexOf("#") === 0)
Expand Down
2 changes: 1 addition & 1 deletion pkg/lib/cockpit/_internal/parentwebsocket.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { transport_origin } from './location';
import { transport_origin } from './location-utils';

/*
* A WebSocket that connects to parent frame. The mechanism
Expand Down
2 changes: 1 addition & 1 deletion pkg/lib/cockpit/_internal/transport.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EventEmitter } from '../event';

import type { JsonObject } from './common';
import { calculate_application, calculate_url } from './location';
import { calculate_application, calculate_url } from './location-utils';
import { ParentWebSocket } from './parentwebsocket';

type ControlCallback = (message: JsonObject) => void;
Expand Down
Loading