Compare commits
3 Commits
0a2891d919
...
974d8f22fd
Author | SHA1 | Date |
---|---|---|
Bauke | 974d8f22fd | |
Bauke | e03f163c30 | |
Bauke | daa46b8755 |
|
@ -7,7 +7,6 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Queue</title>
|
<title>Queue</title>
|
||||||
<link rel="shortcut icon" href="/queue.png" type="image/png">
|
<link rel="shortcut icon" href="/queue.png" type="image/png">
|
||||||
<link rel="stylesheet" href="./index.scss">
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="love">
|
<body class="love">
|
||||||
|
@ -15,7 +14,7 @@
|
||||||
This WebExtension doesn't work without JavaScript enabled, sorry! 😭
|
This WebExtension doesn't work without JavaScript enabled, sorry! 😭
|
||||||
</noscript>
|
</noscript>
|
||||||
|
|
||||||
<script type="module" src="./index.ts"></script>
|
<script type="module" src="./setup.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -34,9 +34,9 @@ const test = process.env.TEST === "true";
|
||||||
const watch = process.env.WATCH === "true";
|
const watch = process.env.WATCH === "true";
|
||||||
|
|
||||||
// Create absolute paths to various directories.
|
// Create absolute paths to various directories.
|
||||||
const buildDir = toAbsolutePath("build");
|
const buildDir = toAbsolutePath("../build");
|
||||||
const outDir = path.join(buildDir, browser);
|
const outDir = path.join(buildDir, browser);
|
||||||
const sourceDir = toAbsolutePath("source");
|
const sourceDir = toAbsolutePath("../source");
|
||||||
|
|
||||||
// Ensure that the output directory exists.
|
// Ensure that the output directory exists.
|
||||||
await fsp.mkdir(outDir, {recursive: true});
|
await fsp.mkdir(outDir, {recursive: true});
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
export * from './page-footer.js';
|
|
||||||
export * from './page-header.js';
|
|
||||||
export * from './page-main.js';
|
|
|
@ -1,39 +0,0 @@
|
||||||
import {PrivacyLink} from '@holllo/preact-components';
|
|
||||||
import {html} from 'htm/preact';
|
|
||||||
import {Component} from 'preact';
|
|
||||||
|
|
||||||
import type {Settings} from '../../settings/settings.js';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
settings: Settings;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class PageFooter extends Component<Props> {
|
|
||||||
render() {
|
|
||||||
const {settings} = this.props;
|
|
||||||
const version = settings.manifest.version;
|
|
||||||
|
|
||||||
const donateAttributes = {
|
|
||||||
href: 'https://liberapay.com/Holllo',
|
|
||||||
};
|
|
||||||
const donateLink = html`
|
|
||||||
<${PrivacyLink} attributes="${donateAttributes}">Donate<//>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const versionAttributes = {
|
|
||||||
href: `https://git.bauke.xyz/Holllo/queue/releases/tag/${version}`,
|
|
||||||
};
|
|
||||||
const versionLink = html`
|
|
||||||
<${PrivacyLink} attributes="${versionAttributes}">v${version}<//>
|
|
||||||
`;
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<footer class="page-footer">
|
|
||||||
<p>
|
|
||||||
${donateLink} 💖 ${versionLink} © Holllo — Free and open-source,
|
|
||||||
forever.
|
|
||||||
</p>
|
|
||||||
</footer>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
import {html} from 'htm/preact';
|
|
||||||
import {Component} from 'preact';
|
|
||||||
|
|
||||||
export class PageHeader extends Component {
|
|
||||||
render() {
|
|
||||||
return html`
|
|
||||||
<header class="page-header">
|
|
||||||
<h1>
|
|
||||||
<span class="icon">⇥</span>
|
|
||||||
Queue
|
|
||||||
</h1>
|
|
||||||
</header>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,189 +0,0 @@
|
||||||
import {ConfirmButton, PrivacyLink} from '@holllo/preact-components';
|
|
||||||
import {Component, html} from 'htm/preact';
|
|
||||||
|
|
||||||
import type {Settings} from '../../settings/settings.js';
|
|
||||||
import {updateBadge} from '../../utilities/badge.js';
|
|
||||||
import type {History} from '../../utilities/history.js';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
history: History;
|
|
||||||
settings: Settings;
|
|
||||||
};
|
|
||||||
|
|
||||||
type State = {
|
|
||||||
queue: Queue.Item[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export class PageMain extends Component<Props, State> {
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
queue: props.settings.queue,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
moveItem = async (id: number, direction: Queue.MoveDirection) => {
|
|
||||||
const {settings} = this.props;
|
|
||||||
await settings.moveQueueItem(id, direction);
|
|
||||||
this.setState({queue: this.props.settings.queue});
|
|
||||||
};
|
|
||||||
|
|
||||||
removeItem = async (id: number) => {
|
|
||||||
const {settings} = this.props;
|
|
||||||
await settings.removeQueueItem(id);
|
|
||||||
await updateBadge(settings);
|
|
||||||
this.setState({queue: this.props.settings.queue});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const isFirefox = import.meta.env.VITE_BROWSER === 'firefox';
|
|
||||||
|
|
||||||
const queueItems = this.state.queue
|
|
||||||
.sort((a, b) => a.sortIndex - b.sortIndex)
|
|
||||||
.map(
|
|
||||||
(item) =>
|
|
||||||
html`
|
|
||||||
<${queueItem}
|
|
||||||
item=${item}
|
|
||||||
move=${this.moveItem}
|
|
||||||
remove=${this.removeItem}
|
|
||||||
/>
|
|
||||||
`,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (queueItems.length === 0) {
|
|
||||||
queueItems.push(html`<li>No items queued. 🤷</li>`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const historyItems = this.props.history.queue
|
|
||||||
.sort((a, b) => b.added.getTime() - a.added.getTime())
|
|
||||||
.map((item) => html`<${queueItem} item=${item} />`);
|
|
||||||
|
|
||||||
let history: HtmComponent | undefined;
|
|
||||||
if (historyItems.length > 0) {
|
|
||||||
history = html`
|
|
||||||
<details class="history">
|
|
||||||
<summary>Queue history</summary>
|
|
||||||
|
|
||||||
<ul class="q-list">
|
|
||||||
${historyItems}
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<main class="page-main">
|
|
||||||
<ul class="q-list">
|
|
||||||
${queueItems}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
${history}
|
|
||||||
|
|
||||||
<details class="usage">
|
|
||||||
<summary>How do I use Queue?</summary>
|
|
||||||
|
|
||||||
<p>Adding links to your queue:</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Right-click any link ${isFirefox ? 'or tab' : ''} and click "Add
|
|
||||||
to Queue".
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Opening the next link from your queue:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Click on the extension icon to open it in the current tab.</li>
|
|
||||||
<li>
|
|
||||||
Right-click the extension icon and click "Open next link in new
|
|
||||||
tab".
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Opening the extension page:</p>
|
|
||||||
<ul>
|
|
||||||
${isFirefox
|
|
||||||
? html`<li>Double-click the extension icon.</li>`
|
|
||||||
: undefined}
|
|
||||||
<li>
|
|
||||||
Right-click the extension icon and click "Open the extension
|
|
||||||
page".
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Deleting queue items:</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Click the red button with the ✗ and then confirm it by clicking
|
|
||||||
again.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
</main>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ItemProps = {
|
|
||||||
item: Queue.Item;
|
|
||||||
move?: (id: number, direction: Queue.MoveDirection) => Promise<void>;
|
|
||||||
remove?: (id: number) => Promise<void>;
|
|
||||||
};
|
|
||||||
|
|
||||||
function queueItem(props: ItemProps): HtmComponent {
|
|
||||||
const added = props.item.added.toLocaleString();
|
|
||||||
const {id, text, url} = props.item;
|
|
||||||
|
|
||||||
const move = [];
|
|
||||||
if (props.move !== undefined) {
|
|
||||||
const moveButtons: Array<[string, Queue.MoveDirection]> = [
|
|
||||||
['↑', 'up'],
|
|
||||||
['↓', 'down'],
|
|
||||||
];
|
|
||||||
move.push(
|
|
||||||
...moveButtons.map(
|
|
||||||
([text, direction]) =>
|
|
||||||
html`
|
|
||||||
<button
|
|
||||||
title="Move item ${direction}"
|
|
||||||
onClick=${async () => props.move!(id, direction)}
|
|
||||||
>
|
|
||||||
${text}
|
|
||||||
</button>
|
|
||||||
`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let remove;
|
|
||||||
if (props.remove !== undefined) {
|
|
||||||
remove = html`
|
|
||||||
<${ConfirmButton}
|
|
||||||
class="confirm-button"
|
|
||||||
click=${async () => props.remove!(id)}
|
|
||||||
confirmClass="confirm"
|
|
||||||
confirmText="✓"
|
|
||||||
extraAttributes=${{title: 'Remove'}}
|
|
||||||
text="✗"
|
|
||||||
timeout=${5 * 1000}
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<li class="q-item">
|
|
||||||
<p class="title">
|
|
||||||
<${PrivacyLink} attributes=${{href: url}}>${text ?? url}<//>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="buttons">${move}${remove}</div>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<time datetime=${added} title="Link queued on ${added}.">
|
|
||||||
${added}
|
|
||||||
</time>
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
`;
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
@use '../../node_modules/modern-normalize/modern-normalize.css';
|
|
||||||
@use 'scss/reset';
|
|
||||||
@use 'scss/mixins';
|
|
||||||
@use 'scss/love';
|
|
||||||
|
|
||||||
// Component styles
|
|
||||||
@use 'scss/components/page-header';
|
|
||||||
@use 'scss/components/page-main';
|
|
||||||
@use 'scss/components/page-footer';
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-size: 62.5%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: var(--db-1);
|
|
||||||
color: var(--df-1);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--da-3);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--df-2);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
import {html} from 'htm/preact';
|
|
||||||
import {Component, render} from 'preact';
|
|
||||||
|
|
||||||
import {Settings} from '../settings/settings.js';
|
|
||||||
import {updateBadge} from '../utilities/badge.js';
|
|
||||||
import {History} from '../utilities/history.js';
|
|
||||||
import {PageFooter, PageHeader, PageMain} from './components/components.js';
|
|
||||||
|
|
||||||
window.addEventListener('DOMContentLoaded', async () => {
|
|
||||||
const history = await History.fromLocalStorage();
|
|
||||||
const settings = await Settings.fromSyncStorage();
|
|
||||||
await updateBadge(settings);
|
|
||||||
|
|
||||||
render(
|
|
||||||
html`<${OptionsPage} history=${history} settings=${settings} />`,
|
|
||||||
document.body,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
history: Queue.Item[];
|
|
||||||
settings: Settings;
|
|
||||||
};
|
|
||||||
|
|
||||||
class OptionsPage extends Component<Props> {
|
|
||||||
render() {
|
|
||||||
const {history, settings} = this.props;
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<${PageHeader} />
|
|
||||||
<${PageMain} history=${history} settings=${settings} />
|
|
||||||
<${PageFooter} settings=${settings} />
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
@use '../mixins';
|
|
||||||
|
|
||||||
.page-footer {
|
|
||||||
@include mixins.responsive-container;
|
|
||||||
|
|
||||||
border: 1px solid var(--df-2);
|
|
||||||
margin-bottom: 16px;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
@use '../mixins';
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
@include mixins.responsive-container;
|
|
||||||
|
|
||||||
border: 1px solid var(--df-2);
|
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
align-items: center;
|
|
||||||
background-color: var(--df-2);
|
|
||||||
color: var(--db-1);
|
|
||||||
display: inline-flex;
|
|
||||||
height: 4.5rem;
|
|
||||||
justify-content: center;
|
|
||||||
margin-right: 8px;
|
|
||||||
width: 4.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
@use '../mixins';
|
|
||||||
|
|
||||||
.page-main {
|
|
||||||
@include mixins.responsive-container;
|
|
||||||
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.q-list {
|
|
||||||
border: 1px solid var(--df-2);
|
|
||||||
list-style: none;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
> li:not(:last-child) {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.q-item {
|
|
||||||
background-color: var(--db-2);
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto min-content;
|
|
||||||
padding: 8px;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: grid;
|
|
||||||
gap: 4px;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
|
|
||||||
button {
|
|
||||||
align-items: center;
|
|
||||||
background-color: var(--da-3);
|
|
||||||
border: none;
|
|
||||||
color: var(--db-1);
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
font-weight: bold;
|
|
||||||
height: 2.5rem;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0;
|
|
||||||
width: 2.5rem;
|
|
||||||
|
|
||||||
&.confirm-button {
|
|
||||||
background-color: var(--la-1);
|
|
||||||
color: var(--df-1);
|
|
||||||
|
|
||||||
&.confirm {
|
|
||||||
background-color: var(--df-1);
|
|
||||||
color: var(--la-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.history,
|
|
||||||
.usage {
|
|
||||||
border: 1px solid var(--df-2);
|
|
||||||
|
|
||||||
&[open] {
|
|
||||||
summary {
|
|
||||||
background-color: var(--df-2);
|
|
||||||
color: var(--db-1);
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> :not(summary) {
|
|
||||||
padding: 0 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
summary {
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--df-1);
|
|
||||||
color: var(--db-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.history {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
.q-list {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.usage {
|
|
||||||
ul {
|
|
||||||
list-style: square;
|
|
||||||
margin: 4px 0 2rem 16px;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
The Love Theme CSS Custom Properties
|
|
||||||
https://love.holllo.cc - version 0.1.0
|
|
||||||
MIT license
|
|
||||||
*/
|
|
||||||
|
|
||||||
.love {
|
|
||||||
/* Love Dark */
|
|
||||||
--df-1: #f2efff;
|
|
||||||
--df-2: #e6deff;
|
|
||||||
--db-1: #1f1731;
|
|
||||||
--db-2: #2a2041;
|
|
||||||
--da-1: #f99fb1;
|
|
||||||
--da-2: #faa56c;
|
|
||||||
--da-3: #d2b83a;
|
|
||||||
--da-4: #96c839;
|
|
||||||
--da-5: #3bd18a;
|
|
||||||
--da-6: #3ecdbf;
|
|
||||||
--da-7: #41c8e5;
|
|
||||||
--da-8: #98b9f8;
|
|
||||||
--da-9: #d5a6f8;
|
|
||||||
--da-10: #f99add;
|
|
||||||
--dg-1: #e2e2e2;
|
|
||||||
--dg-2: #c6c6c6;
|
|
||||||
--dg-3: #ababab;
|
|
||||||
|
|
||||||
/* Love Light */
|
|
||||||
--lf-1: #1f1731;
|
|
||||||
--lf-2: #2a2041;
|
|
||||||
--lb-1: #f2efff;
|
|
||||||
--lb-2: #e6deff;
|
|
||||||
--la-1: #8b123c;
|
|
||||||
--la-2: #6a3b11;
|
|
||||||
--la-3: #514610;
|
|
||||||
--la-4: #384d10;
|
|
||||||
--la-5: #115133;
|
|
||||||
--la-6: #124f49;
|
|
||||||
--la-7: #144d5a;
|
|
||||||
--la-8: #17477e;
|
|
||||||
--la-9: #6f1995;
|
|
||||||
--la-10: #81156a;
|
|
||||||
--lg-1: #1b1b1b;
|
|
||||||
--lg-2: #303030;
|
|
||||||
--lg-3: #474747;
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
@use 'variables';
|
|
||||||
|
|
||||||
@mixin responsive-container {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
width: variables.$large-breakpoint;
|
|
||||||
|
|
||||||
@media (max-width: variables.$large-breakpoint) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
ol,
|
|
||||||
ul,
|
|
||||||
li,
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
$small-breakpoint: 600px;
|
|
||||||
$medium-breakpoint: 900px;
|
|
||||||
$large-breakpoint: 1200px;
|
|
||||||
$extra-large-breakpoint: 1800px;
|
|
Loading…
Reference in New Issue