mirror of
https://github.com/zadam/trilium.git
synced 2026-03-12 07:00:24 +01:00
Compare commits
1 Commits
renovate/h
...
renovate/r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ae9ac1ad5 |
@@ -16,7 +16,7 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"packageManager": "pnpm@10.32.0",
|
||||
"devDependencies": {
|
||||
"@redocly/cli": "2.20.2",
|
||||
"@redocly/cli": "2.21.0",
|
||||
"archiver": "7.0.1",
|
||||
"fs-extra": "11.3.4",
|
||||
"js-yaml": "4.1.1",
|
||||
|
||||
@@ -803,13 +803,12 @@
|
||||
"web-view": "عرض الويب",
|
||||
"mind-map": "خريطة ذهنية",
|
||||
"geo-map": "خريطة جغرافية",
|
||||
"task-list": "قائمة المهام",
|
||||
"spreadsheet": "جدول البيانات"
|
||||
"task-list": "قائمة المهام"
|
||||
},
|
||||
"shared_switch": {
|
||||
"shared": "مشترك",
|
||||
"toggle-on-title": "مشاركة الملاحظة",
|
||||
"toggle-off-title": "إلغاء مشاركة الملاحظة"
|
||||
"toggle-off-title": "الغاء مشاركة الملاحظة"
|
||||
},
|
||||
"template_switch": {
|
||||
"template": "قالب"
|
||||
@@ -1287,10 +1286,8 @@
|
||||
"search-for": "بحث ل \"{{term}}\""
|
||||
},
|
||||
"protect_note": {
|
||||
"toggle-off": "إزالة الحماية عن الملاحظة",
|
||||
"toggle-on": "حماية الملاحظة",
|
||||
"toggle-on-hint": "الملاحظة غير محمة، انقر لحمايتها",
|
||||
"toggle-off-hint": "الملاحظة محمية، انقر لإزالة الحماية منها"
|
||||
"toggle-off": "ازالة الحماية عن الملاحظة",
|
||||
"toggle-on": "حماية الملاحظة"
|
||||
},
|
||||
"open-help-page": "فتح صفحة المساعدة",
|
||||
"empty": {
|
||||
|
||||
@@ -1036,7 +1036,7 @@
|
||||
"file_preview_not_available": "File preview is not available for this file format.",
|
||||
"too_big": "The preview only shows the first {{maxNumChars}} characters of the file for performance reasons. Download the file and open it externally to be able to see the entire content."
|
||||
},
|
||||
"media": {
|
||||
"video": {
|
||||
"play": "Play (Space)",
|
||||
"pause": "Pause (Space)",
|
||||
"back-10s": "Back 10s (Left arrow key)",
|
||||
@@ -1051,7 +1051,7 @@
|
||||
"exit-picture-in-picture": "Exit picture-in-picture",
|
||||
"fullscreen": "Fullscreen (F)",
|
||||
"exit-fullscreen": "Exit fullscreen",
|
||||
"unsupported-format": "Media preview is not available for this file format:\n{{mime}}",
|
||||
"unsupported-format": "Video preview is not available for this file format.",
|
||||
"zoom-to-fit": "Zoom to fill",
|
||||
"zoom-reset": "Reset zoom to fill"
|
||||
},
|
||||
|
||||
@@ -1780,8 +1780,7 @@
|
||||
"ai-chat": "Czat AI",
|
||||
"task-list": "Lista zadań",
|
||||
"new-feature": "Nowość",
|
||||
"collections": "Kolekcje",
|
||||
"spreadsheet": "Arkusz"
|
||||
"collections": "Kolekcje"
|
||||
},
|
||||
"protect_note": {
|
||||
"toggle-on": "Chroń notatkę",
|
||||
|
||||
@@ -83,7 +83,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (note.type === "file" && (note.mime === "application/pdf" || note.mime.startsWith("video/") || note.mime.startsWith("audio/"))) {
|
||||
if (note.type === "file" && (note.mime === "application/pdf" || note.mime.startsWith("video/"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (note.type === "file" && (MIME_TYPES_WITH_BACKGROUND_EFFECTS.includes(note.mime) || note.mime.startsWith("audio/"))) {
|
||||
if (note.type === "file" && MIME_TYPES_WITH_BACKGROUND_EFFECTS.includes(note.mime)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
color: var(--muted-text-color);
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
white-space: pre-line;
|
||||
|
||||
.tn-icon {
|
||||
font-size: 4em;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import "./File.css";
|
||||
|
||||
import FNote from "../../entities/fnote";
|
||||
import { t } from "../../services/i18n";
|
||||
import { getUrlForDownload } from "../../services/open";
|
||||
import Alert from "../react/Alert";
|
||||
import { useNoteBlob } from "../react/hooks";
|
||||
import AudioPreview from "./file/Audio";
|
||||
import PdfPreview from "./file/Pdf";
|
||||
import VideoPreview from "./file/Video";
|
||||
import { TypeWidgetProps } from "./type_widget";
|
||||
@@ -42,6 +43,16 @@ function TextPreview({ content }: { content: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
function AudioPreview({ note }: { note: FNote }) {
|
||||
return (
|
||||
<audio
|
||||
class="audio-preview"
|
||||
src={getUrlForDownload(`api/notes/${note.noteId}/open-partial`)}
|
||||
controls
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function NoPreview() {
|
||||
return (
|
||||
<Alert className="file-preview-not-available" type="info">
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import FNote from "../../../entities/fnote";
|
||||
import { t } from "../../../services/i18n";
|
||||
import { getUrlForDownload } from "../../../services/open";
|
||||
import Icon from "../../react/Icon";
|
||||
import NoItems from "../../react/NoItems";
|
||||
import { LoopButton, PlaybackSpeed, PlayPauseButton, SeekBar, SkipButton, VolumeControl } from "./MediaPlayer";
|
||||
|
||||
export default function AudioPreview({ note }: { note: FNote }) {
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const audioRef = useRef<HTMLAudioElement>(null);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const togglePlayback = useCallback(() => {
|
||||
const audio = audioRef.current;
|
||||
if (!audio) return;
|
||||
if (audio.paused) {
|
||||
audio.play();
|
||||
} else {
|
||||
audio.pause();
|
||||
}
|
||||
}, []);
|
||||
const onKeyDown = useKeyboardShortcuts(audioRef, togglePlayback);
|
||||
|
||||
useEffect(() => setError(false), [note.noteId]);
|
||||
const onError = useCallback(() => setError(true), []);
|
||||
|
||||
if (error) {
|
||||
return <NoItems icon="bx bx-volume-mute" text={t("media.unsupported-format", { mime: note.mime.replace("/", "-") })} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef} className="audio-preview-wrapper" onKeyDown={onKeyDown} tabIndex={0}>
|
||||
<audio
|
||||
class="audio-preview"
|
||||
src={getUrlForDownload(`api/notes/${note.noteId}/open-partial`)}
|
||||
ref={audioRef}
|
||||
onPlay={() => setPlaying(true)}
|
||||
onPause={() => setPlaying(false)}
|
||||
onError={onError}
|
||||
/>
|
||||
<div className="audio-preview-icon-wrapper">
|
||||
<Icon icon="bx bx-music" className="audio-preview-icon" />
|
||||
</div>
|
||||
<div className="media-preview-controls">
|
||||
<SeekBar mediaRef={audioRef} />
|
||||
|
||||
<div class="media-buttons-row">
|
||||
<div className="left">
|
||||
<PlaybackSpeed mediaRef={audioRef} />
|
||||
</div>
|
||||
|
||||
<div className="center">
|
||||
<div className="spacer" />
|
||||
<SkipButton mediaRef={audioRef} seconds={-10} icon="bx bx-rewind" text={t("media.back-10s")} />
|
||||
<PlayPauseButton playing={playing} togglePlayback={togglePlayback} />
|
||||
<SkipButton mediaRef={audioRef} seconds={30} icon="bx bx-fast-forward" text={t("media.forward-30s")} />
|
||||
<LoopButton mediaRef={audioRef} />
|
||||
</div>
|
||||
|
||||
<div className="right">
|
||||
<VolumeControl mediaRef={audioRef} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function useKeyboardShortcuts(audioRef: MutableRef<HTMLAudioElement | null>, togglePlayback: () => void) {
|
||||
return useCallback((e: KeyboardEvent) => {
|
||||
const audio = audioRef.current;
|
||||
if (!audio) return;
|
||||
|
||||
switch (e.key) {
|
||||
case " ":
|
||||
e.preventDefault();
|
||||
togglePlayback();
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
e.preventDefault();
|
||||
audio.currentTime = Math.max(0, audio.currentTime - (e.ctrlKey ? 60 : 10));
|
||||
break;
|
||||
case "ArrowRight":
|
||||
e.preventDefault();
|
||||
audio.currentTime = Math.min(audio.duration, audio.currentTime + (e.ctrlKey ? 60 : 10));
|
||||
break;
|
||||
case "m":
|
||||
case "M":
|
||||
e.preventDefault();
|
||||
audio.muted = !audio.muted;
|
||||
break;
|
||||
case "ArrowUp":
|
||||
e.preventDefault();
|
||||
audio.volume = Math.min(1, audio.volume + 0.05);
|
||||
break;
|
||||
case "ArrowDown":
|
||||
e.preventDefault();
|
||||
audio.volume = Math.max(0, audio.volume - 0.05);
|
||||
break;
|
||||
case "Home":
|
||||
e.preventDefault();
|
||||
audio.currentTime = 0;
|
||||
break;
|
||||
case "End":
|
||||
e.preventDefault();
|
||||
audio.currentTime = audio.duration;
|
||||
break;
|
||||
}
|
||||
}, [ audioRef, togglePlayback ]);
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
.media-preview-controls {
|
||||
padding: 1.25em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
|
||||
.media-buttons-row {
|
||||
display: flex;
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
width: var(--icon-button-size, 32px);
|
||||
height: var(--icon-button-size, 32px);
|
||||
}
|
||||
|
||||
.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.play-button {
|
||||
--icon-button-size: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.media-seekbar-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
|
||||
.media-time {
|
||||
font-size: 0.85em;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.media-trackbar {
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.media-volume-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25em;
|
||||
|
||||
.media-volume-slider {
|
||||
width: 80px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.speed-dropdown {
|
||||
position: relative;
|
||||
|
||||
.tn-icon {
|
||||
transform: translateY(-10%);
|
||||
}
|
||||
|
||||
.media-speed-label {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transform: translateY(15%);
|
||||
text-align: center;
|
||||
font-size: 0.6rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.audio-preview-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.audio-preview-icon-wrapper {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 8em;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
import "./MediaPlayer.css";
|
||||
|
||||
import { RefObject } from "preact";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
import { t } from "../../../services/i18n";
|
||||
import ActionButton from "../../react/ActionButton";
|
||||
import Dropdown from "../../react/Dropdown";
|
||||
import Icon from "../../react/Icon";
|
||||
|
||||
export function SeekBar({ mediaRef }: { mediaRef: RefObject<HTMLVideoElement | HTMLAudioElement> }) {
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
const [duration, setDuration] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
|
||||
const onTimeUpdate = () => setCurrentTime(media.currentTime);
|
||||
const onDurationChange = () => setDuration(media.duration);
|
||||
|
||||
media.addEventListener("timeupdate", onTimeUpdate);
|
||||
media.addEventListener("durationchange", onDurationChange);
|
||||
return () => {
|
||||
media.removeEventListener("timeupdate", onTimeUpdate);
|
||||
media.removeEventListener("durationchange", onDurationChange);
|
||||
};
|
||||
}, [ mediaRef ]);
|
||||
|
||||
const onSeek = (e: Event) => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
media.currentTime = parseFloat((e.target as HTMLInputElement).value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="media-seekbar-row">
|
||||
<span class="media-time">{formatTime(currentTime)}</span>
|
||||
<input
|
||||
type="range"
|
||||
class="media-trackbar"
|
||||
min={0}
|
||||
max={duration || 0}
|
||||
step={0.1}
|
||||
value={currentTime}
|
||||
onInput={onSeek}
|
||||
/>
|
||||
<span class="media-time">-{formatTime(Math.max(0, duration - currentTime))}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function formatTime(seconds: number): string {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
export function PlayPauseButton({ playing, togglePlayback }: {
|
||||
playing: boolean,
|
||||
togglePlayback: () => void
|
||||
}) {
|
||||
return (
|
||||
<ActionButton
|
||||
className="play-button"
|
||||
icon={playing ? "bx bx-pause" : "bx bx-play"}
|
||||
text={playing ? t("media.pause") : t("media.play")}
|
||||
onClick={togglePlayback}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function VolumeControl({ mediaRef }: { mediaRef: RefObject<HTMLVideoElement | HTMLAudioElement> }) {
|
||||
const [volume, setVolume] = useState(() => mediaRef.current?.volume ?? 1);
|
||||
const [muted, setMuted] = useState(() => mediaRef.current?.muted ?? false);
|
||||
|
||||
// Sync state when the media element changes volume externally.
|
||||
useEffect(() => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
|
||||
setVolume(media.volume);
|
||||
setMuted(media.muted);
|
||||
|
||||
const onVolumeChange = () => {
|
||||
setVolume(media.volume);
|
||||
setMuted(media.muted);
|
||||
};
|
||||
media.addEventListener("volumechange", onVolumeChange);
|
||||
return () => media.removeEventListener("volumechange", onVolumeChange);
|
||||
}, [ mediaRef ]);
|
||||
|
||||
const onVolumeChange = (e: Event) => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
const val = parseFloat((e.target as HTMLInputElement).value);
|
||||
media.volume = val;
|
||||
setVolume(val);
|
||||
if (val > 0 && media.muted) {
|
||||
media.muted = false;
|
||||
setMuted(false);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleMute = () => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
media.muted = !media.muted;
|
||||
setMuted(media.muted);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="media-volume-row">
|
||||
<ActionButton
|
||||
icon={muted || volume === 0 ? "bx bx-volume-mute" : volume < 0.5 ? "bx bx-volume-low" : "bx bx-volume-full"}
|
||||
text={muted ? t("media.unmute") : t("media.mute")}
|
||||
onClick={toggleMute}
|
||||
/>
|
||||
<input
|
||||
type="range"
|
||||
class="media-volume-slider"
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.05}
|
||||
value={muted ? 0 : volume}
|
||||
onInput={onVolumeChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function SkipButton({ mediaRef, seconds, icon, text }: { mediaRef: RefObject<HTMLVideoElement | HTMLAudioElement>, seconds: number, icon: string, text: string }) {
|
||||
const skip = () => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
media.currentTime = Math.max(0, Math.min(media.duration, media.currentTime + seconds));
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionButton icon={icon} text={text} onClick={skip} />
|
||||
);
|
||||
}
|
||||
|
||||
export function LoopButton({ mediaRef }: { mediaRef: RefObject<HTMLVideoElement | HTMLAudioElement> }) {
|
||||
const [loop, setLoop] = useState(() => mediaRef.current?.loop ?? false);
|
||||
|
||||
useEffect(() => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
setLoop(media.loop);
|
||||
|
||||
const observer = new MutationObserver(() => setLoop(media.loop));
|
||||
observer.observe(media, { attributes: true, attributeFilter: ["loop"] });
|
||||
return () => observer.disconnect();
|
||||
}, [ mediaRef ]);
|
||||
|
||||
const toggle = () => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
media.loop = !media.loop;
|
||||
setLoop(media.loop);
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
className={loop ? "active" : ""}
|
||||
icon="bx bx-repeat"
|
||||
text={loop ? t("media.disable-loop") : t("media.loop")}
|
||||
onClick={toggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const PLAYBACK_SPEEDS = [0.5, 1, 1.25, 1.5, 2];
|
||||
|
||||
export function PlaybackSpeed({ mediaRef }: { mediaRef: RefObject<HTMLVideoElement | HTMLAudioElement> }) {
|
||||
const [speed, setSpeed] = useState(() => mediaRef.current?.playbackRate ?? 1);
|
||||
|
||||
useEffect(() => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
|
||||
setSpeed(media.playbackRate);
|
||||
|
||||
const onRateChange = () => setSpeed(media.playbackRate);
|
||||
media.addEventListener("ratechange", onRateChange);
|
||||
return () => media.removeEventListener("ratechange", onRateChange);
|
||||
}, [ mediaRef ]);
|
||||
|
||||
const selectSpeed = (rate: number) => {
|
||||
const media = mediaRef.current;
|
||||
if (!media) return;
|
||||
media.playbackRate = rate;
|
||||
setSpeed(rate);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
iconAction
|
||||
hideToggleArrow
|
||||
buttonClassName="speed-dropdown"
|
||||
text={<>
|
||||
<Icon icon="bx bx-tachometer" />
|
||||
<span class="media-speed-label">{speed}x</span>
|
||||
</>}
|
||||
title={t("media.playback-speed")}
|
||||
>
|
||||
{PLAYBACK_SPEEDS.map((rate) => (
|
||||
<li key={rate}>
|
||||
<button
|
||||
class={`dropdown-item ${rate === speed ? "active" : ""}`}
|
||||
onClick={() => selectSpeed(rate)}
|
||||
>
|
||||
{rate}x
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
background-color: black;
|
||||
background-color: black;
|
||||
|
||||
.video-preview {
|
||||
background-color: black;
|
||||
@@ -13,23 +13,102 @@
|
||||
&.controls-hidden {
|
||||
cursor: pointer;
|
||||
|
||||
.media-preview-controls {
|
||||
.video-preview-controls {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.media-preview-controls {
|
||||
--icon-button-hover-color: white;
|
||||
--icon-button-hover-background: rgba(255, 255, 255, 0.2);
|
||||
opacity: 1;
|
||||
transition: opacity 300ms ease;
|
||||
.video-preview-controls {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 1.25em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(6px);
|
||||
color: white;
|
||||
--icon-button-hover-color: white;
|
||||
--icon-button-hover-background: rgba(255, 255, 255, 0.2);
|
||||
opacity: 1;
|
||||
transition: opacity 300ms ease;
|
||||
|
||||
.video-buttons-row {
|
||||
display: flex;
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
width: var(--icon-button-size, 32px);
|
||||
height: var(--icon-button-size, 32px);
|
||||
}
|
||||
|
||||
.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.play-button {
|
||||
--icon-button-size: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-seekbar-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.video-trackbar {
|
||||
flex: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.video-time {
|
||||
font-size: 0.85em;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.video-volume-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25em;
|
||||
}
|
||||
|
||||
.video-volume-slider {
|
||||
width: 80px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.speed-dropdown {
|
||||
position: relative;
|
||||
|
||||
.tn-icon {
|
||||
transform: translateY(-10%);
|
||||
}
|
||||
|
||||
.video-speed-label {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transform: translateY(15%);
|
||||
text-align: center;
|
||||
font-size: 0.6rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import "./Video.css";
|
||||
|
||||
import { RefObject } from "preact";
|
||||
import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import FNote from "../../../entities/fnote";
|
||||
import { t } from "../../../services/i18n";
|
||||
import { getUrlForDownload } from "../../../services/open";
|
||||
import ActionButton from "../../react/ActionButton";
|
||||
import Dropdown from "../../react/Dropdown";
|
||||
import Icon from "../../react/Icon";
|
||||
import NoItems from "../../react/NoItems";
|
||||
import { LoopButton, PlaybackSpeed, PlayPauseButton, SeekBar, SkipButton, VolumeControl } from "./MediaPlayer";
|
||||
|
||||
function formatTime(seconds: number): string {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
const AUTO_HIDE_DELAY = 3000;
|
||||
|
||||
@@ -33,56 +40,11 @@ export default function VideoPreview({ note }: { note: FNote }) {
|
||||
}, []);
|
||||
|
||||
const onVideoClick = useCallback((e: MouseEvent) => {
|
||||
if ((e.target as HTMLElement).closest(".media-preview-controls")) return;
|
||||
if ((e.target as HTMLElement).closest(".video-preview-controls")) return;
|
||||
togglePlayback();
|
||||
}, [togglePlayback]);
|
||||
|
||||
const onKeyDown = useKeyboardShortcuts(videoRef, wrapperRef, togglePlayback, flashControls);
|
||||
|
||||
if (error) {
|
||||
return <NoItems icon="bx bx-video-off" text={t("media.unsupported-format", { mime: note.mime.replace("/", "-") })} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef} className={`video-preview-wrapper ${controlsVisible ? "" : "controls-hidden"}`} tabIndex={0} onClick={onVideoClick} onKeyDown={onKeyDown} onMouseMove={onMouseMove}>
|
||||
<video
|
||||
ref={videoRef}
|
||||
class="video-preview"
|
||||
src={getUrlForDownload(`api/notes/${note.noteId}/open-partial`)}
|
||||
datatype={note?.mime}
|
||||
onPlay={() => setPlaying(true)}
|
||||
onPause={() => setPlaying(false)}
|
||||
onError={onError}
|
||||
/>
|
||||
|
||||
<div className="media-preview-controls">
|
||||
<SeekBar mediaRef={videoRef} />
|
||||
<div class="media-buttons-row">
|
||||
<div className="left">
|
||||
<PlaybackSpeed mediaRef={videoRef} />
|
||||
<RotateButton videoRef={videoRef} />
|
||||
</div>
|
||||
<div className="center">
|
||||
<div className="spacer" />
|
||||
<SkipButton mediaRef={videoRef} seconds={-10} icon="bx bx-rewind" text={t("media.back-10s")} />
|
||||
<PlayPauseButton playing={playing} togglePlayback={togglePlayback} />
|
||||
<SkipButton mediaRef={videoRef} seconds={30} icon="bx bx-fast-forward" text={t("media.forward-30s")} />
|
||||
<LoopButton mediaRef={videoRef} />
|
||||
</div>
|
||||
<div className="right">
|
||||
<VolumeControl mediaRef={videoRef} />
|
||||
<ZoomToFitButton videoRef={videoRef} />
|
||||
<PictureInPictureButton videoRef={videoRef} />
|
||||
<FullscreenButton targetRef={wrapperRef} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function useKeyboardShortcuts(videoRef: MutableRef<HTMLVideoElement | null>, wrapperRef: MutableRef<HTMLDivElement | null>, togglePlayback: () => void, flashControls: () => void) {
|
||||
return useCallback((e: KeyboardEvent) => {
|
||||
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
|
||||
@@ -138,7 +100,48 @@ function useKeyboardShortcuts(videoRef: MutableRef<HTMLVideoElement | null>, wra
|
||||
flashControls();
|
||||
break;
|
||||
}
|
||||
}, [ wrapperRef, videoRef, togglePlayback, flashControls ]);
|
||||
}, [togglePlayback, flashControls]);
|
||||
|
||||
if (error) {
|
||||
return <NoItems icon="bx bx-video-off" text={t("video.unsupported-format")} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef} className={`video-preview-wrapper ${controlsVisible ? "" : "controls-hidden"}`} tabIndex={0} onClick={onVideoClick} onKeyDown={onKeyDown} onMouseMove={onMouseMove}>
|
||||
<video
|
||||
ref={videoRef}
|
||||
class="video-preview"
|
||||
src={getUrlForDownload(`api/notes/${note.noteId}/open-partial`)}
|
||||
datatype={note?.mime}
|
||||
onPlay={() => setPlaying(true)}
|
||||
onPause={() => setPlaying(false)}
|
||||
onError={onError}
|
||||
/>
|
||||
|
||||
<div className="video-preview-controls">
|
||||
<SeekBar videoRef={videoRef} />
|
||||
<div class="video-buttons-row">
|
||||
<div className="left">
|
||||
<PlaybackSpeed videoRef={videoRef} />
|
||||
<RotateButton videoRef={videoRef} />
|
||||
</div>
|
||||
<div className="center">
|
||||
<div className="spacer" />
|
||||
<SkipButton videoRef={videoRef} seconds={-10} icon="bx bx-rewind" text={t("video.back-10s")} />
|
||||
<PlayPauseButton videoRef={videoRef} playing={playing} />
|
||||
<SkipButton videoRef={videoRef} seconds={30} icon="bx bx-fast-forward" text={t("video.forward-30s")} />
|
||||
<LoopButton videoRef={videoRef} />
|
||||
</div>
|
||||
<div className="right">
|
||||
<VolumeControl videoRef={videoRef} />
|
||||
<ZoomToFitButton videoRef={videoRef} />
|
||||
<PictureInPictureButton videoRef={videoRef} />
|
||||
<FullscreenButton targetRef={wrapperRef} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function useAutoHideControls(videoRef: RefObject<HTMLVideoElement>, playing: boolean) {
|
||||
@@ -150,7 +153,7 @@ function useAutoHideControls(videoRef: RefObject<HTMLVideoElement>, playing: boo
|
||||
if (videoRef.current && !videoRef.current.paused) {
|
||||
hideTimerRef.current = setTimeout(() => setVisible(false), AUTO_HIDE_DELAY);
|
||||
}
|
||||
}, [ videoRef]);
|
||||
}, []);
|
||||
|
||||
const onMouseMove = useCallback(() => {
|
||||
setVisible(true);
|
||||
@@ -171,6 +174,219 @@ function useAutoHideControls(videoRef: RefObject<HTMLVideoElement>, playing: boo
|
||||
return { visible, onMouseMove, flash: onMouseMove };
|
||||
}
|
||||
|
||||
function PlayPauseButton({ videoRef, playing }: { videoRef: RefObject<HTMLVideoElement>, playing: boolean }) {
|
||||
const togglePlayback = () => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
|
||||
if (video.paused) {
|
||||
video.play();
|
||||
} else {
|
||||
video.pause();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
className="play-button"
|
||||
icon={playing ? "bx bx-pause" : "bx bx-play"}
|
||||
text={playing ? t("video.pause") : t("video.play")}
|
||||
onClick={togglePlayback}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function SkipButton({ videoRef, seconds, icon, text }: { videoRef: RefObject<HTMLVideoElement>, seconds: number, icon: string, text: string }) {
|
||||
const skip = () => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
video.currentTime = Math.max(0, Math.min(video.duration, video.currentTime + seconds));
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionButton icon={icon} text={text} onClick={skip} />
|
||||
);
|
||||
}
|
||||
|
||||
function SeekBar({ videoRef }: { videoRef: RefObject<HTMLVideoElement> }) {
|
||||
const [currentTime, setCurrentTime] = useState(0);
|
||||
const [duration, setDuration] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
|
||||
const onTimeUpdate = () => setCurrentTime(video.currentTime);
|
||||
const onDurationChange = () => setDuration(video.duration);
|
||||
|
||||
video.addEventListener("timeupdate", onTimeUpdate);
|
||||
video.addEventListener("durationchange", onDurationChange);
|
||||
return () => {
|
||||
video.removeEventListener("timeupdate", onTimeUpdate);
|
||||
video.removeEventListener("durationchange", onDurationChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onSeek = (e: Event) => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
video.currentTime = parseFloat((e.target as HTMLInputElement).value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="video-seekbar-row">
|
||||
<span class="video-time">{formatTime(currentTime)}</span>
|
||||
<input
|
||||
type="range"
|
||||
class="video-trackbar"
|
||||
min={0}
|
||||
max={duration || 0}
|
||||
step={0.1}
|
||||
value={currentTime}
|
||||
onInput={onSeek}
|
||||
/>
|
||||
<span class="video-time">-{formatTime(Math.max(0, duration - currentTime))}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function VolumeControl({ videoRef }: { videoRef: RefObject<HTMLVideoElement> }) {
|
||||
const [volume, setVolume] = useState(() => videoRef.current?.volume ?? 1);
|
||||
const [muted, setMuted] = useState(() => videoRef.current?.muted ?? false);
|
||||
|
||||
// Sync state when the video element changes volume externally.
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
|
||||
setVolume(video.volume);
|
||||
setMuted(video.muted);
|
||||
|
||||
const onVolumeChange = () => {
|
||||
setVolume(video.volume);
|
||||
setMuted(video.muted);
|
||||
};
|
||||
video.addEventListener("volumechange", onVolumeChange);
|
||||
return () => video.removeEventListener("volumechange", onVolumeChange);
|
||||
}, []);
|
||||
|
||||
const onVolumeChange = (e: Event) => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
const val = parseFloat((e.target as HTMLInputElement).value);
|
||||
video.volume = val;
|
||||
setVolume(val);
|
||||
if (val > 0 && video.muted) {
|
||||
video.muted = false;
|
||||
setMuted(false);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleMute = () => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
video.muted = !video.muted;
|
||||
setMuted(video.muted);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="video-volume-row">
|
||||
<ActionButton
|
||||
icon={muted || volume === 0 ? "bx bx-volume-mute" : volume < 0.5 ? "bx bx-volume-low" : "bx bx-volume-full"}
|
||||
text={muted ? t("video.unmute") : t("video.mute")}
|
||||
onClick={toggleMute}
|
||||
/>
|
||||
<input
|
||||
type="range"
|
||||
class="video-volume-slider"
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.05}
|
||||
value={muted ? 0 : volume}
|
||||
onInput={onVolumeChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const PLAYBACK_SPEEDS = [0.5, 1, 1.25, 1.5, 2];
|
||||
|
||||
function PlaybackSpeed({ videoRef }: { videoRef: RefObject<HTMLVideoElement> }) {
|
||||
const [speed, setSpeed] = useState(() => videoRef.current?.playbackRate ?? 1);
|
||||
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
|
||||
setSpeed(video.playbackRate);
|
||||
|
||||
const onRateChange = () => setSpeed(video.playbackRate);
|
||||
video.addEventListener("ratechange", onRateChange);
|
||||
return () => video.removeEventListener("ratechange", onRateChange);
|
||||
}, []);
|
||||
|
||||
const selectSpeed = (rate: number) => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
video.playbackRate = rate;
|
||||
setSpeed(rate);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
iconAction
|
||||
hideToggleArrow
|
||||
buttonClassName="speed-dropdown"
|
||||
text={<>
|
||||
<Icon icon="bx bx-tachometer" />
|
||||
<span class="video-speed-label">{speed}x</span>
|
||||
</>}
|
||||
title={t("video.playback-speed")}
|
||||
>
|
||||
{PLAYBACK_SPEEDS.map((rate) => (
|
||||
<li key={rate}>
|
||||
<button
|
||||
class={`dropdown-item ${rate === speed ? "active" : ""}`}
|
||||
onClick={() => selectSpeed(rate)}
|
||||
>
|
||||
{rate}x
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
function LoopButton({ videoRef }: { videoRef: RefObject<HTMLVideoElement> }) {
|
||||
const [loop, setLoop] = useState(() => videoRef.current?.loop ?? false);
|
||||
|
||||
useEffect(() => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
setLoop(video.loop);
|
||||
|
||||
const observer = new MutationObserver(() => setLoop(video.loop));
|
||||
observer.observe(video, { attributes: true, attributeFilter: ["loop"] });
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
const toggle = () => {
|
||||
const video = videoRef.current;
|
||||
if (!video) return;
|
||||
video.loop = !video.loop;
|
||||
setLoop(video.loop);
|
||||
};
|
||||
|
||||
return (
|
||||
<ActionButton
|
||||
className={loop ? "active" : ""}
|
||||
icon="bx bx-repeat"
|
||||
text={loop ? t("video.disable-loop") : t("video.loop")}
|
||||
onClick={toggle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function RotateButton({ videoRef }: { videoRef: RefObject<HTMLVideoElement> }) {
|
||||
const [rotation, setRotation] = useState(0);
|
||||
|
||||
@@ -198,7 +414,7 @@ function RotateButton({ videoRef }: { videoRef: RefObject<HTMLVideoElement> }) {
|
||||
return (
|
||||
<ActionButton
|
||||
icon="bx bx-rotate-right"
|
||||
text={t("media.rotate")}
|
||||
text={t("video.rotate")}
|
||||
onClick={rotate}
|
||||
/>
|
||||
);
|
||||
@@ -219,7 +435,7 @@ function ZoomToFitButton({ videoRef }: { videoRef: RefObject<HTMLVideoElement> }
|
||||
<ActionButton
|
||||
className={fitted ? "active" : ""}
|
||||
icon={fitted ? "bx bx-collapse" : "bx bx-expand"}
|
||||
text={fitted ? t("media.zoom-reset") : t("media.zoom-to-fit")}
|
||||
text={fitted ? t("video.zoom-reset") : t("video.zoom-to-fit")}
|
||||
onClick={toggle}
|
||||
/>
|
||||
);
|
||||
@@ -244,7 +460,7 @@ function PictureInPictureButton({ videoRef }: { videoRef: RefObject<HTMLVideoEle
|
||||
video.removeEventListener("enterpictureinpicture", onEnter);
|
||||
video.removeEventListener("leavepictureinpicture", onLeave);
|
||||
};
|
||||
}, [ videoRef, supported ]);
|
||||
}, [supported]);
|
||||
|
||||
if (!supported) return null;
|
||||
|
||||
@@ -262,7 +478,7 @@ function PictureInPictureButton({ videoRef }: { videoRef: RefObject<HTMLVideoEle
|
||||
return (
|
||||
<ActionButton
|
||||
icon={active ? "bx bx-exit" : "bx bx-window-open"}
|
||||
text={active ? t("media.exit-picture-in-picture") : t("media.picture-in-picture")}
|
||||
text={active ? t("video.exit-picture-in-picture") : t("video.picture-in-picture")}
|
||||
onClick={toggle}
|
||||
/>
|
||||
);
|
||||
@@ -291,7 +507,7 @@ function FullscreenButton({ targetRef }: { targetRef: RefObject<HTMLElement> })
|
||||
return (
|
||||
<ActionButton
|
||||
icon={isFullscreen ? "bx bx-exit-fullscreen" : "bx bx-fullscreen"}
|
||||
text={isFullscreen ? t("media.exit-fullscreen") : t("media.fullscreen")}
|
||||
text={isFullscreen ? t("video.exit-fullscreen") : t("video.fullscreen")}
|
||||
onClick={toggleFullscreen}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"html": "1.0.0",
|
||||
"html2plaintext": "2.1.4",
|
||||
"http-proxy-agent": "7.0.2",
|
||||
"https-proxy-agent": "8.0.0",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"i18next": "25.8.17",
|
||||
"i18next-fs-backend": "2.6.1",
|
||||
"image-type": "6.0.0",
|
||||
|
||||
@@ -197,12 +197,5 @@
|
||||
"description": "Trilium Notes는 간편한 접근 및 관리를 위해 유료 서비스인 PikaPods에서 호스팅할 수 있습니다. Trilium 팀과 직접 제휴되어있지는 않습니다.",
|
||||
"download_pikapod": "PikaPods에서 설치하기",
|
||||
"download_triliumcc": "또는 trilium.cc를 참조하세요"
|
||||
},
|
||||
"resources": {
|
||||
"title": "리소스",
|
||||
"icon_packs": "아이콘 팩",
|
||||
"icon_packs_intro": "아이콘 팩을 사용하여 노트에 사용할 수 있는 아이콘 종류를 늘려보세요. 아이콘 팩에 대한 자세한 내용은 <DocumentationLink>공식 문서</DocumentationLink>를 참조하세요.",
|
||||
"download": "다운로드",
|
||||
"website": "웹사이트"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,9 +50,7 @@
|
||||
"canvas_description": "Розташовуйте фігури, зображення та текст на нескінченному полотні, використовуючи ту саму технологію, що й excalidraw.com. Ідеально підходить для діаграм, ескізів та візуального планування.",
|
||||
"mermaid_description": "Створюйте діаграми, такі як блок-схеми, діаграми класів та послідовностей, діаграми Ганта та багато іншого, використовуючи синтаксис Mermaid.",
|
||||
"others_list": "та інші: <0>карта нотаток</0>, <1>карта зв'язків</1>, <2>збережені пошуки</2>, <3>візуалізація нотаток</3> та <4>веб-перегляди</4>.",
|
||||
"mermaid_title": "Mermaid діаграми",
|
||||
"mindmap_title": "Карта думок",
|
||||
"mindmap_description": "Візуально упорядкуйте свої думки або проведіть мозковий штурм."
|
||||
"mermaid_title": "Mermaid діаграми"
|
||||
},
|
||||
"extensibility_benefits": {
|
||||
"title": "Спільне використання та розширюваність",
|
||||
@@ -61,9 +59,7 @@
|
||||
"share_title": "Діліться нотатками в Інтернеті",
|
||||
"share_description": "Якщо у Вас є сервер, Ви можете використати його, щоб поділитися частиною своїх нотаток з іншими людьми.",
|
||||
"api_title": "REST API",
|
||||
"api_description": "Взаємодійте з Trilium програмно, використовуючи його вбудований REST API.",
|
||||
"scripting_title": "Розширений скриптинг",
|
||||
"scripting_description": "Створюйте власні інтеграції в Trilium за допомогою користувацьких віджетів або серверної логіки."
|
||||
"api_description": "Взаємодійте з Trilium програмно, використовуючи його вбудований REST API."
|
||||
},
|
||||
"collections": {
|
||||
"title": "Колекції",
|
||||
@@ -112,8 +108,7 @@
|
||||
"header": {
|
||||
"get-started": "Почати",
|
||||
"documentation": "Документація",
|
||||
"support-us": "Підтримайте нас",
|
||||
"resources": "Ресурси"
|
||||
"support-us": "Підтримайте нас"
|
||||
},
|
||||
"footer": {
|
||||
"copyright_and_the": " і ",
|
||||
@@ -153,8 +148,7 @@
|
||||
"description_arm64": "Сумісний з пристроями ARM (наприклад, з Qualcomm Snapdragon).",
|
||||
"quick_start": "Щоб встановити через Winget:",
|
||||
"download_exe": "Завантажити інсталятор (.exe)",
|
||||
"download_zip": "Портативний (.zip)",
|
||||
"download_scoop": "Scoop"
|
||||
"download_zip": "Портативний (.zip)"
|
||||
},
|
||||
"download_helper_desktop_linux": {
|
||||
"title_x64": "Linux 64-bit",
|
||||
@@ -165,44 +159,23 @@
|
||||
"download_deb": ".deb",
|
||||
"download_rpm": ".rpm",
|
||||
"download_flatpak": ".flatpak",
|
||||
"download_nixpkgs": "nixpkgs",
|
||||
"download_zip": "Portable (.zip)",
|
||||
"download_aur": "AUR"
|
||||
"download_nixpkgs": "nixpkgs"
|
||||
},
|
||||
"download_helper_desktop_macos": {
|
||||
"title_x64": "macOS для Intel",
|
||||
"title_arm64": "macOS для Apple Silicon",
|
||||
"quick_start": "Для того, щоб встановити за допомогою Homebrew:",
|
||||
"download_homebrew_cask": "Homebrew Cask",
|
||||
"description_x64": "Для комп’ютерів Mac на базі Intel з macOS Monterey або пізнішої версії.",
|
||||
"description_arm64": "Для комп'ютерів Apple Silicon Mac, таких як ті, що мають чіпи M1 та M2.",
|
||||
"download_dmg": "Завантажити інсталятор (.dmg)",
|
||||
"download_zip": "Portable (.zip)"
|
||||
"download_homebrew_cask": "Homebrew Cask"
|
||||
},
|
||||
"download_helper_server_docker": {
|
||||
"download_dockerhub": "Docker Hub",
|
||||
"download_ghcr": "ghcr.io",
|
||||
"title": "Self-hosted using Docker",
|
||||
"description": "Легке розгортання на Windows, Linux або macOS за допомогою контейнера Docker."
|
||||
"download_ghcr": "ghcr.io"
|
||||
},
|
||||
"download_helper_server_linux": {
|
||||
"download_tar_x64": "x64 (.tar.xz)",
|
||||
"download_tar_arm64": "ARM (.tar.xz)",
|
||||
"title": "Self-hosted on Linux",
|
||||
"description": "Розгорніть Trilium Notes на власному сервері або VPS, сумісному з більшістю дистрибутивів.",
|
||||
"download_nixos": "NixOS module"
|
||||
"download_tar_arm64": "ARM (.tar.xz)"
|
||||
},
|
||||
"download_helper_server_hosted": {
|
||||
"title": "Платний хостинг",
|
||||
"description": "Нотатки Trilium розміщені на PikaPods, платному сервісі для легкого доступу та керування. Не пов'язаний безпосередньо з командою Trilium.",
|
||||
"download_pikapod": "Налаштування на PikaPods",
|
||||
"download_triliumcc": "Або див. trilium.cc"
|
||||
},
|
||||
"resources": {
|
||||
"title": "Ресурси",
|
||||
"icon_packs": "Пакети піктограм",
|
||||
"icon_packs_intro": "Розширте вибір доступних піктограм для ваших нотаток за допомогою пакету піктограм. Щоб отримати докладнішу інформацію про пакети піктограм, див. <DocumentationLink>офіційну документацію</DocumentationLink>.",
|
||||
"download": "Завантажити",
|
||||
"website": "Вебсайт"
|
||||
"title": "Платний хостинг"
|
||||
}
|
||||
}
|
||||
|
||||
28
docs/README-ko.md
vendored
28
docs/README-ko.md
vendored
@@ -263,19 +263,23 @@ docs](https://github.com/TriliumNext/Trilium/tree/main/docs/Developer%20Guide/De
|
||||
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - 텍스트 노트의 시각적 편집기입니다. 프리미엄
|
||||
기능을 제공해주셔서 감사합니다.
|
||||
* [CodeMirror](https://github.com/codemirror/CodeMirror) - 수많은 언어를 지원하는 코드 편집기.
|
||||
* [Excalidraw](https://github.com/excalidraw/excalidraw) - Canvas 노트에서 사용되는 무한
|
||||
화이트보드입니다.
|
||||
* [Mind Elixir](https://github.com/SSShooter/mind-elixir-core) - 마인드맵 기능을 제공합니다.
|
||||
* [Leaflet](https://github.com/Leaflet/Leaflet) - 지리 지도를 렌더링 합니다.
|
||||
* [Tabulator](https://github.com/olifolkerd/tabulator) - 컬렉션에서 사용되는 인터랙티브
|
||||
테이블입니다.
|
||||
* [FancyTree](https://github.com/mar10/fancytree) - 독보적으로 기능이 풍부한 트리 라이브러리입니다.
|
||||
* [jsPlumb](https://github.com/jsplumb/jsplumb) - 시각적 연결 라이브러리입니다. [관계
|
||||
맵](https://docs.triliumnotes.org/user-guide/note-types/relation-map) 과 [링크
|
||||
맵](https://docs.triliumnotes.org/user-guide/advanced-usage/note-map#link-map)에
|
||||
사용됩니다
|
||||
* [Excalidraw](https://github.com/excalidraw/excalidraw) - the infinite
|
||||
whiteboard used in Canvas notes.
|
||||
* [Mind Elixir](https://github.com/SSShooter/mind-elixir-core) - providing the
|
||||
mind map functionality.
|
||||
* [Leaflet](https://github.com/Leaflet/Leaflet) - for rendering geographical
|
||||
maps.
|
||||
* [Tabulator](https://github.com/olifolkerd/tabulator) - for the interactive
|
||||
table used in collections.
|
||||
* [FancyTree](https://github.com/mar10/fancytree) - feature-rich tree library
|
||||
without real competition.
|
||||
* [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library.
|
||||
Used in [relation
|
||||
maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and
|
||||
[link
|
||||
maps](https://docs.triliumnotes.org/user-guide/advanced-usage/note-map#link-map)
|
||||
|
||||
## 🤝 후원
|
||||
## 🤝 Support
|
||||
|
||||
Trilium is built and maintained with [hundreds of hours of
|
||||
work](https://github.com/TriliumNext/Trilium/graphs/commit-activity). Your
|
||||
|
||||
46
docs/README-uk.md
vendored
46
docs/README-uk.md
vendored
@@ -95,8 +95,8 @@ Trilium Notes — це безкоштовний кросплатформний
|
||||
безпечнішого входу
|
||||
* [Синхронізація](https://docs.triliumnotes.org/user-guide/setup/synchronization)
|
||||
із власним сервером синхронізації
|
||||
* існують [сторонні сервіси для розміщення сервера
|
||||
синхронізації](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting)
|
||||
* there are [3rd party services for hosting synchronisation
|
||||
server](https://docs.triliumnotes.org/user-guide/setup/server/cloud-hosting)
|
||||
* [Спільне
|
||||
використання](https://docs.triliumnotes.org/user-guide/advanced-usage/sharing)
|
||||
(публікація) нотаток у загальнодоступному інтернеті
|
||||
@@ -105,11 +105,10 @@ Trilium Notes — це безкоштовний кросплатформний
|
||||
з деталізацією для кожної нотатки
|
||||
* Створення ескізних схем на основі [Excalidraw](https://excalidraw.com/) (тип
|
||||
нотатки "полотно")
|
||||
* [Карти
|
||||
зв'язків](https://docs.triliumnotes.org/user-guide/note-types/relation-map) та
|
||||
[карти
|
||||
нотаток/посилань](https://docs.triliumnotes.org/user-guide/note-types/note-map)
|
||||
для візуалізації нотаток та їх зв'язків
|
||||
* [Relation
|
||||
maps](https://docs.triliumnotes.org/user-guide/note-types/relation-map) and
|
||||
[note/link maps](https://docs.triliumnotes.org/user-guide/note-types/note-map)
|
||||
for visualizing notes and their relations
|
||||
* Інтелект-карти, засновані на [Mind Elixir](https://docs.mind-elixir.com/)
|
||||
* [Геокарти](https://docs.triliumnotes.org/user-guide/collections/geomap) з
|
||||
географічними позначками та GPX-треками
|
||||
@@ -149,18 +148,19 @@ TriliumNext:
|
||||
надав репозиторій Trilium спільнотному проекту, який знаходиться за адресою
|
||||
https://github.com/TriliumNext
|
||||
|
||||
### ⬆️Переходите із Zadam/Trilium?
|
||||
### ⬆️Migrating from Zadam/Trilium?
|
||||
|
||||
Немає жодних спеціальних кроків для міграції з екземпляра zadam/Trilium до
|
||||
екземпляра TriliumNext/Trilium. Просто [встановіть
|
||||
TriliumNext/Trilium](#-installation) як завжди, і він використовуватиме вашу
|
||||
існуючу базу даних.
|
||||
There are no special migration steps to migrate from a zadam/Trilium instance to
|
||||
a TriliumNext/Trilium instance. Simply [install
|
||||
TriliumNext/Trilium](#-installation) as usual and it will use your existing
|
||||
database.
|
||||
|
||||
Версії до [v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4)
|
||||
включно сумісні з останньою версією zadam/trilium
|
||||
[v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Будь-які
|
||||
пізніші версії TriliumNext/Trilium мають збільшені версії синхронізації, що
|
||||
запобігає прямій міграції.
|
||||
Versions up to and including
|
||||
[v0.90.4](https://github.com/TriliumNext/Trilium/releases/tag/v0.90.4) are
|
||||
compatible with the latest zadam/trilium version of
|
||||
[v0.63.7](https://github.com/zadam/trilium/releases/tag/v0.63.7). Any later
|
||||
versions of TriliumNext/Trilium have their sync versions incremented which
|
||||
prevents direct migration.
|
||||
|
||||
## Обговоріть це з нами
|
||||
|
||||
@@ -189,8 +189,8 @@ TriliumNext/Trilium](#-installation) як завжди, і він викорис
|
||||
Якщо ваш дистрибутив зазначено в таблиці нижче, використовуйте пакет вашого
|
||||
дистрибутива.
|
||||
|
||||
[](https://repology.org/project/triliumnext/versions)
|
||||
[](https://repology.org/project/triliumnext/versions)
|
||||
|
||||
Ви також можете завантажити бінарний реліз для вашої платформи зі сторінки
|
||||
[останнього релізу](https://github.com/TriliumNext/Trilium/releases/latest),
|
||||
@@ -281,10 +281,10 @@ pnpm run --filter desktop electron-forge:make --arch=x64 --platform=win32
|
||||
|
||||
### Документація розробника
|
||||
|
||||
Будь ласка, перегляньте
|
||||
[документацію](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md)
|
||||
для отримання детальної інформації. Якщо у вас виникнуть додаткові запитання,
|
||||
звертайтеся до нас за посиланнями, описаними в розділі «Обговоріть з нами» вище.
|
||||
Please view the [documentation
|
||||
guide](https://github.com/TriliumNext/Trilium/blob/main/docs/Developer%20Guide/Developer%20Guide/Environment%20Setup.md)
|
||||
for details. If you have more questions, feel free to reach out via the links
|
||||
described in the "Discuss with us" section above.
|
||||
|
||||
## 👏 Привітання
|
||||
|
||||
|
||||
137
pnpm-lock.yaml
generated
137
pnpm-lock.yaml
generated
@@ -156,8 +156,8 @@ importers:
|
||||
apps/build-docs:
|
||||
devDependencies:
|
||||
'@redocly/cli':
|
||||
specifier: 2.20.2
|
||||
version: 2.20.2(@opentelemetry/api@1.9.0)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)
|
||||
specifier: 2.21.0
|
||||
version: 2.21.0(@opentelemetry/api@1.9.0)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)
|
||||
archiver:
|
||||
specifier: 7.0.1
|
||||
version: 7.0.1
|
||||
@@ -755,8 +755,8 @@ importers:
|
||||
specifier: 7.0.2
|
||||
version: 7.0.2
|
||||
https-proxy-agent:
|
||||
specifier: 8.0.0
|
||||
version: 8.0.0
|
||||
specifier: 7.0.6
|
||||
version: 7.0.6
|
||||
i18next:
|
||||
specifier: 25.8.17
|
||||
version: 25.8.17(typescript@5.9.3)
|
||||
@@ -5144,27 +5144,27 @@ packages:
|
||||
'@redocly/ajv@8.18.0':
|
||||
resolution: {integrity: sha512-F+LMD2IDIXuHxgpLJh3nkLj9+tSaEzoUWd+7fONGq5pe2169FUDjpEkOfEpoGLz1sbZni/69p07OsecNfAOpqA==}
|
||||
|
||||
'@redocly/cli@2.20.2':
|
||||
resolution: {integrity: sha512-wfEoGFoXq1vZjd9uEtTd9mIixF5I5Ci1rusK/9HHcS6UGy3o2kuvrSn1daDHl1T3KcG5YFqQoLzzEgcL4Je7KQ==}
|
||||
'@redocly/cli@2.21.0':
|
||||
resolution: {integrity: sha512-cplB+XAB3YBA/BGyRAbBm+LPWPs9O9B86mE8pHHXnzht7TKRoetkKEPaPoTpxbaELg5iNlGrrnIukDv92D1O9A==}
|
||||
engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
'@redocly/config@0.22.2':
|
||||
resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==}
|
||||
|
||||
'@redocly/config@0.44.0':
|
||||
resolution: {integrity: sha512-UHKkWcCNZrGiKBbrQ1CE08ElrOUGm5H97Zn8+wkp80Uu2AT/go5In1sbqvhHxViPYtu1MLdy7qKiifSyOL3W/A==}
|
||||
'@redocly/config@0.44.1':
|
||||
resolution: {integrity: sha512-l6/ZE+/RBfNDdhzltau6cbW8+k5PgJbJBMqaBrlQlZQlmGBHMxqGyDaon4dPLj0jdi37gsMQ3yf95JBY/vaDSg==}
|
||||
|
||||
'@redocly/openapi-core@1.34.5':
|
||||
resolution: {integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==}
|
||||
engines: {node: '>=18.17.0', npm: '>=9.5.0'}
|
||||
|
||||
'@redocly/openapi-core@2.20.2':
|
||||
resolution: {integrity: sha512-L3rzEZWMxq9SpAHP8k9C+/Fqxex4vxXgSZ5hQ+dg7++LXre67ZbT3RkhKsmYadJB/EUdbS6Z+h1Ont5TAI6zyQ==}
|
||||
'@redocly/openapi-core@2.21.0':
|
||||
resolution: {integrity: sha512-f8PwbX61AyKR5rt4mpwB/1v+njPuLh2R5veyRg4GDJh1WPFgb3lR2YY3MzNHQUyUj0VY2OXJ8c/FizG9GaoM+w==}
|
||||
engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'}
|
||||
|
||||
'@redocly/respect-core@2.20.2':
|
||||
resolution: {integrity: sha512-oUCp+H83py0DEq5DJ+XmGPJQW9Yoq/tgml4OGwQrPEt1sC7QF7GjMRCA1HnRS4/dQ9psWGyFj9FwI1fCupQ8uw==}
|
||||
'@redocly/respect-core@2.21.0':
|
||||
resolution: {integrity: sha512-2P/7q9vSv8yaL9u42L57TbUVV0EPW+fYDyApzmynYVDF9jr/NJ2BGPslTl3GUV8KwvWijeVuYRjaJ7HCWJBs9w==}
|
||||
engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'}
|
||||
|
||||
'@replit/codemirror-indentation-markers@6.5.3':
|
||||
@@ -7468,10 +7468,6 @@ packages:
|
||||
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
agent-base@8.0.0:
|
||||
resolution: {integrity: sha512-QT8i0hCz6C/KQ+KTAbSNwCHDGdmUJl2tp2ZpNlGSWCfhUNVbYG2WLE3MdZGBAgXPV4GAvjGMxo+C1hroyxmZEg==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
agentkeepalive@4.6.0:
|
||||
resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
@@ -9809,6 +9805,9 @@ packages:
|
||||
fast-uri@3.1.0:
|
||||
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
|
||||
|
||||
fast-xml-builder@1.1.2:
|
||||
resolution: {integrity: sha512-NJAmiuVaJEjVa7TjLZKlYd7RqmzOC91EtPFXHvlTcqBVo50Qh7XV5IwvXi1c7NRz2Q/majGX9YLcwJtWgHjtkA==}
|
||||
|
||||
fast-xml-parser@4.4.1:
|
||||
resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==}
|
||||
hasBin: true
|
||||
@@ -9817,6 +9816,10 @@ packages:
|
||||
resolution: {integrity: sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==}
|
||||
hasBin: true
|
||||
|
||||
fast-xml-parser@5.5.3:
|
||||
resolution: {integrity: sha512-Ymnuefk6VzAhT3SxLzVUw+nMio/wB1NGypHkgetwtXcK1JfryaHk4DWQFGVwQ9XgzyS5iRZ7C2ZGI4AMsdMZ6A==}
|
||||
hasBin: true
|
||||
|
||||
fastest-levenshtein@1.0.16:
|
||||
resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==}
|
||||
engines: {node: '>= 4.9.1'}
|
||||
@@ -10597,10 +10600,6 @@ packages:
|
||||
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
https-proxy-agent@8.0.0:
|
||||
resolution: {integrity: sha512-YYeW+iCnAS3xhvj2dvVoWgsbca3RfQy/IlaNHHOtDmU0jMqPI9euIq3Y9BJETdxk16h9NHHCKqp/KB9nIMStCQ==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
human-signals@2.1.0:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
@@ -12612,6 +12611,9 @@ packages:
|
||||
openapi-sampler@1.7.0:
|
||||
resolution: {integrity: sha512-fWq32F5vqGpgRJYIarC/9Y1wC9tKnRDcCOjsDJ7MIcSv2HsE7kNifcXIZ8FVtNStBUWxYrEk/MKqVF0SwZ5gog==}
|
||||
|
||||
openapi-sampler@1.7.2:
|
||||
resolution: {integrity: sha512-OKytvqB5XIaTgA9xtw8W8UTar+uymW2xPVpFN0NihMtuHPdPTGxBEhGnfFnJW5g/gOSIvkP+H0Xh3XhVI9/n7g==}
|
||||
|
||||
openapi-types@12.1.3:
|
||||
resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
|
||||
|
||||
@@ -12834,6 +12836,10 @@ packages:
|
||||
resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
path-expression-matcher@1.1.3:
|
||||
resolution: {integrity: sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
path-is-absolute@1.0.1:
|
||||
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -17297,6 +17303,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-core': 47.4.0
|
||||
'@ckeditor/ckeditor5-utils': 47.4.0
|
||||
ckeditor5: 47.4.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-code-block@47.4.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
|
||||
dependencies:
|
||||
@@ -17489,6 +17497,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-utils': 47.4.0
|
||||
ckeditor5: 47.4.0
|
||||
es-toolkit: 1.39.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-editor-classic@47.4.0':
|
||||
dependencies:
|
||||
@@ -18224,6 +18234,8 @@ snapshots:
|
||||
'@ckeditor/ckeditor5-icons': 47.4.0
|
||||
'@ckeditor/ckeditor5-ui': 47.4.0
|
||||
'@ckeditor/ckeditor5-utils': 47.4.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ckeditor/ckeditor5-upload@47.4.0':
|
||||
dependencies:
|
||||
@@ -20854,7 +20866,7 @@ snapshots:
|
||||
|
||||
'@radix-ui/react-collection@1.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
'@radix-ui/react-compose-refs': 1.0.0(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.0.0(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 1.0.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
@@ -20876,7 +20888,7 @@ snapshots:
|
||||
|
||||
'@radix-ui/react-compose-refs@1.0.0(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
react: 19.2.4
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.1(@types/react@19.1.7)(react@19.2.4)':
|
||||
@@ -21286,7 +21298,7 @@ snapshots:
|
||||
|
||||
'@radix-ui/react-slot@1.0.1(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
'@radix-ui/react-compose-refs': 1.0.0(react@19.2.4)
|
||||
react: 19.2.4
|
||||
|
||||
@@ -21327,7 +21339,7 @@ snapshots:
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.0.0(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
react: 19.2.4
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.1.7)(react@19.2.4)':
|
||||
@@ -21386,7 +21398,7 @@ snapshots:
|
||||
|
||||
'@radix-ui/react-use-layout-effect@1.0.0(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
react: 19.2.4
|
||||
|
||||
'@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.1.7)(react@19.2.4)':
|
||||
@@ -21435,7 +21447,7 @@ snapshots:
|
||||
|
||||
'@rc-component/portal@1.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
classnames: 2.5.1
|
||||
rc-util: 5.44.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
@@ -21443,7 +21455,7 @@ snapshots:
|
||||
|
||||
'@rc-component/trigger@2.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
'@rc-component/portal': 1.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
classnames: 2.5.1
|
||||
rc-motion: 2.9.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
@@ -21459,14 +21471,14 @@ snapshots:
|
||||
json-schema-traverse: 1.0.0
|
||||
require-from-string: 2.0.2
|
||||
|
||||
'@redocly/cli@2.20.2(@opentelemetry/api@1.9.0)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)':
|
||||
'@redocly/cli@2.21.0(@opentelemetry/api@1.9.0)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)':
|
||||
dependencies:
|
||||
'@opentelemetry/exporter-trace-otlp-http': 0.202.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/semantic-conventions': 1.34.0
|
||||
'@redocly/openapi-core': 2.20.2
|
||||
'@redocly/respect-core': 2.20.2
|
||||
'@redocly/openapi-core': 2.21.0
|
||||
'@redocly/respect-core': 2.21.0
|
||||
abort-controller: 3.0.0
|
||||
ajv: '@redocly/ajv@8.18.0'
|
||||
ajv-formats: 3.0.1(@redocly/ajv@8.18.0)
|
||||
@@ -21500,7 +21512,7 @@ snapshots:
|
||||
|
||||
'@redocly/config@0.22.2': {}
|
||||
|
||||
'@redocly/config@0.44.0':
|
||||
'@redocly/config@0.44.1':
|
||||
dependencies:
|
||||
json-schema-to-ts: 2.7.2
|
||||
|
||||
@@ -21518,10 +21530,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@redocly/openapi-core@2.20.2':
|
||||
'@redocly/openapi-core@2.21.0':
|
||||
dependencies:
|
||||
'@redocly/ajv': 8.18.0
|
||||
'@redocly/config': 0.44.0
|
||||
'@redocly/config': 0.44.1
|
||||
ajv: '@redocly/ajv@8.18.0'
|
||||
ajv-formats: 3.0.1(@redocly/ajv@8.18.0)
|
||||
colorette: 1.4.0
|
||||
@@ -21531,18 +21543,18 @@ snapshots:
|
||||
pluralize: 8.0.0
|
||||
yaml-ast-parser: 0.0.43
|
||||
|
||||
'@redocly/respect-core@2.20.2':
|
||||
'@redocly/respect-core@2.21.0':
|
||||
dependencies:
|
||||
'@faker-js/faker': 7.6.0
|
||||
'@noble/hashes': 1.8.0
|
||||
'@redocly/ajv': 8.18.0
|
||||
'@redocly/openapi-core': 2.20.2
|
||||
'@redocly/openapi-core': 2.21.0
|
||||
ajv: '@redocly/ajv@8.18.0'
|
||||
better-ajv-errors: 1.2.0(@redocly/ajv@8.18.0)
|
||||
colorette: 2.0.20
|
||||
json-pointer: 0.6.2
|
||||
jsonpath-rfc9535: 1.3.0
|
||||
openapi-sampler: 1.7.0
|
||||
openapi-sampler: 1.7.2
|
||||
outdent: 0.8.0
|
||||
picomatch: 4.0.3
|
||||
|
||||
@@ -25086,8 +25098,6 @@ snapshots:
|
||||
|
||||
agent-base@7.1.4: {}
|
||||
|
||||
agent-base@8.0.0: {}
|
||||
|
||||
agentkeepalive@4.6.0:
|
||||
dependencies:
|
||||
humanize-ms: 1.2.1
|
||||
@@ -27100,7 +27110,7 @@ snapshots:
|
||||
|
||||
dom-helpers@5.2.1:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
csstype: 3.2.3
|
||||
|
||||
dom-serialize@2.2.1:
|
||||
@@ -28167,6 +28177,10 @@ snapshots:
|
||||
|
||||
fast-uri@3.1.0: {}
|
||||
|
||||
fast-xml-builder@1.1.2:
|
||||
dependencies:
|
||||
path-expression-matcher: 1.1.3
|
||||
|
||||
fast-xml-parser@4.4.1:
|
||||
dependencies:
|
||||
strnum: 1.1.2
|
||||
@@ -28175,6 +28189,12 @@ snapshots:
|
||||
dependencies:
|
||||
strnum: 2.1.2
|
||||
|
||||
fast-xml-parser@5.5.3:
|
||||
dependencies:
|
||||
fast-xml-builder: 1.1.2
|
||||
path-expression-matcher: 1.1.3
|
||||
strnum: 2.1.2
|
||||
|
||||
fastest-levenshtein@1.0.16: {}
|
||||
|
||||
fastq@1.19.1:
|
||||
@@ -28645,7 +28665,7 @@ snapshots:
|
||||
|
||||
glob@13.0.6:
|
||||
dependencies:
|
||||
minimatch: 10.2.2
|
||||
minimatch: 10.2.4
|
||||
minipass: 7.1.3
|
||||
path-scurry: 2.0.2
|
||||
|
||||
@@ -29180,14 +29200,7 @@ snapshots:
|
||||
|
||||
https-proxy-agent@7.0.6:
|
||||
dependencies:
|
||||
agent-base: 7.1.4
|
||||
debug: 4.4.3(supports-color@8.1.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
https-proxy-agent@8.0.0:
|
||||
dependencies:
|
||||
agent-base: 8.0.0
|
||||
agent-base: 7.1.3
|
||||
debug: 4.4.3(supports-color@8.1.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -29792,7 +29805,7 @@ snapshots:
|
||||
|
||||
json-schema-to-ts@2.7.2:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
'@types/json-schema': 7.0.15
|
||||
ts-algebra: 1.2.2
|
||||
|
||||
@@ -31544,6 +31557,12 @@ snapshots:
|
||||
fast-xml-parser: 5.3.6
|
||||
json-pointer: 0.6.2
|
||||
|
||||
openapi-sampler@1.7.2:
|
||||
dependencies:
|
||||
'@types/json-schema': 7.0.15
|
||||
fast-xml-parser: 5.5.3
|
||||
json-pointer: 0.6.2
|
||||
|
||||
openapi-types@12.1.3: {}
|
||||
|
||||
opener@1.5.2: {}
|
||||
@@ -31819,6 +31838,8 @@ snapshots:
|
||||
|
||||
path-exists@5.0.0: {}
|
||||
|
||||
path-expression-matcher@1.1.3: {}
|
||||
|
||||
path-is-absolute@1.0.1: {}
|
||||
|
||||
path-key@2.0.1: {}
|
||||
@@ -31991,7 +32012,7 @@ snapshots:
|
||||
|
||||
polished@4.3.1:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
|
||||
portfinder@1.0.36:
|
||||
dependencies:
|
||||
@@ -32392,7 +32413,7 @@ snapshots:
|
||||
|
||||
postcss@8.4.49:
|
||||
dependencies:
|
||||
nanoid: 5.1.5
|
||||
nanoid: 5.1.6
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
@@ -32660,7 +32681,7 @@ snapshots:
|
||||
|
||||
rc-motion@2.9.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
classnames: 2.5.1
|
||||
rc-util: 5.44.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
@@ -32668,7 +32689,7 @@ snapshots:
|
||||
|
||||
rc-overflow@1.5.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
classnames: 2.5.1
|
||||
rc-resize-observer: 1.4.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
rc-util: 5.44.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
@@ -32677,7 +32698,7 @@ snapshots:
|
||||
|
||||
rc-resize-observer@1.4.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
classnames: 2.5.1
|
||||
rc-util: 5.44.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
react: 19.2.4
|
||||
@@ -32686,7 +32707,7 @@ snapshots:
|
||||
|
||||
rc-util@5.44.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
react-is: 18.3.1
|
||||
@@ -32714,7 +32735,7 @@ snapshots:
|
||||
|
||||
react-beautiful-dnd@13.1.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
css-box-model: 1.2.1
|
||||
memoize-one: 5.2.1
|
||||
raf-schd: 4.0.3
|
||||
@@ -32783,7 +32804,7 @@ snapshots:
|
||||
|
||||
react-redux@7.2.9(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
'@types/react-redux': 7.1.34
|
||||
hoist-non-react-statics: 3.3.2
|
||||
loose-envify: 1.4.0
|
||||
@@ -32980,7 +33001,7 @@ snapshots:
|
||||
|
||||
redux@4.2.1:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.4
|
||||
'@babel/runtime': 7.28.6
|
||||
|
||||
reflect.getprototypeof@1.0.10:
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user