Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
f3c23e89
Commit
f3c23e89
authored
Mar 24, 2014
by
Andy Armstrong
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #3027 from edx/andya/asset-loading-indicator
Add a loading indicator to the Files & Uploads page.
parents
664f78dc
f92c3372
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
236 additions
and
178 deletions
+236
-178
cms/static/coffee/spec/views/assets_spec.coffee
+14
-0
cms/static/js/spec/create_sinon.js
+16
-6
cms/static/js/views/assets.js
+85
-67
cms/static/js/views/baseview.js
+8
-0
cms/static/js/views/paging.js
+106
-103
cms/templates/asset_index.html
+7
-2
No files found.
cms/static/coffee/spec/views/assets_spec.coffee
View file @
f3c23e89
...
@@ -236,6 +236,20 @@ define ["jasmine", "js/spec/create_sinon", "squire"],
...
@@ -236,6 +236,20 @@ define ["jasmine", "js/spec/create_sinon", "squire"],
create_sinon
.
respondWithJson
(
requests
,
@
mockAssetsResponse
)
create_sinon
.
respondWithJson
(
requests
,
@
mockAssetsResponse
)
return
requests
return
requests
it
"should show a status indicator while loading"
,
->
appendSetFixtures
(
'<div class="ui-loading"/>'
)
expect
(
$
(
'.ui-loading'
).
is
(
':visible'
)).
toBe
(
true
)
setup
.
call
(
this
)
expect
(
$
(
'.ui-loading'
).
is
(
':visible'
)).
toBe
(
false
)
it
"should hide the status indicator if an error occurs while loading"
,
->
requests
=
create_sinon
.
requests
(
this
)
appendSetFixtures
(
'<div class="ui-loading"/>'
)
expect
(
$
(
'.ui-loading'
).
is
(
':visible'
)).
toBe
(
true
)
@
view
.
setPage
(
0
)
create_sinon
.
respondWithError
(
requests
)
expect
(
$
(
'.ui-loading'
).
is
(
':visible'
)).
toBe
(
false
)
it
"should render both assets"
,
->
it
"should render both assets"
,
->
requests
=
setup
.
call
(
this
)
requests
=
setup
.
call
(
this
)
expect
(
@
view
.
$el
).
toContainText
(
"test asset 1"
)
expect
(
@
view
.
$el
).
toContainText
(
"test asset 1"
)
...
...
cms/static/js/spec/create_sinon.js
View file @
f3c23e89
define
([
"sinon"
],
function
(
sinon
)
{
define
([
"sinon"
],
function
(
sinon
)
{
var
fakeServer
,
fakeRequests
,
respondWithJson
,
respondWithError
;
/* These utility methods are used by Jasmine tests to create a mock server or
/* These utility methods are used by Jasmine tests to create a mock server or
* get reference to mock requests. In either case, the cleanup (restore) is done with
* get reference to mock requests. In either case, the cleanup (restore) is done with
* an after function.
* an after function.
...
@@ -15,7 +17,7 @@ define(["sinon"], function(sinon) {
...
@@ -15,7 +17,7 @@ define(["sinon"], function(sinon) {
* Get a reference to the mocked server, and respond
* Get a reference to the mocked server, and respond
* to all requests with the specified statusCode.
* to all requests with the specified statusCode.
*/
*/
var
fakeServer
=
function
(
statusCode
,
that
)
{
fakeServer
=
function
(
statusCode
,
that
)
{
var
server
=
sinon
.
fakeServer
.
create
();
var
server
=
sinon
.
fakeServer
.
create
();
that
.
after
(
function
()
{
that
.
after
(
function
()
{
server
.
restore
();
server
.
restore
();
...
@@ -29,9 +31,9 @@ define(["sinon"], function(sinon) {
...
@@ -29,9 +31,9 @@ define(["sinon"], function(sinon) {
* return a reference to the Array. This allows tests
* return a reference to the Array. This allows tests
* to respond for individual requests.
* to respond for individual requests.
*/
*/
var
fakeRequests
=
function
(
that
)
{
fakeRequests
=
function
(
that
)
{
var
requests
=
[]
;
var
requests
=
[]
,
var
xhr
=
sinon
.
useFakeXMLHttpRequest
();
xhr
=
sinon
.
useFakeXMLHttpRequest
();
xhr
.
onCreate
=
function
(
request
)
{
xhr
.
onCreate
=
function
(
request
)
{
requests
.
push
(
request
);
requests
.
push
(
request
);
};
};
...
@@ -43,16 +45,24 @@ define(["sinon"], function(sinon) {
...
@@ -43,16 +45,24 @@ define(["sinon"], function(sinon) {
return
requests
;
return
requests
;
};
};
var
respondWithJson
=
function
(
requests
,
jsonResponse
,
requestIndex
)
{
respondWithJson
=
function
(
requests
,
jsonResponse
,
requestIndex
)
{
requestIndex
=
requestIndex
||
requests
.
length
-
1
;
requestIndex
=
requestIndex
||
requests
.
length
-
1
;
requests
[
requestIndex
].
respond
(
200
,
requests
[
requestIndex
].
respond
(
200
,
{
"Content-Type"
:
"application/json"
},
{
"Content-Type"
:
"application/json"
},
JSON
.
stringify
(
jsonResponse
));
JSON
.
stringify
(
jsonResponse
));
};
};
respondWithError
=
function
(
requests
,
requestIndex
)
{
requestIndex
=
requestIndex
||
requests
.
length
-
1
;
requests
[
requestIndex
].
respond
(
500
,
{
"Content-Type"
:
"application/json"
},
JSON
.
stringify
({
}));
};
return
{
return
{
"server"
:
fakeServer
,
"server"
:
fakeServer
,
"requests"
:
fakeRequests
,
"requests"
:
fakeRequests
,
"respondWithJson"
:
respondWithJson
"respondWithJson"
:
respondWithJson
,
"respondWithError"
:
respondWithError
};
};
});
});
cms/static/js/views/assets.js
View file @
f3c23e89
define
([
"js/views/paging"
,
"js/views/asset"
,
"js/views/paging_header"
,
"js/views/paging_footer"
],
define
([
"j
query"
,
"underscore"
,
"gettext"
,
"j
s/views/paging"
,
"js/views/asset"
,
"js/views/paging_header"
,
"js/views/paging_footer"
],
function
(
PagingView
,
AssetView
,
PagingHeader
,
PagingFooter
)
{
function
(
$
,
_
,
gettext
,
PagingView
,
AssetView
,
PagingHeader
,
PagingFooter
)
{
var
AssetsView
=
PagingView
.
extend
({
var
AssetsView
=
PagingView
.
extend
({
// takes AssetCollection as model
// takes AssetCollection as model
events
:
{
events
:
{
"click .column-sort-link"
:
"onToggleColumn"
"click .column-sort-link"
:
"onToggleColumn"
},
},
initialize
:
function
()
{
initialize
:
function
()
{
PagingView
.
prototype
.
initialize
.
call
(
this
);
PagingView
.
prototype
.
initialize
.
call
(
this
);
var
collection
=
this
.
collection
;
var
collection
=
this
.
collection
;
this
.
template
=
_
.
template
(
$
(
"#asset-library-tpl"
).
text
());
this
.
template
=
_
.
template
(
$
(
"#asset-library-tpl"
).
text
());
this
.
listenTo
(
collection
,
'destroy'
,
this
.
handleDestroy
);
this
.
listenTo
(
collection
,
'destroy'
,
this
.
handleDestroy
);
this
.
registerSortableColumn
(
'js-asset-name-col'
,
gettext
(
'Name'
),
'display_name'
,
'asc'
);
this
.
registerSortableColumn
(
'js-asset-name-col'
,
gettext
(
'Name'
),
'display_name'
,
'asc'
);
this
.
registerSortableColumn
(
'js-asset-date-col'
,
gettext
(
'Date Added'
),
'date_added'
,
'desc'
);
this
.
registerSortableColumn
(
'js-asset-date-col'
,
gettext
(
'Date Added'
),
'date_added'
,
'desc'
);
this
.
setInitialSortColumn
(
'js-asset-date-col'
);
this
.
setInitialSortColumn
(
'js-asset-date-col'
);
},
this
.
showLoadingIndicator
();
},
render
:
function
()
{
render
:
function
()
{
this
.
$el
.
html
(
this
.
template
());
// Wait until the content is loaded the first time to render
this
.
tableBody
=
this
.
$
(
'#asset-table-body'
);
return
this
;
this
.
pagingHeader
=
new
PagingHeader
({
view
:
this
,
el
:
$
(
'#asset-paging-header'
)});
},
this
.
pagingFooter
=
new
PagingFooter
({
view
:
this
,
el
:
$
(
'#asset-paging-footer'
)});
this
.
pagingHeader
.
render
();
this
.
pagingFooter
.
render
();
// Hide the contents until the collection has loaded the first time
getTableBody
:
function
()
{
this
.
$
(
'.asset-library'
).
hide
();
var
tableBody
=
this
.
tableBody
;
this
.
$
(
'.no-asset-content'
).
hide
();
if
(
!
tableBody
)
{
this
.
hideLoadingIndicator
();
return
this
;
// Create the table
},
this
.
$el
.
html
(
this
.
template
());
tableBody
=
this
.
$
(
'#asset-table-body'
);
this
.
tableBody
=
tableBody
;
this
.
pagingHeader
=
new
PagingHeader
({
view
:
this
,
el
:
$
(
'#asset-paging-header'
)});
this
.
pagingFooter
=
new
PagingFooter
({
view
:
this
,
el
:
$
(
'#asset-paging-footer'
)});
this
.
pagingHeader
.
render
();
this
.
pagingFooter
.
render
();
renderPageItems
:
function
()
{
// Hide the contents until the collection has loaded the first time
var
self
=
this
,
this
.
$
(
'.asset-library'
).
hide
();
assets
=
this
.
collection
,
this
.
$
(
'.no-asset-content'
).
hide
();
hasAssets
=
assets
.
length
>
0
;
}
self
.
tableBody
.
empty
();
return
tableBody
;
if
(
hasAssets
)
{
},
assets
.
each
(
function
(
asset
)
{
renderPageItems
:
function
()
{
var
view
=
new
AssetView
({
model
:
asset
});
var
self
=
this
,
self
.
tableBody
.
append
(
view
.
render
().
el
);
assets
=
this
.
collection
,
hasAssets
=
assets
.
length
>
0
,
tableBody
=
this
.
getTableBody
();
tableBody
.
empty
();
if
(
hasAssets
)
{
assets
.
each
(
function
(
asset
)
{
var
view
=
new
AssetView
({
model
:
asset
});
tableBody
.
append
(
view
.
render
().
el
);
}
);
}
self
.
$
(
'.asset-library'
).
toggle
(
hasAssets
);
self
.
$
(
'.no-asset-content'
).
toggle
(
!
hasAssets
);
return
this
;
},
onError
:
function
()
{
this
.
hideLoadingIndicator
();
},
handleDestroy
:
function
(
model
)
{
this
.
collection
.
fetch
({
reset
:
true
});
// reload the collection to get a fresh page full of items
analytics
.
track
(
'Deleted Asset'
,
{
'course'
:
course_location_analytics
,
'id'
:
model
.
get
(
'url'
)
});
});
}
},
self
.
$
(
'.asset-library'
).
toggle
(
hasAssets
);
self
.
$
(
'.no-asset-content'
).
toggle
(
!
hasAssets
);
return
this
;
},
handleDestroy
:
function
(
model
,
collection
,
options
)
{
addAsset
:
function
(
model
)
{
this
.
collection
.
fetch
({
reset
:
true
});
// reload the collection to get a fresh page full of items
// Switch the sort column back to the default (most recent date added) and show the first page
analytics
.
track
(
'Deleted Asset'
,
{
// so that the new asset is shown at the top of the page.
'course'
:
course_location_analytics
,
this
.
setInitialSortColumn
(
'js-asset-date-col'
);
'id'
:
model
.
get
(
'url'
)
this
.
setPage
(
0
);
});
},
addAsset
:
function
(
model
)
{
analytics
.
track
(
'Uploaded a File'
,
{
// Switch the sort column back to the default (most recent date added) and show the first page
'course'
:
course_location_analytics
,
// so that the new asset is shown at the top of the page.
'asset_url'
:
model
.
get
(
'url'
)
this
.
setInitialSortColumn
(
'js-asset-date-col'
);
}
);
this
.
setPage
(
0
);
},
analytics
.
track
(
'Uploaded a File'
,
{
onToggleColumn
:
function
(
event
)
{
'course'
:
course_location_analytics
,
var
columnName
=
event
.
target
.
id
;
'asset_url'
:
model
.
get
(
'url'
)
this
.
toggleSortOrder
(
columnName
);
}
});
});
},
onToggleColumn
:
function
(
event
)
{
var
columnName
=
event
.
target
.
id
;
this
.
toggleSortOrder
(
columnName
);
}
});
return
AssetsView
;
return
AssetsView
;
});
// end define();
});
// end define();
cms/static/js/views/baseview.js
View file @
f3c23e89
...
@@ -46,6 +46,14 @@ define(["jquery", "underscore", "backbone", "js/utils/handle_iframe_binding"],
...
@@ -46,6 +46,14 @@ define(["jquery", "underscore", "backbone", "js/utils/handle_iframe_binding"],
event
.
preventDefault
();
event
.
preventDefault
();
target
.
closest
(
'.expand-collapse'
).
toggleClass
(
'expand'
).
toggleClass
(
'collapse'
);
target
.
closest
(
'.expand-collapse'
).
toggleClass
(
'expand'
).
toggleClass
(
'collapse'
);
target
.
closest
(
'.is-collapsible, .window'
).
toggleClass
(
'collapsed'
);
target
.
closest
(
'.is-collapsible, .window'
).
toggleClass
(
'collapsed'
);
},
showLoadingIndicator
:
function
()
{
$
(
'.ui-loading'
).
show
();
},
hideLoadingIndicator
:
function
()
{
$
(
'.ui-loading'
).
hide
();
}
}
});
});
...
...
cms/static/js/views/paging.js
View file @
f3c23e89
define
([
"backbone"
,
"js/views/feedback_alert"
,
"gettext"
],
function
(
Backbone
,
AlertView
,
gettext
)
{
define
([
"underscore"
,
"js/views/baseview"
,
"js/views/feedback_alert"
,
"gettext"
],
function
(
_
,
BaseView
,
AlertView
,
gettext
)
{
var
PagingView
=
Backbone
.
View
.
extend
({
var
PagingView
=
Base
View
.
extend
({
// takes a Backbone Paginator as a model
// takes a Backbone Paginator as a model
sortableColumns
:
{},
sortableColumns
:
{},
initialize
:
function
()
{
initialize
:
function
()
{
Backbone
.
View
.
prototype
.
initialize
.
call
(
this
);
Base
View
.
prototype
.
initialize
.
call
(
this
);
var
collection
=
this
.
collection
;
var
collection
=
this
.
collection
;
collection
.
bind
(
'add'
,
_
.
bind
(
this
.
onPageRefresh
,
this
));
collection
.
bind
(
'add'
,
_
.
bind
(
this
.
onPageRefresh
,
this
));
collection
.
bind
(
'remove'
,
_
.
bind
(
this
.
onPageRefresh
,
this
));
collection
.
bind
(
'remove'
,
_
.
bind
(
this
.
onPageRefresh
,
this
));
collection
.
bind
(
'reset'
,
_
.
bind
(
this
.
onPageRefresh
,
this
));
collection
.
bind
(
'reset'
,
_
.
bind
(
this
.
onPageRefresh
,
this
));
},
},
onPageRefresh
:
function
()
{
onPageRefresh
:
function
()
{
var
sortColumn
=
this
.
sortColumn
;
var
sortColumn
=
this
.
sortColumn
;
this
.
renderPageItems
();
this
.
renderPageItems
();
this
.
$
(
'.column-sort-link'
).
removeClass
(
'current-sort'
);
this
.
$
(
'.column-sort-link'
).
removeClass
(
'current-sort'
);
this
.
$
(
'#'
+
sortColumn
).
addClass
(
'current-sort'
);
this
.
$
(
'#'
+
sortColumn
).
addClass
(
'current-sort'
);
},
},
setPage
:
function
(
page
)
{
setPage
:
function
(
page
)
{
var
self
=
this
,
var
self
=
this
,
collection
=
self
.
collection
,
collection
=
self
.
collection
,
oldPage
=
collection
.
currentPage
;
oldPage
=
collection
.
currentPage
;
collection
.
goTo
(
page
,
{
collection
.
goTo
(
page
,
{
reset
:
true
,
reset
:
true
,
success
:
function
()
{
success
:
function
()
{
window
.
scrollTo
(
0
,
0
);
window
.
scrollTo
(
0
,
0
);
},
},
error
:
function
(
collection
,
response
,
options
)
{
error
:
function
(
collection
)
{
collection
.
currentPage
=
oldPage
;
collection
.
currentPage
=
oldPage
;
}
self
.
onError
();
});
}
},
});
},
nextPage
:
function
()
{
onError
:
function
()
{
var
collection
=
this
.
collection
,
// Do nothing by default
currentPage
=
collection
.
currentPage
,
},
lastPage
=
collection
.
totalPages
-
1
;
if
(
currentPage
<
lastPage
)
{
this
.
setPage
(
currentPage
+
1
);
}
},
previousPage
:
function
()
{
nextPage
:
function
()
{
var
collection
=
this
.
collection
,
var
collection
=
this
.
collection
,
currentPage
=
collection
.
currentPage
;
currentPage
=
collection
.
currentPage
,
if
(
currentPage
>
0
)
{
lastPage
=
collection
.
totalPages
-
1
;
this
.
setPage
(
currentPage
-
1
);
if
(
currentPage
<
lastPage
)
{
}
this
.
setPage
(
currentPage
+
1
);
},
}
},
previousPage
:
function
()
{
var
collection
=
this
.
collection
,
currentPage
=
collection
.
currentPage
;
if
(
currentPage
>
0
)
{
this
.
setPage
(
currentPage
-
1
);
}
},
/**
/**
* Registers information about a column that can be sorted.
* Registers information about a column that can be sorted.
* @param columnName The element name of the column.
* @param columnName The element name of the column.
* @param displayName The display name for the column in the current locale.
* @param displayName The display name for the column in the current locale.
* @param fieldName The database field name that is represented by this column.
* @param fieldName The database field name that is represented by this column.
* @param defaultSortDirection The default sort direction for the column
* @param defaultSortDirection The default sort direction for the column
*/
*/
registerSortableColumn
:
function
(
columnName
,
displayName
,
fieldName
,
defaultSortDirection
)
{
registerSortableColumn
:
function
(
columnName
,
displayName
,
fieldName
,
defaultSortDirection
)
{
this
.
sortableColumns
[
columnName
]
=
{
this
.
sortableColumns
[
columnName
]
=
{
displayName
:
displayName
,
displayName
:
displayName
,
fieldName
:
fieldName
,
fieldName
:
fieldName
,
defaultSortDirection
:
defaultSortDirection
defaultSortDirection
:
defaultSortDirection
};
};
},
},
sortableColumnInfo
:
function
(
sortColumn
)
{
sortableColumnInfo
:
function
(
sortColumn
)
{
var
sortInfo
=
this
.
sortableColumns
[
sortColumn
];
var
sortInfo
=
this
.
sortableColumns
[
sortColumn
];
if
(
!
sortInfo
)
{
if
(
!
sortInfo
)
{
throw
"Unregistered sort column '"
+
sortColumn
+
'"'
;
throw
"Unregistered sort column '"
+
sortColumn
+
'"'
;
}
}
return
sortInfo
;
return
sortInfo
;
},
},
sortDisplayName
:
function
()
{
sortDisplayName
:
function
()
{
var
sortColumn
=
this
.
sortColumn
,
var
sortColumn
=
this
.
sortColumn
,
sortInfo
=
this
.
sortableColumnInfo
(
sortColumn
);
sortInfo
=
this
.
sortableColumnInfo
(
sortColumn
);
return
sortInfo
.
displayName
;
return
sortInfo
.
displayName
;
},
},
sortDirectionName
:
function
()
{
sortDirectionName
:
function
()
{
var
collection
=
this
.
collection
;
var
collection
=
this
.
collection
,
if
(
collection
.
sortDirection
===
'asc'
)
{
ascending
=
collection
.
sortDirection
===
'asc'
;
return
gettext
(
"ascending"
);
return
ascending
?
gettext
(
"ascending"
)
:
gettext
(
"descending"
);
}
else
{
},
return
gettext
(
"descending"
);
}
},
setInitialSortColumn
:
function
(
sortColumn
)
{
setInitialSortColumn
:
function
(
sortColumn
)
{
var
collection
=
this
.
collection
,
var
collection
=
this
.
collection
,
sortInfo
=
this
.
sortableColumns
[
sortColumn
];
sortInfo
=
this
.
sortableColumns
[
sortColumn
];
collection
.
sortField
=
sortInfo
.
fieldName
;
collection
.
sortField
=
sortInfo
.
fieldName
;
collection
.
sortDirection
=
sortInfo
.
defaultSortDirection
;
collection
.
sortDirection
=
sortInfo
.
defaultSortDirection
;
this
.
sortColumn
=
sortColumn
;
this
.
sortColumn
=
sortColumn
;
},
},
toggleSortOrder
:
function
(
sortColumn
)
{
toggleSortOrder
:
function
(
sortColumn
)
{
var
collection
=
this
.
collection
,
var
collection
=
this
.
collection
,
sortInfo
=
this
.
sortableColumnInfo
(
sortColumn
),
sortInfo
=
this
.
sortableColumnInfo
(
sortColumn
),
sortField
=
sortInfo
.
fieldName
,
sortField
=
sortInfo
.
fieldName
,
defaultSortDirection
=
sortInfo
.
defaultSortDirection
;
defaultSortDirection
=
sortInfo
.
defaultSortDirection
;
if
(
collection
.
sortField
===
sortField
)
{
if
(
collection
.
sortField
===
sortField
)
{
collection
.
sortDirection
=
collection
.
sortDirection
===
'asc'
?
'desc'
:
'asc'
;
collection
.
sortDirection
=
collection
.
sortDirection
===
'asc'
?
'desc'
:
'asc'
;
}
else
{
}
else
{
collection
.
sortField
=
sortField
;
collection
.
sortField
=
sortField
;
collection
.
sortDirection
=
defaultSortDirection
;
collection
.
sortDirection
=
defaultSortDirection
;
}
this
.
sortColumn
=
sortColumn
;
this
.
setPage
(
0
);
}
}
this
.
sortColumn
=
sortColumn
;
});
this
.
setPage
(
0
);
}
});
return
PagingView
;
return
PagingView
;
});
// end define();
});
// end define();
cms/templates/asset_index.html
View file @
f3c23e89
...
@@ -27,7 +27,7 @@ require(["domReady", "jquery", "js/models/asset", "js/collections/asset",
...
@@ -27,7 +27,7 @@ require(["domReady", "jquery", "js/models/asset", "js/collections/asset",
var
assets
=
new
AssetCollection
();
var
assets
=
new
AssetCollection
();
assets
.
url
=
"${asset_callback_url}"
;
assets
.
url
=
"${asset_callback_url}"
;
var
assetsView
=
new
AssetsView
({
collection
:
assets
,
el
:
$
(
'
#asset-library
'
)});
var
assetsView
=
new
AssetsView
({
collection
:
assets
,
el
:
$
(
'
.assets-wrapper
'
)});
assetsView
.
render
();
assetsView
.
render
();
assetsView
.
setPage
(
0
);
assetsView
.
setPage
(
0
);
...
@@ -148,7 +148,12 @@ require(["domReady", "jquery", "js/models/asset", "js/collections/asset",
...
@@ -148,7 +148,12 @@ require(["domReady", "jquery", "js/models/asset", "js/collections/asset",
<div
class=
"wrapper-content wrapper"
>
<div
class=
"wrapper-content wrapper"
>
<section
class=
"content"
>
<section
class=
"content"
>
<article
id=
"asset-library"
class=
"content-primary"
role=
"main"
></article>
<article
class=
"content-primary"
role=
"main"
>
<div
class=
"assets-wrapper"
/>
<div
class=
"ui-loading"
>
<p><span
class=
"spin"
><i
class=
"icon-refresh"
></i></span>
<span
class=
"copy"
>
${_("Loading
…
")}
</span></p>
</div>
</article>
<aside
class=
"content-supplementary"
role=
"complimentary"
>
<aside
class=
"content-supplementary"
role=
"complimentary"
>
<div
class=
"bit"
>
<div
class=
"bit"
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment