dsh-long-plugins 3.0.0 → 3.0.2
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/client/client.js +338 -2
- package/dsh.plugin.json +1 -1
- package/lib/index.js +7 -1
- package/package.json +1 -1
package/client/client.js
CHANGED
|
@@ -13,8 +13,19 @@ window.__ModuleLoader__.load({
|
|
|
13
13
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
14
14
|
|
|
15
15
|
const React = require('react')
|
|
16
|
+
const API_PATH = '/api/dsh-uploads'
|
|
17
|
+
const DOWNLOAD_PATH = '/api/dsh-uploads/download'
|
|
18
|
+
const PREVIEW_PATH = '/api/dsh-uploads/preview'
|
|
16
19
|
const HIDDEN_LABEL = '__dsh_upload_hidden__:'
|
|
17
20
|
|
|
21
|
+
function downloadUrl(name) {
|
|
22
|
+
return `${DOWNLOAD_PATH}?name=${encodeURIComponent(name)}`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function previewUrl(name) {
|
|
26
|
+
return `${PREVIEW_PATH}?name=${encodeURIComponent(name)}`
|
|
27
|
+
}
|
|
28
|
+
|
|
18
29
|
function errorMessage(error) {
|
|
19
30
|
return error instanceof Error ? error.message : String(error)
|
|
20
31
|
}
|
|
@@ -617,6 +628,305 @@ window.__ModuleLoader__.load({
|
|
|
617
628
|
)
|
|
618
629
|
}
|
|
619
630
|
|
|
631
|
+
function UploadSettingsSection() {
|
|
632
|
+
const [state, setState] = React.useState({
|
|
633
|
+
loading: true,
|
|
634
|
+
root: '',
|
|
635
|
+
maxFileBytes: 0,
|
|
636
|
+
totalMaxBytes: 0,
|
|
637
|
+
usedBytes: 0,
|
|
638
|
+
files: [],
|
|
639
|
+
error: '',
|
|
640
|
+
})
|
|
641
|
+
const [deleting, setDeleting] = React.useState('')
|
|
642
|
+
const [batchBusy, setBatchBusy] = React.useState(false)
|
|
643
|
+
const [selected, setSelected] = React.useState(() => new Set())
|
|
644
|
+
const [preview, setPreview] = React.useState(null)
|
|
645
|
+
const [previewMaximized, setPreviewMaximized] = React.useState(false)
|
|
646
|
+
const [dayFilter, setDayFilter] = React.useState('')
|
|
647
|
+
const [search, setSearch] = React.useState('')
|
|
648
|
+
const [deleteEnabled, setDeleteEnabled] = React.useState(false)
|
|
649
|
+
|
|
650
|
+
async function refresh() {
|
|
651
|
+
setState((current) => ({ ...current, loading: true, error: '' }))
|
|
652
|
+
try {
|
|
653
|
+
const response = await fetch(API_PATH, { cache: 'no-store' })
|
|
654
|
+
const body = await responseJson(response)
|
|
655
|
+
setState({
|
|
656
|
+
loading: false,
|
|
657
|
+
root: body.root,
|
|
658
|
+
maxFileBytes: body.maxFileBytes,
|
|
659
|
+
totalMaxBytes: body.totalMaxBytes,
|
|
660
|
+
usedBytes: body.usedBytes,
|
|
661
|
+
files: body.files,
|
|
662
|
+
error: '',
|
|
663
|
+
})
|
|
664
|
+
} catch (error) {
|
|
665
|
+
setState((current) => ({ ...current, loading: false, error: errorMessage(error) }))
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
React.useEffect(() => {
|
|
670
|
+
refresh()
|
|
671
|
+
}, [])
|
|
672
|
+
|
|
673
|
+
async function remove(name) {
|
|
674
|
+
if (!globalThis.confirm(`确定删除“${name}”吗?此操作不可恢复。`)) return
|
|
675
|
+
setDeleting(name)
|
|
676
|
+
try {
|
|
677
|
+
const response = await fetch(`${API_PATH}?name=${encodeURIComponent(name)}`, { method: 'DELETE' })
|
|
678
|
+
await responseJson(response)
|
|
679
|
+
if (preview !== null && preview.name === name) closePreview()
|
|
680
|
+
await refresh()
|
|
681
|
+
} catch (error) {
|
|
682
|
+
setState((current) => ({ ...current, error: errorMessage(error) }))
|
|
683
|
+
} finally {
|
|
684
|
+
setDeleting('')
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
async function batchDelete() {
|
|
689
|
+
const names = Array.from(selected || [])
|
|
690
|
+
if (names.length === 0) return
|
|
691
|
+
if (!globalThis.confirm(`确定删除所选 ${names.length} 个文件吗?此操作不可恢复。`)) return
|
|
692
|
+
setBatchBusy(true)
|
|
693
|
+
let err = ''
|
|
694
|
+
for (const name of names) {
|
|
695
|
+
try {
|
|
696
|
+
const response = await fetch(`${API_PATH}?name=${encodeURIComponent(name)}`, { method: 'DELETE' })
|
|
697
|
+
if (!response.ok) { const b = await response.json().catch(() => ({})); err = err || (b.error || `HTTP ${response.status}`) }
|
|
698
|
+
} catch (e) { err = err || errorMessage(e) }
|
|
699
|
+
}
|
|
700
|
+
setState((current) => ({ ...current, error: err }))
|
|
701
|
+
setBatchBusy(false)
|
|
702
|
+
setSelected(new Set())
|
|
703
|
+
await refresh()
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
async function previewFile(name) {
|
|
707
|
+
setPreviewMaximized(false)
|
|
708
|
+
try {
|
|
709
|
+
const isOffice = /\.(docx|xlsx|pptx)$/i.test(name)
|
|
710
|
+
if (isOffice) {
|
|
711
|
+
// 先打开弹窗并提示转换中(NAS 上 docx/xlsx 转换可能耗时 1~2 秒)
|
|
712
|
+
setPreview({ name, officeLoading: true })
|
|
713
|
+
const response = await fetch(previewUrl(name), { cache: 'no-store' })
|
|
714
|
+
if (!response.ok) {
|
|
715
|
+
const body = await response.json().catch(() => ({}))
|
|
716
|
+
throw new Error(body.error || `HTTP ${response.status}`)
|
|
717
|
+
}
|
|
718
|
+
const data = await response.json().catch(() => ({}))
|
|
719
|
+
setPreview({
|
|
720
|
+
name,
|
|
721
|
+
officeHtml: data.officeHtml ?? '<p style="font-family:sans-serif;padding:12px">(无法渲染此文档)</p>',
|
|
722
|
+
})
|
|
723
|
+
return
|
|
724
|
+
}
|
|
725
|
+
// 无法内嵌预览的类型(压缩包、程序、视频、字体等):点预览直接下载。
|
|
726
|
+
if (!isInlinePreviewable(name)) {
|
|
727
|
+
triggerDownload(downloadUrl(name), name)
|
|
728
|
+
return
|
|
729
|
+
}
|
|
730
|
+
const response = await fetch(previewUrl(name), { cache: 'no-store' })
|
|
731
|
+
if (!response.ok) {
|
|
732
|
+
const body = await response.json().catch(() => ({}))
|
|
733
|
+
throw new Error(body.error || `HTTP ${response.status}`)
|
|
734
|
+
}
|
|
735
|
+
const type = response.headers.get('content-type') || ''
|
|
736
|
+
if (type.startsWith('image/')) {
|
|
737
|
+
const blob = await response.blob()
|
|
738
|
+
const url = URL.createObjectURL(blob)
|
|
739
|
+
setPreview({ url, name })
|
|
740
|
+
return
|
|
741
|
+
}
|
|
742
|
+
// PDF / txt / 其它可内嵌文件:弹窗内嵌预览(iframe 指向预览端点),
|
|
743
|
+
// 只有用户点「打开」才在新浏览器标签中打开。
|
|
744
|
+
setPreview({ url: previewUrl(name), name })
|
|
745
|
+
} catch (error) {
|
|
746
|
+
setState((current) => ({ ...current, error: errorMessage(error) }))
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function closePreview() {
|
|
751
|
+
if (preview?.url) URL.revokeObjectURL(preview.url)
|
|
752
|
+
setPreview(null)
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/** Group files by calendar day of their modifiedAt (YYYY-MM-DD, desc). */
|
|
756
|
+
function groupFilesByDate(files) {
|
|
757
|
+
const groups = []
|
|
758
|
+
const byDay = new Map()
|
|
759
|
+
for (const file of files) {
|
|
760
|
+
let day = ''
|
|
761
|
+
try {
|
|
762
|
+
day = localDay(file.modifiedAt)
|
|
763
|
+
} catch {
|
|
764
|
+
day = '未知日期'
|
|
765
|
+
}
|
|
766
|
+
if (!byDay.has(day)) byDay.set(day, [])
|
|
767
|
+
byDay.get(day).push(file)
|
|
768
|
+
}
|
|
769
|
+
const days = [...byDay.keys()].sort((a, b) => b.localeCompare(a))
|
|
770
|
+
for (const day of days) groups.push({ day, files: byDay.get(day) })
|
|
771
|
+
return groups
|
|
772
|
+
}
|
|
773
|
+
// 日期筛选 + 文件名搜索:先按所选天/关键词过滤 state.files,再始终按天分组(空=全部)。
|
|
774
|
+
const shownFiles = (dayFilter || search)
|
|
775
|
+
? state.files.filter((f) => (dayFilter ? (() => { try { return localDay(f.modifiedAt) === dayFilter } catch { return false } })() : true) && matchesSearch(f, search))
|
|
776
|
+
: state.files
|
|
777
|
+
const dateGroups = groupFilesByDate(shownFiles)
|
|
778
|
+
|
|
779
|
+
return React.createElement(
|
|
780
|
+
'section',
|
|
781
|
+
{ className: 'dsh-upload-settings' },
|
|
782
|
+
React.createElement(
|
|
783
|
+
'div',
|
|
784
|
+
{ className: 'dsh-upload-settings-head' },
|
|
785
|
+
React.createElement(
|
|
786
|
+
'div',
|
|
787
|
+
null,
|
|
788
|
+
React.createElement('h2', null, '上传文件'),
|
|
789
|
+
React.createElement('p', null, '管理从输入框上传到 Harness 容器中的文件。'),
|
|
790
|
+
),
|
|
791
|
+
React.createElement(
|
|
792
|
+
'div',
|
|
793
|
+
{ className: 'dsh-upload-head-actions' },
|
|
794
|
+
React.createElement(SearchPopup, { value: search, onChange: setSearch, placeholder: '搜索文件名…' }),
|
|
795
|
+
React.createElement(DateFilter, { value: dayFilter, onChange: setDayFilter, placeholder: '选择日期' }),
|
|
796
|
+
React.createElement(
|
|
797
|
+
'button',
|
|
798
|
+
{ type: 'button', className: 'dsh-upload-refresh', disabled: state.loading, onClick: refresh },
|
|
799
|
+
state.loading ? '刷新中…' : '刷新',
|
|
800
|
+
),
|
|
801
|
+
React.createElement(
|
|
802
|
+
'label',
|
|
803
|
+
{ className: 'dsh-upload-del-toggle' },
|
|
804
|
+
React.createElement('input', { type: 'checkbox', checked: deleteEnabled, onChange: (e) => { setDeleteEnabled(e.target.checked); if (!e.target.checked) setSelected(new Set()) } }),
|
|
805
|
+
'开启删除',
|
|
806
|
+
),
|
|
807
|
+
deleteEnabled && React.createElement('label',
|
|
808
|
+
{ className: 'dsh-upload-del-toggle' },
|
|
809
|
+
React.createElement('input', { type: 'checkbox', checked: (state.files || []).length > 0 && (selected || new Set()).size === (state.files || []).length, onChange: (e) => { setSelected(e.target.checked ? new Set((state.files || []).map((f) => f.name)) : new Set()) } }),
|
|
810
|
+
'全选',
|
|
811
|
+
),
|
|
812
|
+
React.createElement(
|
|
813
|
+
'button',
|
|
814
|
+
{ type: 'button', className: 'dsh-upload-batchdel', disabled: !deleteEnabled || (selected || new Set()).size === 0 || batchBusy, onClick: batchDelete },
|
|
815
|
+
batchBusy ? '删除中…' : `批量删除(${(selected || new Set()).size})`,
|
|
816
|
+
),
|
|
817
|
+
),
|
|
818
|
+
),
|
|
819
|
+
React.createElement(
|
|
820
|
+
'div',
|
|
821
|
+
{ className: 'dsh-upload-root' },
|
|
822
|
+
React.createElement('span', null, '固定目录'),
|
|
823
|
+
React.createElement('code', null, state.root || '读取中…'),
|
|
824
|
+
state.maxFileBytes
|
|
825
|
+
? React.createElement('small', null, `单文件上限 ${sizeText(state.maxFileBytes)}`)
|
|
826
|
+
: null,
|
|
827
|
+
state.totalMaxBytes
|
|
828
|
+
? React.createElement('small', null, `已使用 ${sizeText(state.usedBytes)} / ${sizeText(state.totalMaxBytes)}`)
|
|
829
|
+
: null,
|
|
830
|
+
),
|
|
831
|
+
state.error ? React.createElement('div', { className: 'dsh-upload-error' }, state.error) : null,
|
|
832
|
+
!state.loading && state.files.length === 0
|
|
833
|
+
? React.createElement('div', { className: 'dsh-upload-empty' }, '当前没有已上传文件。')
|
|
834
|
+
: null,
|
|
835
|
+
!state.loading && (dayFilter !== '' || search !== '') && shownFiles.length === 0 && state.files.length > 0
|
|
836
|
+
? React.createElement('div', { className: 'dsh-upload-empty' }, '没有匹配的文件。')
|
|
837
|
+
: null,
|
|
838
|
+
preview
|
|
839
|
+
? React.createElement(
|
|
840
|
+
'div',
|
|
841
|
+
{ className: 'dsh-upload-preview-overlay' + (previewMaximized ? ' dsh-upload-preview-overlay-max' : ''), onClick: closePreview },
|
|
842
|
+
React.createElement(
|
|
843
|
+
'div',
|
|
844
|
+
{ className: 'dsh-upload-preview-card' + (previewMaximized ? ' dsh-upload-preview-card-max' : ''), onClick: (event) => event.stopPropagation() },
|
|
845
|
+
React.createElement(
|
|
846
|
+
'div',
|
|
847
|
+
{ className: 'dsh-upload-preview-head' },
|
|
848
|
+
React.createElement('strong', null, preview.name),
|
|
849
|
+
React.createElement(
|
|
850
|
+
'div',
|
|
851
|
+
{ className: 'dsh-upload-preview-actions', style: { display: 'flex', gap: 8, alignItems: 'center' } },
|
|
852
|
+
!preview.officeLoading && !(preview.url && preview.url.startsWith('blob:')) && preview.name !== void 0
|
|
853
|
+
? React.createElement('a', { href: preview.officeHtml !== void 0 ? downloadUrl(preview.name) : previewUrl(preview.name), target: '_blank', rel: 'noopener noreferrer', className: 'dsh-upload-preview-open' }, '打开')
|
|
854
|
+
: null,
|
|
855
|
+
!preview.officeLoading && !(preview.url && preview.url.startsWith('blob:')) && preview.name !== void 0
|
|
856
|
+
? React.createElement('a', { href: downloadUrl(preview.name), download: preview.name, className: 'dsh-upload-preview-open' }, '下载')
|
|
857
|
+
: null,
|
|
858
|
+
React.createElement('button', { type: 'button', onClick: () => { const m = !previewMaximized; setPreviewMaximized(m); try { const el = document.querySelector('.dsh-upload-preview-card'); if (m && el && el.requestFullscreen) el.requestFullscreen(); else if (document.fullscreenElement) document.exitFullscreen() } catch (e) {} } }, previewMaximized ? '还原' : '放大'),
|
|
859
|
+
React.createElement('button', { type: 'button', className: 'dsh-upload-preview-del', disabled: deleting === preview.name, onClick: () => remove(preview.name) }, deleting === preview.name ? '删除中…' : '删除'),
|
|
860
|
+
React.createElement('button', { type: 'button', onClick: closePreview }, '关闭'),
|
|
861
|
+
),
|
|
862
|
+
),
|
|
863
|
+
preview.url && preview.url.startsWith('blob:')
|
|
864
|
+
? React.createElement('img', { src: preview.url, alt: preview.name, className: 'dsh-upload-preview-img' })
|
|
865
|
+
: React.createElement(
|
|
866
|
+
'div',
|
|
867
|
+
{ style: { display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0, background: '#fff' } },
|
|
868
|
+
preview.officeLoading === true
|
|
869
|
+
? React.createElement('div', { className: 'dsh-upload-preview-loading' }, '转换中…')
|
|
870
|
+
: preview.officeHtml !== void 0
|
|
871
|
+
? React.createElement('iframe', { title: preview.name, srcDoc: preview.officeHtml, style: previewMaximized ? { width: '100%', height: 'calc(100vh - 60px)', border: 'none', background: '#fff', flex: 1 } : { width: '100%', height: '70vh', border: 'none', background: '#fff' } })
|
|
872
|
+
: preview.url && /\.pdf$/i.test(preview.name)
|
|
873
|
+
? React.createElement('embed', { src: preview.url, type: 'application/pdf', title: preview.name, style: previewMaximized ? { width: '100%', height: 'calc(100vh - 60px)', border: 'none', background: '#fff', flex: 1 } : { width: '100%', height: '70vh', border: 'none', background: '#fff' } })
|
|
874
|
+
: React.createElement('iframe', { title: preview.name, src: preview.url, style: previewMaximized ? { width: '100%', height: 'calc(100vh - 60px)', border: 'none', background: '#fff', flex: 1 } : { width: '100%', height: '70vh', border: 'none', background: '#fff' } }),
|
|
875
|
+
),
|
|
876
|
+
),
|
|
877
|
+
)
|
|
878
|
+
: null,
|
|
879
|
+
React.createElement(
|
|
880
|
+
'div',
|
|
881
|
+
{ className: 'dsh-upload-list' },
|
|
882
|
+
(dateGroups === null ? [{ day: null, files: shownFiles }] : dateGroups).map((group) => React.createElement(
|
|
883
|
+
'div',
|
|
884
|
+
{ key: group.day ?? '__all__', className: 'dsh-upload-group' },
|
|
885
|
+
group.day !== null
|
|
886
|
+
? React.createElement(
|
|
887
|
+
'div',
|
|
888
|
+
{ className: 'dsh-upload-group-day' },
|
|
889
|
+
group.day,
|
|
890
|
+
React.createElement('span', null, `${group.files.length} 个文件`),
|
|
891
|
+
)
|
|
892
|
+
: null,
|
|
893
|
+
group.files.map((file) => React.createElement(
|
|
894
|
+
'div',
|
|
895
|
+
{ className: 'dsh-upload-row', key: file.name },
|
|
896
|
+
deleteEnabled && React.createElement('input', { type: 'checkbox', className: 'dsh-upload-select', checked: (selected || new Set()).has(file.name), onChange: (e) => { const s = new Set(selected || []); if (e.target.checked) s.add(file.name); else s.delete(file.name); setSelected(s) } }),
|
|
897
|
+
React.createElement(
|
|
898
|
+
'span',
|
|
899
|
+
{ className: 'dsh-upload-file-name', title: file.path },
|
|
900
|
+
React.createElement('span', { className: 'dsh-upload-file-label' }, file.name.replace(/\.[^.]+$/, '')),
|
|
901
|
+
React.createElement('span', { className: 'dsh-upload-file-type' }, fileTypeLabel(file.name)),
|
|
902
|
+
),
|
|
903
|
+
React.createElement('span', { className: 'dsh-upload-file-meta' }, `${sizeText(file.size)} · ${dateText(file.modifiedAt)}`),
|
|
904
|
+
React.createElement(
|
|
905
|
+
'div',
|
|
906
|
+
{ className: 'dsh-upload-actions' },
|
|
907
|
+
React.createElement(
|
|
908
|
+
'button',
|
|
909
|
+
{ type: 'button', className: 'dsh-upload-copy', onClick: () => {
|
|
910
|
+
const s = String(file.path || file.name)
|
|
911
|
+
const base = state.root ? String(state.root).replace(/\/[^/]*$/, '') : ''
|
|
912
|
+
copyText(base && s.indexOf(base) === 0 ? s.slice(base.length).replace(/^\/+/, '') : s)
|
|
913
|
+
} },
|
|
914
|
+
'复制路径',
|
|
915
|
+
),
|
|
916
|
+
React.createElement(
|
|
917
|
+
'button',
|
|
918
|
+
{ type: 'button', disabled: !deleteEnabled || deleting === file.name, onClick: () => remove(file.name) },
|
|
919
|
+
deleting === file.name ? '删除中…' : '删除',
|
|
920
|
+
),
|
|
921
|
+
React.createElement('a', { href: downloadUrl(file.name), download: file.name }, '下载'),
|
|
922
|
+
React.createElement('button', { type: 'button', className: 'dsh-upload-preview', onClick: () => previewFile(file.name) }, '预览'),
|
|
923
|
+
),
|
|
924
|
+
)),
|
|
925
|
+
)),
|
|
926
|
+
),
|
|
927
|
+
)
|
|
928
|
+
}
|
|
929
|
+
|
|
620
930
|
function apply(ctx) {
|
|
621
931
|
const mod = window.__dshLongMod || (() => true)
|
|
622
932
|
ctx.effect(() => {
|
|
@@ -627,6 +937,15 @@ window.__ModuleLoader__.load({
|
|
|
627
937
|
document.head.appendChild(style)
|
|
628
938
|
return () => style.remove()
|
|
629
939
|
}, 'dsh-long-plugins: uploads+workspace styles')
|
|
940
|
+
// 上传文件预览/管理设置区:由 uploadPreview 控制
|
|
941
|
+
if (mod('uploadPreview')) {
|
|
942
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
943
|
+
name: 'settings.section',
|
|
944
|
+
id: 'uploaded-files',
|
|
945
|
+
order: 30,
|
|
946
|
+
label: '上传文件',
|
|
947
|
+
}, UploadSettingsSection))
|
|
948
|
+
}
|
|
630
949
|
// 输出文件(工作区)设置区:由 workspace 控制
|
|
631
950
|
if (mod('workspace')) {
|
|
632
951
|
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
@@ -2297,6 +2616,7 @@ window.__ModuleLoader__.load({
|
|
|
2297
2616
|
.dsh-glass-note{margin:0;font-size:12px;color:var(--dsw-alias-label-tertiary)}
|
|
2298
2617
|
`
|
|
2299
2618
|
const DSH_LONG_MODULES = [
|
|
2619
|
+
['uploadPreview', '上传文件预览/管理'],
|
|
2300
2620
|
['skillDocs', '技能管理'],
|
|
2301
2621
|
['balance', '账户余额'],
|
|
2302
2622
|
['sessionCost', '会话成本'],
|
|
@@ -2374,9 +2694,25 @@ window.__ModuleLoader__.load({
|
|
|
2374
2694
|
|
|
2375
2695
|
// 模块开关缓存:页面加载时同步读取(让 main apply 的 modEnabled 门控生效);
|
|
2376
2696
|
// 配置加载(applyTheme)/保存(dsh-long 保存)后更新。开关在下次刷新生效。
|
|
2697
|
+
// 模块开关:以服务端为准(启动时同步拉取,避免 localStorage 缓存过期导致模块被误关)
|
|
2377
2698
|
try {
|
|
2378
|
-
const
|
|
2379
|
-
|
|
2699
|
+
const _x = new XMLHttpRequest()
|
|
2700
|
+
_x.open('GET', '/api/dsh-uploads/modules-config', false)
|
|
2701
|
+
_x.send()
|
|
2702
|
+
if (_x.status === 200) {
|
|
2703
|
+
const _b = JSON.parse(_x.responseText)
|
|
2704
|
+
if (_b && _b.cfg && _b.cfg.modules && typeof _b.cfg.modules === 'object') {
|
|
2705
|
+
window.__dshLongModules = _b.cfg.modules
|
|
2706
|
+
try { localStorage.setItem('dsh-long:modules', JSON.stringify(_b.cfg.modules)) } catch (e) {}
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
} catch (_) {}
|
|
2710
|
+
// 回退:服务端不可用时用 localStorage 缓存
|
|
2711
|
+
try {
|
|
2712
|
+
if (!window.__dshLongModules) {
|
|
2713
|
+
const _m = localStorage.getItem('dsh-long:modules')
|
|
2714
|
+
if (_m) window.__dshLongModules = JSON.parse(_m)
|
|
2715
|
+
}
|
|
2380
2716
|
} catch (_) {}
|
|
2381
2717
|
// 全局模块开关查询(供各组件/apply 内部判断)
|
|
2382
2718
|
window.__dshLongMod = (n) => (window.__dshLongModules ? window.__dshLongModules[n] !== false : true)
|
package/dsh.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-long-plugins",
|
|
3
3
|
"description": "Merged DSH web plugins: upload manager (drag-and-drop attach any file to the message), workspace output files, skill docs, account balance, Markdown-to-Word (md2docx), and polished file preview (Word & PowerPoint real preview with zoom/pan, rendered Markdown), plus an auto-repair install for DSH core patches (reverse-proxy WebSocket heartbeat, upgrade relink). | 一个插件整合 DSH Web 的常用增强:上传管理(拖放上传:拖任意文件到会话框直接附加)、工作区「输出文件」面板、技能文档浏览、账户余额显示、Markdown 转 Word(md2docx)、Word/PowerPoint 真实预览(可缩放、翻页);并自带修复安装,自动重连 DSH 核心补丁(反代 WebSocket 心跳、升级重连)。",
|
|
4
|
-
"version": "3.0.
|
|
4
|
+
"version": "3.0.2",
|
|
5
5
|
"entry": {
|
|
6
6
|
"name": "dsh-long-plugins",
|
|
7
7
|
"inject": [
|
package/lib/index.js
CHANGED
|
@@ -1649,7 +1649,7 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
|
|
|
1649
1649
|
};
|
|
1650
1650
|
|
|
1651
1651
|
// 各模块开关(dsh-long 设置区):读写 ~/.dsh-long-plugins/modules.json(独立于 dsh-span 的 RA-Span 配置)。
|
|
1652
|
-
const MODULES_DEFAULTS = { skillDocs: true, balance: true, mobile: true, workspace: true, turnRuler: false, sessionCost: false };
|
|
1652
|
+
const MODULES_DEFAULTS = { uploadPreview: true, skillDocs: true, balance: true, mobile: true, workspace: true, turnRuler: false, sessionCost: false };
|
|
1653
1653
|
const modulesFile = join(homedir(), ".dsh-long-plugins", "modules.json");
|
|
1654
1654
|
async function readModulesJSON() {
|
|
1655
1655
|
try {
|
|
@@ -2477,6 +2477,12 @@ export async function apply(ctx, config = {}) {
|
|
|
2477
2477
|
if (!isTrustedUploadRequest(req, trustedHosts)) throw new HttpError(403, "forbidden");
|
|
2478
2478
|
};
|
|
2479
2479
|
|
|
2480
|
+
ctx.effect(() => ctx.webServer.register({
|
|
2481
|
+
kind: "exact",
|
|
2482
|
+
path: API_PATH,
|
|
2483
|
+
handler: handlers.api,
|
|
2484
|
+
}), "dsh-long-plugins: upload/list/delete route");
|
|
2485
|
+
|
|
2480
2486
|
ctx.effect(() => ctx.webServer.register({
|
|
2481
2487
|
kind: "exact",
|
|
2482
2488
|
path: DOWNLOAD_PATH,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-long-plugins",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "Merged DSH web plugins: upload manager (drag-and-drop attach any file to the message), workspace output files, skill docs, account balance, and polished file preview (Word & PowerPoint real preview with zoom/pan, rendered Markdown), plus an auto-repair install for DSH core patches (reverse-proxy WebSocket heartbeat, upgrade relink). | 一个插件整合 DSH Web 的常用增强:上传管理(拖放上传:拖任意文件到会话框直接附加)、工作区「输出文件」面板、技能文档浏览、账户余额显示、Word/PowerPoint 真实预览(可缩放、翻页);并自带修复安装,自动重连 DSH 核心补丁(反代 WebSocket 心跳、升级重连)。Office 读取/生成已独立到 dsh-office-reader;RA-Span 已独立到 dsh-span。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|