avo 1.3.4 → 1.3.5.pre.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of avo might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/app/components/avo/common/key_value_component.html.erb +53 -0
- data/app/components/avo/common/key_value_component.rb +11 -0
- data/app/components/avo/edit/fields/key_value_field_component.html.erb +3 -0
- data/app/components/avo/edit/fields/key_value_field_component.rb +4 -0
- data/app/components/avo/edit/fields/trix_field_component.html.erb +19 -10
- data/app/components/avo/show/fields/key_value_field_component.html.erb +3 -0
- data/app/components/avo/show/fields/key_value_field_component.rb +4 -0
- data/app/controllers/avo/application_controller.rb +5 -1
- data/app/controllers/avo/attachments_controller.rb +14 -3
- data/app/packs/entrypoints/application.js +14 -11
- data/app/packs/js/controllers/fields/code_field_controller.js +1 -0
- data/app/packs/js/controllers/fields/date_field_controller.js +1 -1
- data/app/packs/js/controllers/fields/key_value_controller.js +137 -0
- data/app/packs/js/controllers/fields/trix_field_controller.js +121 -0
- data/app/views/avo/partials/_javascript.html.erb +3 -1
- data/config/routes.rb +4 -0
- data/lib/avo.rb +1 -1
- data/lib/avo/fields/key_value_field.rb +24 -1
- data/lib/avo/fields/trix_field.rb +4 -0
- data/lib/avo/version.rb +1 -1
- data/public/avo-packs/css/{application-af3e670d.css → application-5bdca030.css} +92 -18
- data/public/avo-packs/css/application-5bdca030.css.br +0 -0
- data/public/avo-packs/css/application-5bdca030.css.gz +0 -0
- data/public/avo-packs/css/application-5bdca030.css.map +1 -0
- data/public/avo-packs/css/application-5bdca030.css.map.br +0 -0
- data/public/avo-packs/css/application-5bdca030.css.map.gz +0 -0
- data/public/avo-packs/js/application-16a456a2b7cb56b01153.js +26 -0
- data/public/avo-packs/js/{application-801f1297670a4c6df1b6.js.LICENSE.txt → application-16a456a2b7cb56b01153.js.LICENSE.txt} +0 -0
- data/public/avo-packs/js/application-16a456a2b7cb56b01153.js.br +0 -0
- data/public/avo-packs/js/application-16a456a2b7cb56b01153.js.gz +0 -0
- data/public/avo-packs/js/application-16a456a2b7cb56b01153.js.map +1 -0
- data/public/avo-packs/js/application-16a456a2b7cb56b01153.js.map.br +0 -0
- data/public/avo-packs/js/application-16a456a2b7cb56b01153.js.map.gz +0 -0
- data/public/avo-packs/manifest.json +15 -15
- metadata +25 -17
- data/public/avo-packs/css/application-af3e670d.css.br +0 -0
- data/public/avo-packs/css/application-af3e670d.css.gz +0 -0
- data/public/avo-packs/css/application-af3e670d.css.map +0 -1
- data/public/avo-packs/css/application-af3e670d.css.map.br +0 -0
- data/public/avo-packs/css/application-af3e670d.css.map.gz +0 -0
- data/public/avo-packs/js/application-801f1297670a4c6df1b6.js +0 -26
- data/public/avo-packs/js/application-801f1297670a4c6df1b6.js.br +0 -0
- data/public/avo-packs/js/application-801f1297670a4c6df1b6.js.gz +0 -0
- data/public/avo-packs/js/application-801f1297670a4c6df1b6.js.map +0 -1
- data/public/avo-packs/js/application-801f1297670a4c6df1b6.js.map.br +0 -0
- data/public/avo-packs/js/application-801f1297670a4c6df1b6.js.map.gz +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6212847ad5e28753e23625482067816d88ec6728f3d35656182b3a6e46b021d2
|
4
|
+
data.tar.gz: 479c5130dd59104ab944af271b85ba398aec7007568116191d82668ab47b44cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1a9663120f63b7458932e061a90d7287c7c7405382d64bcf5fb75c7c64c342a6a461a4ba06ae85478fc8e34efba204ecc99b7a3508541753d124153d4f519ae
|
7
|
+
data.tar.gz: 7dcb4d3494ff81a33d793f14e7fbd8d25aa178f6d22293fbaf27c6601c7426c87703f93674a5fb5db20d6c5b1b2b3a1aab24901571bd39a3da138f0d7df92a9a
|
data/Gemfile.lock
CHANGED
@@ -0,0 +1,53 @@
|
|
1
|
+
<div class="w-full shadow-lg rounded-lg overflow-hidden"
|
2
|
+
data-controller="key-value"
|
3
|
+
data-key-value-target="controller"
|
4
|
+
data-options="<%= @field.options.to_json %>"
|
5
|
+
data-input-classes="<%= input_classes %>"
|
6
|
+
data-editable="<%= @view.in?([:edit, :create]) %>"
|
7
|
+
>
|
8
|
+
<div class="w-full flex flex-col">
|
9
|
+
<div class="flex w-full">
|
10
|
+
<div class="flex w-full bg-gray-800 shadow overflow-hidden">
|
11
|
+
<div class="w-1/2 py-3 px-3 uppercase font-semibold text-xs text-white border-gray-600 border-r">
|
12
|
+
<%= @field.key_label %>
|
13
|
+
</div>
|
14
|
+
<div class="w-1/2 py-3 px-3 uppercase font-semibold text-xs text-white">
|
15
|
+
<%= @field.value_label %>
|
16
|
+
</div>
|
17
|
+
<% if @view.in?([:edit, :create]) %>
|
18
|
+
<div class="flex items-center justify-center p-2 px-3 border-l border-gray-600">
|
19
|
+
<a href="javascript:void(0);"
|
20
|
+
title="<%= @field.action_text %>"
|
21
|
+
data-tippy="tooltip"
|
22
|
+
data-button="add-row"
|
23
|
+
data-action="click->key-value#addRow"
|
24
|
+
<% if @field.disable_adding_rows %>
|
25
|
+
class="cursor-not-allowed"
|
26
|
+
<% end %>
|
27
|
+
>
|
28
|
+
<%= svg 'plus-circle', class: 'text-gray-400 h-5 hover:text-gray-500' %>
|
29
|
+
</a>
|
30
|
+
</div>
|
31
|
+
<% end %>
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
<div data-key-value-target="rows"></div>
|
35
|
+
</div>
|
36
|
+
<% if @form.present? %>
|
37
|
+
<%= @form.text_area @field.id,
|
38
|
+
value: @field.parsed_value,
|
39
|
+
class: 'hidden',
|
40
|
+
placeholder: @field.placeholder,
|
41
|
+
'data-key-value-target': 'input',
|
42
|
+
'data-view': :edit
|
43
|
+
%>
|
44
|
+
<% else %>
|
45
|
+
<%= text_area_tag @field.id,
|
46
|
+
@field.parsed_value,
|
47
|
+
class: 'hidden',
|
48
|
+
placeholder: @field.placeholder,
|
49
|
+
'data-key-value-target': 'input',
|
50
|
+
'data-view': :edit
|
51
|
+
%>
|
52
|
+
<% end %>
|
53
|
+
</div>
|
@@ -1,11 +1,20 @@
|
|
1
|
-
<%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal do %>
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
1
|
+
<%= edit_field_wrapper field: @field, index: @index, form: @form, resource: @resource, displayed_in_modal: @displayed_in_modal, full_width: true do %>
|
2
|
+
<div
|
3
|
+
data-controller="trix-field"
|
4
|
+
data-trix-field-target="controller"
|
5
|
+
data-resource-name="<%= @resource.model_class.model_name.route_key %>"
|
6
|
+
data-resource-id="<%= @resource.model.id %>"
|
7
|
+
data-attachments-disabled="<%= @field.attachments_disabled %>"
|
8
|
+
data-attachment-key="<%= @field.attachment_key %>"
|
9
|
+
>
|
10
|
+
<% trix_id = "trix_#{@resource.name.underscore}_#{@field.id}" %>
|
11
|
+
<trix-editor data-trix-field-target="editor" input="<%= trix_id %>" placeholder="<%= @field.placeholder %>"><%== @field.value %></trix-editor>
|
12
|
+
<%= @form.text_area @field.id,
|
13
|
+
id: trix_id,
|
14
|
+
class: helpers.input_classes('w-full hidden', has_error: (@resource.model.present? and @resource.model.errors.include?(@field.id))),
|
15
|
+
placeholder: @field.placeholder,
|
16
|
+
disabled: @field.readonly,
|
17
|
+
rows: @field.meta[:rows]
|
18
|
+
%>
|
19
|
+
</div>
|
11
20
|
<% end %>
|
@@ -42,7 +42,7 @@ module Avo
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def check_avo_license
|
45
|
-
unless on_root_path || on_resources_path
|
45
|
+
unless on_root_path || on_resources_path || on_api_path
|
46
46
|
if @license.invalid? || @license.lacks(:custom_tools)
|
47
47
|
if Rails.env.development?
|
48
48
|
@custom_tools_alert_visible = true
|
@@ -272,5 +272,9 @@ module Avo
|
|
272
272
|
def on_resources_path
|
273
273
|
request.original_url.match?(/.*\/#{Avo.configuration.namespace}\/resources\/.*/)
|
274
274
|
end
|
275
|
+
|
276
|
+
def on_api_path
|
277
|
+
request.original_url.match?(/.*\/#{Avo.configuration.namespace}\/avo_api\/.*/)
|
278
|
+
end
|
275
279
|
end
|
276
280
|
end
|
@@ -2,9 +2,20 @@ require_dependency "avo/application_controller"
|
|
2
2
|
|
3
3
|
module Avo
|
4
4
|
class AttachmentsController < ApplicationController
|
5
|
-
before_action :set_resource_name, only: :destroy
|
6
|
-
before_action :set_resource, only: :destroy
|
7
|
-
before_action :set_model, only: :destroy
|
5
|
+
before_action :set_resource_name, only: [:destroy, :create]
|
6
|
+
before_action :set_resource, only: [:destroy, :create]
|
7
|
+
before_action :set_model, only: [:destroy, :create]
|
8
|
+
|
9
|
+
def create
|
10
|
+
blob = ActiveStorage::Blob.create_and_upload! io: params[:file], filename: params[:filename]
|
11
|
+
|
12
|
+
@model.send(params[:attachment_key]).attach blob
|
13
|
+
|
14
|
+
render json: {
|
15
|
+
url: main_app.url_for(blob),
|
16
|
+
href: main_app.url_for(blob)
|
17
|
+
}
|
18
|
+
end
|
8
19
|
|
9
20
|
def show
|
10
21
|
end
|
@@ -2,7 +2,6 @@
|
|
2
2
|
import 'core-js/stable'
|
3
3
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
4
4
|
import 'regenerator-runtime/runtime'
|
5
|
-
import 'trix'
|
6
5
|
import * as Mousetrap from 'mousetrap'
|
7
6
|
import { Application } from 'stimulus'
|
8
7
|
import { Turbo } from '@hotwired/turbo-rails'
|
@@ -19,6 +18,19 @@ window.Turbolinks = Turbo
|
|
19
18
|
|
20
19
|
Mousetrap.bind('r r r', () => Turbo.visit(window.location.href, { action: 'replace' }))
|
21
20
|
|
21
|
+
function initTippy() {
|
22
|
+
tippy('[data-tippy="tooltip"]', {
|
23
|
+
theme: 'light',
|
24
|
+
content(reference) {
|
25
|
+
const title = reference.getAttribute('title')
|
26
|
+
reference.removeAttribute('title')
|
27
|
+
|
28
|
+
return title
|
29
|
+
},
|
30
|
+
})
|
31
|
+
}
|
32
|
+
window.initTippy = initTippy
|
33
|
+
|
22
34
|
const application = Application.start()
|
23
35
|
|
24
36
|
const context = require.context('./../js/controllers', true, /\.js$/)
|
@@ -29,16 +41,7 @@ application.load(definitionsFromContext(fieldsContext))
|
|
29
41
|
|
30
42
|
document.addEventListener('turbo:load', () => {
|
31
43
|
document.body.classList.remove('turbo-loading')
|
32
|
-
|
33
|
-
tippy('[data-tippy="tooltip"]', {
|
34
|
-
theme: 'light',
|
35
|
-
content(reference) {
|
36
|
-
const title = reference.getAttribute('title')
|
37
|
-
reference.removeAttribute('title')
|
38
|
-
|
39
|
-
return title
|
40
|
-
},
|
41
|
-
})
|
44
|
+
initTippy()
|
42
45
|
})
|
43
46
|
document.addEventListener('turbo:visit', () => document.body.classList.add('turbo-loading'))
|
44
47
|
document.addEventListener('turbo:submit-start', () => document.body.classList.add('turbo-loading'))
|
@@ -12,6 +12,7 @@ import 'codemirror/mode/shell/shell'
|
|
12
12
|
import 'codemirror/mode/sql/sql'
|
13
13
|
import 'codemirror/mode/vue/vue'
|
14
14
|
import 'codemirror/mode/xml/xml'
|
15
|
+
import 'codemirror/mode/yaml/yaml'
|
15
16
|
|
16
17
|
import { Controller } from 'stimulus'
|
17
18
|
import { castBoolean } from '@/js/helpers/cast_boolean'
|
@@ -33,7 +33,7 @@ export default class extends Controller {
|
|
33
33
|
|
34
34
|
// enable timezone display
|
35
35
|
if (enableTime) {
|
36
|
-
currentValue = DateTime.fromISO(this.inputTarget.value, { zone: window.timezone })
|
36
|
+
currentValue = DateTime.fromISO(this.inputTarget.value, { zone: window.Avo.configuration.timezone })
|
37
37
|
currentValue = currentValue.setZone(this.inputTarget.dataset.timezone)
|
38
38
|
currentValue = currentValue.toISO()
|
39
39
|
|
@@ -0,0 +1,137 @@
|
|
1
|
+
/* eslint-disable max-len */
|
2
|
+
import { Controller } from 'stimulus'
|
3
|
+
import { castBoolean } from '@/js/helpers/cast_boolean'
|
4
|
+
|
5
|
+
export default class extends Controller {
|
6
|
+
static targets = ['input', 'controller', 'rows']
|
7
|
+
|
8
|
+
fieldValue = []
|
9
|
+
|
10
|
+
options = {}
|
11
|
+
|
12
|
+
get keyInputDisabled() {
|
13
|
+
return !this.options.editable || this.options.disable_editing_keys
|
14
|
+
}
|
15
|
+
|
16
|
+
get valueInputDisabled() {
|
17
|
+
return !this.options.editable
|
18
|
+
}
|
19
|
+
|
20
|
+
connect() {
|
21
|
+
this.setOptions()
|
22
|
+
|
23
|
+
try {
|
24
|
+
const objectValue = JSON.parse(this.inputTarget.value)
|
25
|
+
Object.keys(objectValue).forEach((key) => this.fieldValue.push([key, objectValue[key]]))
|
26
|
+
} catch (error) {
|
27
|
+
this.fieldValue = []
|
28
|
+
}
|
29
|
+
|
30
|
+
this.updateKeyValueComponent()
|
31
|
+
}
|
32
|
+
|
33
|
+
addRow() {
|
34
|
+
if (this.options.disable_adding_rows || !this.options.editable) return
|
35
|
+
this.fieldValue.push(['', ''])
|
36
|
+
this.updateKeyValueComponent()
|
37
|
+
this.focusLastRow()
|
38
|
+
}
|
39
|
+
|
40
|
+
deleteRow(event) {
|
41
|
+
if (this.options.disable_deleting_rows || !this.options.editable) return
|
42
|
+
const { index } = event.target.dataset
|
43
|
+
this.fieldValue.splice(index, 1)
|
44
|
+
this.updateTextareaInput()
|
45
|
+
this.updateKeyValueComponent()
|
46
|
+
}
|
47
|
+
|
48
|
+
focusLastRow() {
|
49
|
+
return this.rowsTarget.querySelector('.flex.key-value-row:last-child .key-value-input-key').focus()
|
50
|
+
}
|
51
|
+
|
52
|
+
valueFieldUpdated(event) {
|
53
|
+
const { value } = event.target
|
54
|
+
const { index } = event.target.dataset
|
55
|
+
this.fieldValue[index][1] = value
|
56
|
+
|
57
|
+
this.updateTextareaInput()
|
58
|
+
}
|
59
|
+
|
60
|
+
keyFieldUpdated(event) {
|
61
|
+
const { value } = event.target
|
62
|
+
const { index } = event.target.dataset
|
63
|
+
this.fieldValue[index][0] = value
|
64
|
+
|
65
|
+
this.updateTextareaInput()
|
66
|
+
}
|
67
|
+
|
68
|
+
updateTextareaInput() {
|
69
|
+
if (!this.hasInputTarget) return
|
70
|
+
let result = {}
|
71
|
+
if (this.fieldValue && this.fieldValue.length > 0) {
|
72
|
+
result = Object.assign(...this.fieldValue.map(([key, val]) => ({ [key]: val })))
|
73
|
+
}
|
74
|
+
this.inputTarget.innerText = JSON.stringify(result)
|
75
|
+
}
|
76
|
+
|
77
|
+
updateKeyValueComponent() {
|
78
|
+
let result = ''
|
79
|
+
let index = 0
|
80
|
+
this.fieldValue.forEach((row) => {
|
81
|
+
const [key, value] = row
|
82
|
+
result += this.interpolatedRow(key, value, index)
|
83
|
+
index++
|
84
|
+
})
|
85
|
+
this.rowsTarget.innerHTML = result
|
86
|
+
window.initTippy()
|
87
|
+
}
|
88
|
+
|
89
|
+
interpolatedRow(key, value, index) {
|
90
|
+
let result = `<div class="flex key-value-row">
|
91
|
+
${this.inputField('key', index, key, value)}
|
92
|
+
${this.inputField('value', index, key, value)}`
|
93
|
+
if (this.options.editable) {
|
94
|
+
result += `<a
|
95
|
+
href="javascript:void(0);"
|
96
|
+
data-index="${index}"
|
97
|
+
data-action="click->key-value#deleteRow"
|
98
|
+
title="${this.options.delete_text}"
|
99
|
+
data-tippy="tooltip"
|
100
|
+
data-button="delete-row"
|
101
|
+
tabindex="-1"
|
102
|
+
${this.options.disable_deleting_rows ? "disabled='disabled'" : ''}
|
103
|
+
class="flex items-center justify-center p-2 px-3 border-none ${this.options.disable_deleting_rows ? 'cursor-not-allowed' : ''}"
|
104
|
+
><svg class="pointer-events-none text-gray-500 h-5 hover:text-gray-500" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg></a>`
|
105
|
+
}
|
106
|
+
result += '</div>'
|
107
|
+
|
108
|
+
return result
|
109
|
+
}
|
110
|
+
|
111
|
+
inputField(id = 'key', index, key, value) {
|
112
|
+
return `<input
|
113
|
+
class="${this.options.inputClasses} !rounded-none border-gray-600 border-r border-l-0 border-b-0 border-t-0 focus:border-gray-300 w-1/2 focus:outline-none outline-none key-value-input-${id}"
|
114
|
+
data-action="input->key-value#${id}FieldUpdated"
|
115
|
+
placeholder="${this.options[`${id}_label`]}"
|
116
|
+
data-index="${index}"
|
117
|
+
${this[`${id}InputDisabled`] ? "disabled='disabled'" : ''}
|
118
|
+
value="${id === 'key' ? key : value}"
|
119
|
+
autofocus
|
120
|
+
/>`
|
121
|
+
}
|
122
|
+
|
123
|
+
setOptions() {
|
124
|
+
let fieldOptions
|
125
|
+
|
126
|
+
try {
|
127
|
+
fieldOptions = JSON.parse(this.controllerTarget.dataset.options)
|
128
|
+
} catch (error) {
|
129
|
+
fieldOptions = {}
|
130
|
+
}
|
131
|
+
this.options = {
|
132
|
+
...fieldOptions,
|
133
|
+
inputClasses: this.controllerTarget.dataset.inputClasses,
|
134
|
+
editable: castBoolean(this.controllerTarget.dataset.editable),
|
135
|
+
}
|
136
|
+
}
|
137
|
+
}
|
@@ -0,0 +1,121 @@
|
|
1
|
+
import 'trix'
|
2
|
+
import { Controller } from 'stimulus'
|
3
|
+
import { castBoolean } from '@/js/helpers/cast_boolean'
|
4
|
+
|
5
|
+
export default class extends Controller {
|
6
|
+
static targets = ['editor', 'controller']
|
7
|
+
|
8
|
+
get resourceId() {
|
9
|
+
return this.controllerTarget.dataset.resourceId
|
10
|
+
}
|
11
|
+
|
12
|
+
get resourceName() {
|
13
|
+
return this.controllerTarget.dataset.resourceName
|
14
|
+
}
|
15
|
+
|
16
|
+
get attachmentKey() {
|
17
|
+
return this.controllerTarget.dataset.attachmentKey
|
18
|
+
}
|
19
|
+
|
20
|
+
get attachmentsDisabled() {
|
21
|
+
return castBoolean(this.controllerTarget.dataset.attachmentsDisabled)
|
22
|
+
}
|
23
|
+
|
24
|
+
get uploadUrl() {
|
25
|
+
return `${window.location.origin}${window.Avo.configuration.root_path}/avo_api/resources/${this.resourceName}/${this.resourceId}/attachments`
|
26
|
+
}
|
27
|
+
|
28
|
+
connect() {
|
29
|
+
if (this.attachmentsDisabled) {
|
30
|
+
// Remove the attachments button
|
31
|
+
this.controllerTarget.querySelector('.trix-button-group--file-tools').remove()
|
32
|
+
}
|
33
|
+
|
34
|
+
window.addEventListener('trix-file-accept', (event) => {
|
35
|
+
if (event.target === this.editorTarget) {
|
36
|
+
// Prevent file uploads for fields that have attachments disabled.
|
37
|
+
if (this.attachmentsDisabled) {
|
38
|
+
event.preventDefault()
|
39
|
+
window.toastr.warning('This field has attachments disabled.')
|
40
|
+
|
41
|
+
return
|
42
|
+
}
|
43
|
+
|
44
|
+
// Prevent file uploads for resources that haven't been saved yet.
|
45
|
+
if (this.resourceId === '') {
|
46
|
+
event.preventDefault()
|
47
|
+
window.toastr.warning("You can't upload files into the Trix editor until you save the resource.")
|
48
|
+
|
49
|
+
return
|
50
|
+
}
|
51
|
+
|
52
|
+
// Prevent file uploads for fields without an attachment key.
|
53
|
+
if (this.attachmentKey === '') {
|
54
|
+
event.preventDefault()
|
55
|
+
window.toastr.warning("You haven't set an <a href='https://google.com' class='!text-blue-700 underline'>attachment_key</a> to this Trix field.")
|
56
|
+
}
|
57
|
+
}
|
58
|
+
})
|
59
|
+
|
60
|
+
window.addEventListener('trix-attachment-add', (event) => {
|
61
|
+
if (event.target === this.editorTarget) {
|
62
|
+
if (event.attachment.file) {
|
63
|
+
this.uploadFileAttachment(event.attachment)
|
64
|
+
}
|
65
|
+
}
|
66
|
+
})
|
67
|
+
}
|
68
|
+
|
69
|
+
uploadFileAttachment(attachment) {
|
70
|
+
this.uploadFile(
|
71
|
+
attachment.file,
|
72
|
+
(progress) => attachment.setUploadProgress(progress),
|
73
|
+
(attributes) => attachment.setAttributes(attributes),
|
74
|
+
)
|
75
|
+
}
|
76
|
+
|
77
|
+
uploadFile(file, progressCallback, successCallback) {
|
78
|
+
const formData = this.createFormData(file)
|
79
|
+
const xhr = new XMLHttpRequest()
|
80
|
+
|
81
|
+
xhr.open('POST', this.uploadUrl, true)
|
82
|
+
|
83
|
+
xhr.setRequestHeader('X-CSRF-Token', document.querySelector('meta[name="csrf-token"]').content)
|
84
|
+
|
85
|
+
xhr.upload.addEventListener('progress', (event) => {
|
86
|
+
// eslint-disable-next-line no-mixed-operators
|
87
|
+
const progress = event.loaded / event.total * 100
|
88
|
+
progressCallback(progress)
|
89
|
+
})
|
90
|
+
|
91
|
+
xhr.addEventListener('load', () => {
|
92
|
+
if (xhr.status === 200) {
|
93
|
+
let response
|
94
|
+
try {
|
95
|
+
response = JSON.parse(xhr.response)
|
96
|
+
} catch (error) {
|
97
|
+
response = {}
|
98
|
+
}
|
99
|
+
|
100
|
+
const attributes = {
|
101
|
+
url: response.url,
|
102
|
+
href: response.href,
|
103
|
+
}
|
104
|
+
|
105
|
+
successCallback(attributes)
|
106
|
+
}
|
107
|
+
})
|
108
|
+
|
109
|
+
xhr.send(formData)
|
110
|
+
}
|
111
|
+
|
112
|
+
createFormData(file) {
|
113
|
+
const data = new FormData()
|
114
|
+
data.append('Content-Type', file.type)
|
115
|
+
data.append('file', file)
|
116
|
+
data.append('filename', file.name)
|
117
|
+
data.append('attachment_key', this.attachmentKey)
|
118
|
+
|
119
|
+
return data
|
120
|
+
}
|
121
|
+
}
|