@domicile-desktop/system-apps 0.0.0-alpha-a330bf5f3aae
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -0
- package/package.json +50 -0
- package/src/app-icons.ts +161 -0
- package/src/bookmark.ts +39 -0
- package/src/curl.ts +54 -0
- package/src/data-dirs.ts +28 -0
- package/src/data-url.ts +12 -0
- package/src/desktop-entry.ts +85 -0
- package/src/environment.ts +37 -0
- package/src/escapes.ts +16 -0
- package/src/exec.ts +44 -0
- package/src/fake-system.ts +176 -0
- package/src/favicon.ts +164 -0
- package/src/favicons.ts +117 -0
- package/src/find-apps.ts +21 -0
- package/src/installed.ts +112 -0
- package/src/mime-apps.ts +81 -0
- package/src/omit.ts +51 -0
- package/src/ranked.ts +60 -0
- package/src/results.ts +46 -0
package/src/omit.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Which desktop file IDs a launcher leaves out, as globs.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether a desktop file ID is left out, per `patterns`.
|
|
5
|
+
*
|
|
6
|
+
* Each pattern is a glob: `*` matches any run, `?` one character and `[…]`
|
|
7
|
+
* one of a set. A pattern starting with `!` takes an ID back, and the last
|
|
8
|
+
* matching pattern wins, so `["*", "!launcher-*"]` keeps only IDs starting
|
|
9
|
+
* with `launcher-`. Throws on a pattern that is not a glob.
|
|
10
|
+
*/
|
|
11
|
+
export const omitting = (
|
|
12
|
+
patterns: readonly string[],
|
|
13
|
+
): ((id: string) => boolean) => {
|
|
14
|
+
const rules = patterns.map((pattern) => {
|
|
15
|
+
const keeps = pattern.startsWith("!");
|
|
16
|
+
return { glob: globOf(keeps ? pattern.slice(1) : pattern), keeps };
|
|
17
|
+
});
|
|
18
|
+
return (id) => {
|
|
19
|
+
const last = rules.findLast(({ glob }) => glob.test(id));
|
|
20
|
+
return last !== undefined && !last.keeps;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** `pattern` as an anchored regular expression. */
|
|
25
|
+
const globOf = (pattern: string): RegExp => {
|
|
26
|
+
const parts = pattern.match(/\[[^\]]*\]|\*|\?|\[|[^*?[]+/g) ?? [];
|
|
27
|
+
return new RegExp(
|
|
28
|
+
`^${parts
|
|
29
|
+
.map((part) => {
|
|
30
|
+
switch (part) {
|
|
31
|
+
case "*": {
|
|
32
|
+
return ".*";
|
|
33
|
+
}
|
|
34
|
+
case "?": {
|
|
35
|
+
return ".";
|
|
36
|
+
}
|
|
37
|
+
case "[": {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`\`${pattern}\` is not a glob: \`[\` is not closed`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
default: {
|
|
43
|
+
// A `[…]` set reads the same as a regular expression's.
|
|
44
|
+
return part.startsWith("[") ? part : RegExp.escape(part);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
.join("")}$`,
|
|
49
|
+
"s",
|
|
50
|
+
);
|
|
51
|
+
};
|
package/src/ranked.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// The launcher's matching rule, shared by applications and bookmarks so their
|
|
2
|
+
// rows agree.
|
|
3
|
+
|
|
4
|
+
/** How to match and order one kind of row. */
|
|
5
|
+
export type Ranking<T> = {
|
|
6
|
+
/** The text every query word must appear in, in lower case. */
|
|
7
|
+
words: (item: T) => string;
|
|
8
|
+
name: (item: T) => string;
|
|
9
|
+
/** Orders equal names, so the order does not depend on a listing's. */
|
|
10
|
+
tieBreak: (item: T) => string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Up to `limit` of `items` matching `query`, best first.
|
|
15
|
+
*
|
|
16
|
+
* Every query word must appear, case-insensitively, in the item's words.
|
|
17
|
+
* Names that start with the query rank first, then sort by name.
|
|
18
|
+
*/
|
|
19
|
+
export const ranked = <T>(
|
|
20
|
+
items: readonly T[],
|
|
21
|
+
query: string,
|
|
22
|
+
limit: number,
|
|
23
|
+
ranking: Ranking<T>,
|
|
24
|
+
): T[] => {
|
|
25
|
+
const typed = query.trim().toLowerCase();
|
|
26
|
+
const words = typed.split(/\s+/).filter((word) => word !== "");
|
|
27
|
+
return items
|
|
28
|
+
.filter((item) => {
|
|
29
|
+
const text = ranking.words(item);
|
|
30
|
+
return words.every((word) => text.includes(word));
|
|
31
|
+
})
|
|
32
|
+
.map((item) => {
|
|
33
|
+
const name = ranking.name(item).toLowerCase();
|
|
34
|
+
return {
|
|
35
|
+
item,
|
|
36
|
+
key: [
|
|
37
|
+
name.startsWith(typed) ? 0 : 1,
|
|
38
|
+
name,
|
|
39
|
+
ranking.tieBreak(item),
|
|
40
|
+
] as const,
|
|
41
|
+
};
|
|
42
|
+
})
|
|
43
|
+
.toSorted((a, b) => compared(a.key, b.key))
|
|
44
|
+
.slice(0, limit)
|
|
45
|
+
.map(({ item }) => item);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** Compares keys element by element, strings by UTF-16 code unit. */
|
|
49
|
+
const compared = (
|
|
50
|
+
a: readonly [number, string, string],
|
|
51
|
+
b: readonly [number, string, string],
|
|
52
|
+
): number => a[0] - b[0] || byCodeUnit(a[1], b[1]) || byCodeUnit(a[2], b[2]);
|
|
53
|
+
|
|
54
|
+
const byCodeUnit = (a: string, b: string): number => {
|
|
55
|
+
if (a < b) {
|
|
56
|
+
return -1;
|
|
57
|
+
} else {
|
|
58
|
+
return a > b ? 1 : 0;
|
|
59
|
+
}
|
|
60
|
+
};
|
package/src/results.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Combining the `Result`s of system calls made side by side.
|
|
2
|
+
|
|
3
|
+
import type { Result } from "@cprussin/option-result";
|
|
4
|
+
import { Ok } from "@cprussin/option-result";
|
|
5
|
+
import type { SystemError } from "@domicile-desktop/sdk/system";
|
|
6
|
+
import { SystemErrorKind } from "@domicile-desktop/sdk/system";
|
|
7
|
+
|
|
8
|
+
/** Every value, or the first error. */
|
|
9
|
+
export const allOk = <
|
|
10
|
+
T extends NonNullable<unknown>,
|
|
11
|
+
E extends NonNullable<unknown>,
|
|
12
|
+
>(
|
|
13
|
+
results: readonly Result<T, E>[],
|
|
14
|
+
): Result<T[], E> =>
|
|
15
|
+
results.reduce<Result<T[], E>>(
|
|
16
|
+
(all, each) =>
|
|
17
|
+
all.andThen((values) => each.map((value) => [...values, value])),
|
|
18
|
+
Ok([]),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* `result`, with `absent` in place of an error that means the path is not
|
|
23
|
+
* there to read: missing, of the other kind, or unreadable. The spec says to
|
|
24
|
+
* skip those. Any other error, such as a locked desktop, is kept.
|
|
25
|
+
*/
|
|
26
|
+
export const orAbsent = <T extends NonNullable<unknown>>(
|
|
27
|
+
result: Result<T, SystemError>,
|
|
28
|
+
absent: T,
|
|
29
|
+
): Result<T, SystemError> =>
|
|
30
|
+
result.orElse((error) => {
|
|
31
|
+
switch (error.kind) {
|
|
32
|
+
case SystemErrorKind.NotFound:
|
|
33
|
+
case SystemErrorKind.NotADirectory:
|
|
34
|
+
case SystemErrorKind.IsADirectory:
|
|
35
|
+
case SystemErrorKind.PermissionDenied: {
|
|
36
|
+
return Ok(absent);
|
|
37
|
+
}
|
|
38
|
+
case SystemErrorKind.AlreadyExists:
|
|
39
|
+
case SystemErrorKind.InvalidInput:
|
|
40
|
+
case SystemErrorKind.Locked:
|
|
41
|
+
case SystemErrorKind.Dbus:
|
|
42
|
+
case SystemErrorKind.Other: {
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|