@khorsheed/dsh-reader 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.en.md +26 -121
- package/README.i18n.yaml +3 -3
- package/README.md +26 -121
- package/cordis.patch.yml +7 -0
- package/lib/client.js +30580 -6892
- package/lib/index.js +4189 -621
- package/lib/typert.host.js +2265 -319
- package/lib/typert.remote-client.d.ts +99 -3
- package/lib/typert.remote-client.js +1618 -240
- package/lib/types/captions.d.ts +99 -0
- package/lib/types/captions.js +236 -0
- package/lib/types/client/MediaPlayer.d.ts +55 -0
- package/lib/types/client/MediaPlayer.js +106 -0
- package/lib/types/client/NotesView.d.ts +57 -0
- package/lib/types/client/NotesView.js +124 -0
- package/lib/types/client/ReaderPane.d.ts +20 -3
- package/lib/types/client/ReaderPane.js +905 -164
- package/lib/types/client/SpeakerLegend.d.ts +18 -0
- package/lib/types/client/SpeakerLegend.js +26 -0
- package/lib/types/client/canvas-sync.d.ts +84 -0
- package/lib/types/client/canvas-sync.js +59 -0
- package/lib/types/client/contract.d.ts +138 -3
- package/lib/types/client/extract-article.d.ts +38 -1
- package/lib/types/client/extract-article.js +501 -28
- package/lib/types/client/icons.d.ts +1 -1
- package/lib/types/client/icons.js +1 -1
- package/lib/types/client/index.js +82 -2
- package/lib/types/client/locales.d.ts +279 -11
- package/lib/types/client/locales.js +278 -10
- package/lib/types/client/notes.d.ts +142 -0
- package/lib/types/client/notes.js +334 -0
- package/lib/types/client/parse-rss.js +40 -0
- package/lib/types/client/preset-visibility.d.ts +93 -0
- package/lib/types/client/preset-visibility.js +115 -0
- package/lib/types/client/selectors.d.ts +5 -0
- package/lib/types/client/selectors.js +5 -0
- package/lib/types/client/session.d.ts +21 -1
- package/lib/types/client/session.js +12 -0
- package/lib/types/client/store.js +4 -1
- package/lib/types/client/translate.d.ts +26 -1
- package/lib/types/client/translate.js +29 -12
- package/lib/types/client/useLocalTranscript.d.ts +43 -0
- package/lib/types/client/useLocalTranscript.js +129 -0
- package/lib/types/client/useMedia.d.ts +68 -0
- package/lib/types/client/useMedia.js +299 -0
- package/lib/types/client/useMediaStates.d.ts +9 -0
- package/lib/types/client/useMediaStates.js +43 -0
- package/lib/types/client/useNotes.d.ts +110 -0
- package/lib/types/client/useNotes.js +423 -0
- package/lib/types/client/usePolish.d.ts +47 -0
- package/lib/types/client/usePolish.js +162 -0
- package/lib/types/image-fetch.d.ts +81 -0
- package/lib/types/image-fetch.js +385 -0
- package/lib/types/index.d.ts +6 -0
- package/lib/types/index.js +3 -0
- package/lib/types/local-transcribe.d.ts +171 -0
- package/lib/types/local-transcribe.js +473 -0
- package/lib/types/media-host.d.ts +91 -0
- package/lib/types/media-host.js +230 -0
- package/lib/types/media.d.ts +100 -0
- package/lib/types/media.js +207 -0
- package/lib/types/notes-types.d.ts +158 -0
- package/lib/types/notes-types.js +8 -0
- package/lib/types/notes.d.ts +213 -0
- package/lib/types/notes.js +718 -0
- package/lib/types/polish.d.ts +41 -0
- package/lib/types/polish.js +136 -0
- package/lib/types/remote.d.ts +127 -1
- package/lib/types/remote.js +162 -1
- package/lib/types/service.d.ts +100 -1
- package/lib/types/service.js +320 -59
- package/lib/types/skill.d.ts +20 -0
- package/lib/types/skill.js +78 -0
- package/lib/types/store.js +30 -8
- package/lib/types/tool.d.ts +23 -0
- package/lib/types/tool.js +40 -0
- package/lib/types/tools.d.ts +69 -0
- package/lib/types/tools.js +400 -0
- package/lib/types/types.d.ts +106 -0
- package/lib/types/types.js +66 -0
- package/lib/types/xiaoyuzhou.d.ts +43 -0
- package/lib/types/xiaoyuzhou.js +134 -0
- package/lib/types/ytdlp.d.ts +111 -0
- package/lib/types/ytdlp.js +269 -0
- package/package.json +29 -20
- package/lib/invariant.js +0 -38
- package/lib/types/invariant.d.ts +0 -29
- package/lib/types/invariant.js +0 -38
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The notes face of the detail view: the article's excerpts with their
|
|
4
|
+
* comments, the conversations that discussed it, and the sheet a new note is
|
|
5
|
+
* written in.
|
|
6
|
+
*
|
|
7
|
+
* One layout for every width (docs/panel-design.md): the list is a single
|
|
8
|
+
* column that only tightens under the panel's compact container, and the
|
|
9
|
+
* sheet rises from the bottom of the pane on a desk and on a phone alike.
|
|
10
|
+
*
|
|
11
|
+
* @module @khorsheed/dsh-reader/client/NotesView
|
|
12
|
+
*/
|
|
13
|
+
import { useState } from 'react';
|
|
14
|
+
import css from './NotesView.module.css';
|
|
15
|
+
import { formatClock } from "../captions.js";
|
|
16
|
+
/** True when the keystroke is ⌘⏎ / Ctrl+⏎ outside an IME composition. */
|
|
17
|
+
function isSubmit(event) {
|
|
18
|
+
return event.key === 'Enter' && (event.metaKey || event.ctrlKey) && !event.nativeEvent.isComposing;
|
|
19
|
+
}
|
|
20
|
+
/** A one-field inline editor (a reply being added or rewritten). */
|
|
21
|
+
function LineEditor({ t, initial, onSave, onCancel }) {
|
|
22
|
+
const [draft, setDraft] = useState(initial);
|
|
23
|
+
const save = async () => { if (draft.trim() !== '' && await onSave(draft))
|
|
24
|
+
onCancel(); };
|
|
25
|
+
return (_jsxs("div", { className: css.editor, children: [_jsx("textarea", { autoFocus: true, className: css.area, value: draft, placeholder: t('notes.replyPlaceholder'), onChange: event => { setDraft(event.target.value); }, onKeyDown: event => {
|
|
26
|
+
if (isSubmit(event)) {
|
|
27
|
+
event.preventDefault();
|
|
28
|
+
void save();
|
|
29
|
+
}
|
|
30
|
+
if (event.key === 'Escape' && !event.nativeEvent.isComposing)
|
|
31
|
+
onCancel();
|
|
32
|
+
} }), _jsxs("div", { className: css.row, children: [_jsx("span", { className: css.spacer }), _jsx("button", { type: "button", className: css.ghost, onClick: onCancel, children: t('action.cancel') }), _jsx("button", { type: "button", className: css.primary, onClick: () => { void save(); }, children: t('notes.save') })] })] }));
|
|
33
|
+
}
|
|
34
|
+
/** 「已在《…》」: where an item went last, when it was synced. */
|
|
35
|
+
function SyncedMark({ t, synced }) {
|
|
36
|
+
const last = synced?.[synced.length - 1];
|
|
37
|
+
return last === undefined ? null : _jsx("span", { className: css.synced, children: t('notes.syncedIn', { canvas: last.canvasTitle }) });
|
|
38
|
+
}
|
|
39
|
+
/** Who wrote a line under a note, as the thread labels it. */
|
|
40
|
+
function replyAuthor(t, reply) {
|
|
41
|
+
if (reply.by === 'agent')
|
|
42
|
+
return t('notes.replyAgent');
|
|
43
|
+
return reply.editedFrom === 'agent' ? t('notes.replyEdited') : t('notes.replyUser');
|
|
44
|
+
}
|
|
45
|
+
/** One note row: excerpt, comment, the conclusions under it, and the gestures on it. */
|
|
46
|
+
function NoteRow({ t, note, missing, selecting, selected, canOpenSession, onToggle, onJump, onEdit, onDelete, onAddReply, onEditReply, onOpenSession, onSync, onPlay }) {
|
|
47
|
+
const [adding, setAdding] = useState(false);
|
|
48
|
+
const [rewriting, setRewriting] = useState(null);
|
|
49
|
+
const replies = note.replies ?? [];
|
|
50
|
+
const [editing, setEditing] = useState(false);
|
|
51
|
+
const [draft, setDraft] = useState(note.comment ?? '');
|
|
52
|
+
const [confirming, setConfirming] = useState(false);
|
|
53
|
+
const save = async () => {
|
|
54
|
+
if (await onEdit(draft))
|
|
55
|
+
setEditing(false);
|
|
56
|
+
};
|
|
57
|
+
return (_jsxs("div", { className: css.note, "data-selected": selected ? 'true' : undefined, children: [_jsxs("div", { className: css.noteHead, children: [selecting && (_jsx("input", { type: "checkbox", className: css.check, checked: selected, "aria-label": note.anchor.quote, onChange: onToggle })), _jsx("button", { type: "button", className: css.excerpt, onClick: selecting ? onToggle : onJump, title: missing ? t('notes.notFound') : undefined, children: note.anchor.quote })] }), note.anchor.translated !== undefined && (_jsxs("div", { className: css.translated, children: [_jsx("span", { className: css.label, children: t('notes.inTranslation') }), note.anchor.translated] })), editing
|
|
58
|
+
? (_jsxs("div", { className: css.editor, children: [_jsx("textarea", { autoFocus: true, className: css.area, value: draft, placeholder: t('notes.commentPlaceholder'), onChange: event => { setDraft(event.target.value); }, onKeyDown: event => {
|
|
59
|
+
if (isSubmit(event)) {
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
void save();
|
|
62
|
+
}
|
|
63
|
+
if (event.key === 'Escape' && !event.nativeEvent.isComposing) {
|
|
64
|
+
setEditing(false);
|
|
65
|
+
setDraft(note.comment ?? '');
|
|
66
|
+
}
|
|
67
|
+
} }), _jsxs("div", { className: css.row, children: [_jsx("span", { className: css.spacer }), _jsx("button", { type: "button", className: css.ghost, onClick: () => { setEditing(false); setDraft(note.comment ?? ''); }, children: t('action.cancel') }), _jsx("button", { type: "button", className: css.primary, onClick: () => { void save(); }, children: t('notes.save') })] })] }))
|
|
68
|
+
: note.comment !== undefined && _jsx("div", { className: css.comment, children: note.comment }), !selecting && (replies.length > 0 || adding) && (_jsxs("div", { className: css.thread, children: [replies.map((reply, index) => rewriting === index
|
|
69
|
+
? (_jsx(LineEditor, { t: t, initial: reply.text, onSave: text => onEditReply(index, text), onCancel: () => { setRewriting(null); } }, `${reply.at}-${String(index)}`))
|
|
70
|
+
: (_jsxs("div", { className: css.reply, children: [_jsxs("div", { className: css.replyWho, "data-agent": reply.by === 'agent' ? 'true' : undefined, children: [_jsx("span", { children: replyAuthor(t, reply) }), reply.sessionId !== undefined && canOpenSession && (_jsxs("button", { type: "button", className: css.link, onClick: () => { onOpenSession(reply.sessionId); }, children: ["\u2197 ", t('notes.backToSession')] })), _jsx("span", { className: css.spacer }), _jsx("button", { type: "button", className: css.link, onClick: () => { setRewriting(index); }, children: t('notes.replyEdit') }), _jsx("button", { type: "button", className: css.link, onClick: () => { void onEditReply(index, ''); }, children: t('notes.replyRemove') })] }), _jsx("div", { className: css.replyText, children: reply.text })] }, `${reply.at}-${String(index)}`))), adding && _jsx(LineEditor, { t: t, initial: "", onSave: onAddReply, onCancel: () => { setAdding(false); } })] })), _jsxs("div", { className: css.foot, children: [note.anchor.time !== undefined && onPlay !== undefined && (_jsxs("button", { type: "button", className: css.play, onClick: onPlay, children: ["\u25B6 ", formatClock(note.anchor.time)] })), _jsxs("span", { children: ["\u00B6", note.anchor.block + 1] }), missing && _jsx("span", { className: css.missing, children: t('notes.notFound') }), _jsx(SyncedMark, { t: t, synced: note.syncedTo }), _jsx("span", { className: css.spacer }), !selecting && !editing && (confirming
|
|
71
|
+
? (_jsxs(_Fragment, { children: [_jsx("span", { children: t('notes.deleteConfirm') }), _jsx("button", { type: "button", className: css.link, onClick: () => { setConfirming(false); }, children: t('action.cancel') }), _jsx("button", { type: "button", className: `${css.link} ${css.danger}`, onClick: onDelete, children: t('notes.delete') })] }))
|
|
72
|
+
: (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.link, onClick: () => { setAdding(true); }, children: t('notes.replyAdd') }), onSync !== undefined && _jsx("button", { type: "button", className: css.link, onClick: onSync, children: t('notes.sync') }), _jsx("button", { type: "button", className: css.link, onClick: () => { setDraft(note.comment ?? ''); setEditing(true); }, children: t('notes.edit') }), _jsx("button", { type: "button", className: css.link, onClick: () => { setConfirming(true); }, children: t('notes.delete') })] })))] })] }));
|
|
73
|
+
}
|
|
74
|
+
/** One discussion note: what a conversation (or a quiz-me round) came to. */
|
|
75
|
+
function DiscussionCard({ t, discussion, canOpenSession, onOpenSession, onJumpPara, onDelete, onSync }) {
|
|
76
|
+
const [confirming, setConfirming] = useState(false);
|
|
77
|
+
const check = discussion.kind === 'check';
|
|
78
|
+
return (_jsxs("div", { className: css.discussion, children: [_jsxs("div", { className: css.discussionHead, children: [_jsx("span", { className: css.kind, "data-kind": discussion.kind, children: check ? t('notes.kindCheck') : t('notes.kindDiscussion') }), _jsx("b", { className: css.discussionTitle, children: discussion.title !== '' ? discussion.title : discussion.createdAt.slice(0, 10) }), discussion.sessionId !== undefined && canOpenSession && (_jsxs("button", { type: "button", className: css.link, onClick: () => { onOpenSession(discussion.sessionId); }, children: [t('notes.backToSession'), " \u2197"] }))] }), _jsx("div", { className: css.pointsLabel, "data-kind": discussion.kind, children: check ? t('notes.pointsCheck') : t('notes.pointsDiscussion') }), _jsx("ul", { className: css.points, children: discussion.points.map((point, index) => (_jsxs("li", { children: [point.text, point.para !== undefined && (_jsxs(_Fragment, { children: [' ', _jsxs("button", { type: "button", className: css.link, onClick: () => { onJumpPara(point.para); }, children: ["\u00B6", point.para, " \u2197"] })] }))] }, String(index)))) }), discussion.aside !== undefined && _jsx("div", { className: css.aside, children: discussion.aside }), _jsxs("div", { className: css.foot, children: [_jsx("span", { children: discussion.title !== '' ? discussion.createdAt.slice(0, 10) : '' }), _jsx(SyncedMark, { t: t, synced: discussion.syncedTo }), _jsx("span", { className: css.spacer }), !confirming && onSync !== undefined && _jsx("button", { type: "button", className: css.link, onClick: onSync, children: t('notes.sync') }), confirming
|
|
79
|
+
? (_jsxs(_Fragment, { children: [_jsx("span", { children: t('notes.deleteConfirm') }), _jsx("button", { type: "button", className: css.link, onClick: () => { setConfirming(false); }, children: t('action.cancel') }), _jsx("button", { type: "button", className: `${css.link} ${css.danger}`, onClick: onDelete, children: t('notes.delete') })] }))
|
|
80
|
+
: _jsx("button", { type: "button", className: css.link, onClick: () => { setConfirming(true); }, children: t('notes.delete') })] })] }));
|
|
81
|
+
}
|
|
82
|
+
/** The article's notes, and the conversations that discussed it. */
|
|
83
|
+
export function NotesView(props) {
|
|
84
|
+
const { t, entry } = props;
|
|
85
|
+
const [selecting, setSelecting] = useState(false);
|
|
86
|
+
const [selected, setSelected] = useState(new Set());
|
|
87
|
+
const notes = entry?.notes ?? [];
|
|
88
|
+
const sessions = entry?.sessions ?? [];
|
|
89
|
+
const discussions = entry?.discussions ?? [];
|
|
90
|
+
const toggle = (id) => {
|
|
91
|
+
setSelected(current => {
|
|
92
|
+
const next = new Set(current);
|
|
93
|
+
if (next.has(id))
|
|
94
|
+
next.delete(id);
|
|
95
|
+
else
|
|
96
|
+
next.add(id);
|
|
97
|
+
return next;
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
const endSelecting = () => { setSelecting(false); setSelected(new Set()); };
|
|
101
|
+
return (_jsxs("div", { className: css.view, children: [_jsxs("div", { className: css.body, children: [_jsxs("div", { className: css.section, children: [_jsx("span", { children: t('notes.excerpts') }), _jsx("span", { className: css.spacer }), notes.length > 0 && (_jsx("button", { type: "button", className: css.link, onClick: () => { if (selecting)
|
|
102
|
+
endSelecting();
|
|
103
|
+
else
|
|
104
|
+
setSelecting(true); }, children: selecting ? t('notes.selectDone') : t('notes.select') }))] }), notes.length === 0 && _jsx("p", { className: css.empty, children: t('notes.empty') }), notes.map(note => (_jsx(NoteRow, { t: t, note: note, missing: props.missing.has(note.id), selecting: selecting, selected: selected.has(note.id), onToggle: () => { toggle(note.id); }, onJump: () => { props.onJump(note); }, onEdit: comment => props.onEdit(note, comment), onDelete: () => { props.onDelete(note); }, canOpenSession: props.canOpenSession, onAddReply: text => props.onAddReply(note, text), onEditReply: (index, text) => props.onEditReply(note, index, text), onOpenSession: props.onOpenSession, onSync: props.sync.available ? () => { props.sync.open({ kind: 'notes', ids: [note.id] }); } : undefined, onPlay: props.onPlay === undefined ? undefined : () => { props.onPlay?.(note); } }, note.id))), _jsx("div", { className: css.section, children: _jsx("span", { children: t('notes.discussionNotes') }) }), discussions.length === 0 && _jsx("p", { className: css.empty, children: t('notes.noDiscussionNotes') }), discussions.map(discussion => (_jsx(DiscussionCard, { t: t, discussion: discussion, canOpenSession: props.canOpenSession, onOpenSession: props.onOpenSession, onJumpPara: props.onJumpPara, onDelete: () => { props.onDeleteDiscussion(discussion); }, onSync: props.sync.available ? () => { props.sync.open({ kind: 'discussion', id: discussion.id }); } : undefined }, discussion.id))), _jsx("div", { className: css.section, children: _jsx("span", { children: t('notes.discussions') }) }), sessions.length === 0 && _jsx("p", { className: css.empty, children: t('notes.noDiscussions') }), sessions.map(session => (_jsxs("div", { className: css.session, children: [_jsx("span", { className: css.sessionTitle, children: props.sessionTitle(session.sessionId) ?? session.title ?? t('notes.untitledSession') }), props.canOpenSession && (_jsxs("button", { type: "button", className: css.link, onClick: () => { props.onOpenSession(session.sessionId); }, children: [t('notes.discussedIn'), " \u2197"] }))] }, session.sessionId)))] }), props.sync.done !== null && (_jsxs("div", { className: css.syncDone, children: [_jsx("span", { children: t('notes.syncDone', { canvas: props.sync.done.title }) }), _jsx("button", { type: "button", className: css.link, onClick: props.sync.openDone, children: t('notes.syncOpen') })] })), props.sync.target !== null && (_jsx(SyncSheet, { t: t, sync: props.sync, count: props.sync.target.kind === 'notes' ? props.sync.target.ids.length : 1, hasReplies: props.sync.target.kind === 'notes'
|
|
105
|
+
&& notes.some(note => props.sync.target.ids.includes(note.id) && (note.replies ?? []).length > 0) })), _jsx("div", { className: css.bar, children: selecting
|
|
106
|
+
? (_jsxs(_Fragment, { children: [_jsx("span", { children: t('notes.selected', { count: selected.size }) }), _jsx("span", { className: css.spacer }), props.sync.available && (_jsx("button", { type: "button", className: css.secondary, disabled: selected.size === 0, onClick: () => { props.sync.open({ kind: 'notes', ids: [...selected] }); endSelecting(); }, children: t('notes.sync') })), _jsx("button", { type: "button", className: css.primary, disabled: selected.size === 0, onClick: () => { props.onDiscuss([...selected]); endSelecting(); }, children: t('notes.discuss') })] }))
|
|
107
|
+
: (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: `${css.secondary} ${css.wide}`, disabled: notes.length === 0, onClick: () => { props.onDiscuss([]); }, children: t('notes.discussAll') }), _jsx("button", { type: "button", className: `${css.primary} ${css.wide}`, onClick: props.onCheck, children: t('notes.check') })] })) })] }));
|
|
108
|
+
}
|
|
109
|
+
/** The 「同步到画布」 sheet: which canvas, and whether the conclusions come along. */
|
|
110
|
+
function SyncSheet({ t, sync, count, hasReplies }) {
|
|
111
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: css.scrim, onClick: sync.cancel }), _jsxs("div", { className: css.sheet, role: "dialog", "aria-label": t('notes.sync'), children: [_jsx("div", { className: css.grab }), _jsx("div", { className: css.sheetHead, children: _jsx("b", { children: t('notes.syncTitle', { count }) }) }), sync.canvases === null && _jsx("p", { className: css.empty, children: "\u2026" }), sync.canvases !== null && sync.canvases.length === 0 && _jsx("p", { className: css.empty, children: t('notes.syncNoCanvas') }), sync.canvases !== null && sync.canvases.length > 0 && (_jsx("div", { className: css.picker, role: "radiogroup", "aria-label": t('notes.sync'), children: sync.canvases.map(canvas => (_jsxs("button", { type: "button", role: "radio", "aria-checked": sync.pick === canvas.id, className: css.option, onClick: () => { sync.setPick(canvas.id); }, children: [_jsx("span", { className: css.optionTitle, children: canvas.title }), _jsxs("span", { className: css.optionMeta, children: [canvas.id === sync.last ? `${t('notes.syncLast')} · ` : '', t('notes.syncCards', { count: canvas.cardCount })] })] }, canvas.id))) })), sync.target?.kind === 'notes' && hasReplies && (_jsxs("label", { className: css.checkRow, children: [_jsx("input", { type: "checkbox", checked: sync.withReplies, onChange: event => { sync.setWithReplies(event.target.checked); } }), t('notes.syncReplies')] })), sync.kinds !== null && (_jsxs("div", { className: css.kinds, children: [_jsx("span", { className: css.hint, children: t('notes.syncKind') }), _jsx("select", { className: css.select, value: sync.kind ?? '', onChange: event => { sync.setKind(event.target.value); }, children: sync.kinds.map(item => _jsx("option", { value: item.id, children: item.label }, item.id)) })] })), _jsxs("div", { className: css.row, children: [sync.error !== null && _jsx("span", { className: css.failed, children: t('notes.syncFailed', { reason: sync.error }) }), _jsx("span", { className: css.spacer }), _jsx("button", { type: "button", className: css.ghost, onClick: sync.cancel, children: t('action.cancel') }), _jsx("button", { type: "button", className: css.primary, disabled: sync.busy || sync.pick === null, onClick: sync.run, children: t('notes.syncRun') })] })] })] }));
|
|
112
|
+
}
|
|
113
|
+
/** The sheet a new note is written in. */
|
|
114
|
+
export function NoteSheet({ t, selection, saving, failed, onSave, onCancel }) {
|
|
115
|
+
const [comment, setComment] = useState('');
|
|
116
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: css.scrim, onClick: onCancel }), _jsxs("div", { className: css.sheet, role: "dialog", "aria-label": t('notes.sheetTitle'), children: [_jsx("div", { className: css.grab }), _jsxs("div", { className: css.sheetHead, children: [_jsx("b", { children: t('notes.sheetTitle') }), _jsx("span", { className: css.chip, children: t('notes.excerpt') })] }), _jsx("div", { className: css.sheetExcerpt, children: selection.translated ?? selection.quote }), _jsx("textarea", { autoFocus: true, className: css.area, value: comment, placeholder: t('notes.commentPlaceholder'), onChange: event => { setComment(event.target.value); }, onKeyDown: event => {
|
|
117
|
+
if (isSubmit(event)) {
|
|
118
|
+
event.preventDefault();
|
|
119
|
+
onSave(comment);
|
|
120
|
+
}
|
|
121
|
+
if (event.key === 'Escape' && !event.nativeEvent.isComposing)
|
|
122
|
+
onCancel();
|
|
123
|
+
} }), _jsxs("div", { className: css.row, children: [_jsx("span", { className: failed ? css.failed : css.hint, children: failed ? t('notes.saveFailed') : t('notes.commentOptional') }), _jsx("span", { className: css.spacer }), _jsx("button", { type: "button", className: css.ghost, onClick: onCancel, children: t('action.cancel') }), _jsxs("button", { type: "button", className: css.primary, disabled: saving, onClick: () => { onSave(comment); }, children: [t('notes.save'), " \u2318\u23CE"] })] })] })] }));
|
|
124
|
+
}
|
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
* round trip.
|
|
11
11
|
* - **the detail view** — clicking a card opens it. This is the whole point:
|
|
12
12
|
* the body renders as DOM text, which is what makes passage quoting work
|
|
13
|
-
* (the QUOTE plugin's own selection menu sees it)
|
|
14
|
-
*
|
|
13
|
+
* (the QUOTE plugin's own selection menu sees it). The one non-text island:
|
|
14
|
+
* an article's content embeds survive as sandboxed iframes (opaque origin,
|
|
15
|
+
* no `allow-same-origin`) or controls-only media elements — neither can see
|
|
16
|
+
* this page, and neither carries quotable text.
|
|
15
17
|
* - **the add dialog** — an overlay in the members-tab idiom, because adding is
|
|
16
18
|
* a momentary act and the wall should stay where it was behind it. The host
|
|
17
19
|
* decides whether a pasted URL is a feed or an article from what comes back
|
|
@@ -30,6 +32,17 @@
|
|
|
30
32
|
import { type ReactNode } from 'react';
|
|
31
33
|
import type { ReaderPaneProps } from './contract.ts';
|
|
32
34
|
import { type ReaderReadingAnchor } from './session.ts';
|
|
35
|
+
/**
|
|
36
|
+
* Whether a fetch failure is one a real browser gets past: a bot challenge,
|
|
37
|
+
* a 401/403 that refuses non-browser requests, or a cookie handshake
|
|
38
|
+
* (Springer Nature's idp bounce). These are the reasons the
|
|
39
|
+
* rendered fetch (the capture package's headless Chrome) exists to answer; a
|
|
40
|
+
* login wall is not among them — a cookie-less browser meets it too.
|
|
41
|
+
*
|
|
42
|
+
* @param reason - the host's recorded message.
|
|
43
|
+
* @returns true when escalating to the rendered fetch is worth a try.
|
|
44
|
+
*/
|
|
45
|
+
export declare function browserSolvable(reason: string): boolean;
|
|
33
46
|
/**
|
|
34
47
|
* One top-level block, measured.
|
|
35
48
|
*
|
|
@@ -66,6 +79,10 @@ export interface ArticleBlockMetrics {
|
|
|
66
79
|
* @returns pixels into the block.
|
|
67
80
|
*/
|
|
68
81
|
export declare function offsetInBlock(block: ArticleBlockMetrics, anchor: ReaderReadingAnchor): number;
|
|
69
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* The reader tab body, inside the shared panel root: the `dsh-panel` size
|
|
84
|
+
* container its compact rules query, and the compact flag the wall's and the
|
|
85
|
+
* detail's headers read (panel-responsive-spec P2).
|
|
86
|
+
*/
|
|
70
87
|
export declare function ReaderPane(props: ReaderPaneProps): ReactNode;
|
|
71
88
|
//# sourceMappingURL=ReaderPane.d.ts.map
|