mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
ui: fix #74 (multi-lang edit for problem)
This commit is contained in:
1 parent
ef5ee468bd
commit
ebaa3f290b
10 files changed
+117
-85
No files matched your search
@@ -142,8 +142,11 @@ class Nunjucks extends nunjucks.Environment {
|
|||||||
s = content;
|
s = content;
|
||||||
}
|
}
|
||||||
if (typeof s === 'object' && !(s instanceof Array)) {
|
if (typeof s === 'object' && !(s instanceof Array)) {
|
||||||
|
const langs = Object.keys(s);
|
||||||
|
const f = langs.filter(i => i.startsWith(language));
|
||||||
if (s[language]) s = s[language];
|
if (s[language]) s = s[language];
|
||||||
else s = s[Object.keys(s)[0]];
|
else if (f.length) s = s[f[0]];
|
||||||
|
else s = s[langs[0]];
|
||||||
}
|
}
|
||||||
if (s instanceof Array) s = buildContent(s, html ? 'html' : 'markdown', (str) => str.translate(language));
|
if (s instanceof Array) s = buildContent(s, html ? 'html' : 'markdown', (str) => str.translate(language));
|
||||||
return html ? xss.process(s) : markdown.render(s);
|
return html ? xss.process(s) : markdown.render(s);
|
||||||
|
|||||||
@@ -40,12 +40,17 @@ export default class CmEditor extends DOMAttachedObject {
|
|||||||
const origin = $dom.get(0);
|
const origin = $dom.get(0);
|
||||||
const ele = document.createElement('div');
|
const ele = document.createElement('div');
|
||||||
const value = $dom.val();
|
const value = $dom.val();
|
||||||
|
const onChange = this.options.onChange;
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
this.editor = new Vditor(ele, {
|
this.editor = new Vditor(ele, {
|
||||||
...config,
|
...config,
|
||||||
...this.options,
|
...this.options,
|
||||||
after: resolve,
|
after: resolve,
|
||||||
input(v) { $dom.val(v); $dom.text(v); },
|
input(v) {
|
||||||
|
$dom.val(v);
|
||||||
|
$dom.text(v);
|
||||||
|
if (onChange) onChange(v);
|
||||||
|
},
|
||||||
value,
|
value,
|
||||||
cache: { id: Math.random().toString() },
|
cache: { id: Math.random().toString() },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,12 +28,8 @@ export default class Tab extends DOMAttachedObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async switchToTab(idx) {
|
async switchToTab(idx) {
|
||||||
if (idx === this.currentIndex) {
|
if (idx === this.currentIndex) return;
|
||||||
return;
|
if (this.isAnimating) return;
|
||||||
}
|
|
||||||
if (this.isAnimating) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const $tabs = this.$content.children();
|
const $tabs = this.$content.children();
|
||||||
const $currentTab = $tabs.eq(this.currentIndex);
|
const $currentTab = $tabs.eq(this.currentIndex);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/ui-default",
|
"name": "@hydrooj/ui-default",
|
||||||
"version": "4.6.17",
|
"version": "4.6.18",
|
||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "hydro.js",
|
"main": "hydro.js",
|
||||||
|
|||||||
@@ -177,50 +177,78 @@ export default new NamedPage(['problem_create', 'problem_edit'], (pagename) => {
|
|||||||
});
|
});
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
CmEditor.getOrConstruct($('textarea[data-markdown-upload]'), {
|
const $main = $('textarea[data-markdown-upload]');
|
||||||
upload: {
|
let content = $main.val();
|
||||||
accept: 'image/*,.mp3, .wav, .zip',
|
let isObject = false;
|
||||||
url: './files',
|
try {
|
||||||
extraData: {
|
content = JSON.parse(content);
|
||||||
type: 'additional_file',
|
isObject = !(content instanceof Array);
|
||||||
operation: 'upload_file',
|
if (!isObject) content = JSON.stringify(content);
|
||||||
},
|
} catch (e) { }
|
||||||
multiple: false,
|
const upload = {
|
||||||
fieldName: 'file',
|
accept: 'image/*,.mp3, .wav, .zip',
|
||||||
setHeaders() {
|
url: './files',
|
||||||
return { accept: 'application/json' };
|
extraData: {
|
||||||
},
|
type: 'additional_file',
|
||||||
format(files, resp) {
|
operation: 'upload_file',
|
||||||
const res = JSON.parse(resp);
|
},
|
||||||
if (res.error) {
|
multiple: false,
|
||||||
return JSON.stringify({
|
fieldName: 'file',
|
||||||
msg: res.error.message,
|
setHeaders() {
|
||||||
code: -1,
|
return { accept: 'application/json' };
|
||||||
data: {
|
},
|
||||||
errFiles: [files[0].name],
|
format(files, resp) {
|
||||||
succMap: {},
|
const res = JSON.parse(resp);
|
||||||
},
|
if (res.error) {
|
||||||
});
|
|
||||||
}
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
msg: '',
|
msg: res.error.message,
|
||||||
code: 0,
|
code: -1,
|
||||||
data: {
|
data: {
|
||||||
errFiles: [],
|
errFiles: [files[0].name],
|
||||||
succMap: {
|
succMap: {},
|
||||||
[files[0].name]: `file://${files[0].name.replace(/[^(a-zA-Z0-9\u4e00-\u9fa5.)]/g, '')
|
|
||||||
.replace(/[?\\/:|<>*[\]()$%{}@~]/g, '')
|
|
||||||
.replace('/\\s/g', '')}`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
filename(name) {
|
return JSON.stringify({
|
||||||
return name.replace(/[^(a-zA-Z0-9\u4e00-\u9fa5.)]/g, '')
|
msg: '',
|
||||||
.replace(/[?\\/:|<>*[\]()$%{}@~]/g, '')
|
code: 0,
|
||||||
.replace('/\\s/g', '');
|
data: {
|
||||||
},
|
errFiles: [],
|
||||||
validate: () => (pagename === 'problem_create' ? i18n('Cannot upload file before problem is created.') : true),
|
succMap: {
|
||||||
|
[files[0].name]: `file://${files[0].name.replace(/[^(a-zA-Z0-9\u4e00-\u9fa5.)]/g, '')
|
||||||
|
.replace(/[?\\/:|<>*[\]()$%{}@~]/g, '')
|
||||||
|
.replace(/\s/g, '')}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
filename(name) {
|
||||||
|
return name.replace(/[^(a-zA-Z0-9\u4e00-\u9fa5.)]/g, '')
|
||||||
|
.replace(/[?\\/:|<>*[\]()$%{}@~]/g, '')
|
||||||
|
.replace(/\s/g, '');
|
||||||
|
},
|
||||||
|
validate: () => (pagename === 'problem_create' ? i18n('Cannot upload file before problem is created.') : true),
|
||||||
|
};
|
||||||
|
$('textarea[data-content]').each(function () {
|
||||||
|
const $dom = $(this);
|
||||||
|
let i = $dom.attr('data-content');
|
||||||
|
let c = '';
|
||||||
|
if (isObject) {
|
||||||
|
if (content[i]) c = content[i];
|
||||||
|
else {
|
||||||
|
const list = Object.keys(content).filter(l => l.startsWith(i));
|
||||||
|
if (list.length) c = content[list[0]];
|
||||||
|
i = list[0];
|
||||||
|
}
|
||||||
|
} else c = content;
|
||||||
|
if (typeof c !== 'string') c = JSON.stringify(c);
|
||||||
|
$dom.val(c);
|
||||||
|
function onChange(val) {
|
||||||
|
const empty = /^\s*$/g.test(val);
|
||||||
|
if (empty) delete content[i];
|
||||||
|
else content[i] = val;
|
||||||
|
$main.val(JSON.stringify(content));
|
||||||
|
}
|
||||||
|
CmEditor.getOrConstruct($dom, { upload, onChange });
|
||||||
|
})
|
||||||
});
|
});
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { NamedPage } from 'vj/misc/Page';
|
|
||||||
|
|
||||||
const page = new NamedPage('problem_import', async () => {
|
|
||||||
});
|
|
||||||
|
|
||||||
export default page;
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{% extends 'layout/simple.html' %}
|
|
||||||
{% block body %}
|
|
||||||
{% if fdoc %}
|
|
||||||
<p>{{ _('Uploaded file info') }}:</p>
|
|
||||||
<p>{{ _('ID') }}: {{ fdoc['_id'] }}</p>
|
|
||||||
<p>{{ _('Userfile doc_id') }}: {{ ufid }}</p>
|
|
||||||
<p>{{ _('MD5') }}: {{ fdoc['md5'] }}</p>
|
|
||||||
<p>{{ _('URL') }}: {{ model.file.url(fdoc) }}</p>
|
|
||||||
{% endif %}
|
|
||||||
<form method="post" enctype="multipart/form-data">
|
|
||||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
|
||||||
<p>
|
|
||||||
{{ _('New file') }}:
|
|
||||||
<input type="text" name="title" placeholder="{{ _('Description') }}">
|
|
||||||
<input type="file" name="file">
|
|
||||||
<input type="submit" value="{{ _('Upload') }}" class="rounded primary button">
|
|
||||||
</p>
|
|
||||||
<p>{{ _('Note: file size should be less than or equal to {0}, and file type should be in {1}. Empty files are not allowed.').format(size(FILE_MAX_LENGTH), ALLOWED_MIMETYPE_PREFIX) }}</p>
|
|
||||||
<p>{{ _('Usage: {0}, Quota: {1}').format(size(usage), size(quota)) }}</p>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -37,12 +37,31 @@
|
|||||||
name:'tag',
|
name:'tag',
|
||||||
value:pdoc['tag']|default([])|join(', ')
|
value:pdoc['tag']|default([])|join(', ')
|
||||||
}) }}
|
}) }}
|
||||||
<div class="row"><div class="columns">
|
<div class="section__tab-container nojs--hide">
|
||||||
<label>
|
<ul class="section__tabs immersive">
|
||||||
{{ _('Content') }}
|
<li class="section__tab-item">
|
||||||
<textarea name="content" class="textbox" data-markdown-upload style="height: 500px">{% if pdoc %}{{ pdoc['content']|toString }}{% else %}{% include 'partials/problem_default.md' %}{% endif %}</textarea>
|
<h1 class="section__tab-title">{{ _('__langname') }}</h1>
|
||||||
</label>
|
<div class="section__tab-main">
|
||||||
</div></div>
|
<div class="section__body">
|
||||||
|
<textarea data-content="{{ _('__id') }}" class="textbox" style="height: 500px"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% for k, v in model.setting.SETTINGS_BY_KEY['viewLang'].range %}
|
||||||
|
{% if k != handler.user.viewLang %}
|
||||||
|
<li class="section__tab-item">
|
||||||
|
<h1 class="section__tab-title">{{ v }}</h1>
|
||||||
|
<div class="section__tab-main">
|
||||||
|
<div class="section__body">
|
||||||
|
<textarea data-content="{{ k }}" class="textbox" style="height: 500px"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<textarea name="content" class="textbox hasjs--hide" data-markdown-upload style="height: 500px">{% if pdoc %}{{ pdoc['content']|toString }}{% else %}{% include 'partials/problem_default.md' %}{% endif %}</textarea>
|
||||||
<div class="row"><div class="columns">
|
<div class="row"><div class="columns">
|
||||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||||
{% if page_name == 'problem_edit' %}
|
{% if page_name == 'problem_edit' %}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
<div class="profile-content">
|
<div class="profile-content">
|
||||||
<div class="section__tab-container immersive">
|
<div class="section__tab-container immersive">
|
||||||
<ul class="section__tabs">
|
<ul class="section__tabs">
|
||||||
<li class="section__tab-item">
|
<li class="section__tab-item">
|
||||||
<h1 class="section__tab-title">{{ _('Bio') }}</h1>
|
<h1 class="section__tab-title">{{ _('Bio') }}</h1>
|
||||||
<div class="section__tab-main">
|
<div class="section__tab-main">
|
||||||
{% if not udoc.bio %}
|
{% if not udoc.bio %}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import i18n from './i18n';
|
import i18n from './i18n';
|
||||||
|
import substitute from './substitute';
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
/**
|
/**
|
||||||
@@ -21,7 +22,14 @@ const request = {
|
|||||||
} else if (errorThrown instanceof Error) {
|
} else if (errorThrown instanceof Error) {
|
||||||
reject(errorThrown);
|
reject(errorThrown);
|
||||||
} else if (typeof jqXHR.responseJSON === 'object' && jqXHR.responseJSON.error) {
|
} else if (typeof jqXHR.responseJSON === 'object' && jqXHR.responseJSON.error) {
|
||||||
reject(new Error(jqXHR.responseJSON.error.message));
|
const error = jqXHR.responseJSON.error;
|
||||||
|
if (error.params) {
|
||||||
|
const message = substitute(error.message, ...error.params);
|
||||||
|
const err = new Error(message);
|
||||||
|
err.rawMessage = error.message;
|
||||||
|
err.params = error.params;
|
||||||
|
reject(err);
|
||||||
|
} else reject(new Error(jqXHR.responseJSON.error.message));
|
||||||
} else {
|
} else {
|
||||||
reject(new Error(textStatus));
|
reject(new Error(textStatus));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user