зеркало из https://github.com/nextcloud/appstore.git
Switch out app list with refactored typescript (#449)
* Switch out app list * path consistency * remove frontend deps since we have a frontend build now
This commit is contained in:
Родитель
35fb530f58
Коммит
567fe4c4ce
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"directory": "nextcloudappstore/core/static/vendor"
|
||||
}
|
|
@ -19,6 +19,7 @@ nextcloudappstore/local_settings.py
|
|||
nextcloudappstore/settings/production.py
|
||||
nextcloudappstore/settings/development.py
|
||||
nextcloudappstore/core/static/public
|
||||
nextcloudappstore/core/static/vendor
|
||||
bin/
|
||||
*.swp
|
||||
*.log
|
||||
|
|
4
Makefile
4
Makefile
|
@ -8,6 +8,7 @@ db=sqlite
|
|||
pyvenv=python3 -m venv
|
||||
npm=npm
|
||||
tslint=node_modules/.bin/tslint
|
||||
bower=node_modules/.bin/bower
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
|
@ -36,6 +37,7 @@ initmigrations:
|
|||
dev-setup:
|
||||
$(npm) install
|
||||
$(npm) run build
|
||||
$(bower) install
|
||||
$(pyvenv) venv
|
||||
$(pip) install --upgrade pip
|
||||
$(pip) install -r $(CURDIR)/requirements/development.txt
|
||||
|
@ -64,6 +66,8 @@ docs:
|
|||
update-dev-deps:
|
||||
$(pip) install --upgrade -r $(CURDIR)/requirements/development.txt
|
||||
$(pip) install --upgrade -r $(CURDIR)/requirements/base.txt
|
||||
$(bower) install --upgrade
|
||||
$(npm) install --upgrade
|
||||
|
||||
.PHONY: authors
|
||||
authors:
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"markdown-it": "^7.0.0",
|
||||
"highlightjs": "^9.4.0",
|
||||
"moment": "^2.14.1",
|
||||
"fetch": "^1.0.0"
|
||||
"fetch": "^1.0.0",
|
||||
"requirejs": "^2.3.3"
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"directory": "vendor"
|
||||
}
|
|
@ -9,7 +9,7 @@ type ErrorMessages = {
|
|||
fields: IDjangoFieldErrors;
|
||||
};
|
||||
|
||||
type DjangoErrors = IDjangoFieldErrors | DjangoGlobalErrors;
|
||||
export type DjangoErrors = IDjangoFieldErrors | DjangoGlobalErrors;
|
||||
|
||||
/**
|
||||
* Parses a JSON error from a Django Restframework API
|
|
@ -1,4 +1,4 @@
|
|||
import {parseJSONError, toIdErrorMap} from './Form';
|
||||
import {parseJSONError, toIdErrorMap} from './ErrorParser';
|
||||
|
||||
describe('Testing the window error result parser', () => {
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import {Form, FormField} from '../../forms/Form';
|
||||
|
||||
export class AppListForm extends Form {
|
||||
|
||||
constructor(form: HTMLFormElement) {
|
||||
super(form);
|
||||
}
|
||||
|
||||
public attachEventListeners() {
|
||||
this.findFields()
|
||||
.forEach((elem: FormField, name: string) => {
|
||||
elem.addEventListener('change', () => {
|
||||
this.form.submit();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import {queryForm} from '../../dom/Facades';
|
||||
import {AppListForm} from '../forms/AppListForm';
|
||||
|
||||
window.onload = () => {
|
||||
const elem = queryForm('#filter-form');
|
||||
const form = new AppListForm(elem);
|
||||
form.attachEventListeners();
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
export class DomElementDoesNotExist extends Error {
|
||||
}
|
|
@ -2,6 +2,9 @@
|
|||
* shortcuts for unreasonably long DOM methods
|
||||
*/
|
||||
|
||||
import {DomElementDoesNotExist} from './DomElementDoesNotExist';
|
||||
import {NotAForm} from './NotAForm';
|
||||
|
||||
export function id(selector: string): HTMLElement | null {
|
||||
return window.document.getElementById(selector);
|
||||
}
|
||||
|
@ -14,6 +17,39 @@ export function queryAll(selector: string): Element[] {
|
|||
return Array.from(window.document.querySelectorAll(selector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to query but throws if no element is found
|
||||
* use this function to fail early if you absolutely expect it to not return
|
||||
* null
|
||||
* @param selector
|
||||
* @throws DomElementDoesNotExist if the query returns no element
|
||||
*/
|
||||
export function queryOrThrow(selector: string): Element {
|
||||
const elem = query(selector);
|
||||
if (elem === null) {
|
||||
const msg = `No element found for selector ${selector}`;
|
||||
throw new DomElementDoesNotExist(msg);
|
||||
} else {
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a form based on a selector
|
||||
* @param selector
|
||||
* @throws NotAForm if the found element is not a form
|
||||
* @throws DomElementDoesNotExist if no element was found
|
||||
* @returns {HTMLFormElement}
|
||||
*/
|
||||
export function queryForm(selector: string): HTMLFormElement {
|
||||
const form = queryOrThrow(selector);
|
||||
if (form instanceof HTMLFormElement) {
|
||||
return form;
|
||||
} else {
|
||||
throw new NotAForm(`Element ${form.tagName} is not a form`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the header for a meta tag with a certain name and returns the content
|
||||
* @param name
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {getMetaValue, testDom} from './Facades';
|
||||
import {DomElementDoesNotExist} from './DomElementDoesNotExist';
|
||||
import {getMetaValue, queryOrThrow, testDom} from './Facades';
|
||||
|
||||
describe('DOM Facades', () => {
|
||||
|
||||
|
@ -12,4 +13,22 @@ describe('DOM Facades', () => {
|
|||
testDom('head', tpl, () => expect(getMetaValue('test')).toBe('value'));
|
||||
});
|
||||
|
||||
it('should throw if no element is found', () => {
|
||||
const tpl = `<link>`;
|
||||
testDom('head', tpl, () => {
|
||||
const msg = 'No element found for selector test';
|
||||
expect(() => queryOrThrow('test'))
|
||||
.toThrow(new DomElementDoesNotExist(msg));
|
||||
});
|
||||
});
|
||||
|
||||
it('should not throw if element is found', () => {
|
||||
const tpl = `<meta name="test" content="value">`;
|
||||
testDom('head', tpl, () => {
|
||||
const msg = 'No element found for selector meta';
|
||||
expect(() => queryOrThrow('meta'))
|
||||
.not.toThrow(new DomElementDoesNotExist(msg));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export class NotAForm extends Error {
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
export type FormField = HTMLInputElement | HTMLTextAreaElement |
|
||||
HTMLSelectElement | HTMLButtonElement;
|
||||
|
||||
export abstract class Form {
|
||||
|
||||
constructor(protected form: HTMLFormElement) {
|
||||
}
|
||||
|
||||
protected findFields(): Map<string, FormField> {
|
||||
const result = new Map<string, FormField>();
|
||||
const fields = [
|
||||
Array.from(this.form.getElementsByTagName('input')),
|
||||
Array.from(this.form.getElementsByTagName('textarea')),
|
||||
Array.from(this.form.getElementsByTagName('select')),
|
||||
Array.from(this.form.getElementsByTagName('button')),
|
||||
];
|
||||
[].concat.apply([], fields) // flatten arrays
|
||||
.forEach((elem: FormField) => result.set(elem.name, elem));
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
moment/*
|
||||
markdown-it/*
|
||||
jquery/*
|
||||
highlightjs/*
|
||||
bootstrap/*
|
||||
fetch/*
|
||||
!moment/min/moment-with-locales.min.js
|
||||
!markdown-it/dist/markdown-it.min.js
|
||||
!jquery/dist/jquery.min.js
|
||||
!highlightjs/styles
|
||||
!highlightjs/highlight.pack.min.js
|
||||
!bootstrap/dist/fonts
|
||||
!bootstrap/dist/css/bootstrap.min.css
|
||||
!bootstrap/dist/css/bootstrap-theme.min.css
|
||||
!bootstrap/dist/js/bootstrap.min.js
|
||||
!fetch/fetch.js
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -20,6 +20,28 @@
|
|||
arrayBuffer: 'ArrayBuffer' in self
|
||||
}
|
||||
|
||||
if (support.arrayBuffer) {
|
||||
var viewClasses = [
|
||||
'[object Int8Array]',
|
||||
'[object Uint8Array]',
|
||||
'[object Uint8ClampedArray]',
|
||||
'[object Int16Array]',
|
||||
'[object Uint16Array]',
|
||||
'[object Int32Array]',
|
||||
'[object Uint32Array]',
|
||||
'[object Float32Array]',
|
||||
'[object Float64Array]'
|
||||
]
|
||||
|
||||
var isDataView = function(obj) {
|
||||
return obj && DataView.prototype.isPrototypeOf(obj)
|
||||
}
|
||||
|
||||
var isArrayBufferView = ArrayBuffer.isView || function(obj) {
|
||||
return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeName(name) {
|
||||
if (typeof name !== 'string') {
|
||||
name = String(name)
|
||||
|
@ -152,14 +174,36 @@
|
|||
|
||||
function readBlobAsArrayBuffer(blob) {
|
||||
var reader = new FileReader()
|
||||
var promise = fileReaderReady(reader)
|
||||
reader.readAsArrayBuffer(blob)
|
||||
return fileReaderReady(reader)
|
||||
return promise
|
||||
}
|
||||
|
||||
function readBlobAsText(blob) {
|
||||
var reader = new FileReader()
|
||||
var promise = fileReaderReady(reader)
|
||||
reader.readAsText(blob)
|
||||
return fileReaderReady(reader)
|
||||
return promise
|
||||
}
|
||||
|
||||
function readArrayBufferAsText(buf) {
|
||||
var view = new Uint8Array(buf)
|
||||
var chars = new Array(view.length)
|
||||
|
||||
for (var i = 0; i < view.length; i++) {
|
||||
chars[i] = String.fromCharCode(view[i])
|
||||
}
|
||||
return chars.join('')
|
||||
}
|
||||
|
||||
function bufferClone(buf) {
|
||||
if (buf.slice) {
|
||||
return buf.slice(0)
|
||||
} else {
|
||||
var view = new Uint8Array(buf.byteLength)
|
||||
view.set(new Uint8Array(buf))
|
||||
return view.buffer
|
||||
}
|
||||
}
|
||||
|
||||
function Body() {
|
||||
|
@ -167,7 +211,9 @@
|
|||
|
||||
this._initBody = function(body) {
|
||||
this._bodyInit = body
|
||||
if (typeof body === 'string') {
|
||||
if (!body) {
|
||||
this._bodyText = ''
|
||||
} else if (typeof body === 'string') {
|
||||
this._bodyText = body
|
||||
} else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
|
||||
this._bodyBlob = body
|
||||
|
@ -175,11 +221,12 @@
|
|||
this._bodyFormData = body
|
||||
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
|
||||
this._bodyText = body.toString()
|
||||
} else if (!body) {
|
||||
this._bodyText = ''
|
||||
} else if (support.arrayBuffer && ArrayBuffer.prototype.isPrototypeOf(body)) {
|
||||
// Only support ArrayBuffers for POST method.
|
||||
// Receiving ArrayBuffers happens via Blobs, instead.
|
||||
} else if (support.arrayBuffer && support.blob && isDataView(body)) {
|
||||
this._bodyArrayBuffer = bufferClone(body.buffer)
|
||||
// IE 10-11 can't handle a DataView body.
|
||||
this._bodyInit = new Blob([this._bodyArrayBuffer])
|
||||
} else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
|
||||
this._bodyArrayBuffer = bufferClone(body)
|
||||
} else {
|
||||
throw new Error('unsupported BodyInit type')
|
||||
}
|
||||
|
@ -204,6 +251,8 @@
|
|||
|
||||
if (this._bodyBlob) {
|
||||
return Promise.resolve(this._bodyBlob)
|
||||
} else if (this._bodyArrayBuffer) {
|
||||
return Promise.resolve(new Blob([this._bodyArrayBuffer]))
|
||||
} else if (this._bodyFormData) {
|
||||
throw new Error('could not read FormData body as blob')
|
||||
} else {
|
||||
|
@ -212,27 +261,28 @@
|
|||
}
|
||||
|
||||
this.arrayBuffer = function() {
|
||||
return this.blob().then(readBlobAsArrayBuffer)
|
||||
}
|
||||
|
||||
this.text = function() {
|
||||
var rejected = consumed(this)
|
||||
if (rejected) {
|
||||
return rejected
|
||||
}
|
||||
|
||||
if (this._bodyBlob) {
|
||||
return readBlobAsText(this._bodyBlob)
|
||||
} else if (this._bodyFormData) {
|
||||
throw new Error('could not read FormData body as text')
|
||||
if (this._bodyArrayBuffer) {
|
||||
return consumed(this) || Promise.resolve(this._bodyArrayBuffer)
|
||||
} else {
|
||||
return Promise.resolve(this._bodyText)
|
||||
return this.blob().then(readBlobAsArrayBuffer)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.text = function() {
|
||||
var rejected = consumed(this)
|
||||
return rejected ? rejected : Promise.resolve(this._bodyText)
|
||||
}
|
||||
|
||||
this.text = function() {
|
||||
var rejected = consumed(this)
|
||||
if (rejected) {
|
||||
return rejected
|
||||
}
|
||||
|
||||
if (this._bodyBlob) {
|
||||
return readBlobAsText(this._bodyBlob)
|
||||
} else if (this._bodyArrayBuffer) {
|
||||
return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer))
|
||||
} else if (this._bodyFormData) {
|
||||
throw new Error('could not read FormData body as text')
|
||||
} else {
|
||||
return Promise.resolve(this._bodyText)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,7 +310,10 @@
|
|||
function Request(input, options) {
|
||||
options = options || {}
|
||||
var body = options.body
|
||||
if (Request.prototype.isPrototypeOf(input)) {
|
||||
|
||||
if (typeof input === 'string') {
|
||||
this.url = input
|
||||
} else {
|
||||
if (input.bodyUsed) {
|
||||
throw new TypeError('Already read')
|
||||
}
|
||||
|
@ -271,12 +324,10 @@
|
|||
}
|
||||
this.method = input.method
|
||||
this.mode = input.mode
|
||||
if (!body) {
|
||||
if (!body && input._bodyInit != null) {
|
||||
body = input._bodyInit
|
||||
input.bodyUsed = true
|
||||
}
|
||||
} else {
|
||||
this.url = input
|
||||
}
|
||||
|
||||
this.credentials = options.credentials || this.credentials || 'omit'
|
||||
|
@ -294,7 +345,7 @@
|
|||
}
|
||||
|
||||
Request.prototype.clone = function() {
|
||||
return new Request(this)
|
||||
return new Request(this, { body: this._bodyInit })
|
||||
}
|
||||
|
||||
function decode(body) {
|
||||
|
@ -310,16 +361,17 @@
|
|||
return form
|
||||
}
|
||||
|
||||
function headers(xhr) {
|
||||
var head = new Headers()
|
||||
var pairs = (xhr.getAllResponseHeaders() || '').trim().split('\n')
|
||||
pairs.forEach(function(header) {
|
||||
var split = header.trim().split(':')
|
||||
var key = split.shift().trim()
|
||||
var value = split.join(':').trim()
|
||||
head.append(key, value)
|
||||
function parseHeaders(rawHeaders) {
|
||||
var headers = new Headers()
|
||||
rawHeaders.split('\r\n').forEach(function(line) {
|
||||
var parts = line.split(':')
|
||||
var key = parts.shift().trim()
|
||||
if (key) {
|
||||
var value = parts.join(':').trim()
|
||||
headers.append(key, value)
|
||||
}
|
||||
})
|
||||
return head
|
||||
return headers
|
||||
}
|
||||
|
||||
Body.call(Request.prototype)
|
||||
|
@ -330,10 +382,10 @@
|
|||
}
|
||||
|
||||
this.type = 'default'
|
||||
this.status = options.status
|
||||
this.status = 'status' in options ? options.status : 200
|
||||
this.ok = this.status >= 200 && this.status < 300
|
||||
this.statusText = options.statusText
|
||||
this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers)
|
||||
this.statusText = 'statusText' in options ? options.statusText : 'OK'
|
||||
this.headers = new Headers(options.headers)
|
||||
this.url = options.url || ''
|
||||
this._initBody(bodyInit)
|
||||
}
|
||||
|
@ -371,35 +423,16 @@
|
|||
|
||||
self.fetch = function(input, init) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var request
|
||||
if (Request.prototype.isPrototypeOf(input) && !init) {
|
||||
request = input
|
||||
} else {
|
||||
request = new Request(input, init)
|
||||
}
|
||||
|
||||
var request = new Request(input, init)
|
||||
var xhr = new XMLHttpRequest()
|
||||
|
||||
function responseURL() {
|
||||
if ('responseURL' in xhr) {
|
||||
return xhr.responseURL
|
||||
}
|
||||
|
||||
// Avoid security warnings on getResponseHeader when not allowed by CORS
|
||||
if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) {
|
||||
return xhr.getResponseHeader('X-Request-URL')
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
xhr.onload = function() {
|
||||
var options = {
|
||||
status: xhr.status,
|
||||
statusText: xhr.statusText,
|
||||
headers: headers(xhr),
|
||||
url: responseURL()
|
||||
headers: parseHeaders(xhr.getAllResponseHeaders() || '')
|
||||
}
|
||||
options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL')
|
||||
var body = 'response' in xhr ? xhr.response : xhr.responseText
|
||||
resolve(new Response(body, options))
|
||||
}
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -1,74 +1,6 @@
|
|||
/*
|
||||
|
||||
Darkula color scheme from the JetBrains family of IDEs
|
||||
|
||||
Deprecated due to a typo in the name and left here for compatibility purpose only.
|
||||
Please use darcula.css instead.
|
||||
*/
|
||||
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
background: #2b2b2b;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
color: #bababa;
|
||||
}
|
||||
|
||||
.hljs-strong,
|
||||
.hljs-emphasis {
|
||||
color: #a8a8a2;
|
||||
}
|
||||
|
||||
.hljs-bullet,
|
||||
.hljs-quote,
|
||||
.hljs-link,
|
||||
.hljs-number,
|
||||
.hljs-regexp,
|
||||
.hljs-literal {
|
||||
color: #6896ba;
|
||||
}
|
||||
|
||||
.hljs-code,
|
||||
.hljs-selector-class {
|
||||
color: #a6e22e;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-section,
|
||||
.hljs-attribute,
|
||||
.hljs-name,
|
||||
.hljs-variable {
|
||||
color: #cb7832;
|
||||
}
|
||||
|
||||
.hljs-params {
|
||||
color: #b9b9b9;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-subst,
|
||||
.hljs-type,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-symbol,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-template-tag,
|
||||
.hljs-template-variable,
|
||||
.hljs-addition {
|
||||
color: #e0c46c;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-deletion,
|
||||
.hljs-meta {
|
||||
color: #7f7f7f;
|
||||
}
|
||||
@import url('darcula.css');
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -4,7 +4,7 @@
|
|||
|
||||
{% block head-title %}{% if current_category %}{{ current_category.name }} - {% else %}{% trans 'All apps' %} - {% endif %}{% endblock %}
|
||||
{% block head %}
|
||||
<script defer="defer" src="{% static 'assets/js/ui/app-list.js' %}"></script>
|
||||
<script src="{% static 'vendor/requirejs/require.js' %}" async data-main="{% static 'public/app/views/List' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block apps %}
|
||||
|
|
Загрузка…
Ссылка в новой задаче