Browse Source

支持上传文件

pull/16/head
hole-thu 3 years ago
parent
commit
a884451a74
  1. 1
      package.json
  2. 27
      src/Config.js
  3. 8
      src/UserAction.css
  4. 213
      src/UserAction.js
  5. 4
      src/flows_api.js

1
package.json

@ -4,7 +4,6 @@
"private": true,
"dependencies": {
"copy-to-clipboard": "^3.3.1",
"fix-orientation": "^1.1.0",
"gh-pages": "^3.0.0",
"highlight.js": "^10.1.1",
"html-to-react": "^1.4.3",

27
src/Config.js

@ -29,10 +29,9 @@ const DEFAULT_CONFIG = {
pressure: false,
easter_egg: true,
color_scheme: 'default',
no_c_post: false,
by_c: false,
block_words_v2: ['#天火', '#桃花石'],
whitelist_cw: [],
ipfs_gateway: ['https://<hash>.ipfs.dweb.link/'],
};
export function load_config() {
@ -386,6 +385,16 @@ export class ConfigUI extends PureComponent {
parse={(string) => string.split('\n')}
/>
<hr />
<ConfigTextArea
id="ipfs_gateway"
callback={this.save_changes_bound}
name="默认ipfs网关"
description={'<hash>表示要替换的哈希值。只会使用第一行的。'}
display={(array) => array.join('\n')}
sift={(array) => array.filter((v) => v)}
parse={(string) => string.split('\n')}
/>
<hr />
<ConfigSwitch
callback={this.save_changes_bound}
id="pressure"
@ -400,20 +409,6 @@ export class ConfigUI extends PureComponent {
description="在某些情况下显示彩蛋"
/>
<hr />
<ConfigSwitch
callback={this.save_changes_bound}
id="no_c_post"
name="忽略折叠的树洞"
description="不获取所有带折叠警告的树洞,折叠警告豁免将不起作用"
/>
<hr />
<ConfigSwitch
callback={this.save_changes_bound}
id="by_c"
name="根据最新回复排序"
description="有最新回复的洞在最上面"
/>
<hr />
<p>
新功能建议或问题反馈请在&nbsp;
<a

8
src/UserAction.css

@ -48,9 +48,11 @@
margin: 0 0.5rem;
}
.post-form-bar input[type='file'] {
border: 0;
padding: 0 0 0 0.5em;
.post-form .file-input {
border: none;
width: 100px;
padding: 0 0 0.5em 0.5em;
color: transparent;
}
@media screen and (max-width: 580px) {

213
src/UserAction.js

@ -9,7 +9,6 @@ import { MessageViewer } from './Message';
import { LoginPopup } from './infrastructure/widgets';
import { ColorPicker } from './color_picker';
import { ConfigUI } from './Config';
import fixOrientation from 'fix-orientation';
import copy from 'copy-to-clipboard';
import { cache } from './cache';
import { API, get_json } from './flows_api';
@ -17,11 +16,6 @@ import { save_attentions } from './Attention';
import './UserAction.css';
const BASE64_RATE = 4 / 3;
const MAX_IMG_DIAM = 8000;
const MAX_IMG_PX = 5000000;
const MAX_IMG_FILESIZE = 450000 * BASE64_RATE;
const REPOSITORY = 'https://git.thu.monster/newthuhole/';
const EMAIL = '[email protected]';
@ -442,20 +436,17 @@ export class PostForm extends Component {
cw: window.CW_BACKUP || '',
allow_search: window.AS_BACKUP || false,
loading_status: 'done',
img_tip: null,
preview: false,
has_poll: !!window.POLL_BACKUP,
poll_options: JSON.parse(window.POLL_BACKUP || '[""]'),
use_title: false,
};
this.img_ref = React.createRef();
this.area_ref = React.createRef();
this.on_change_bound = this.on_change.bind(this);
this.on_allow_search_change_bound = this.on_allow_search_change.bind(this);
this.on_use_title_change_bound = this.on_use_title_change.bind(this);
this.on_cw_change_bound = this.on_cw_change.bind(this);
this.on_poll_option_change_bound = this.on_poll_option_change.bind(this);
this.on_img_change_bound = this.on_img_change.bind(this);
this.color_picker = new ColorPicker();
}
@ -550,141 +541,10 @@ export class PostForm extends Component {
});
}
proc_img(file) {
return new Promise((resolve, reject) => {
function return_url(url) {
const idx = url.indexOf(';base64,');
if (idx === -1) throw new Error('img not base64 encoded');
return url.substr(idx + 8);
}
let reader = new FileReader();
function on_got_img(url) {
const image = new Image();
image.onload = () => {
let width = image.width;
let height = image.height;
let compressed = false;
if (width > MAX_IMG_DIAM) {
height = (height * MAX_IMG_DIAM) / width;
width = MAX_IMG_DIAM;
compressed = true;
}
if (height > MAX_IMG_DIAM) {
width = (width * MAX_IMG_DIAM) / height;
height = MAX_IMG_DIAM;
compressed = true;
}
if (height * width > MAX_IMG_PX) {
let rate = Math.sqrt((height * width) / MAX_IMG_PX);
height /= rate;
width /= rate;
compressed = true;
}
console.log('chosen img size', width, height);
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
ctx.drawImage(image, 0, 0, width, height);
let quality_l = 0.1,
quality_r = 0.9,
quality,
new_url;
while (quality_r - quality_l >= 0.03) {
quality = (quality_r + quality_l) / 2;
new_url = canvas.toDataURL('image/jpeg', quality);
console.log(
quality_l,
quality_r,
'trying quality',
quality,
'size',
new_url.length,
);
if (new_url.length <= MAX_IMG_FILESIZE) quality_l = quality;
else quality_r = quality;
}
if (quality_l >= 0.101) {
console.log('chosen img quality', quality);
resolve({
img: return_url(new_url),
quality: quality,
width: Math.round(width),
height: Math.round(height),
compressed: compressed,
});
} else {
reject('图片过大,无法上传');
}
};
image.src = url;
}
reader.onload = (event) => {
fixOrientation(event.target.result, {}, (fixed_dataurl) => {
on_got_img(fixed_dataurl);
});
};
reader.readAsDataURL(file);
});
}
on_img_change() {
if (this.img_ref.current && this.img_ref.current.files.length)
this.setState(
{
img_tip: '(正在处理图片……)',
},
() => {
this.proc_img(this.img_ref.current.files[0])
.then((d) => {
this.setState({
img_tip:
`${d.compressed ? '压缩到' : '尺寸'} ${d.width}*${
d.height
} / ` +
`质量 ${Math.floor(d.quality * 100)}% / ${Math.floor(
d.img.length / BASE64_RATE / 1000,
)}KB`,
});
})
.catch((e) => {
this.setState({
img_tip: `图片无效:${e}`,
});
});
},
);
else
this.setState({
img_tip: null,
});
}
on_submit(event) {
if (event) event.preventDefault();
if (this.state.loading_status === 'loading') return;
if (!this.state.text) return;
/*
if (this.img_ref.current.files.length) {
this.setState({
loading_status: 'processing',
});
this.proc_img(this.img_ref.current.files[0])
.then((d) => {
this.setState({
loading_status: 'loading',
});
this.do_post(this.state.text, d.img);
})
.catch((e) => {
alert(e);
});
} else */
{
this.setState({
loading_status: 'loading',
@ -712,6 +572,51 @@ export class PostForm extends Component {
this.setState({ poll_options: poll_options });
}
on_file_change(event) {
console.log(event);
let tar = event.target;
let f = event.target.files[0];
if (f) {
tar.setAttribute('disabled', 'disabled');
let data = new FormData();
data.append('file', f);
fetch(API_BASE + '/upload', {
method: 'POST',
headers: {
'User-Token': this.props.token,
},
body: data,
})
.then(get_json)
.then((json) => {
if (json.code !== 0) {
throw new Error(json.msg);
}
console.log(json);
let url =
(window.config.ipfs_gateway[0] || '<hash>(无ipfs网关)').replaceAll(
'<hash>',
json.data.hash,
) + json.data.filename;
let new_text =
this.state.text +
'\n' +
(f.type.startsWith('image/') ? `![](${url})` : url);
this.setState({ text: new_text });
this.area_ref.current.set(new_text);
tar.removeAttribute('disabled');
})
.catch((e) => {
console.error(e);
alert('上传失败\n' + e);
tar.removeAttribute('disabled');
});
// event.target.value = null;
}
}
render() {
const { has_poll, poll_options, preview, loading_status } = this.state;
return (
@ -743,8 +648,7 @@ export class PostForm extends Component {
{loading_status !== 'done' ? (
<button disabled="disabled">
<span className="icon icon-loading" />
&nbsp;
{loading_status === 'processing' ? '处理' : '上传'}
&nbsp;上传
</button>
) : (
<button type="submit">
@ -774,19 +678,6 @@ export class PostForm extends Component {
)}
</div>
</div>
{!!this.state.img_tip && (
<p className="post-form-img-tip">
<a
onClick={() => {
this.img_ref.current.value = '';
this.on_img_change();
}}
>
删除图片
</a>
{this.state.img_tip}
</p>
)}
{preview ? (
<div className="post-preview">
<HighlightedMarkdown
@ -797,6 +688,13 @@ export class PostForm extends Component {
</div>
) : (
<>
<span>上传并插入文件: </span>
<input
className="file-input"
type="file"
name="file"
onChange={this.on_file_change.bind(this)}
/>
<input
type="text"
placeholder="折叠警告(留空表示不折叠)"
@ -852,9 +750,8 @@ export class PostForm extends Component {
</p>
<p>
<small>
插入图片请使用图片外链Markdown格式 ![](图片链接)
支持动图支持多图推荐的图床
<a href="https://imgchr.com/" target="_blank">
首选ipfs网关可以在设置中修改如效果不佳仍可使用图床例如
<a href="https://imgtu.com/" target="_blank">
路过图床
</a>

4
src/flows_api.js

@ -144,9 +144,7 @@ export const API = {
get_list: async (page, token, submode) => {
let response = await fetch(
`${API_BASE}/getlist?p=${page}${
window.config.no_c_post ? '&no_cw' : ''
}&order_mode=${submode}`,
`${API_BASE}/getlist?p=${page}&order_mode=${submode}`,
{
headers: { 'User-Token': token },
},

Loading…
Cancel
Save