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:
Bernhard Posselt 2017-03-17 22:42:53 +01:00 коммит произвёл GitHub
Родитель 35fb530f58
Коммит 567fe4c4ce
26 изменённых файлов: 3264 добавлений и 513 удалений

3
.bowerrc Normal file
Просмотреть файл

@ -0,0 +1,3 @@
{
"directory": "nextcloudappstore/core/static/vendor"
}

1
.gitignore поставляемый
Просмотреть файл

@ -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

Просмотреть файл

@ -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');

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Разница между файлами не показана из-за своего большого размера Загрузить разницу

2145
nextcloudappstore/core/static/vendor/requirejs/require.js поставляемый Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -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 %}