mirror of
https://github.com/cuigh/swirl
synced 2024-12-28 23:02:02 +00:00
Support add stack archive by uploading file
This commit is contained in:
parent
3005cd6edb
commit
0c2dae4834
@ -338,6 +338,7 @@ var Swirl;
|
||||
switch (this.options.encoder) {
|
||||
case "none":
|
||||
settings.contentType = false;
|
||||
settings.processData = false;
|
||||
break;
|
||||
case "json":
|
||||
settings.contentType = "application/json; charset=UTF-8";
|
||||
@ -438,7 +439,7 @@ var Swirl;
|
||||
}
|
||||
class WidthRule extends LengthRule {
|
||||
getLength(value) {
|
||||
var doubleByteChars = value.match(/[^\x00-\xff]/ig);
|
||||
let doubleByteChars = value.match(/[^\x00-\xff]/ig);
|
||||
return value.length + (doubleByteChars == null ? 0 : doubleByteChars.length);
|
||||
}
|
||||
}
|
||||
@ -1865,6 +1866,71 @@ var Swirl;
|
||||
})(Setting = Swirl.Setting || (Swirl.Setting = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
var Swirl;
|
||||
(function (Swirl) {
|
||||
var Stack;
|
||||
(function (Stack) {
|
||||
var Archive;
|
||||
(function (Archive) {
|
||||
var Validator = Swirl.Core.Validator;
|
||||
var Notification = Swirl.Core.Notification;
|
||||
class ContentRequiredRule {
|
||||
validate($form, $input, arg) {
|
||||
let el = $input[0];
|
||||
if ($("#type-" + arg).prop("checked")) {
|
||||
console.log(el.value);
|
||||
return { ok: el.checkValidity ? el.checkValidity() : true, error: el.validationMessage };
|
||||
}
|
||||
return { ok: true };
|
||||
}
|
||||
}
|
||||
class EditPage {
|
||||
constructor() {
|
||||
Validator.register("content", new ContentRequiredRule(), "");
|
||||
this.editor = CodeMirror.fromTextArea($("#txt-content")[0], { lineNumbers: true });
|
||||
$("#file-content").change(e => {
|
||||
let file = e.target;
|
||||
if (file.files.length > 0) {
|
||||
$('#filename').text(file.files[0].name);
|
||||
}
|
||||
});
|
||||
$("#type-input,#type-upload").click(e => {
|
||||
let type = $(e.target).val();
|
||||
$("#div-input").toggle(type == "input");
|
||||
$("#div-upload").toggle(type == "upload");
|
||||
});
|
||||
$("#btn-submit").click(this.submit.bind(this));
|
||||
}
|
||||
submit(e) {
|
||||
this.editor.save();
|
||||
let results = Validator.bind("#div-form").validate();
|
||||
if (results.length > 0) {
|
||||
return;
|
||||
}
|
||||
let data = new FormData();
|
||||
data.append('name', $("#name").val());
|
||||
if ($("#type-input").prop("checked")) {
|
||||
data.append('content', $('#txt-content').val());
|
||||
}
|
||||
else {
|
||||
let file = $('#file-content')[0];
|
||||
data.append('content', file.files[0]);
|
||||
}
|
||||
let url = $(e.target).data("url") || "";
|
||||
$ajax.post(url, data).encoder("none").trigger(e.target).json((r) => {
|
||||
if (r.success) {
|
||||
location.href = "/stack/archive/";
|
||||
}
|
||||
else {
|
||||
Notification.show("danger", `FAILED: ${r.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Archive.EditPage = EditPage;
|
||||
})(Archive = Stack.Archive || (Stack.Archive = {}));
|
||||
})(Stack = Swirl.Stack || (Swirl.Stack = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
var Swirl;
|
||||
(function (Swirl) {
|
||||
var Stack;
|
||||
(function (Stack) {
|
||||
|
File diff suppressed because one or more lines are too long
@ -314,6 +314,7 @@ namespace Swirl.Core {
|
||||
switch (this.options.encoder) {
|
||||
case "none":
|
||||
settings.contentType = false;
|
||||
settings.processData = false;
|
||||
break;
|
||||
case "json":
|
||||
settings.contentType = "application/json; charset=UTF-8";
|
||||
|
@ -8,7 +8,7 @@ namespace Swirl.Core {
|
||||
type InputElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | HTMLButtonElement;
|
||||
|
||||
/**
|
||||
* 输入控件验证结果
|
||||
* The result of validation.
|
||||
*
|
||||
* @interface ValidationResult
|
||||
*/
|
||||
@ -32,7 +32,7 @@ namespace Swirl.Core {
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML5 表单元素原生验证器
|
||||
* HTML5 form element native validator
|
||||
*
|
||||
* @class NativeRule
|
||||
* @implements {ValidationRule}
|
||||
@ -45,7 +45,7 @@ namespace Swirl.Core {
|
||||
}
|
||||
|
||||
/**
|
||||
* 必填字段验证器
|
||||
* Required validator
|
||||
*
|
||||
* @class RequiredRule
|
||||
* @implements {ValidationRule}
|
||||
@ -57,7 +57,7 @@ namespace Swirl.Core {
|
||||
}
|
||||
|
||||
/**
|
||||
* 必选字段验证器(用于 radio 和 checkbox), 示例: checked, checked(2), checked(1~2)
|
||||
* Checked validator(for radio/checkbox), e.g. checked, checked(2), checked(1~2)
|
||||
*
|
||||
* @class CheckedRule
|
||||
* @implements {ValidationRule}
|
||||
@ -71,7 +71,7 @@ namespace Swirl.Core {
|
||||
}
|
||||
|
||||
/**
|
||||
* 电子邮箱验证器
|
||||
* Email validator
|
||||
*
|
||||
* @class EmailValidator
|
||||
* @implements {ValidationRule}
|
||||
@ -85,7 +85,7 @@ namespace Swirl.Core {
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP/FTP 地址验证器
|
||||
* HTTP/FTP URL validator
|
||||
*
|
||||
* @class UrlValidator
|
||||
* @implements {ValidationRule}
|
||||
@ -99,7 +99,7 @@ namespace Swirl.Core {
|
||||
}
|
||||
|
||||
/**
|
||||
* IPV4 地址验证器
|
||||
* IPV4 address validator
|
||||
*
|
||||
* @class IPValidator
|
||||
* @implements {ValidationRule}
|
||||
@ -113,7 +113,7 @@ namespace Swirl.Core {
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段匹配验证器(如密码)
|
||||
* Match validator(e.g. password confirmation)
|
||||
*
|
||||
* @class MatchValidator
|
||||
* @implements {ValidationRule}
|
||||
@ -125,7 +125,7 @@ namespace Swirl.Core {
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串长度验证器
|
||||
* String length validator.
|
||||
*
|
||||
* @class LengthValidator
|
||||
* @implements {ValidationRule}
|
||||
@ -155,14 +155,14 @@ namespace Swirl.Core {
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串宽度验证器(中文字符宽度为2)
|
||||
* String width validator, the width of CJK characters is considered as 2.
|
||||
*
|
||||
* @class WidthValidator
|
||||
* @extends {LengthRule}
|
||||
*/
|
||||
class WidthRule extends LengthRule {
|
||||
protected getLength(value: string): number {
|
||||
var doubleByteChars = value.match(/[^\x00-\xff]/ig);
|
||||
let doubleByteChars = value.match(/[^\x00-\xff]/ig);
|
||||
return value.length + (doubleByteChars == null ? 0 : doubleByteChars.length);
|
||||
}
|
||||
}
|
||||
@ -181,7 +181,7 @@ namespace Swirl.Core {
|
||||
}
|
||||
|
||||
/**
|
||||
* 正则表达式验证器
|
||||
* Regex validator
|
||||
*
|
||||
* @class RegexValidator
|
||||
* @implements {ValidationRule}
|
||||
@ -195,7 +195,7 @@ namespace Swirl.Core {
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务器端验证器
|
||||
* Remote validator
|
||||
*
|
||||
* @class RemoteRule
|
||||
* @implements {ValidationRule}
|
||||
|
70
assets/swirl/ts/stack/archive/edit.ts
Normal file
70
assets/swirl/ts/stack/archive/edit.ts
Normal file
@ -0,0 +1,70 @@
|
||||
///<reference path="../../core/core.ts" />
|
||||
namespace Swirl.Stack.Archive {
|
||||
import Validator = Swirl.Core.Validator;
|
||||
import AjaxResult = Swirl.Core.AjaxResult;
|
||||
import Notification = Swirl.Core.Notification;
|
||||
import ValidationRule = Swirl.Core.ValidationRule;
|
||||
|
||||
class ContentRequiredRule implements ValidationRule {
|
||||
validate($form: JQuery, $input: JQuery, arg?: string): {ok: boolean, error?: string} {
|
||||
let el = <HTMLInputElement>$input[0];
|
||||
if ($("#type-" + arg).prop("checked")) {
|
||||
console.log(el.value);
|
||||
return {ok: el.checkValidity ? el.checkValidity() : true, error: el.validationMessage};
|
||||
}
|
||||
return {ok: true}
|
||||
}
|
||||
}
|
||||
|
||||
export class EditPage {
|
||||
private editor: any;
|
||||
|
||||
constructor() {
|
||||
Validator.register("content", new ContentRequiredRule(), "");
|
||||
|
||||
this.editor = CodeMirror.fromTextArea($("#txt-content")[0], {lineNumbers: true});
|
||||
|
||||
$("#file-content").change(e => {
|
||||
let file = <HTMLInputElement>e.target;
|
||||
if (file.files.length > 0) {
|
||||
$('#filename').text(file.files[0].name);
|
||||
}
|
||||
});
|
||||
$("#type-input,#type-upload").click(e => {
|
||||
let type = $(e.target).val();
|
||||
$("#div-input").toggle(type == "input");
|
||||
$("#div-upload").toggle(type == "upload");
|
||||
});
|
||||
$("#btn-submit").click(this.submit.bind(this))
|
||||
}
|
||||
|
||||
private submit(e: JQueryEventObject) {
|
||||
this.editor.save();
|
||||
|
||||
let results = Validator.bind("#div-form").validate();
|
||||
if (results.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = new FormData();
|
||||
data.append('name', $("#name").val());
|
||||
if ($("#type-input").prop("checked")) {
|
||||
data.append('content', $('#txt-content').val());
|
||||
} else {
|
||||
let file = <HTMLInputElement>$('#file-content')[0];
|
||||
data.append('content', file.files[0]);
|
||||
}
|
||||
|
||||
let url = $(e.target).data("url") || "";
|
||||
$ajax.post(url, data).encoder("none").trigger(e.target).json((r: AjaxResult) => {
|
||||
if (r.success) {
|
||||
location.href = "/stack/archive/"
|
||||
} else {
|
||||
Notification.show("danger", `FAILED: ${r.message}`);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare var CodeMirror: any;
|
@ -173,16 +173,18 @@ func stackArchiveNew(ctx web.Context) error {
|
||||
func stackArchiveCreate(ctx web.Context) error {
|
||||
archive := &model.Archive{}
|
||||
err := ctx.Bind(archive)
|
||||
if err == nil {
|
||||
// Validate format
|
||||
_, err = compose.Parse(archive.Name, archive.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
archive.CreatedBy = ctx.User().ID()
|
||||
archive.UpdatedBy = archive.CreatedBy
|
||||
err = biz.Archive.Create(archive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate format
|
||||
_, err = compose.Parse(archive.Name, archive.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
archive.CreatedBy = ctx.User().ID()
|
||||
archive.UpdatedBy = archive.CreatedBy
|
||||
err = biz.Archive.Create(archive)
|
||||
return ajaxResult(ctx, err)
|
||||
}
|
||||
|
2
main.go
2
main.go
@ -23,7 +23,7 @@ func main() {
|
||||
misc.BindOptions()
|
||||
|
||||
app.Name = "Swirl"
|
||||
app.Version = "0.6.1"
|
||||
app.Version = "0.6.2"
|
||||
app.Desc = "A web management UI for Docker, focused on swarm cluster"
|
||||
app.Action = func(ctx *app.Context) {
|
||||
misc.LoadOptions()
|
||||
|
@ -3,9 +3,9 @@ package model
|
||||
import "time"
|
||||
|
||||
type Archive struct {
|
||||
ID string `bson:"_id" json:"id,omitempty"`
|
||||
ID string `bson:"_id" json:"id,omitempty" bind:"id=path"`
|
||||
Name string `bson:"name" json:"name,omitempty"`
|
||||
Content string `bson:"content" json:"content,omitempty"`
|
||||
Content string `bson:"content" json:"content,omitempty" bind:"content=form,content=file"`
|
||||
CreatedBy string `bson:"created_by" json:"created_by,omitempty"`
|
||||
CreatedAt time.Time `bson:"created_at" json:"created_at,omitempty"`
|
||||
UpdatedBy string `bson:"updated_by" json:"updated_by,omitempty"`
|
||||
|
@ -22,10 +22,10 @@
|
||||
<nav class="tabs is-boxed">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/stack/task/">Tasks</a>
|
||||
<a href="/stack/task/">{{ i18n("menu.stack.task") }}</a>
|
||||
</li>
|
||||
<li class="is-active">
|
||||
<a href="/stack/archive/">Archives</a>
|
||||
<a href="/stack/archive/">{{ i18n("menu.stack.archive") }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@ -36,9 +36,9 @@
|
||||
<div class="container">
|
||||
<nav class="breadcrumb has-succeeds-separator is-small is-marginless" aria-label="breadcrumbs">
|
||||
<ul>
|
||||
<li><a href="/">Dashboard</a></li>
|
||||
<li><a href="/stack/archive/">Archives</a></li>
|
||||
<li class="is-active"><a>Detail</a></li>
|
||||
<li><a href="/">{{ i18n("menu.dashboard") }}</a></li>
|
||||
<li><a href="/stack/archive/">{{ i18n("menu.stack.archive") }}</a></li>
|
||||
<li class="is-active"><a>{{ i18n("menu.detail") }}</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
@ -55,8 +55,8 @@
|
||||
<nav class="navbar has-shadow">
|
||||
<div class="container">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item is-tab is-active" href="/stack/archive/{{.Archive.ID}}/detail">Detail</a>
|
||||
<a class="navbar-item is-tab" href="/stack/archive/{{.Archive.ID}}/edit">Edit</a>
|
||||
<a class="navbar-item is-tab is-active" href="/stack/archive/{{.Archive.ID}}/detail">{{ i18n("menu.detail") }}</a>
|
||||
<a class="navbar-item is-tab" href="/stack/archive/{{.Archive.ID}}/edit">{{ i18n("menu.edit") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@ -64,16 +64,16 @@
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<dl>
|
||||
<dt>Created at</dt>
|
||||
<dt>{{ i18n("field.created-at") }}</dt>
|
||||
<dd>{{ time(.Archive.CreatedAt) }}</dd>
|
||||
<dt>Updated at</dt>
|
||||
<dt>{{ i18n("field.updated-at") }}</dt>
|
||||
<dd>{{ time(.Archive.UpdatedAt) }}</dd>
|
||||
<dt>Content</dt>
|
||||
<dd class="content"><pre class="is-paddingless"><code class="yaml">{{ .Archive.Content }}</code></pre></dd>
|
||||
</dl>
|
||||
<a href="/stack/archive/" class="button is-primary">
|
||||
<span class="icon"><i class="fa fa-reply"></i></span>
|
||||
<span>Return</span>
|
||||
<span>{{ i18n("button.return") }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -1,4 +1,5 @@
|
||||
{{ extends "../../_layouts/default" }}
|
||||
{{ import "../../_modules/form" }}
|
||||
|
||||
{{ block style() }}
|
||||
<link rel="stylesheet" href="/assets/codemirror/codemirror.css?v=5.30">
|
||||
@ -7,7 +8,7 @@
|
||||
{{ block script() }}
|
||||
<script src="/assets/codemirror/codemirror.js?v=5.30"></script>
|
||||
<script src="/assets/codemirror/mode/yaml.js?v=5.30"></script>
|
||||
<script>var editor = CodeMirror.fromTextArea(document.getElementById("txt-content"), {lineNumbers: true});</script>
|
||||
<script>$(() => new Swirl.Stack.Archive.EditPage())</script>
|
||||
{{ end }}
|
||||
|
||||
{{ block body() }}
|
||||
@ -64,30 +65,48 @@
|
||||
</nav>
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<form method="post" action="update" data-form="ajax-json" data-url="/stack/archive/">
|
||||
<input name="id" value="{{ .Archive.ID }}" type="hidden">
|
||||
<div class="field">
|
||||
<label class="label">{{ i18n("field.name") }}</label>
|
||||
<div class="control">
|
||||
<input name="name" class="input" value="{{ .Archive.Name }}" type="text" placeholder="" data-v-rule="native;regex" data-v-arg-regex="^[a-z0-9_-]+$" data-v-msg-regex="Name can contain only letters, digits, '_' and '-'." required>
|
||||
</div>
|
||||
<div id="div-form" class="container">
|
||||
<div class="field">
|
||||
<label class="label">{{ i18n("field.name") }}</label>
|
||||
<div class="control">
|
||||
<input id="name" name="name" class="input" value="{{ .Archive.Name }}" type="text" placeholder="" data-v-rule="native;regex" data-v-arg-regex="^[a-z0-9_-]+$" data-v-msg-regex="Name can contain only letters, digits, '_' and '-'." required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Content</label>
|
||||
<div class="control">
|
||||
<textarea id="txt-content" name="content" class="textarea code" rows="20" placeholder="Compose file content" data-v-rule="native" required>{{ .Archive.Content }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
{{ yield radio(name="type", value="input", label="Input", checked="input") }}
|
||||
{{ yield radio(name="type", value="upload", label="Upload") }}
|
||||
</div>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-primary">{{ i18n("button.submit") }}</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a href="/stack/archive/" class="button is-link">{{ i18n("button.cancel") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="div-input" class="field">
|
||||
<label class="label">Content</label>
|
||||
<div class="control">
|
||||
<textarea id="txt-content" name="content" class="textarea" rows="20" placeholder="Compose file content" data-v-rule="content" data-v-arg-content="input" required>{{ .Archive.Content }}</textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div id="div-upload" class="field" style="display: none">
|
||||
<label class="label">Content</label>
|
||||
<div class="file has-name is-fullwidth">
|
||||
<label class="file-label">
|
||||
<input id="file-content" name="content" class="file-input" type="file" data-v-rule="content" data-v-arg-content="upload" required>
|
||||
<span class="file-cta">
|
||||
<span class="file-icon">
|
||||
<i class="fa fa-upload"></i>
|
||||
</span>
|
||||
<span class="file-label">Choose a file…</span>
|
||||
</span>
|
||||
<span id="filename" class="file-name"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button id="btn-submit" type="submit" class="button is-primary" data-url="update">{{ i18n("button.submit") }}</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a href="/stack/archive/" class="button is-link">{{ i18n("button.cancel") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{ end }}
|
@ -1,4 +1,5 @@
|
||||
{{ extends "../../_layouts/default" }}
|
||||
{{ import "../../_modules/form" }}
|
||||
|
||||
{{ block style() }}
|
||||
<link rel="stylesheet" href="/assets/codemirror/codemirror.css?v=5.30">
|
||||
@ -7,7 +8,7 @@
|
||||
{{ block script() }}
|
||||
<script src="/assets/codemirror/codemirror.js?v=5.30"></script>
|
||||
<script src="/assets/codemirror/mode/yaml.js?v=5.30"></script>
|
||||
<script>var editor = CodeMirror.fromTextArea(document.getElementById("txt-content"), {lineNumbers: true});</script>
|
||||
<script>$(() => new Swirl.Stack.Archive.EditPage())</script>
|
||||
{{ end }}
|
||||
|
||||
{{ block body() }}
|
||||
@ -34,31 +35,50 @@
|
||||
</div>
|
||||
</section>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div id="div-form" class="container">
|
||||
<h2 class="title">Create stack archive</h2>
|
||||
<hr>
|
||||
<form method="post" data-form="ajax-json" data-url="/stack/archive/">
|
||||
<div class="field">
|
||||
<label class="label">{{ i18n("field.name") }}</label>
|
||||
<div class="control">
|
||||
<input name="name" class="input" type="text" placeholder="" data-v-rule="native;regex" data-v-arg-regex="^[a-z0-9_-]+$" data-v-msg-regex="Name can contain only letters, digits, '_' and '-'." required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ i18n("field.name") }}</label>
|
||||
<div class="control">
|
||||
<input id="name" name="name" class="input" type="text" placeholder="" data-v-rule="native;regex" data-v-arg-regex="^[a-z0-9_-]+$" data-v-msg-regex="Name can contain only letters, digits, '_' and '-'." required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Content</label>
|
||||
<div class="control">
|
||||
<textarea id="txt-content" name="content" class="textarea" rows="20" placeholder="Compose file content" data-v-rule="native" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
{{ yield radio(name="type", value="input", label="Input", checked="input") }}
|
||||
{{ yield radio(name="type", value="upload", label="Upload") }}
|
||||
</div>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button type="submit" class="button is-primary">{{ i18n("button.submit") }}</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a href="/stack/archive/" class="button is-link">{{ i18n("button.cancel") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="div-input" class="field">
|
||||
<label class="label">Content</label>
|
||||
<div class="control">
|
||||
<textarea id="txt-content" name="content" class="textarea" rows="20" placeholder="Compose file content" data-v-rule="content" data-v-arg-content="input" required></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div id="div-upload" class="field" style="display: none">
|
||||
<label class="label">Content</label>
|
||||
<div class="file has-name is-fullwidth">
|
||||
<label class="file-label">
|
||||
<input id="file-content" name="content" class="file-input" type="file" data-v-rule="content" data-v-arg-content="upload" required>
|
||||
<span class="file-cta">
|
||||
<span class="file-icon">
|
||||
<i class="fa fa-upload"></i>
|
||||
</span>
|
||||
<span class="file-label">Choose a file…</span>
|
||||
</span>
|
||||
<span id="filename" class="file-name"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button id="btn-submit" type="submit" class="button is-primary">{{ i18n("button.submit") }}</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<a href="/stack/archive/" class="button is-link">{{ i18n("button.cancel") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{ end }}
|
Loading…
Reference in New Issue
Block a user