@pallasoft/ps-file-components 2.3.0 → 2.4.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/dist/components/ps-file-icon-button.js +1 -1
- package/dist/components/ps-file-list.js +1 -1
- package/dist/components/ps-file-picker.js +1 -1
- package/dist/components/ps-file-search-bar.d.ts +10 -0
- package/dist/components/ps-file-search-bar.js +25 -0
- package/dist/components/ps-file-viewer-content.js +9 -0
- package/dist/components/ps-file-viewer-pane.d.ts +1 -1
- package/dist/components/ps-file-viewer-pane.js +26 -2
- package/dist/components/ps-file-viewer.js +11 -23
- package/dist/components/ps-html-document.d.ts +8 -0
- package/dist/components/ps-html-document.js +23 -0
- package/dist/components/ps-html-viewer.d.ts +2 -0
- package/dist/components/ps-html-viewer.js +17 -0
- package/dist/components/ps-image-text-highlights.d.ts +9 -0
- package/dist/components/ps-image-text-highlights.js +31 -0
- package/dist/components/ps-image-viewer.d.ts +1 -1
- package/dist/components/ps-image-viewer.js +11 -2
- package/dist/components/ps-pdf-page.js +1 -1
- package/dist/components/ps-pdf-viewer.js +6 -2
- package/dist/components/ps-sheet-viewer.d.ts +2 -0
- package/dist/components/ps-sheet-viewer.js +26 -0
- package/dist/components/ps-table-view.d.ts +7 -0
- package/dist/components/ps-table-view.js +17 -0
- package/dist/components/ps-text-viewer.js +7 -14
- package/dist/components/ps-upload-row.js +1 -1
- package/dist/components/ps-word-viewer.d.ts +2 -0
- package/dist/components/ps-word-viewer.js +17 -0
- package/dist/constants/ps-file-kind-icons.js +3 -0
- package/dist/constants/ps-file-kind.d.ts +3 -0
- package/dist/constants/ps-file-kind.js +3 -0
- package/dist/constants/ps-file-type-config.d.ts +6 -1
- package/dist/constants/ps-file-type-config.js +16 -2
- package/dist/constants/ps-file-viewer-config.d.ts +1 -0
- package/dist/constants/ps-file-viewer-config.js +1 -0
- package/dist/constants/ps-file-viewer-kinds.d.ts +2 -2
- package/dist/constants/ps-file-viewer-kinds.js +17 -2
- package/dist/constants/ps-file-viewer-labels.d.ts +3 -0
- package/dist/constants/ps-file-viewer-labels.js +3 -0
- package/dist/constants/ps-html-sanitize-config.d.ts +8 -0
- package/dist/constants/ps-html-sanitize-config.js +26 -0
- package/dist/esm/components/ps-file-icon-button.js +1 -1
- package/dist/esm/components/ps-file-list.js +1 -1
- package/dist/esm/components/ps-file-picker.js +1 -1
- package/dist/esm/components/ps-file-search-bar.js +21 -0
- package/dist/esm/components/ps-file-viewer-content.js +9 -0
- package/dist/esm/components/ps-file-viewer-pane.js +27 -3
- package/dist/esm/components/ps-file-viewer.js +13 -25
- package/dist/esm/components/ps-html-document.js +19 -0
- package/dist/esm/components/ps-html-viewer.js +13 -0
- package/dist/esm/components/ps-image-text-highlights.js +27 -0
- package/dist/esm/components/ps-image-viewer.js +13 -4
- package/dist/esm/components/ps-pdf-page.js +1 -1
- package/dist/esm/components/ps-pdf-viewer.js +6 -2
- package/dist/esm/components/ps-sheet-viewer.js +22 -0
- package/dist/esm/components/ps-table-view.js +13 -0
- package/dist/esm/components/ps-text-viewer.js +7 -14
- package/dist/esm/components/ps-upload-row.js +1 -1
- package/dist/esm/components/ps-word-viewer.js +13 -0
- package/dist/esm/constants/ps-file-kind-icons.js +3 -0
- package/dist/esm/constants/ps-file-kind.js +3 -0
- package/dist/esm/constants/ps-file-type-config.js +16 -2
- package/dist/esm/constants/ps-file-viewer-config.js +1 -0
- package/dist/esm/constants/ps-file-viewer-kinds.js +17 -2
- package/dist/esm/constants/ps-file-viewer-labels.js +3 -0
- package/dist/esm/constants/ps-html-sanitize-config.js +23 -0
- package/dist/esm/hooks/use-ps-file-search.js +37 -0
- package/dist/esm/hooks/use-ps-file-text.js +4 -19
- package/dist/esm/hooks/use-ps-find-shortcut.js +23 -0
- package/dist/esm/hooks/use-ps-loaded-file.js +19 -0
- package/dist/esm/index.js +33 -0
- package/dist/esm/models/ps-file-search-state.js +1 -0
- package/dist/esm/models/ps-html-highlight-result.js +1 -0
- package/dist/esm/models/ps-html-text-node.js +1 -0
- package/dist/esm/models/ps-image-match.js +1 -0
- package/dist/esm/models/ps-image-text-box.js +1 -0
- package/dist/esm/models/ps-image-text-layer.js +1 -0
- package/dist/esm/models/ps-image-text-word.js +1 -0
- package/dist/esm/models/ps-item-match-part.js +1 -0
- package/dist/esm/models/ps-item-match.js +1 -0
- package/dist/esm/models/ps-loaded-file-state.js +1 -0
- package/dist/esm/models/ps-normalized-text.js +1 -0
- package/dist/esm/models/ps-pdf-text-item.js +1 -0
- package/dist/esm/models/ps-pdf-viewport-like.js +1 -0
- package/dist/esm/models/ps-search-item.js +1 -0
- package/dist/esm/models/ps-sheet-data.js +1 -0
- package/dist/esm/services/ps-file-fetch-service.js +14 -0
- package/dist/esm/services/ps-file-search-service.js +15 -0
- package/dist/esm/services/ps-file-type-service.js +12 -1
- package/dist/esm/services/ps-html-file-service.js +7 -0
- package/dist/esm/services/ps-html-service.js +83 -0
- package/dist/esm/services/ps-image-text-service.js +6 -0
- package/dist/esm/services/ps-label-service.js +5 -0
- package/dist/esm/services/ps-pdf-service.js +39 -16
- package/dist/esm/services/ps-sheet-service.js +23 -0
- package/dist/esm/services/ps-text-search-service.js +21 -26
- package/dist/esm/services/ps-word-service.js +15 -0
- package/dist/hooks/use-ps-file-search.d.ts +2 -0
- package/dist/hooks/use-ps-file-search.js +41 -0
- package/dist/hooks/use-ps-file-text.js +4 -19
- package/dist/hooks/use-ps-find-shortcut.d.ts +2 -0
- package/dist/hooks/use-ps-find-shortcut.js +27 -0
- package/dist/hooks/use-ps-loaded-file.d.ts +2 -0
- package/dist/hooks/use-ps-loaded-file.js +23 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +33 -0
- package/dist/models/ps-file-search-state.d.ts +13 -0
- package/dist/models/ps-file-search-state.js +2 -0
- package/dist/models/ps-file-viewer-pane-props.d.ts +3 -1
- package/dist/models/ps-html-highlight-result.d.ts +4 -0
- package/dist/models/ps-html-highlight-result.js +2 -0
- package/dist/models/ps-html-text-node.d.ts +4 -0
- package/dist/models/ps-html-text-node.js +2 -0
- package/dist/models/ps-image-match.d.ts +7 -0
- package/dist/models/ps-image-match.js +2 -0
- package/dist/models/ps-image-text-box.d.ts +2 -0
- package/dist/models/ps-image-text-box.js +2 -0
- package/dist/models/ps-image-text-layer.d.ts +2 -0
- package/dist/models/ps-image-text-layer.js +2 -0
- package/dist/models/ps-image-text-word.d.ts +2 -0
- package/dist/models/ps-image-text-word.js +2 -0
- package/dist/models/ps-item-match-part.d.ts +2 -0
- package/dist/models/ps-item-match-part.js +2 -0
- package/dist/models/ps-item-match.d.ts +2 -0
- package/dist/models/ps-item-match.js +2 -0
- package/dist/models/ps-loaded-file-state.d.ts +4 -0
- package/dist/models/ps-loaded-file-state.js +2 -0
- package/dist/models/ps-normalized-text.d.ts +2 -0
- package/dist/models/ps-normalized-text.js +2 -0
- package/dist/models/ps-pdf-match.d.ts +1 -0
- package/dist/models/ps-pdf-text-item.d.ts +7 -0
- package/dist/models/ps-pdf-text-item.js +2 -0
- package/dist/models/ps-pdf-viewport-like.d.ts +5 -0
- package/dist/models/ps-pdf-viewport-like.js +2 -0
- package/dist/models/ps-search-item.d.ts +2 -0
- package/dist/models/ps-search-item.js +2 -0
- package/dist/models/ps-sheet-data.d.ts +4 -0
- package/dist/models/ps-sheet-data.js +2 -0
- package/dist/models/ps-text-match.d.ts +2 -4
- package/dist/models/ps-text-segment.d.ts +2 -5
- package/dist/models/ps-viewer-file.d.ts +2 -0
- package/dist/services/ps-file-fetch-service.d.ts +5 -0
- package/dist/services/ps-file-fetch-service.js +18 -0
- package/dist/services/ps-file-search-service.d.ts +5 -0
- package/dist/services/ps-file-search-service.js +19 -0
- package/dist/services/ps-file-type-service.js +12 -1
- package/dist/services/ps-html-file-service.d.ts +3 -0
- package/dist/services/ps-html-file-service.js +11 -0
- package/dist/services/ps-html-service.d.ts +7 -0
- package/dist/services/ps-html-service.js +120 -0
- package/dist/services/ps-image-text-service.d.ts +5 -0
- package/dist/services/ps-image-text-service.js +10 -0
- package/dist/services/ps-label-service.d.ts +3 -0
- package/dist/services/ps-label-service.js +9 -0
- package/dist/services/ps-pdf-service.d.ts +3 -0
- package/dist/services/ps-pdf-service.js +39 -16
- package/dist/services/ps-sheet-service.d.ts +5 -0
- package/dist/services/ps-sheet-service.js +60 -0
- package/dist/services/ps-text-search-service.d.ts +7 -0
- package/dist/services/ps-text-search-service.js +21 -26
- package/dist/services/ps-word-service.d.ts +3 -0
- package/dist/services/ps-word-service.js +52 -0
- package/package.json +6 -3
|
@@ -1,14 +1,23 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef, useState } from 'react';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { usePsDragPan } from '../hooks/use-ps-drag-pan';
|
|
4
|
+
import { PsImageTextService } from '../services/ps-image-text-service';
|
|
4
5
|
import { PsPanService } from '../services/ps-pan-service';
|
|
5
|
-
|
|
6
|
+
import { PsImageTextHighlights } from './ps-image-text-highlights';
|
|
7
|
+
export const PsImageViewer = ({ activeMatch, file, labels, onMatchCount, query, rotation, zoom, }) => {
|
|
6
8
|
const [offset, setOffset] = useState({ x: 0, y: 0 });
|
|
7
9
|
const [hasFailed, setHasFailed] = useState(false);
|
|
8
10
|
const pan = useRef(null);
|
|
9
11
|
const base = useRef({ x: 0, y: 0 });
|
|
10
12
|
const frame = useRef(0);
|
|
11
13
|
const pending = useRef({ x: 0, y: 0 });
|
|
14
|
+
const image = useRef(null);
|
|
15
|
+
const layer = file.textLayer;
|
|
16
|
+
const matches = useMemo(() => (layer === undefined ? [] : PsImageTextService.matches(layer, query)), [layer, query]);
|
|
17
|
+
const count = matches.length === 0 ? 0 : matches[matches.length - 1].matchIndex + 1;
|
|
18
|
+
const activeIndex = count === 0 ? -1 : activeMatch % count;
|
|
19
|
+
const imageTransform = `scale(${zoom}) rotate(${rotation}deg)`;
|
|
20
|
+
useEffect(() => onMatchCount(count), [count]);
|
|
12
21
|
useEffect(() => {
|
|
13
22
|
setOffset({ x: 0, y: 0 });
|
|
14
23
|
setHasFailed(false);
|
|
@@ -44,5 +53,5 @@ export const PsImageViewer = ({ file, labels, rotation, zoom }) => {
|
|
|
44
53
|
});
|
|
45
54
|
if (hasFailed)
|
|
46
55
|
return _jsx("p", { className: 'm-auto text-sm text-white/70', children: labels.loadFailed });
|
|
47
|
-
return (_jsx("div", { className: `h-full w-full touch-none overflow-hidden ${zoom > 1 ? 'cursor-grab active:cursor-grabbing' : ''}`, ...handlers, children:
|
|
56
|
+
return (_jsx("div", { className: `h-full w-full touch-none overflow-hidden ${zoom > 1 ? 'cursor-grab active:cursor-grabbing' : ''}`, ...handlers, children: _jsxs("div", { className: 'relative flex h-full w-full items-center justify-center', ref: pan, style: { transform: PsPanService.translate(offset) }, children: [_jsx("img", { alt: file.name, className: 'max-h-full max-w-full select-none object-contain shadow-2xl transition-transform duration-150', draggable: false, onError: () => setHasFailed(true), ref: image, src: file.url, style: { transform: imageTransform } }), matches.length > 0 ? (_jsx(PsImageTextHighlights, { activeIndex: activeIndex, image: image, matches: matches, transform: imageTransform })) : null] }) }));
|
|
48
57
|
};
|
|
@@ -22,7 +22,7 @@ export const PsPdfPage = ({ activeMatch, document, label, matches, pageNumber, r
|
|
|
22
22
|
task?.cancel();
|
|
23
23
|
};
|
|
24
24
|
}, [document, pageNumber, rotation, scale, isInView]);
|
|
25
|
-
return (_jsxs("div", { "aria-label": `${label} ${pageNumber}`, className: 'relative mx-auto bg-white shadow-2xl', "data-ps-pdf-page": pageNumber, ref: container, style: size === null ? { height: 800, width: 600 } : { height: size.height, width: size.width }, children: [_jsx("canvas", { className: 'block', ref: canvas }), matches.map((match, index) => (_jsx("span", { className: match === activeMatch
|
|
25
|
+
return (_jsxs("div", { "aria-label": `${label} ${pageNumber}`, className: 'relative mx-auto bg-white shadow-2xl', "data-ps-pdf-page": pageNumber, ref: container, style: size === null ? { height: 800, width: 600 } : { height: size.height, width: size.width }, children: [_jsx("canvas", { className: 'block', ref: canvas }), matches.map((match, index) => (_jsx("span", { className: activeMatch !== null && match.matchIndex === activeMatch.matchIndex
|
|
26
26
|
? 'pointer-events-none absolute rounded-sm bg-warning/60 ring-2 ring-warning'
|
|
27
27
|
: 'pointer-events-none absolute rounded-sm bg-warning/35', style: {
|
|
28
28
|
height: `${match.height * 100}%`,
|
|
@@ -76,13 +76,17 @@ export const PsPdfViewer = ({ activeMatch, file, labels, onMatchCount, pdfWorker
|
|
|
76
76
|
if (!isCurrent)
|
|
77
77
|
return;
|
|
78
78
|
setMatches(found);
|
|
79
|
-
onMatchCount(found.length);
|
|
79
|
+
onMatchCount(found.length === 0 ? 0 : found[found.length - 1].matchIndex + 1);
|
|
80
80
|
});
|
|
81
81
|
return () => {
|
|
82
82
|
isCurrent = false;
|
|
83
83
|
};
|
|
84
84
|
}, [document, query, rotation]);
|
|
85
|
-
const
|
|
85
|
+
const matchTotal = matches.length === 0 ? 0 : matches[matches.length - 1].matchIndex + 1;
|
|
86
|
+
const activeRect = matchTotal === 0
|
|
87
|
+
? undefined
|
|
88
|
+
: matches.find((match) => match.matchIndex === activeMatch % matchTotal);
|
|
89
|
+
const active = activeRect === undefined ? null : activeRect;
|
|
86
90
|
const scrollToPage = (pageNumber) => {
|
|
87
91
|
const node = scroller.current?.querySelector(`[data-ps-pdf-page='${pageNumber}']`);
|
|
88
92
|
if (node instanceof HTMLElement)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { PsSpinner } from '@pallasoft/ps-core-components';
|
|
4
|
+
import { PsFileViewerConfig } from '../constants/ps-file-viewer-config';
|
|
5
|
+
import { usePsLoadedFile } from '../hooks/use-ps-loaded-file';
|
|
6
|
+
import { PsLabelService } from '../services/ps-label-service';
|
|
7
|
+
import { PsSheetService } from '../services/ps-sheet-service';
|
|
8
|
+
import { PsTableView } from './ps-table-view';
|
|
9
|
+
export const PsSheetViewer = ({ activeMatch, file, labels, onMatchCount, query, zoom, }) => {
|
|
10
|
+
const { hasFailed, value } = usePsLoadedFile(file.url, PsSheetService.load);
|
|
11
|
+
const [sheetIndex, setSheetIndex] = useState(0);
|
|
12
|
+
const sheet = value === null || value.length === 0 ? null : value[Math.min(sheetIndex, value.length - 1)];
|
|
13
|
+
const rows = useMemo(() => (sheet === null ? [] : sheet.rows.slice(0, PsFileViewerConfig.maxSheetRows)), [sheet]);
|
|
14
|
+
useEffect(() => setSheetIndex(0), [file.id]);
|
|
15
|
+
if (hasFailed || (value !== null && value.length === 0))
|
|
16
|
+
return _jsx("p", { className: 'm-auto text-sm text-white/70', children: labels.loadFailed });
|
|
17
|
+
if (value === null || sheet === null)
|
|
18
|
+
return _jsx(PsSpinner, { className: 'm-auto', color: 'current', size: 'large' });
|
|
19
|
+
return (_jsxs("div", { className: 'flex h-full w-full min-w-0 flex-col gap-2', children: [_jsx("div", { className: 'min-h-0 flex-1 overflow-auto', children: _jsx("div", { className: 'mx-auto w-full max-w-6xl rounded-lg bg-surface p-5 text-ink shadow-2xl', style: { fontSize: `${zoom * 0.875}rem` }, children: _jsx(PsTableView, { activeMatch: activeMatch, onMatchCount: onMatchCount, query: query, rows: rows }, sheet.name) }) }), _jsxs("div", { className: 'flex shrink-0 flex-wrap items-center gap-2 text-xs text-white/70', children: [value.length > 1 ? (_jsx("div", { className: 'flex flex-wrap gap-1', role: 'tablist', children: value.map((entry, index) => (_jsx("button", { "aria-selected": entry === sheet, className: entry === sheet
|
|
20
|
+
? 'ps-focus-ring ps-control-compact rounded-ps-control bg-white/20 px-3 text-white'
|
|
21
|
+
: 'ps-focus-ring ps-control-compact rounded-ps-control px-3 text-white/70 hover:bg-white/10', onClick: () => setSheetIndex(index), role: 'tab', type: 'button', children: entry.name }, `${entry.name}-${index}`))) })) : null, sheet.rows.length > rows.length ? (_jsx("span", { children: PsLabelService.fill(labels.sheetTruncated, { count: PsFileViewerConfig.maxSheetRows }) })) : null] })] }));
|
|
22
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
3
|
+
import { usePsActiveMatchScroll } from '../hooks/use-ps-active-match-scroll';
|
|
4
|
+
import { PsTextSearchService } from '../services/ps-text-search-service';
|
|
5
|
+
import { PsHighlightedText } from './ps-highlighted-text';
|
|
6
|
+
export const PsTableView = ({ activeMatch, onMatchCount, query, rows }) => {
|
|
7
|
+
const container = useRef(null);
|
|
8
|
+
const cells = useMemo(() => PsTextSearchService.tableSegments(rows, query), [rows, query]);
|
|
9
|
+
const count = useMemo(() => PsTextSearchService.countSegments(cells), [cells]);
|
|
10
|
+
useEffect(() => onMatchCount(count), [count]);
|
|
11
|
+
usePsActiveMatchScroll(container, activeMatch, query);
|
|
12
|
+
return (_jsx("table", { className: 'w-full border-collapse text-left', ref: container, children: _jsx("tbody", { children: cells.map((row, rowIndex) => (_jsx("tr", { className: rowIndex === 0 ? 'bg-primary-soft/50 font-semibold' : 'border-t border-line', children: row.map((cell, cellIndex) => (_jsx("td", { className: 'whitespace-nowrap px-3 py-1.5 align-top', children: _jsx(PsHighlightedText, { activeMatch: activeMatch, segments: cell }) }, cellIndex))) }, rowIndex))) }) }));
|
|
13
|
+
};
|
|
@@ -8,31 +8,24 @@ import { PsCsvService } from '../services/ps-csv-service';
|
|
|
8
8
|
import { PsFileTypeService } from '../services/ps-file-type-service';
|
|
9
9
|
import { PsTextSearchService } from '../services/ps-text-search-service';
|
|
10
10
|
import { PsHighlightedText } from './ps-highlighted-text';
|
|
11
|
+
import { PsTableView } from './ps-table-view';
|
|
11
12
|
export const PsTextViewer = ({ activeMatch, file, labels, onMatchCount, query, zoom, }) => {
|
|
12
13
|
const { hasFailed, text } = usePsFileText(file.url);
|
|
13
14
|
const container = useRef(null);
|
|
14
15
|
const isCsv = PsFileTypeService.kindOf(file.contentType, file.name) === PsFileKind.csv;
|
|
15
16
|
const rows = useMemo(() => (isCsv && text !== null ? PsCsvService.parse(text) : []), [isCsv, text]);
|
|
16
|
-
const cells = useMemo(() => {
|
|
17
|
-
let offset = 0;
|
|
18
|
-
return rows.map((row) => row.map((cell) => {
|
|
19
|
-
const matches = PsTextSearchService.find(cell, query);
|
|
20
|
-
const segments = PsTextSearchService.segments(cell, matches, offset);
|
|
21
|
-
offset += matches.length;
|
|
22
|
-
return segments;
|
|
23
|
-
}));
|
|
24
|
-
}, [rows, query]);
|
|
25
17
|
const segments = useMemo(() => isCsv || text === null
|
|
26
18
|
? []
|
|
27
19
|
: PsTextSearchService.segments(text, PsTextSearchService.find(text, query)), [isCsv, text, query]);
|
|
28
|
-
const count =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
const count = segments.filter((part) => part.isMatch).length;
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!isCsv)
|
|
23
|
+
onMatchCount(count);
|
|
24
|
+
}, [isCsv, count]);
|
|
32
25
|
usePsActiveMatchScroll(container, activeMatch, query);
|
|
33
26
|
if (hasFailed)
|
|
34
27
|
return _jsx("p", { className: 'm-auto text-sm text-white/70', children: labels.loadFailed });
|
|
35
28
|
if (text === null)
|
|
36
29
|
return _jsx(PsSpinner, { className: 'm-auto', color: 'current', size: 'large' });
|
|
37
|
-
return (_jsx("div", { className: 'h-full w-full overflow-auto', ref: container, children: _jsx("div", { className: 'mx-auto w-full max-w-5xl rounded-lg bg-surface p-5 text-ink shadow-2xl', style: { fontSize: `${zoom * 0.875}rem` }, children: isCsv ? (_jsx(
|
|
30
|
+
return (_jsx("div", { className: 'h-full w-full overflow-auto', ref: container, children: _jsx("div", { className: 'mx-auto w-full max-w-5xl rounded-lg bg-surface p-5 text-ink shadow-2xl', style: { fontSize: `${zoom * 0.875}rem` }, children: isCsv ? (_jsx(PsTableView, { activeMatch: activeMatch, onMatchCount: onMatchCount, query: query, rows: rows })) : (_jsx("pre", { className: 'whitespace-pre-wrap break-words font-mono leading-relaxed', children: _jsx(PsHighlightedText, { activeMatch: activeMatch, segments: segments }) })) }) }));
|
|
38
31
|
};
|
|
@@ -16,5 +16,5 @@ export const PsUploadRow = ({ item, labels, onCancel, onRemove, onRetry }) => {
|
|
|
16
16
|
: item.status === PsUploadStatus.failed && item.error
|
|
17
17
|
? item.error
|
|
18
18
|
: labels[item.status];
|
|
19
|
-
return (_jsxs("li", { className: 'flex items-center gap-3 rounded-ps-control border border-line px-3 py-2.5', children: [_jsx(PsFileKindIcon, { contentType: item.file.type, name: item.file.name }), _jsxs("div", { className: 'flex min-w-0 flex-1 flex-col gap-1.5', children: [_jsx("p", { className: 'truncate text-sm font-medium text-ink', children: item.file.name }), _jsx(PsProgressBar, { label: item.file.name, tone: PsUploadStatusTones[item.status], value: isProblem ? 1 : item.progress }), _jsxs("p", { className: `truncate text-xs ${isProblem ? 'text-danger' : item.status === PsUploadStatus.done ? 'text-success' : 'text-
|
|
19
|
+
return (_jsxs("li", { className: 'flex items-center gap-3 rounded-ps-control border border-line px-3 py-2.5', children: [_jsx(PsFileKindIcon, { contentType: item.file.type, name: item.file.name }), _jsxs("div", { className: 'flex min-w-0 flex-1 flex-col gap-1.5', children: [_jsx("p", { className: 'truncate text-sm font-medium text-ink', children: item.file.name }), _jsx(PsProgressBar, { label: item.file.name, tone: PsUploadStatusTones[item.status], value: isProblem ? 1 : item.progress }), _jsxs("p", { className: `truncate text-xs ${isProblem ? 'text-danger' : item.status === PsUploadStatus.done ? 'text-success' : 'text-muted'}`, role: item.status === PsUploadStatus.failed ? 'alert' : undefined, children: [size, " \u00B7 ", status] })] }), _jsxs("div", { className: 'flex w-20 shrink-0 items-center justify-end gap-1', children: [isProblem ? (_jsx(PsFileIconButton, { iconKey: PsIconKey.Refresh, label: `${labels.retry} ${item.file.name}`, onClick: onRetry })) : null, isActive ? (_jsx(PsFileIconButton, { iconKey: PsIconKey.Close, label: `${labels.cancel} ${item.file.name}`, onClick: onCancel, tone: 'danger' })) : item.status === PsUploadStatus.done ? (_jsx("span", { className: 'ps-control-square ps-control-compact inline-flex items-center justify-center text-success', children: _jsx(PsIcon, { "aria-label": labels.done, className: 'h-4 w-4', iconKey: PsIconKey.Check }) })) : (_jsx(PsFileIconButton, { iconKey: PsIconKey.Close, label: `${labels.remove} ${item.file.name}`, onClick: onRemove }))] })] }));
|
|
20
20
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { PsSpinner } from '@pallasoft/ps-core-components';
|
|
3
|
+
import { usePsLoadedFile } from '../hooks/use-ps-loaded-file';
|
|
4
|
+
import { PsWordService } from '../services/ps-word-service';
|
|
5
|
+
import { PsHtmlDocument } from './ps-html-document';
|
|
6
|
+
export const PsWordViewer = ({ activeMatch, file, labels, onMatchCount, query, zoom, }) => {
|
|
7
|
+
const { hasFailed, value } = usePsLoadedFile(file.url, PsWordService.load);
|
|
8
|
+
if (hasFailed)
|
|
9
|
+
return _jsx("p", { className: 'm-auto text-sm text-white/70', children: labels.loadFailed });
|
|
10
|
+
if (value === null)
|
|
11
|
+
return _jsx(PsSpinner, { className: 'm-auto', color: 'current', size: 'large' });
|
|
12
|
+
return (_jsx(PsHtmlDocument, { activeMatch: activeMatch, html: value, onMatchCount: onMatchCount, query: query, zoom: zoom }));
|
|
13
|
+
};
|
|
@@ -3,9 +3,12 @@ import { PsFileKind } from './ps-file-kind';
|
|
|
3
3
|
export const PsFileKindIcons = {
|
|
4
4
|
[PsFileKind.audio]: PsIconKey.Audio,
|
|
5
5
|
[PsFileKind.csv]: PsIconKey.Sheet,
|
|
6
|
+
[PsFileKind.html]: PsIconKey.Text,
|
|
6
7
|
[PsFileKind.image]: PsIconKey.Image,
|
|
7
8
|
[PsFileKind.other]: PsIconKey.File,
|
|
8
9
|
[PsFileKind.pdf]: PsIconKey.Doc,
|
|
10
|
+
[PsFileKind.sheet]: PsIconKey.Sheet,
|
|
9
11
|
[PsFileKind.text]: PsIconKey.Text,
|
|
10
12
|
[PsFileKind.video]: PsIconKey.Video,
|
|
13
|
+
[PsFileKind.word]: PsIconKey.Doc,
|
|
11
14
|
};
|
|
@@ -1,26 +1,40 @@
|
|
|
1
1
|
import { PsFileKind } from './ps-file-kind';
|
|
2
2
|
export const PsFileTypeConfig = {
|
|
3
|
-
csvTypes: ['text/csv', 'application/csv'
|
|
3
|
+
csvTypes: ['text/csv', 'application/csv'],
|
|
4
|
+
excelLegacyType: 'application/vnd.ms-excel',
|
|
4
5
|
extensionKinds: {
|
|
6
|
+
aac: PsFileKind.audio,
|
|
7
|
+
avif: PsFileKind.image,
|
|
8
|
+
bmp: PsFileKind.image,
|
|
5
9
|
csv: PsFileKind.csv,
|
|
10
|
+
docx: PsFileKind.word,
|
|
6
11
|
gif: PsFileKind.image,
|
|
7
|
-
|
|
12
|
+
htm: PsFileKind.html,
|
|
13
|
+
html: PsFileKind.html,
|
|
8
14
|
jpeg: PsFileKind.image,
|
|
9
15
|
jpg: PsFileKind.image,
|
|
10
16
|
json: PsFileKind.text,
|
|
11
17
|
log: PsFileKind.text,
|
|
18
|
+
m4a: PsFileKind.audio,
|
|
12
19
|
md: PsFileKind.text,
|
|
13
20
|
mov: PsFileKind.video,
|
|
14
21
|
mp3: PsFileKind.audio,
|
|
15
22
|
mp4: PsFileKind.video,
|
|
23
|
+
ogg: PsFileKind.audio,
|
|
16
24
|
pdf: PsFileKind.pdf,
|
|
17
25
|
png: PsFileKind.image,
|
|
18
26
|
svg: PsFileKind.image,
|
|
27
|
+
tsv: PsFileKind.text,
|
|
19
28
|
txt: PsFileKind.text,
|
|
20
29
|
wav: PsFileKind.audio,
|
|
21
30
|
webm: PsFileKind.video,
|
|
22
31
|
webp: PsFileKind.image,
|
|
32
|
+
xlsx: PsFileKind.sheet,
|
|
23
33
|
xml: PsFileKind.text,
|
|
24
34
|
},
|
|
35
|
+
htmlTypes: ['text/html', 'application/xhtml+xml'],
|
|
36
|
+
sheetTypes: ['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
|
|
25
37
|
textTypes: ['application/json', 'application/xml'],
|
|
38
|
+
unsupportedImageTypes: ['image/heic', 'image/heif', 'image/heic-sequence', 'image/heif-sequence'],
|
|
39
|
+
wordTypes: ['application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
|
|
26
40
|
};
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { PsFileKind } from './ps-file-kind';
|
|
2
2
|
export const PsFileViewerKinds = {
|
|
3
3
|
rotatable: [PsFileKind.image, PsFileKind.pdf],
|
|
4
|
-
searchable: [
|
|
5
|
-
|
|
4
|
+
searchable: [
|
|
5
|
+
PsFileKind.csv,
|
|
6
|
+
PsFileKind.html,
|
|
7
|
+
PsFileKind.pdf,
|
|
8
|
+
PsFileKind.sheet,
|
|
9
|
+
PsFileKind.text,
|
|
10
|
+
PsFileKind.word,
|
|
11
|
+
],
|
|
12
|
+
zoomable: [
|
|
13
|
+
PsFileKind.csv,
|
|
14
|
+
PsFileKind.html,
|
|
15
|
+
PsFileKind.image,
|
|
16
|
+
PsFileKind.pdf,
|
|
17
|
+
PsFileKind.sheet,
|
|
18
|
+
PsFileKind.text,
|
|
19
|
+
PsFileKind.word,
|
|
20
|
+
],
|
|
6
21
|
};
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export const PsFileViewerLabels = {
|
|
2
2
|
close: 'Close',
|
|
3
|
+
closeSearch: 'Close search',
|
|
3
4
|
delete: 'Delete',
|
|
4
5
|
deleteConfirm: 'Delete file',
|
|
5
6
|
deleteConfirmBody: 'will be deleted. You cannot undo this.',
|
|
6
7
|
deleteCancel: 'Cancel',
|
|
7
8
|
download: 'Download',
|
|
8
9
|
loadFailed: 'This file could not be opened.',
|
|
10
|
+
matchCount: '{current} of {total}',
|
|
9
11
|
next: 'Next file',
|
|
10
12
|
nextMatch: 'Next match',
|
|
11
13
|
noMatches: 'No matches',
|
|
@@ -14,6 +16,7 @@ export const PsFileViewerLabels = {
|
|
|
14
16
|
previousMatch: 'Previous match',
|
|
15
17
|
rotate: 'Rotate',
|
|
16
18
|
search: 'Search in file',
|
|
19
|
+
sheetTruncated: 'Showing the first {count} rows',
|
|
17
20
|
unsupported: 'This file type cannot be previewed here. Download it to open it.',
|
|
18
21
|
zoomIn: 'Zoom in',
|
|
19
22
|
zoomOut: 'Zoom out',
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const PsHtmlSanitizeConfig = {
|
|
2
|
+
anchorRel: 'noopener noreferrer',
|
|
3
|
+
anchorTarget: '_blank',
|
|
4
|
+
blockSelector: 'p,li,td,th,tr,h1,h2,h3,h4,h5,h6,div,pre,blockquote,section,article,caption',
|
|
5
|
+
forbiddenAttributes: ['style', 'srcset'],
|
|
6
|
+
forbiddenTags: [
|
|
7
|
+
'style',
|
|
8
|
+
'form',
|
|
9
|
+
'input',
|
|
10
|
+
'button',
|
|
11
|
+
'textarea',
|
|
12
|
+
'select',
|
|
13
|
+
'iframe',
|
|
14
|
+
'frame',
|
|
15
|
+
'object',
|
|
16
|
+
'embed',
|
|
17
|
+
'link',
|
|
18
|
+
'meta',
|
|
19
|
+
'base',
|
|
20
|
+
'script',
|
|
21
|
+
],
|
|
22
|
+
mediaTags: ['img', 'picture', 'source', 'video', 'audio', 'track'],
|
|
23
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { usePsDebouncedValue } from '@pallasoft/ps-core-components';
|
|
3
|
+
import { PsFileViewerConfig } from '../constants/ps-file-viewer-config';
|
|
4
|
+
export const usePsFileSearch = (resetKey) => {
|
|
5
|
+
const [search, setSearch] = useState('');
|
|
6
|
+
const [activeMatch, setActiveMatch] = useState(0);
|
|
7
|
+
const [matchCount, setMatchCount] = useState(0);
|
|
8
|
+
const inputRef = useRef(null);
|
|
9
|
+
const query = usePsDebouncedValue(search, PsFileViewerConfig.searchDebounceMs);
|
|
10
|
+
const reset = useCallback(() => {
|
|
11
|
+
setSearch('');
|
|
12
|
+
setActiveMatch(0);
|
|
13
|
+
setMatchCount(0);
|
|
14
|
+
}, []);
|
|
15
|
+
useEffect(reset, [resetKey]);
|
|
16
|
+
useEffect(() => setActiveMatch(0), [query]);
|
|
17
|
+
const focus = useCallback(() => {
|
|
18
|
+
inputRef.current?.focus();
|
|
19
|
+
inputRef.current?.select();
|
|
20
|
+
}, []);
|
|
21
|
+
const step = useCallback((delta) => {
|
|
22
|
+
if (matchCount > 0)
|
|
23
|
+
setActiveMatch((current) => (current + delta + matchCount) % matchCount);
|
|
24
|
+
}, [matchCount]);
|
|
25
|
+
return {
|
|
26
|
+
activeMatch,
|
|
27
|
+
focus,
|
|
28
|
+
inputRef,
|
|
29
|
+
matchCount,
|
|
30
|
+
query,
|
|
31
|
+
reset,
|
|
32
|
+
search,
|
|
33
|
+
setMatchCount,
|
|
34
|
+
setSearch,
|
|
35
|
+
step,
|
|
36
|
+
};
|
|
37
|
+
};
|
|
@@ -1,21 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PsFileFetchService } from '../services/ps-file-fetch-service';
|
|
2
|
+
import { usePsLoadedFile } from './use-ps-loaded-file';
|
|
2
3
|
export const usePsFileText = (url) => {
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const controller = new AbortController();
|
|
6
|
-
setState({ hasFailed: false, text: null });
|
|
7
|
-
fetch(url, { credentials: 'include', signal: controller.signal })
|
|
8
|
-
.then((response) => {
|
|
9
|
-
if (!response.ok)
|
|
10
|
-
throw new Error(response.statusText);
|
|
11
|
-
return response.text();
|
|
12
|
-
})
|
|
13
|
-
.then((text) => setState({ hasFailed: false, text }))
|
|
14
|
-
.catch(() => {
|
|
15
|
-
if (!controller.signal.aborted)
|
|
16
|
-
setState({ hasFailed: true, text: null });
|
|
17
|
-
});
|
|
18
|
-
return () => controller.abort();
|
|
19
|
-
}, [url]);
|
|
20
|
-
return state;
|
|
4
|
+
const { hasFailed, value } = usePsLoadedFile(url, PsFileFetchService.text);
|
|
5
|
+
return { hasFailed, text: value };
|
|
21
6
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { PsFileSearchService } from '../services/ps-file-search-service';
|
|
3
|
+
export const usePsFindShortcut = (scope, isEnabled, onFind) => {
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
if (!isEnabled)
|
|
6
|
+
return;
|
|
7
|
+
const onKey = (event) => {
|
|
8
|
+
if (!PsFileSearchService.isFindShortcut(event))
|
|
9
|
+
return;
|
|
10
|
+
if (scope !== null) {
|
|
11
|
+
const node = scope.current;
|
|
12
|
+
if (node === null)
|
|
13
|
+
return;
|
|
14
|
+
if (!node.contains(document.activeElement) && !node.matches(':hover'))
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
event.preventDefault();
|
|
18
|
+
onFind();
|
|
19
|
+
};
|
|
20
|
+
window.addEventListener('keydown', onKey);
|
|
21
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
22
|
+
}, [scope, isEnabled, onFind]);
|
|
23
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
export const usePsLoadedFile = (url, load) => {
|
|
3
|
+
const [state, setState] = useState({ hasFailed: false, value: null });
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
const controller = new AbortController();
|
|
6
|
+
setState({ hasFailed: false, value: null });
|
|
7
|
+
load(url, controller.signal)
|
|
8
|
+
.then((value) => {
|
|
9
|
+
if (!controller.signal.aborted)
|
|
10
|
+
setState({ hasFailed: false, value });
|
|
11
|
+
})
|
|
12
|
+
.catch(() => {
|
|
13
|
+
if (!controller.signal.aborted)
|
|
14
|
+
setState({ hasFailed: true, value: null });
|
|
15
|
+
});
|
|
16
|
+
return () => controller.abort();
|
|
17
|
+
}, [url, load]);
|
|
18
|
+
return state;
|
|
19
|
+
};
|
package/dist/esm/index.js
CHANGED
|
@@ -18,6 +18,13 @@ export * from './components/ps-text-viewer';
|
|
|
18
18
|
export * from './components/ps-unsupported-viewer';
|
|
19
19
|
export * from './components/ps-upload-modal';
|
|
20
20
|
export * from './components/ps-upload-row';
|
|
21
|
+
export * from './components/ps-file-search-bar';
|
|
22
|
+
export * from './components/ps-html-document';
|
|
23
|
+
export * from './components/ps-html-viewer';
|
|
24
|
+
export * from './components/ps-image-text-highlights';
|
|
25
|
+
export * from './components/ps-sheet-viewer';
|
|
26
|
+
export * from './components/ps-table-view';
|
|
27
|
+
export * from './components/ps-word-viewer';
|
|
21
28
|
export * from './constants/ps-file-icon-button-classes';
|
|
22
29
|
export * from './constants/ps-file-kind-icons';
|
|
23
30
|
export * from './constants/ps-file-kind';
|
|
@@ -33,6 +40,7 @@ export * from './constants/ps-upload-messages';
|
|
|
33
40
|
export * from './constants/ps-upload-queue-config';
|
|
34
41
|
export * from './constants/ps-upload-status-tones';
|
|
35
42
|
export * from './constants/ps-upload-status';
|
|
43
|
+
export * from './constants/ps-html-sanitize-config';
|
|
36
44
|
export * from './models/ps-drag-pan-handlers';
|
|
37
45
|
export * from './models/ps-drag-pan-options';
|
|
38
46
|
export * from './models/ps-file-icon-button-tone';
|
|
@@ -59,6 +67,20 @@ export * from './models/ps-viewer-file';
|
|
|
59
67
|
export * from './models/ps-xhr-response-type';
|
|
60
68
|
export * from './models/ps-xhr-upload-request';
|
|
61
69
|
export * from './models/ps-xhr-upload-response';
|
|
70
|
+
export * from './models/ps-file-search-state';
|
|
71
|
+
export * from './models/ps-html-highlight-result';
|
|
72
|
+
export * from './models/ps-image-match';
|
|
73
|
+
export * from './models/ps-image-text-box';
|
|
74
|
+
export * from './models/ps-image-text-layer';
|
|
75
|
+
export * from './models/ps-image-text-word';
|
|
76
|
+
export * from './models/ps-item-match';
|
|
77
|
+
export * from './models/ps-item-match-part';
|
|
78
|
+
export * from './models/ps-loaded-file-state';
|
|
79
|
+
export * from './models/ps-normalized-text';
|
|
80
|
+
export * from './models/ps-pdf-text-item';
|
|
81
|
+
export * from './models/ps-pdf-viewport-like';
|
|
82
|
+
export * from './models/ps-search-item';
|
|
83
|
+
export * from './models/ps-sheet-data';
|
|
62
84
|
export * from './services/ps-csv-service';
|
|
63
85
|
export * from './services/ps-file-selection-service';
|
|
64
86
|
export * from './services/ps-file-size-service';
|
|
@@ -67,11 +89,22 @@ export * from './services/ps-pdf-service';
|
|
|
67
89
|
export * from './services/ps-text-search-service';
|
|
68
90
|
export * from './services/ps-upload-queue-service';
|
|
69
91
|
export * from './services/ps-xhr-upload-service';
|
|
92
|
+
export * from './services/ps-file-fetch-service';
|
|
93
|
+
export * from './services/ps-file-search-service';
|
|
94
|
+
export * from './services/ps-html-file-service';
|
|
95
|
+
export * from './services/ps-html-service';
|
|
96
|
+
export * from './services/ps-image-text-service';
|
|
97
|
+
export * from './services/ps-label-service';
|
|
98
|
+
export * from './services/ps-sheet-service';
|
|
99
|
+
export * from './services/ps-word-service';
|
|
70
100
|
export * from './hooks/use-ps-active-match-scroll';
|
|
71
101
|
export * from './hooks/use-ps-drag-pan';
|
|
72
102
|
export * from './hooks/use-ps-file-text';
|
|
73
103
|
export * from './hooks/use-ps-in-view';
|
|
74
104
|
export * from './hooks/use-ps-upload-queue';
|
|
105
|
+
export * from './hooks/use-ps-file-search';
|
|
106
|
+
export * from './hooks/use-ps-find-shortcut';
|
|
107
|
+
export * from './hooks/use-ps-loaded-file';
|
|
75
108
|
export * from './components/ps-document-scanner';
|
|
76
109
|
export * from './constants/ps-document-scanner-labels';
|
|
77
110
|
export * from './constants/ps-scan-format';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class PsFileFetchService {
|
|
2
|
+
static async response(url, signal) {
|
|
3
|
+
const response = await fetch(url, { credentials: 'include', signal });
|
|
4
|
+
if (!response.ok)
|
|
5
|
+
throw new Error(response.statusText);
|
|
6
|
+
return response;
|
|
7
|
+
}
|
|
8
|
+
static async text(url, signal) {
|
|
9
|
+
return (await PsFileFetchService.response(url, signal)).text();
|
|
10
|
+
}
|
|
11
|
+
static async buffer(url, signal) {
|
|
12
|
+
return (await PsFileFetchService.response(url, signal)).arrayBuffer();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PsFileKind } from '../constants/ps-file-kind';
|
|
2
|
+
import { PsFileViewerKinds } from '../constants/ps-file-viewer-kinds';
|
|
3
|
+
export class PsFileSearchService {
|
|
4
|
+
static canSearch(kind, hasTextLayer) {
|
|
5
|
+
if (PsFileViewerKinds.searchable.includes(kind))
|
|
6
|
+
return true;
|
|
7
|
+
return kind === PsFileKind.image && hasTextLayer;
|
|
8
|
+
}
|
|
9
|
+
static isFindShortcut(event) {
|
|
10
|
+
return ((event.metaKey || event.ctrlKey) &&
|
|
11
|
+
!event.altKey &&
|
|
12
|
+
!event.shiftKey &&
|
|
13
|
+
event.key.toLowerCase() === 'f');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -7,10 +7,21 @@ export class PsFileTypeService {
|
|
|
7
7
|
}
|
|
8
8
|
static kindOf(contentType, name) {
|
|
9
9
|
const type = contentType.toLowerCase();
|
|
10
|
+
const extension = PsFileTypeService.extension(name);
|
|
10
11
|
if (type === 'application/pdf')
|
|
11
12
|
return PsFileKind.pdf;
|
|
12
13
|
if (PsFileTypeConfig.csvTypes.includes(type))
|
|
13
14
|
return PsFileKind.csv;
|
|
15
|
+
if (type === PsFileTypeConfig.excelLegacyType)
|
|
16
|
+
return extension === 'xls' || extension === 'xlsb' ? PsFileKind.other : PsFileKind.csv;
|
|
17
|
+
if (PsFileTypeConfig.sheetTypes.includes(type))
|
|
18
|
+
return PsFileKind.sheet;
|
|
19
|
+
if (PsFileTypeConfig.wordTypes.includes(type))
|
|
20
|
+
return PsFileKind.word;
|
|
21
|
+
if (PsFileTypeConfig.htmlTypes.includes(type))
|
|
22
|
+
return PsFileKind.html;
|
|
23
|
+
if (PsFileTypeConfig.unsupportedImageTypes.includes(type))
|
|
24
|
+
return PsFileKind.other;
|
|
14
25
|
if (type.startsWith('image/'))
|
|
15
26
|
return PsFileKind.image;
|
|
16
27
|
if (type.startsWith('video/'))
|
|
@@ -20,7 +31,7 @@ export class PsFileTypeService {
|
|
|
20
31
|
if (type.startsWith('text/') ||
|
|
21
32
|
PsFileTypeConfig.textTypes.includes(type))
|
|
22
33
|
return PsFileKind.text;
|
|
23
|
-
const byExtension = PsFileTypeConfig.extensionKinds[
|
|
34
|
+
const byExtension = PsFileTypeConfig.extensionKinds[extension];
|
|
24
35
|
return byExtension === undefined ? PsFileKind.other : byExtension;
|
|
25
36
|
}
|
|
26
37
|
static isAccepted(file, accept) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { PsFileFetchService } from './ps-file-fetch-service';
|
|
2
|
+
import { PsHtmlService } from './ps-html-service';
|
|
3
|
+
export class PsHtmlFileService {
|
|
4
|
+
static async load(url, signal) {
|
|
5
|
+
return PsHtmlService.sanitize(await PsFileFetchService.text(url, signal), false);
|
|
6
|
+
}
|
|
7
|
+
}
|