Compare commits

..

5 Commits

Author SHA1 Message Date
bc9dd34425 fix FileViewer.vue layout 2025-07-11 20:18:48 +08:00
8e2f49b243 add preview style for FileViewer.vue tab 2025-07-11 20:12:08 +08:00
d885beb56d support preview img,audio,video for FileDetail.vue 2025-07-11 20:07:37 +08:00
54b2d27b81 support resize for FileDetail.vue 2025-07-11 00:27:59 +08:00
4e0a32701e fix repository log overflow container 2025-07-08 16:43:43 +08:00
7 changed files with 644 additions and 250 deletions

View File

@ -9,6 +9,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@marsio/vue-split-pane": "^1.0.0",
"less": "^4.3.0", "less": "^4.3.0",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"tdesign-vue-next": "^1.14.2", "tdesign-vue-next": "^1.14.2",
@ -33,8 +34,8 @@
"eslint-plugin-prettier": "^5.2.3", "eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-promise": "^7.2.1", "eslint-plugin-promise": "^7.2.1",
"eslint-plugin-vue": "^9.32.0", "eslint-plugin-vue": "^9.32.0",
"prettier": "^3.5.2",
"less": "^4.3.0", "less": "^4.3.0",
"prettier": "^3.5.2",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"unplugin-auto-import": "^19.3.0", "unplugin-auto-import": "^19.3.0",
"unplugin-vue-components": "^28.7.0", "unplugin-vue-components": "^28.7.0",

168
pnpm-lock.yaml generated
View File

@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
'@marsio/vue-split-pane':
specifier: ^1.0.0
version: 1.0.0(typescript@5.8.3)
less: less:
specifier: ^4.3.0 specifier: ^4.3.0
version: 4.3.0 version: 4.3.0
@ -667,6 +670,36 @@ packages:
resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
'@emotion/babel-plugin@11.13.5':
resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==}
'@emotion/cache@11.14.0':
resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==}
'@emotion/css@11.13.5':
resolution: {integrity: sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==}
'@emotion/hash@0.9.2':
resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
'@emotion/memoize@0.9.0':
resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==}
'@emotion/serialize@1.3.3':
resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==}
'@emotion/sheet@1.4.0':
resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
'@emotion/unitless@0.10.0':
resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
'@emotion/utils@1.4.2':
resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==}
'@emotion/weak-memoize@0.4.0':
resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==}
'@esbuild/aix-ppc64@0.25.5': '@esbuild/aix-ppc64@0.25.5':
resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -878,6 +911,12 @@ packages:
'@leichtgewicht/ip-codec@2.0.5': '@leichtgewicht/ip-codec@2.0.5':
resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==}
'@marsio/vue-draggable@1.0.9':
resolution: {integrity: sha512-gjj4cgFd9uQUdTMU3mbNKarASb4UAPH4D9DL4WicsjLtCjAXbthpls0GtfMLuPlfmFAhBjhQAzKMhpGDIEa9VA==}
'@marsio/vue-split-pane@1.0.0':
resolution: {integrity: sha512-7XEaVDjVhnoYSVcbgPvONT/U3F6n/oKbcNaj0i+lE2EfP2P38kIKyhHbSgbSGx+8i/DAP5vQtcjLIcleWb4N+A==}
'@microsoft/api-extractor-model@7.30.6': '@microsoft/api-extractor-model@7.30.6':
resolution: {integrity: sha512-znmFn69wf/AIrwHya3fxX6uB5etSIn6vg4Q4RB/tb5VDDs1rqREc+AvMC/p19MUN13CZ7+V/8pkYPTj7q8tftg==} resolution: {integrity: sha512-znmFn69wf/AIrwHya3fxX6uB5etSIn6vg4Q4RB/tb5VDDs1rqREc+AvMC/p19MUN13CZ7+V/8pkYPTj7q8tftg==}
@ -1699,6 +1738,10 @@ packages:
babel-plugin-dynamic-import-node@2.3.3: babel-plugin-dynamic-import-node@2.3.3:
resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==} resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==}
babel-plugin-macros@3.1.0:
resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
engines: {node: '>=10', npm: '>=6'}
babel-plugin-polyfill-corejs2@0.4.12: babel-plugin-polyfill-corejs2@0.4.12:
resolution: {integrity: sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==} resolution: {integrity: sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==}
peerDependencies: peerDependencies:
@ -1880,6 +1923,10 @@ packages:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'} engines: {node: '>=0.8'}
clsx@1.2.1:
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
engines: {node: '>=6'}
color-convert@1.9.3: color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
@ -2111,6 +2158,9 @@ packages:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
convert-source-map@1.9.0:
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
convert-source-map@2.0.0: convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
@ -2154,6 +2204,9 @@ packages:
peerDependencies: peerDependencies:
postcss: ^8.0.9 postcss: ^8.0.9
css-in-js-utils@3.1.0:
resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==}
css-loader@6.11.0: css-loader@6.11.0:
resolution: {integrity: sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==} resolution: {integrity: sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==}
engines: {node: '>= 12.13.0'} engines: {node: '>= 12.13.0'}
@ -2632,6 +2685,9 @@ packages:
resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
engines: {node: '>=8'} engines: {node: '>=8'}
find-root@1.1.0:
resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
find-up@4.1.0: find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -2867,6 +2923,9 @@ packages:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'} engines: {node: '>=10.17.0'}
hyphenate-style-name@1.1.0:
resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==}
iconv-lite@0.4.24: iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2919,6 +2978,9 @@ packages:
inherits@2.0.4: inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
inline-style-prefixer@7.0.1:
resolution: {integrity: sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==}
ipaddr.js@1.9.1: ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
@ -4105,6 +4167,10 @@ packages:
source-map-support@0.5.21: source-map-support@0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
source-map@0.5.7:
resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
engines: {node: '>=0.10.0'}
source-map@0.6.1: source-map@0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -4201,6 +4267,9 @@ packages:
peerDependencies: peerDependencies:
postcss: ^8.2.15 postcss: ^8.2.15
stylis@4.2.0:
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
supports-color@5.5.0: supports-color@5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -5535,6 +5604,60 @@ snapshots:
'@discoveryjs/json-ext@0.5.7': {} '@discoveryjs/json-ext@0.5.7': {}
'@emotion/babel-plugin@11.13.5':
dependencies:
'@babel/helper-module-imports': 7.25.9
'@babel/runtime': 7.26.9
'@emotion/hash': 0.9.2
'@emotion/memoize': 0.9.0
'@emotion/serialize': 1.3.3
babel-plugin-macros: 3.1.0
convert-source-map: 1.9.0
escape-string-regexp: 4.0.0
find-root: 1.1.0
source-map: 0.5.7
stylis: 4.2.0
transitivePeerDependencies:
- supports-color
'@emotion/cache@11.14.0':
dependencies:
'@emotion/memoize': 0.9.0
'@emotion/sheet': 1.4.0
'@emotion/utils': 1.4.2
'@emotion/weak-memoize': 0.4.0
stylis: 4.2.0
'@emotion/css@11.13.5':
dependencies:
'@emotion/babel-plugin': 11.13.5
'@emotion/cache': 11.14.0
'@emotion/serialize': 1.3.3
'@emotion/sheet': 1.4.0
'@emotion/utils': 1.4.2
transitivePeerDependencies:
- supports-color
'@emotion/hash@0.9.2': {}
'@emotion/memoize@0.9.0': {}
'@emotion/serialize@1.3.3':
dependencies:
'@emotion/hash': 0.9.2
'@emotion/memoize': 0.9.0
'@emotion/unitless': 0.10.0
'@emotion/utils': 1.4.2
csstype: 3.1.3
'@emotion/sheet@1.4.0': {}
'@emotion/unitless@0.10.0': {}
'@emotion/utils@1.4.2': {}
'@emotion/weak-memoize@0.4.0': {}
'@esbuild/aix-ppc64@0.25.5': '@esbuild/aix-ppc64@0.25.5':
optional: true optional: true
@ -5675,6 +5798,25 @@ snapshots:
'@leichtgewicht/ip-codec@2.0.5': {} '@leichtgewicht/ip-codec@2.0.5': {}
'@marsio/vue-draggable@1.0.9(typescript@5.8.3)':
dependencies:
clsx: 1.2.1
lodash: 4.17.21
vue: 3.5.17(typescript@5.8.3)
transitivePeerDependencies:
- typescript
'@marsio/vue-split-pane@1.0.0(typescript@5.8.3)':
dependencies:
'@emotion/css': 11.13.5
'@marsio/vue-draggable': 1.0.9(typescript@5.8.3)
inline-style-prefixer: 7.0.1
lodash: 4.17.21
vue: 3.5.17(typescript@5.8.3)
transitivePeerDependencies:
- supports-color
- typescript
'@microsoft/api-extractor-model@7.30.6(@types/node@22.15.33)': '@microsoft/api-extractor-model@7.30.6(@types/node@22.15.33)':
dependencies: dependencies:
'@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc': 0.15.1
@ -6821,6 +6963,12 @@ snapshots:
dependencies: dependencies:
object.assign: 4.1.7 object.assign: 4.1.7
babel-plugin-macros@3.1.0:
dependencies:
'@babel/runtime': 7.26.9
cosmiconfig: 7.1.0
resolve: 1.22.10
babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.26.9): babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.26.9):
dependencies: dependencies:
'@babel/compat-data': 7.26.8 '@babel/compat-data': 7.26.8
@ -7054,6 +7202,8 @@ snapshots:
clone@1.0.4: {} clone@1.0.4: {}
clsx@1.2.1: {}
color-convert@1.9.3: color-convert@1.9.3:
dependencies: dependencies:
color-name: 1.1.3 color-name: 1.1.3
@ -7116,6 +7266,8 @@ snapshots:
content-type@1.0.5: {} content-type@1.0.5: {}
convert-source-map@1.9.0: {}
convert-source-map@2.0.0: {} convert-source-map@2.0.0: {}
cookie-signature@1.0.6: {} cookie-signature@1.0.6: {}
@ -7168,6 +7320,10 @@ snapshots:
dependencies: dependencies:
postcss: 8.5.6 postcss: 8.5.6
css-in-js-utils@3.1.0:
dependencies:
hyphenate-style-name: 1.1.0
css-loader@6.11.0(webpack@5.98.0): css-loader@6.11.0(webpack@5.98.0):
dependencies: dependencies:
icss-utils: 5.1.0(postcss@8.5.6) icss-utils: 5.1.0(postcss@8.5.6)
@ -7719,6 +7875,8 @@ snapshots:
make-dir: 3.1.0 make-dir: 3.1.0
pkg-dir: 4.2.0 pkg-dir: 4.2.0
find-root@1.1.0: {}
find-up@4.1.0: find-up@4.1.0:
dependencies: dependencies:
locate-path: 5.0.0 locate-path: 5.0.0
@ -7956,6 +8114,8 @@ snapshots:
human-signals@2.1.0: {} human-signals@2.1.0: {}
hyphenate-style-name@1.1.0: {}
iconv-lite@0.4.24: iconv-lite@0.4.24:
dependencies: dependencies:
safer-buffer: 2.1.2 safer-buffer: 2.1.2
@ -7996,6 +8156,10 @@ snapshots:
inherits@2.0.4: {} inherits@2.0.4: {}
inline-style-prefixer@7.0.1:
dependencies:
css-in-js-utils: 3.1.0
ipaddr.js@1.9.1: {} ipaddr.js@1.9.1: {}
ipaddr.js@2.2.0: {} ipaddr.js@2.2.0: {}
@ -9163,6 +9327,8 @@ snapshots:
buffer-from: 1.1.2 buffer-from: 1.1.2
source-map: 0.6.1 source-map: 0.6.1
source-map@0.5.7: {}
source-map@0.6.1: {} source-map@0.6.1: {}
sourcemap-codec@1.4.8: {} sourcemap-codec@1.4.8: {}
@ -9261,6 +9427,8 @@ snapshots:
postcss: 8.5.6 postcss: 8.5.6
postcss-selector-parser: 6.1.2 postcss-selector-parser: 6.1.2
stylis@4.2.0: {}
supports-color@5.5.0: supports-color@5.5.0:
dependencies: dependencies:
has-flag: 3.0.0 has-flag: 3.0.0

View File

@ -21,14 +21,26 @@ async function listFile(name: string, branch: string, path: string): Promise<Fil
return axios.get(`${BASE_URI}/${name}:${branch}/file/list${path}`); return axios.get(`${BASE_URI}/${name}:${branch}/file/list${path}`);
} }
async function fileRaw(name: string, branch: string, path: string): Promise<ArrayBuffer> { function fileRawURL(name: string, branch: string, path: string): string {
return `${API_HOST}${BASE_URI}/${name}:${branch}/file/raw${path}`;
}
async function fileRawByURL(rawURL: string): Promise<ArrayBuffer> {
return axios({ return axios({
url: `${BASE_URI}/${name}:${branch}/file/raw${path}`, url: rawURL,
method: "GET", method: "GET",
responseType: "arraybuffer" responseType: "arraybuffer"
}); });
} }
async function fileRaw(name: string, branch: string, path: string): Promise<ArrayBuffer> {
return fileRawByURL(fileRawURL(name, branch, path));
}
async function fileMimeType(name: string, branch: string, path: string): Promise<string> {
return axios.get(`${BASE_URI}/${name}:${branch}/file/mime${path}`);
}
function downloadArchive(name: string, branch: string) { function downloadArchive(name: string, branch: string) {
window.open(`${API_HOST}${BASE_URI}/${name}:${branch}/archive`); window.open(`${API_HOST}${BASE_URI}/${name}:${branch}/archive`);
} }
@ -43,6 +55,9 @@ export default {
pagePush, pagePush,
listFile, listFile,
fileRaw, fileRaw,
fileRawURL,
fileRawByURL,
fileMimeType,
downloadArchive, downloadArchive,
pageLog pageLog
}; };

View File

@ -0,0 +1,363 @@
<template>
<div class="file-viewer diselect">
<t-tabs
class="tabs"
theme="card"
v-model="filePath"
:onRemove="onTabRemove"
>
<t-tab-panel
v-for="file in files"
:key="file.path"
:value="file.path"
:label="file.name"
:removable="file.removable"
@click="file.isPreview = false"
>
<template #label>
<span
class="black"
:class="{ 'light-gray': file.isPreview }"
v-text="file.name"
@click="file.isPreview = false"
></span>
</template>
</t-tab-panel>
</t-tabs>
<div v-if="file" :class="`viewer ${clazz}`" @click="file.isPreview = false">
<loading class="loading" :showOn="isLoading" />
<div
v-if="file.viewerType === FileViewerType.NOT_SUPPORT"
class="not-support"
>
<p class="tips">此文件不支持预览</p>
<t-button
v-if="file.rawFile.size < IOSize.MB"
@click="previewAsText"
>尝试以文本文件解析预览</t-button>
</div>
<markdown-view
v-if="file.viewerType === FileViewerType.MARKDOWN || file.viewerType === FileViewerType.CODE"
class="content selectable"
:show-code-border="false"
max-height="auto"
:content="file.data"
/>
<div
v-if="file.viewerType === FileViewerType.TEXT"
class="container selectable"
>
<div class="value" v-text="file.data"></div>
</div>
<div v-if="mediaComponent && file.rawURL" class="container">
<component
:is="mediaComponent"
class="value"
:src="file.rawURL"
:controls="true"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { IOSize, Loading, MarkdownView, Prismjs, PrismjsViewer, Toolkit } from "timi-web";
import { File, FileType, FileViewerType, OpenFile } from "@/types/Repository.ts";
import RepositoryAPI from "@/api/RepositoryAPI.ts";
defineOptions({
name: "FileViewer"
});
const props = withDefaults(defineProps<{
repoName: string;
branch: string;
}>(), {
});
const { repoName, branch } = toRefs(props);
const isLoading = ref<boolean>(false);
const files = ref<OpenFile[]>([]);
const file = ref<OpenFile>();
const filePath = ref<string>();
watch(file, file => filePath.value = file?.path);
const clazz = computed(() => {
if (file.value && file.value.viewerType) {
return Toolkit.className(file.value.viewerType.toString().toLowerCase());
}
return "";
});
// ---------- 切换分支 ----------
watch(branch, () => {
files.value = [];
file.value = undefined;
});
// ---------- 打开文件 ----------
function openFile(treeFile: File) {
if (treeFile.type === FileType.FILE) {
// 已打开该文件
let opened = files.value.find(item => item.path === treeFile.path);
const isReadme = treeFile.path === "README.md";
if (!opened) {
opened = {
path: treeFile.path,
name: treeFile.name,
rawURL: RepositoryAPI.fileRawURL(repoName.value, branch.value, `/${treeFile.path}`),
rawFile: treeFile,
isPreview: !isReadme,
removable: !isReadme
} as OpenFile;
// 如果存在已打开预览,则替换该预览项
const previewIndex = files.value.findIndex(item => item.isPreview);
if (previewIndex === -1) {
files.value.push(opened);
} else {
files.value.splice(previewIndex, 1, opened);
}
}
opened.activatedAt = new Date().getTime();
file.value = opened;
}
}
defineExpose({
openFile
});
// ---------- 切换文件 ----------
// 从 tab 切换文件
watch(filePath, value => {
if (filePath.value) {
const targetFile = files.value.find(item => item.path === value);
if (targetFile) {
targetFile.activatedAt = new Date().getTime();
file.value = targetFile;
return;
}
}
file.value = undefined;
});
watch(file, async () => {
if (file.value) {
if (!file.value.mimeType) {
isLoading.value = true;
// 获取 Mime-Type
file.value.mimeType = await RepositoryAPI.fileMimeType(repoName.value, branch.value, `/${file.value.path}`);
}
file.value.prismjsType = file.value.prismjsType ?? Prismjs.typeFromFileName(file.value.name);
if (file.value.prismjsType) {
if (!file.value.raw) {
isLoading.value = true;
file.value.raw = await RepositoryAPI.fileRawByURL(file.value.rawURL);
}
// 解码为文本
const properties = Prismjs.getFileProperties(file.value.prismjsType);
if (properties) {
if (!file.value.data) {
isLoading.value = true;
const data = new TextDecoder("utf-8").decode(file.value.raw);
if (properties.viewer === PrismjsViewer.CODE) {
file.value.data = Toolkit.format("```${type}\n${data}\n```", {
type: properties.prismjs,
data
});
} else {
file.value.data = data;
}
}
if (properties.viewer === PrismjsViewer.MARKDOWN) {
file.value.viewerType = FileViewerType.MARKDOWN;
} else if (properties.viewer === PrismjsViewer.CODE) {
file.value.viewerType = FileViewerType.CODE;
} else {
file.value.viewerType = FileViewerType.TEXT;
}
}
} else {
if (!file.value.viewerType) {
if (file.value.mimeType) {
switch (true) {
case file.value.mimeType.startsWith("image/"):
file.value.viewerType = FileViewerType.IMAGE;
break;
case file.value.mimeType.startsWith("audio/"):
file.value.viewerType = FileViewerType.AUDIO;
break;
case file.value.mimeType.startsWith("video/"):
file.value.viewerType = FileViewerType.VIDEO;
break;
case file.value.mimeType.startsWith("text/"):
file.value.viewerType = FileViewerType.TEXT;
if (!file.value.data) {
if (!file.value.raw) {
isLoading.value = true;
file.value.raw = await RepositoryAPI.fileRawByURL(file.value.rawURL);
}
file.value.data = new TextDecoder("utf-8").decode(file.value.raw);
}
break;
default:
file.value.viewerType = FileViewerType.NOT_SUPPORT;
}
} else {
file.value.viewerType = FileViewerType.NOT_SUPPORT;
}
}
}
isLoading.value = false;
}
});
// ---------- 关闭文件 ----------
const onTabRemove = (options: { index: number }) => {
files.value.splice(options.index, 1);
if (0 < files.value.length) {
file.value = files.value[files.value.length - 1];
} else {
file.value = undefined;
}
};
// ---------- 尝试以文本文件解码预览 ----------
async function previewAsText() {
if (file.value) {
// 解码为文本
if (!file.value.data) {
if (!file.value.raw) {
file.value.raw = await RepositoryAPI.fileRawByURL(file.value.rawURL);
}
file.value.data = new TextDecoder("utf-8").decode(file.value.raw);
}
file.value.viewerType = FileViewerType.TEXT;
}
}
// ---------- 媒体文件 ----------
const mediaComponent = computed(() => {
if (file.value) {
switch (file.value.viewerType) {
case FileViewerType.IMAGE:
return "img";
case FileViewerType.VIDEO:
return FileViewerType.VIDEO.toString().toLowerCase();
case FileViewerType.AUDIO:
return FileViewerType.AUDIO.toString().toLowerCase();
}
}
return undefined;
});
</script>
<style scoped lang="less">
.file-viewer {
height: 100%;
.tabs {
top: calc(50px + 49px + 30px);
position: sticky;
z-index: 9;
border-top: var(--tui-border);
border-bottom: var(--tui-border);
}
.viewer {
height: calc(100% - 33px);
&.text {
.container {
width: calc(100% - 8px);
min-height: calc(100% - 4px);
padding: 2px 4px;
overflow: auto;
.value {
font-size: 14px;
white-space: pre;
}
}
}
&.code {
:deep(.tui-markdown-view) {
height: 100%;
position: relative;
&::before {
content: "";
top: 0;
left: 0;
width: 3em;
height: 100%;
position: absolute;
background: rgba(242, 242, 242, .9);
border-right: 1px solid #999;
}
pre[class^="language-"] {
height: 100%;
}
}
:deep(pre[class*="language-"]) {
margin: 0;
border: none;
}
}
&.markdown {
height: 100%;
padding: .5rem 1rem;
}
&.image,
&.audio,
&.video {
.container {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
audio {
width: 40rem;
margin: 0 2rem;
}
video {
width: 100%;
height: 100%;
max-height: 60vh;
object-fit: contain;
background: #000;
}
.value {
max-width: 100%;
}
}
}
.loading {
margin-top: 2rem;
}
.not-support {
text-align: center;
}
}
}
</style>

View File

@ -1,4 +1,4 @@
import { Model, Page, UserView } from "timi-web"; import { Model, Page, PrismjsType, UserView } from "timi-web";
export type Repository = { export type Repository = {
id: number; id: number;
@ -61,3 +61,30 @@ export type File = {
children: boolean; children: boolean;
} }
export enum FileViewerType {
NOT_SUPPORT = "NOT_SUPPORT",
AUDIO = "AUDIO",
VIDEO = "VIDEO",
IMAGE = "IMAGE",
TEXT = "TEXT",
CODE = "CODE",
MARKDOWN = "MARKDOWN",
}
export type OpenFile = {
path: string;
name: string;
raw?: ArrayBuffer;
rawURL: string;
rawFile: File;
data?: string;
removable: boolean;
isPreview: boolean;
activatedAt?: number;
mimeType?: string;
viewerType?: FileViewerType;
prismjsType?: PrismjsType;
}

View File

@ -38,7 +38,7 @@ onMounted(doFetchEvent);
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.repository-log { .repository-log {
width: 100%; width: calc(100% - 4rem);
padding: 1rem 2rem; padding: 1rem 2rem;
} }
</style> </style>

View File

@ -1,6 +1,10 @@
<template> <template>
<div class="file-detail" v-if="repositoryStore.value && repositoryStore.value.branchList"> <split-pane
<div class="left"> class="file-detail"
v-if="repositoryStore.value && repositoryStore.value.branchList"
split="vertical"
>
<pane class="left" initialSize="25%" minSize="10%">
<t-select class="branch" v-model="branch" label="分支:" :borderless="true"> <t-select class="branch" v-model="branch" label="分支:" :borderless="true">
<t-option <t-option
v-for="item in repositoryStore.value.branchList" v-for="item in repositoryStore.value.branchList"
@ -16,6 +20,7 @@
:data="treeItems" :data="treeItems"
:keys="treeMap" :keys="treeMap"
:line="true" :line="true"
:label="treeLabel"
:activable="true" :activable="true"
:transition="false" :transition="false"
:active-multiple="false" :active-multiple="false"
@ -32,9 +37,9 @@
</template> </template>
</t-tree> </t-tree>
</div> </div>
</div> </pane>
<div class="right"> <pane class="right" minSize="50%">
<div class="header"> <div class="header bg-white">
<div class="clone-label">克隆地址</div> <div class="clone-label">克隆地址</div>
<t-select class="clone-type" v-model="cloneType" borderless> <t-select class="clone-type" v-model="cloneType" borderless>
<t-option <t-option
@ -54,60 +59,24 @@
<t-button theme="success" @click="doCopyCloneAddr">复制</t-button> <t-button theme="success" @click="doCopyCloneAddr">复制</t-button>
<t-button @click="RepositoryAPI.downloadArchive(repositoryStore.value.name, branch)">下载源码</t-button> <t-button @click="RepositoryAPI.downloadArchive(repositoryStore.value.name, branch)">下载源码</t-button>
</div> </div>
<t-tabs <file-viewer
class="tabs" v-if="branch"
theme="card" ref="fileViewerRef"
v-model="tabActivated" :repo-name="repositoryStore.value!.name"
:onRemove="onTabRemove" :branch="branch"
>
<t-tab-panel
v-for="file in tabFiles"
:key="file.path"
:value="file.path"
:label="file.name"
:removable="file.removable"
@click="onClickOpenFile(file)"
>
<template #label>
<span
v-text="file.name"
@click="onClickOpenFile(file)"
></span>
</template>
</t-tab-panel>
</t-tabs>
<div v-if="tabActivatedFileViewer"
:class="`viewer ${viewerClass}`"
@click="onClickViewer"
>
<markdown-view
v-if="tabActivatedFile && tabActivatedFileViewer !== PrismjsViewer.TEXT"
class="content selectable"
:show-code-border="false"
max-height="auto"
:content="tabActivatedFile.data"
@click="onClickViewer"
/> />
<textarea </pane>
v-if="tabActivatedFile && tabActivatedFileViewer === PrismjsViewer.TEXT" </split-pane>
class="content selectable"
:value="tabActivatedFile.data"
readonly
></textarea>
</div>
<div v-else class="viewer not-support">
<div class="content">不支持预览</div>
</div>
</div>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Icon, MarkdownView, Prismjs, PrismjsType, PrismjsViewer, Toolkit } from "timi-web"; import { Icon } from "timi-web";
import type { TreeInstanceFunctions, TreeProps } from "tdesign-vue-next"; import type { TreeInstanceFunctions, TreeProps } from "tdesign-vue-next";
import RepositoryAPI from "@/api/RepositoryAPI"; import RepositoryAPI from "@/api/RepositoryAPI";
import { File, FileType } from "@/types/Repository"; import { File, FileType } from "@/types/Repository";
import { useRepositoryStore } from "@/store/repository.ts"; import { useRepositoryStore } from "@/store/repository.ts";
import useClipboard from "vue-clipboard3"; import useClipboard from "vue-clipboard3";
import SplitPane, { Pane } from "@marsio/vue-split-pane";
import FileViewer from "@/components/FileViewer.vue";
const router = useRouter(); const router = useRouter();
const { toClipboard } = useClipboard(); const { toClipboard } = useClipboard();
@ -115,6 +84,14 @@ const repositoryStore = useRepositoryStore();
// ---------- 分支 ---------- // ---------- 分支 ----------
const branch = ref(); const branch = ref();
watch(branch, async () => {
if (!branch.value) {
return;
}
await router.replace(`/${repositoryStore.value!.name}/${branch.value}`);
});
onMounted(async () => { onMounted(async () => {
if (repositoryStore.value && repositoryStore.value.defaultBranch) { if (repositoryStore.value && repositoryStore.value.defaultBranch) {
branch.value = repositoryStore.value.defaultBranch; branch.value = repositoryStore.value.defaultBranch;
@ -141,26 +118,28 @@ watch(branch, async () => {
const items = treeItems.value; const items = treeItems.value;
for (const item of items) { for (const item of items) {
if (item.path === "README.md") { if (item.path === "README.md") {
await doOpenFile(tree.value.getItem(item.path).data as File); doOpenFile(tree.value.getItem(item.path).data as File);
break; break;
} }
} }
} }
}); });
const treeLabel: TreeProps["label"] = (_h, node) => node.data.name;
const treeLoad: TreeProps["load"] = node => { const treeLoad: TreeProps["load"] = async (node) => {
return new Promise((resolve) => {
Toolkit.async(async () => {
const file = node.data; const file = node.data;
const result = await RepositoryAPI.listFile( const result = await RepositoryAPI.listFile(
repositoryStore.value!.name, repositoryStore.value!.name,
branch.value, branch.value,
`/${file.path}` `/${file.path}`
); );
if (result.length === 1 && result[0].type === FileType.DIRECTORY) {
const deep = result[0];
file.name = `${file.name}/${deep.name}`;
file.path = `${file.path}/${deep.name}`;
return await treeLoad!(node);
}
result.forEach((item) => item.children = item.type === FileType.DIRECTORY); result.forEach((item) => item.children = item.type === FileType.DIRECTORY);
resolve(result); return result;
});
});
}; };
// ---------- 克隆地址 ---------- // ---------- 克隆地址 ----------
@ -185,134 +164,37 @@ async function doCopyCloneAddr() {
// ---------- 打开文件 ---------- // ---------- 打开文件 ----------
type OpenFile = { const fileViewerRef = ref<InstanceType<typeof FileViewer>>();
path: string; function doOpenFile(file: File) {
name: string; fileViewerRef.value!.openFile(file);
data?: string;
removable: boolean;
isPreview: boolean;
activatedAt?: number;
prismjsType?: PrismjsType;
} }
const tabFiles = ref<OpenFile[]>([]);
const tabActivated = ref();
const tabActivatedFile = ref<OpenFile>();
const tabActivatedFileViewer = ref<PrismjsViewer>();
const viewerClass = computed(() => {
if (tabActivatedFileViewer.value) {
return Toolkit.className(tabActivatedFileViewer.value.toString().toLowerCase());
}
return "";
});
watch(branch, async () => {
if (!branch.value) {
return;
}
await router.replace(`/${repositoryStore.value!.name}/${branch.value}`);
tabFiles.value = [];
tabActivated.value = null;
});
watch(tabActivated, value => {
if (tabActivated.value) {
const targetFile = tabFiles.value.find(item => item.path === value);
if (targetFile) {
targetFile.activatedAt = new Date().getTime();
tabActivatedFile.value = targetFile;
return;
}
}
tabActivatedFile.value = undefined;
});
watch(tabActivatedFile, () => {
if (tabActivatedFile.value && tabActivatedFile.value.prismjsType) {
const properties = Prismjs.getFileProperties(tabActivatedFile.value.prismjsType);
if (properties) {
tabActivatedFileViewer.value = properties.viewer;
return;
}
}
tabActivatedFileViewer.value = undefined;
});
const doOpenFile = async (file: File) => {
if (file.type === FileType.FILE) {
let opened = tabFiles.value.find(item => item.path === file.path);
const previewIndex = tabFiles.value.findIndex(item => item.isPreview);
const isReadme = file.path === "README.md";
if (!opened) {
opened = {
path: file.path,
name: file.name,
isPreview: !isReadme,
removable: !isReadme,
prismjsType: PrismjsType.PlainText
};
if (previewIndex === -1) {
tabFiles.value.push(opened);
} else {
// 如果存在已打开预览,则替换该预览项
tabFiles.value.splice(previewIndex, 1, opened);
}
const raw = await RepositoryAPI.fileRaw(repositoryStore.value!.name, branch.value, `/${opened.path}`);
const decoder = new TextDecoder("utf-8");
opened.prismjsType = Prismjs.typeFromFileName(opened.name);
if (opened.prismjsType) {
const properties = Prismjs.getFileProperties(opened.prismjsType);
if (properties && properties.viewer === PrismjsViewer.CODE) {
opened.data = "```" + properties.prismjs + "\n" + decoder.decode(raw) + "\n```";
} else {
opened.data = decoder.decode(raw);
}
} else {
opened.data = "该文件暂不支持预览";
}
}
opened.activatedAt = new Date().getTime();
tabActivated.value = file.path;
tabActivatedFile.value = opened;
}
};
// TODO 明确类型会构建失败
const onTreeActivated = (_value: any, context: { node: any }) => { const onTreeActivated = (_value: any, context: { node: any }) => {
doOpenFile(context.node.data); doOpenFile(context.node.data);
}; };
const onTabRemove = (options: { index: number }) => {
tabFiles.value.splice(options.index, 1);
if (0 < tabFiles.value.length) {
tabActivated.value = tabFiles.value[tabFiles.value.length - 1].path;
} else {
tabActivated.value = null;
}
};
const onClickOpenFile = (openFile: OpenFile) => {
openFile.isPreview = false;
};
const onClickViewer = async () => {
const activated = tabFiles.value.find(item => item.path === tabActivated.value);
if (activated) {
activated.isPreview = false;
}
};
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.file-detail { .file-detail {
// 100% 可视高度 - 上下外边距 - 边框 - 顶部 - 菜单 - 底部
height: calc(100vh - 128px * 2 - 2px - 50px - 50px - 94px);
display: flex; display: flex;
overflow: unset;
min-height: 520px;
:deep(.vue-splitpane-resizer) {
opacity: 1;
background: var(--tui-light-gray) padding-box;
}
.left { .left {
width: 16rem; width: 100%;
z-index: 4; height: 100% !important;
display: flex; display: flex;
position: relative;
flex-direction: column; flex-direction: column;
.branch { .branch {
top: calc(50px + 49px);
z-index: 1;
position: sticky;
border-bottom: var(--tui-border); border-bottom: var(--tui-border);
:deep(.t-select-input--borderless) { :deep(.t-select-input--borderless) {
@ -336,8 +218,8 @@ const onClickViewer = async () => {
} }
.tree-container { .tree-container {
height: 100%; top: calc(50px + 49px + 30px);
overflow: auto; position: sticky;
.tree { .tree {
@ -360,7 +242,7 @@ const onClickViewer = async () => {
.right { .right {
width: calc(100% - 16rem); width: calc(100% - 16rem);
display: flex; display: flex;
border-left: var(--tui-border); max-height: 100% !important;
flex-direction: column; flex-direction: column;
.header { .header {
@ -395,68 +277,6 @@ const onClickViewer = async () => {
} }
} }
} }
.tabs {
border-top: var(--tui-border);
:deep(.t-tabs__nav-container.t-tabs__nav--card::after) {
background: var(--tui-light-gray);
}
}
.viewer {
height: 100%;
overflow: auto;
&.text {
.content {
width: calc(100% - 8px);
height: calc(100% - 4px);
border: none;
padding: 2px 4px;
font-size: 14px;
word-wrap: normal;
}
}
&.code {
:deep(.tui-markdown-view) {
height: 100%;
position: relative;
&::before {
content: "";
top: 0;
left: 0;
width: 3em;
height: 100%;
position: absolute;
background: rgba(242, 242, 242, .9);
border-right: 1px solid #999;
}
pre[class^="language-"] {
height: 100%;
}
}
}
&.markdown {
height: 100%;
padding: .5rem 1rem;
}
&.not-support {
text-align: center;
}
:deep(pre[class*="language-"]) {
margin: 0;
border: none;
}
}
} }
} }