mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
Reduce duplication across modal components
This commit is contained in:
parent
93f38924a6
commit
3f6d904236
5 changed files with 249 additions and 337 deletions
|
@ -6,16 +6,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import * as tr from "@generated/ftl";
|
import * as tr from "@generated/ftl";
|
||||||
import { renderMarkdown } from "@tslib/helpers";
|
import { renderMarkdown } from "@tslib/helpers";
|
||||||
import Carousel from "bootstrap/js/dist/carousel";
|
import Carousel from "bootstrap/js/dist/carousel";
|
||||||
import Modal from "bootstrap/js/dist/modal";
|
import { createEventDispatcher, onMount } from "svelte";
|
||||||
import { createEventDispatcher, getContext, onDestroy, onMount } from "svelte";
|
import Modal from "./Modal.svelte";
|
||||||
|
|
||||||
import { infoCircle } from "$lib/components/icons";
|
import { infoCircle } from "$lib/components/icons";
|
||||||
import { registerModalClosingHandler } from "$lib/sveltelib/modal-closing";
|
|
||||||
import { pageTheme } from "$lib/sveltelib/theme";
|
import { pageTheme } from "$lib/sveltelib/theme";
|
||||||
|
|
||||||
import Badge from "./Badge.svelte";
|
import Badge from "./Badge.svelte";
|
||||||
import Col from "./Col.svelte";
|
import Col from "./Col.svelte";
|
||||||
import { modalsKey } from "./context-keys";
|
|
||||||
import HelpSection from "./HelpSection.svelte";
|
import HelpSection from "./HelpSection.svelte";
|
||||||
import Icon from "./Icon.svelte";
|
import Icon from "./Icon.svelte";
|
||||||
import Row from "./Row.svelte";
|
import Row from "./Row.svelte";
|
||||||
|
@ -27,50 +25,20 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
export let helpSections: HelpItem[];
|
export let helpSections: HelpItem[];
|
||||||
export let fsrs = false;
|
export let fsrs = false;
|
||||||
|
|
||||||
export const modalKey: string = Math.random().toString(36).substring(2);
|
|
||||||
|
|
||||||
const modals = getContext<Map<string, Modal>>(modalsKey);
|
|
||||||
|
|
||||||
let modal: Modal;
|
|
||||||
let carousel: Carousel;
|
let carousel: Carousel;
|
||||||
|
|
||||||
let modalRef: HTMLDivElement;
|
let modal: Modal;
|
||||||
let carouselRef: HTMLDivElement;
|
let carouselRef: HTMLDivElement;
|
||||||
|
|
||||||
function onOkClicked(): void {
|
|
||||||
modal.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
const { set: setModalOpen, remove: removeModalClosingHandler } =
|
|
||||||
registerModalClosingHandler(onOkClicked);
|
|
||||||
|
|
||||||
function onShown() {
|
|
||||||
setModalOpen(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onHidden() {
|
|
||||||
setModalOpen(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
modalRef.addEventListener("shown.bs.modal", onShown);
|
|
||||||
modalRef.addEventListener("hidden.bs.modal", onHidden);
|
|
||||||
modal = new Modal(modalRef, { keyboard: false });
|
|
||||||
carousel = new Carousel(carouselRef, { interval: false, ride: false });
|
carousel = new Carousel(carouselRef, { interval: false, ride: false });
|
||||||
/* Bootstrap's Carousel.Event interface doesn't seem to work as a type here */
|
/* Bootstrap's Carousel.Event interface doesn't seem to work as a type here */
|
||||||
carouselRef.addEventListener("slide.bs.carousel", (e: any) => {
|
carouselRef.addEventListener("slide.bs.carousel", (e: any) => {
|
||||||
activeIndex = e.to;
|
activeIndex = e.to;
|
||||||
});
|
});
|
||||||
dispatch("mount", { modal: modal, carousel: carousel });
|
dispatch("mount", { modal: modal, carousel: carousel });
|
||||||
modals.set(modalKey, modal);
|
|
||||||
});
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
removeModalClosingHandler();
|
|
||||||
modalRef.removeEventListener("shown.bs.modal", onShown);
|
|
||||||
modalRef.removeEventListener("hidden.bs.modal", onHidden);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let activeIndex = startIndex;
|
let activeIndex = startIndex;
|
||||||
|
@ -80,119 +48,98 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
<Icon icon={infoCircle} />
|
<Icon icon={infoCircle} />
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
||||||
<div
|
<Modal bind:this={modal} dialogClass="modal-lg">
|
||||||
bind:this={modalRef}
|
<div slot="header" class="modal-header">
|
||||||
class="modal fade"
|
<div style="display: flex;">
|
||||||
tabindex="-1"
|
<h1 class="modal-title" id="modalLabel">
|
||||||
aria-labelledby="modalLabel"
|
{title}
|
||||||
aria-hidden="true"
|
</h1>
|
||||||
>
|
<button
|
||||||
<div class="modal-dialog modal-lg">
|
type="button"
|
||||||
<div class="modal-content">
|
class="btn-close"
|
||||||
<div class="modal-header">
|
class:invert={$pageTheme.isDark}
|
||||||
<div style="display: flex;">
|
data-bs-dismiss="modal"
|
||||||
<h1 class="modal-title" id="modalLabel">
|
aria-label="Close"
|
||||||
{title}
|
></button>
|
||||||
</h1>
|
</div>
|
||||||
<button
|
{#if url}
|
||||||
type="button"
|
<div class="chapter-redirect">
|
||||||
class="btn-close"
|
{@html renderMarkdown(
|
||||||
class:invert={$pageTheme.isDark}
|
tr.helpForMoreInfo({
|
||||||
data-bs-dismiss="modal"
|
link: `<a href="${url}" title="${tr.helpOpenManualChapter({
|
||||||
aria-label="Close"
|
name: title,
|
||||||
></button>
|
})}">${title}</a>`,
|
||||||
</div>
|
}),
|
||||||
{#if url}
|
)}
|
||||||
<div class="chapter-redirect">
|
|
||||||
{@html renderMarkdown(
|
|
||||||
tr.helpForMoreInfo({
|
|
||||||
link: `<a href="${url}" title="${tr.helpOpenManualChapter(
|
|
||||||
{
|
|
||||||
name: title,
|
|
||||||
},
|
|
||||||
)}">${title}</a>`,
|
|
||||||
}),
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
{/if}
|
||||||
<Row --cols={4}>
|
</div>
|
||||||
<Col --col-size={1}>
|
<div slot="body" class="modal-body">
|
||||||
<nav>
|
<Row --cols={4}>
|
||||||
<div id="nav">
|
<Col --col-size={1}>
|
||||||
<ul>
|
<nav>
|
||||||
{#each helpSections as item, i}
|
<div id="nav">
|
||||||
<li>
|
<ul>
|
||||||
<button
|
{#each helpSections as item, i}
|
||||||
on:click={() => {
|
<li>
|
||||||
activeIndex = i;
|
<button
|
||||||
carousel.to(activeIndex);
|
on:click={() => {
|
||||||
}}
|
activeIndex = i;
|
||||||
class:active={i == activeIndex}
|
carousel.to(activeIndex);
|
||||||
class:d-none={fsrs
|
}}
|
||||||
? item.sched ===
|
class:active={i == activeIndex}
|
||||||
HelpItemScheduler.SM2
|
|
||||||
: item.sched ==
|
|
||||||
HelpItemScheduler.FSRS}
|
|
||||||
>
|
|
||||||
{item.title}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</Col>
|
|
||||||
<Col --col-size={3}>
|
|
||||||
<div
|
|
||||||
id="helpSectionIndicators"
|
|
||||||
class="carousel slide"
|
|
||||||
bind:this={carouselRef}
|
|
||||||
>
|
|
||||||
<div class="carousel-inner">
|
|
||||||
{#each helpSections as item, i}
|
|
||||||
<div
|
|
||||||
class="carousel-item"
|
|
||||||
class:active={i == startIndex}
|
|
||||||
class:d-none={fsrs
|
class:d-none={fsrs
|
||||||
? item.sched === HelpItemScheduler.SM2
|
? item.sched === HelpItemScheduler.SM2
|
||||||
: item.sched == HelpItemScheduler.FSRS}
|
: item.sched == HelpItemScheduler.FSRS}
|
||||||
>
|
>
|
||||||
<HelpSection {item} />
|
{item.title}
|
||||||
</div>
|
</button>
|
||||||
{/each}
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</Col>
|
||||||
|
<Col --col-size={3}>
|
||||||
|
<div
|
||||||
|
id="helpSectionIndicators"
|
||||||
|
class="carousel slide"
|
||||||
|
bind:this={carouselRef}
|
||||||
|
>
|
||||||
|
<div class="carousel-inner">
|
||||||
|
{#each helpSections as item, i}
|
||||||
|
<div
|
||||||
|
class="carousel-item"
|
||||||
|
class:active={i == startIndex}
|
||||||
|
class:d-none={fsrs
|
||||||
|
? item.sched === HelpItemScheduler.SM2
|
||||||
|
: item.sched == HelpItemScheduler.FSRS}
|
||||||
|
>
|
||||||
|
<HelpSection {item} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/each}
|
||||||
</Col>
|
</div>
|
||||||
</Row>
|
</div>
|
||||||
</div>
|
</Col>
|
||||||
<div class="modal-footer">
|
</Row>
|
||||||
<button type="button" class="btn btn-primary" on:click={onOkClicked}>
|
|
||||||
{tr.helpOk()}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div slot="footer" class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-primary" on:click={modal.onOkClicked}>
|
||||||
|
{tr.helpOk()}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#nav {
|
#nav {
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal {
|
|
||||||
z-index: 1066;
|
|
||||||
background-color: rgba($color: black, $alpha: 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-title {
|
.modal-title {
|
||||||
margin-inline-end: 0.75rem;
|
margin-inline-end: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
:global(.modal-content) {
|
||||||
background-color: var(--canvas);
|
|
||||||
color: var(--fg);
|
|
||||||
border-radius: var(--border-radius-medium, 10px);
|
border-radius: var(--border-radius-medium, 10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
98
ts/lib/components/Modal.svelte
Normal file
98
ts/lib/components/Modal.svelte
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
<!--
|
||||||
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import Modal from "bootstrap/js/dist/modal";
|
||||||
|
import { getContext, onDestroy, onMount } from "svelte";
|
||||||
|
|
||||||
|
import { registerModalClosingHandler } from "$lib/sveltelib/modal-closing";
|
||||||
|
|
||||||
|
import { modalsKey } from "./context-keys";
|
||||||
|
|
||||||
|
export let modalKey: string = Math.random().toString(36).substring(2);
|
||||||
|
export const dialogClass: string = "";
|
||||||
|
export const onOkClicked: () => void = () => {};
|
||||||
|
export const onCancelClicked: () => void = () => {};
|
||||||
|
export const onShown: () => void = () => {};
|
||||||
|
export const onHidden: () => void = () => {};
|
||||||
|
|
||||||
|
const modals = getContext<Map<string, Modal>>(modalsKey);
|
||||||
|
|
||||||
|
let modal: Modal;
|
||||||
|
let modalRef: HTMLDivElement;
|
||||||
|
|
||||||
|
function onOkClicked_(): void {
|
||||||
|
modal.hide();
|
||||||
|
onOkClicked();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCancelClicked_(): void {
|
||||||
|
modal.hide();
|
||||||
|
onCancelClicked();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function show(): void {
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hide(): void {
|
||||||
|
modal.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { onOkClicked_ as acceptHandler, onCancelClicked_ as cancelHandler };
|
||||||
|
|
||||||
|
const { set: setModalOpen, remove: removeModalClosingHandler } =
|
||||||
|
registerModalClosingHandler(onOkClicked_);
|
||||||
|
|
||||||
|
function onShown_() {
|
||||||
|
setModalOpen(true);
|
||||||
|
onShown();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHidden_() {
|
||||||
|
setModalOpen(false);
|
||||||
|
onHidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
modalRef.addEventListener("shown.bs.modal", onShown_);
|
||||||
|
modalRef.addEventListener("hidden.bs.modal", onHidden_);
|
||||||
|
modal = new Modal(modalRef, { keyboard: false });
|
||||||
|
modals.set(modalKey, modal);
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
removeModalClosingHandler();
|
||||||
|
modalRef.removeEventListener("shown.bs.modal", onShown_);
|
||||||
|
modalRef.removeEventListener("hidden.bs.modal", onHidden_);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
bind:this={modalRef}
|
||||||
|
class="modal fade"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-labelledby="modalLabel"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div class="modal-dialog {dialogClass}">
|
||||||
|
<div class="modal-content">
|
||||||
|
<slot name="header" />
|
||||||
|
<slot name="body" />
|
||||||
|
<slot name="footer" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.modal {
|
||||||
|
z-index: 1066;
|
||||||
|
background-color: rgba($color: black, $alpha: 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background-color: var(--canvas);
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -3,11 +3,7 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Modal from "bootstrap/js/dist/modal";
|
import Modal from "$lib/components/Modal.svelte";
|
||||||
import { getContext, onDestroy, onMount } from "svelte";
|
|
||||||
|
|
||||||
import { modalsKey } from "$lib/components/context-keys";
|
|
||||||
import { registerModalClosingHandler } from "$lib/sveltelib/modal-closing";
|
|
||||||
import { pageTheme } from "$lib/sveltelib/theme";
|
import { pageTheme } from "$lib/sveltelib/theme";
|
||||||
|
|
||||||
export let title: string;
|
export let title: string;
|
||||||
|
@ -16,117 +12,63 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
export let onOk: (text: string) => void;
|
export let onOk: (text: string) => void;
|
||||||
$: value = initialValue;
|
$: value = initialValue;
|
||||||
|
|
||||||
export const modalKey: string = Math.random().toString(36).substring(2);
|
|
||||||
|
|
||||||
const modals = getContext<Map<string, Modal>>(modalsKey);
|
|
||||||
|
|
||||||
let modalRef: HTMLDivElement;
|
|
||||||
let modal: Modal;
|
|
||||||
|
|
||||||
let inputRef: HTMLInputElement;
|
let inputRef: HTMLInputElement;
|
||||||
|
let modal: Modal;
|
||||||
|
export let modalKey: string;
|
||||||
|
|
||||||
function onOkClicked(): void {
|
function onOkClicked(): void {
|
||||||
onOk(inputRef.value);
|
onOk(inputRef.value);
|
||||||
modal.hide();
|
|
||||||
value = initialValue;
|
value = initialValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCancelClicked(): void {
|
function onCancelClicked(): void {
|
||||||
modal.hide();
|
|
||||||
value = initialValue;
|
value = initialValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onShown(): void {
|
function onShown(): void {
|
||||||
inputRef.focus();
|
inputRef.focus();
|
||||||
setModalOpen(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onHidden() {
|
|
||||||
setModalOpen(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { set: setModalOpen, remove: removeModalClosingHandler } =
|
|
||||||
registerModalClosingHandler(onCancelClicked);
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
modalRef.addEventListener("shown.bs.modal", onShown);
|
|
||||||
modalRef.addEventListener("hidden.bs.modal", onHidden);
|
|
||||||
modal = new Modal(modalRef, { keyboard: false });
|
|
||||||
modals.set(modalKey, modal);
|
|
||||||
});
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
removeModalClosingHandler();
|
|
||||||
modalRef.removeEventListener("shown.bs.modal", onShown);
|
|
||||||
modalRef.removeEventListener("hidden.bs.modal", onHidden);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<Modal bind:this={modal} bind:modalKey {onOkClicked} {onShown} {onCancelClicked}>
|
||||||
bind:this={modalRef}
|
<div slot="header" class="modal-header">
|
||||||
class="modal fade"
|
<h5 class="modal-title" id="modalLabel">{title}</h5>
|
||||||
tabindex="-1"
|
<button
|
||||||
aria-labelledby="modalLabel"
|
type="button"
|
||||||
aria-hidden="true"
|
class="btn-close"
|
||||||
>
|
data-bs-dismiss="modal"
|
||||||
<div class="modal-dialog">
|
aria-label="Close"
|
||||||
<div class="modal-content" class:default-colors={$pageTheme.isDark}>
|
></button>
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="modalLabel">{title}</h5>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn-close"
|
|
||||||
class:invert={$pageTheme.isDark}
|
|
||||||
data-bs-dismiss="modal"
|
|
||||||
aria-label="Close"
|
|
||||||
></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form on:submit|preventDefault={onOkClicked}>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="prompt-input" class="col-form-label">
|
|
||||||
{prompt}:
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="prompt-input"
|
|
||||||
bind:this={inputRef}
|
|
||||||
type="text"
|
|
||||||
class:nightMode={$pageTheme.isDark}
|
|
||||||
class="form-control"
|
|
||||||
bind:value
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-secondary"
|
|
||||||
on:click={onCancelClicked}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-primary" on:click={onOkClicked}>
|
|
||||||
OK
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div slot="body" class="modal-body">
|
||||||
|
<form on:submit|preventDefault={modal.acceptHandler}>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="prompt-input" class="col-form-label">
|
||||||
|
{prompt}:
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="prompt-input"
|
||||||
|
bind:this={inputRef}
|
||||||
|
type="text"
|
||||||
|
class:nightMode={$pageTheme.isDark}
|
||||||
|
class="form-control"
|
||||||
|
bind:value
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div slot="footer" class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" on:click={modal.cancelHandler}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-primary" on:click={onOkClicked}>OK</button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@use "$lib/sass/night-mode" as nightmode;
|
@use "../../lib/sass/night-mode" as nightmode;
|
||||||
|
|
||||||
.nightMode {
|
.nightMode {
|
||||||
@include nightmode.input;
|
@include nightmode.input;
|
||||||
}
|
}
|
||||||
|
|
||||||
.default-colors {
|
|
||||||
background-color: var(--canvas);
|
|
||||||
color: var(--fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.invert {
|
|
||||||
filter: invert(1) grayscale(100%) brightness(200%);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -3,35 +3,14 @@ Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Modal from "bootstrap/js/dist/modal";
|
|
||||||
import { getContext, onDestroy, onMount } from "svelte";
|
|
||||||
import * as tr from "@generated/ftl";
|
import * as tr from "@generated/ftl";
|
||||||
|
import Modal from "$lib/components/Modal.svelte";
|
||||||
import { modalsKey } from "$lib/components/context-keys";
|
|
||||||
import { registerModalClosingHandler } from "$lib/sveltelib/modal-closing";
|
|
||||||
import { pageTheme } from "$lib/sveltelib/theme";
|
import { pageTheme } from "$lib/sveltelib/theme";
|
||||||
import type { HistoryEntry } from "./types";
|
import type { HistoryEntry } from "./types";
|
||||||
import { searchInBrowser } from "@generated/backend";
|
import { searchInBrowser } from "@generated/backend";
|
||||||
|
|
||||||
export const modalKey: string = Math.random().toString(36).substring(2);
|
|
||||||
export let history: HistoryEntry[] = [];
|
export let history: HistoryEntry[] = [];
|
||||||
|
export let modal: Modal;
|
||||||
const modals = getContext<Map<string, Modal>>(modalsKey);
|
|
||||||
|
|
||||||
let modalRef: HTMLDivElement;
|
|
||||||
let modal: Modal;
|
|
||||||
|
|
||||||
function onCancelClicked(): void {
|
|
||||||
modal.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onShown(): void {
|
|
||||||
setModalOpen(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onHidden() {
|
|
||||||
setModalOpen(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onEntryClick(entry: HistoryEntry): void {
|
function onEntryClick(entry: HistoryEntry): void {
|
||||||
searchInBrowser({
|
searchInBrowser({
|
||||||
|
@ -42,91 +21,41 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
});
|
});
|
||||||
modal.hide();
|
modal.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { set: setModalOpen, remove: removeModalClosingHandler } =
|
|
||||||
registerModalClosingHandler(onCancelClicked);
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
modalRef.addEventListener("shown.bs.modal", onShown);
|
|
||||||
modalRef.addEventListener("hidden.bs.modal", onHidden);
|
|
||||||
modal = new Modal(modalRef, { keyboard: false });
|
|
||||||
modals.set(modalKey, modal);
|
|
||||||
});
|
|
||||||
|
|
||||||
onDestroy(() => {
|
|
||||||
removeModalClosingHandler();
|
|
||||||
modalRef.removeEventListener("shown.bs.modal", onShown);
|
|
||||||
modalRef.removeEventListener("hidden.bs.modal", onHidden);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<Modal bind:this={modal}>
|
||||||
bind:this={modalRef}
|
<div slot="header" class="modal-header">
|
||||||
class="modal fade"
|
<h5 class="modal-title" id="modalLabel">{tr.addingHistory()}</h5>
|
||||||
class:nightMode={$pageTheme.isDark}
|
<button
|
||||||
tabindex="-1"
|
type="button"
|
||||||
aria-labelledby="modalLabel"
|
class="btn-close"
|
||||||
aria-hidden="true"
|
data-bs-dismiss="modal"
|
||||||
>
|
aria-label="Close"
|
||||||
<div class="modal-dialog">
|
></button>
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title" id="modalLabel">{tr.addingHistory()}</h5>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn-close"
|
|
||||||
data-bs-dismiss="modal"
|
|
||||||
aria-label="Close"
|
|
||||||
></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<ul class="history-list">
|
|
||||||
{#each history as entry}
|
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="history-entry"
|
|
||||||
on:click={() => onEntryClick(entry)}
|
|
||||||
>
|
|
||||||
{entry.text}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-secondary"
|
|
||||||
on:click={onCancelClicked}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div slot="body" class="modal-body" class:nightMode={$pageTheme.isDark}>
|
||||||
|
<ul class="history-list">
|
||||||
|
{#each history as entry}
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="history-entry"
|
||||||
|
on:click={() => onEntryClick(entry)}
|
||||||
|
>
|
||||||
|
{entry.text}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div slot="footer" class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" on:click={modal.cancelHandler}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.modal {
|
|
||||||
--link-color: #007bff;
|
|
||||||
--canvas-elevated-hover: rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nightMode.modal {
|
|
||||||
--link-color: #4dabf7;
|
|
||||||
--canvas-elevated-hover: rgba(255, 255, 255, 0.1): ;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nightMode .modal-content {
|
|
||||||
background-color: var(--canvas);
|
|
||||||
color: var(--fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nightMode .btn-close {
|
|
||||||
filter: invert(1) grayscale(100%) brightness(200%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-list {
|
.history-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
|
@ -15,9 +15,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import LabelName from "./LabelName.svelte";
|
import LabelName from "./LabelName.svelte";
|
||||||
import { EditorState, type EditorMode } from "./types";
|
import { EditorState, type EditorMode } from "./types";
|
||||||
import { ContextMenu, Item } from "$lib/context-menu";
|
import { ContextMenu, Item } from "$lib/context-menu";
|
||||||
import type Modal from "bootstrap/js/dist/modal";
|
|
||||||
import { getContext } from "svelte";
|
|
||||||
import { modalsKey } from "$lib/components/context-keys";
|
|
||||||
|
|
||||||
export interface NoteEditorAPI {
|
export interface NoteEditorAPI {
|
||||||
fields: EditorFieldAPI[];
|
fields: EditorFieldAPI[];
|
||||||
|
@ -451,9 +448,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
await addCurrentNote(1n);
|
await addCurrentNote(1n);
|
||||||
}
|
}
|
||||||
|
|
||||||
const modals = getContext<Map<string, Modal>>(modalsKey);
|
let historyModal: Modal;
|
||||||
let modalKey: string;
|
|
||||||
|
|
||||||
let history: HistoryEntry[] = [];
|
let history: HistoryEntry[] = [];
|
||||||
|
|
||||||
export async function addNoteToHistory(note: Note) {
|
export async function addNoteToHistory(note: Note) {
|
||||||
|
@ -476,7 +471,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onHistory() {
|
export function onHistory() {
|
||||||
modals.get(modalKey)!.show();
|
historyModal.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveOnPageHide() {
|
export function saveOnPageHide() {
|
||||||
|
@ -724,6 +719,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import HistoryModal from "./HistoryModal.svelte";
|
import HistoryModal from "./HistoryModal.svelte";
|
||||||
import { HelpPageLinkRequest_HelpPage } from "@generated/anki/links_pb";
|
import { HelpPageLinkRequest_HelpPage } from "@generated/anki/links_pb";
|
||||||
import { MessageBoxType } from "@generated/anki/frontend_pb";
|
import { MessageBoxType } from "@generated/anki/frontend_pb";
|
||||||
|
import type Modal from "$lib/components/Modal.svelte";
|
||||||
|
|
||||||
$: isIOImageLoaded = false;
|
$: isIOImageLoaded = false;
|
||||||
$: ioImageLoadedStore.set(isIOImageLoaded);
|
$: ioImageLoadedStore.set(isIOImageLoaded);
|
||||||
|
@ -1389,7 +1385,7 @@ components and functionality for general note editing.
|
||||||
|
|
||||||
{#if !isLegacy}
|
{#if !isLegacy}
|
||||||
<ActionButtons {mode} {onClose} {onAdd} {onHistory} {history} />
|
<ActionButtons {mode} {onClose} {onAdd} {onHistory} {history} />
|
||||||
<HistoryModal bind:modalKey {history} />
|
<HistoryModal bind:modal={historyModal} {history} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !isLegacy}
|
{#if !isLegacy}
|
||||||
|
|
Loading…
Reference in a new issue