Display objects of activities. Rework how activities are displayed.
This commit is contained in:
parent
9431963b13
commit
86a84b287b
13
apmail.css
13
apmail.css
|
@ -189,18 +189,11 @@ p {
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
section.activity-object-field:after {
|
||||
content: "";
|
||||
display: block;
|
||||
border-style: dotted;
|
||||
border-width: thin;
|
||||
margin: 0;
|
||||
margin-top: 0.1em;
|
||||
margin-bottom: 0.1em;
|
||||
}
|
||||
|
||||
section.activity-object-content {
|
||||
background-color: hsl(240, 10%, 95%);
|
||||
padding: 0.1em;
|
||||
padding-top: 0.3em;
|
||||
padding-bottom: 0.3em;
|
||||
}
|
||||
|
||||
#profile-display-name {
|
||||
|
|
49
index.html
49
index.html
|
@ -91,32 +91,31 @@
|
|||
</main>
|
||||
<!-- Show Activity page -->
|
||||
<main id="show-activity" style="display:none;">
|
||||
<h4>Activity <span id="activity-type"></span></h4>
|
||||
<p>Published: <span id="activity-published"></span></p>
|
||||
<section>
|
||||
From:
|
||||
<span id="activity-actor-icon"></span>
|
||||
<p style="display:inline-block;">
|
||||
<span id="activity-actor-display-name"></span><br/>
|
||||
<a id="activity-actor-address"></a>
|
||||
</p>
|
||||
</section>
|
||||
<!-- Activity info -->
|
||||
<details>
|
||||
<summary>Audience</summary>
|
||||
<p>To: <ul id="activity-to" class="recipient-list"></ul></p>
|
||||
<p>Cc: <ul id="activity-cc" class="recipient-list"></ul></p>
|
||||
<summary><h4 style="display:inline;"><span id="activity-type"></span> Activity</h4></summary>
|
||||
<p>Published: <span id="activity-published"></span></p>
|
||||
<h4>Actor</h4>
|
||||
<span id="activity-actor-icon"></span>
|
||||
<p style="display:inline-block;">
|
||||
<span id="activity-actor-display-name"></span><br/>
|
||||
<a id="activity-actor-address"></a>
|
||||
</p>
|
||||
<h4>Audience</h4>
|
||||
<p>To: <ul id="activity-to" class="recipient-list"></ul></p>
|
||||
<p>Cc: <ul id="activity-cc" class="recipient-list"></ul></p>
|
||||
<details>
|
||||
<summary>Show source</summary>
|
||||
<textarea rows="30" cols="120" readonly id="activity-code-source">
|
||||
</textarea>
|
||||
</details>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Show source</summary>
|
||||
<textarea rows="30" cols="120" readonly id="activity-code-source">
|
||||
</textarea>
|
||||
</details>
|
||||
<!-- Object display -->
|
||||
<!-- Object info -->
|
||||
<section id="activity-object">
|
||||
<h4>Object <span id="activity-object-type"></span></h4>
|
||||
<h4><span id="activity-object-type"></span> Object</h4>
|
||||
<p>Published: <span id="activity-object-published"></span></p>
|
||||
<section>
|
||||
Author:
|
||||
Author:<br/>
|
||||
<span id="activity-object-actor-icon"></span>
|
||||
<p style="display:inline-block;">
|
||||
<span id="activity-object-actor-display-name"></span><br/>
|
||||
|
@ -128,9 +127,11 @@
|
|||
<p>To: <ul id="activity-object-to" class="recipient-list"></ul></p>
|
||||
<p>Cc: <ul id="activity-object-cc" class="recipient-list"></ul></p>
|
||||
</details>
|
||||
<h5><span id="activity-object-title"></span><h5>
|
||||
<p id="activity-object-summary"><p>
|
||||
<p id="activity-object-content"><p>
|
||||
<section class="activity-object-content">
|
||||
<h5><span id="activity-object-title"></span></h5>
|
||||
<em><p id="activity-object-summary"><p></em>
|
||||
<p id="activity-object-content"><p>
|
||||
</section>
|
||||
<details>
|
||||
<summary>Attachments</summary>
|
||||
<ul id="activity-object-attachments" class="attachments"></ul>
|
||||
|
|
87
render.js
87
render.js
|
@ -48,40 +48,6 @@ const Render = {
|
|||
}
|
||||
str_data = str_data.replace(/[&<>"']/g, x => replace_map[x])
|
||||
return str_data
|
||||
},
|
||||
// Activity in timeline
|
||||
// By type of activity
|
||||
timelineActivity: {
|
||||
'Create': function(activity) {
|
||||
var display = '<section class="timeline-activity" onclick="UI.showActivity(\'' + activity.id + '\');">'
|
||||
+ 'New ' + activity.object.type + '<br/>'
|
||||
+ '<strong>' + activity.actor.displayName() + '</strong><br/>'
|
||||
if (activity.object.summary) {
|
||||
display = display + '<em>' + activity.object.summary + '</em>'
|
||||
} else {
|
||||
display = display + '<em>No summary</em>'
|
||||
}
|
||||
display = display + '</section>'
|
||||
return display
|
||||
},
|
||||
'Like': function(activity) {
|
||||
return '<section class="timeline-activity" onclick="UI.showActivity(\'' + activity.id + '\');">'
|
||||
+ 'Like <br/>'
|
||||
+ '<strong>' + activity.actor.displayName() + '</strong>'
|
||||
+ '</section>'
|
||||
},
|
||||
'Announce': function(activity) {
|
||||
return '<section class="timeline-activity" onclick="UI.showActivity(\'' + activity.id + '\');">'
|
||||
+ 'Announce <br/>'
|
||||
+ '<strong>' + activity.actor.displayName() + '</strong>'
|
||||
+ '</section>'
|
||||
},
|
||||
'Delete': function(activity) {
|
||||
return '<section class="timeline-activity" onclick="UI.showActivity(\'' + activity.id + '\');">'
|
||||
+ 'Delete <br/>'
|
||||
+ '<strong>' + activity.actor.displayName() + '</strong>'
|
||||
+ '</section>'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,19 +152,33 @@ const UI = {
|
|||
}).join('')
|
||||
Elem('activity-code-source').value = JSON.stringify(activity.raw, null, 1)
|
||||
// Object of activity
|
||||
// Elem('activity-object-type')
|
||||
// Elem('activity-object-published')
|
||||
// Elem('activity-object-actor-icon')
|
||||
// Elem('activity-object-actor-display-name')
|
||||
// Elem('activity-object-actor-address')
|
||||
// Elem('activity-object-to')
|
||||
// Elem('activity-object-cc')
|
||||
// Elem('activity-object-title')
|
||||
// Elem('activity-object-summary')
|
||||
// Elem('activity-object-content')
|
||||
// Elem('activity-object-attachments')
|
||||
// Elem('activity-object-tags')
|
||||
// Elem('activity-object-code-source')
|
||||
if (activity.object) {
|
||||
Elem('activity-object').style.display = 'block'
|
||||
Elem('activity-object-type').innerText = activity.object.type
|
||||
Elem('activity-object-published').innerText = activity.object.published ? activity.object.published.toLocaleString() : ''
|
||||
const icon = activity.object.actor.info.icon ? activity.object.actor.info.icon : Icons['unknown-user']
|
||||
Elem('activity-object-actor-icon').innerHTML = '<img src="' + icon + '" width="48" height="48" />'
|
||||
Elem('activity-object-actor-display-name').innerText = activity.object.actor.displayName()
|
||||
Elem('activity-object-actor-address').innerText = activity.object.actor.address()
|
||||
Elem('activity-object-actor-address').href = activity.object.actor.urls.profile
|
||||
Elem('activity-object-to').innerHTML = activity.object.to.map(
|
||||
function(element) {
|
||||
return '<li class="actor-display">' + Render.audienceActor(element) + '</li>'
|
||||
}).join('')
|
||||
Elem('activity-object-cc').innerHTML = activity.object.cc.map(
|
||||
function(element) {
|
||||
return '<li class="actor-display">' + Render.audienceActor(element) + '</li>'
|
||||
}).join('')
|
||||
Elem('activity-object-code-source').value = JSON.stringify(activity.object.raw, null, 1)
|
||||
Elem('activity-object-title').innerText = activity.object.title
|
||||
Elem('activity-object-summary').innerText = activity.object.summary
|
||||
Elem('activity-object-content').innerHTML = activity.object.content
|
||||
// Elem('activity-object-attachments')
|
||||
// Elem('activity-object-tags')
|
||||
} else {
|
||||
// Hide the element
|
||||
Elem('activity-object').style.display = 'none'
|
||||
}
|
||||
},
|
||||
'send-message': function(_) {
|
||||
Elem('send-message-to-recipient').value = ''
|
||||
|
@ -281,13 +261,9 @@ const UI = {
|
|||
function(load_ok, failure_message) {
|
||||
if (load_ok) {
|
||||
Elem('timeline-data').innerHTML = UI.timeline.activities.map(function(activity) {
|
||||
if (Render.timelineActivity[activity.type]) {
|
||||
return Render.timelineActivity[activity.type](activity)
|
||||
} else {
|
||||
return '<section class="timeline-activity">'
|
||||
+ 'Other activity (' + activity.type + ')<br/>'
|
||||
+ '<strong>' + activity.actor.displayName() + '</strong></section>'
|
||||
}
|
||||
return '<section class="timeline-activity" onclick="UI.showActivity(\'' + activity.id + '\');">'
|
||||
+ activity.type + ' Activity<br/>'
|
||||
+ '<strong>' + activity.actor.displayName() + '</strong></section>'
|
||||
}).join('')
|
||||
if (UI.timeline.prev) {
|
||||
Elem('timeline-prev-top').style.display = 'block'
|
||||
|
@ -301,8 +277,7 @@ const UI = {
|
|||
UI.displayTimelineError(failure_message)
|
||||
Elem('timeline-data').innerHTML = ''
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Elem('timeline').style.display = 'none'
|
||||
}
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
const {Actor} = require('./actor.js')
|
||||
const {KnownActors} = require('./known-actors.js')
|
||||
|
||||
// ASObject class: represent of Object of the ActivityStream spec
|
||||
const ASObject = function(raw_object) {
|
||||
this.raw = raw_object
|
||||
this.id = raw_object.id
|
||||
this.type = raw_object.type
|
||||
this.published = raw_object.published ? new Date(raw_object.published) : undefined
|
||||
this.title = raw_object.title ? raw_object.title : ''
|
||||
this.summary = raw_object.summary ? raw_object.summary : ''
|
||||
this.content = raw_object.content ? raw_object.content : ''
|
||||
// actor, to, cc are filled in loadActors
|
||||
this.actor = new Actor()
|
||||
this.to = []
|
||||
this.cc = []
|
||||
}
|
||||
// Fetch function
|
||||
// Callback takes 3 parameters: load_ok, fetched_object, failure_message
|
||||
ASObject.fetch = function(url, token, callback) {
|
||||
if (typeof url === 'object') {
|
||||
// No need to fetch: already an object
|
||||
const obj = new ASObject(url)
|
||||
obj.load(token, function(load_ok, failure_message) {
|
||||
if (load_ok) {
|
||||
callback(true, obj, undefined)
|
||||
} else {
|
||||
callback(false, undefined, failure_message)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Use ActivityPub protocol to get the object
|
||||
// The id is the link to the object on the server
|
||||
const request = new XMLHttpRequest()
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState == 4 && request.status == 200) {
|
||||
const answer = JSON.parse(request.responseText)
|
||||
if (answer) {
|
||||
const obj = new ASObject(answer)
|
||||
obj.load(token, function(load_ok, failure_message) {
|
||||
if (load_ok) {
|
||||
callback(true, obj, undefined)
|
||||
} else {
|
||||
callback(false, undefined, 'Unable to retrieve actors of objects.')
|
||||
console.log(answer)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
callback(false, undefined, 'Unable to retrieve object.')
|
||||
console.log(answer)
|
||||
}
|
||||
} else if (request.readyState == 4) {
|
||||
callback(false, undefined, 'Error during retrieval of object.')
|
||||
}
|
||||
}
|
||||
request.open('GET', url, true)
|
||||
if (token) {
|
||||
request.setRequestHeader('Authorization', 'Bearer ' + token)
|
||||
}
|
||||
request.setRequestHeader('Content-Type', 'application/activity+json')
|
||||
request.setRequestHeader('Accept', 'application/activity+json')
|
||||
request.send()
|
||||
}
|
||||
}
|
||||
|
||||
ASObject.prototype = {
|
||||
// Load the actors present in the actor, to and cc fields
|
||||
loadActors: function(token, callback) {
|
||||
// Load the author
|
||||
// Try: actor, then attributedTo
|
||||
var profile = this.raw.actor
|
||||
if (!profile) {
|
||||
profile = this.raw.attributedTo
|
||||
}
|
||||
KnownActors.retrieve(
|
||||
profile,
|
||||
token,
|
||||
function(load_ok, actor, failure_message) {
|
||||
if (load_ok) {
|
||||
this.actor = actor
|
||||
}
|
||||
// Load the actors in the "to" array, skip to "cc" if there is no "to", and stop is there is no "cc" either
|
||||
if (this.raw.to && Array.isArray(this.raw.to)) {
|
||||
this.loadToActors(this.raw.to.values(), token, callback)
|
||||
} else if (this.raw.to && typeof this.raw.to === 'string') {
|
||||
// Only one element in the array
|
||||
this.loadToActors([this.raw.to].values(), token, callback)
|
||||
} else if (this.raw.cc && Array.isArray(this.raw.cc)) {
|
||||
this.loadCcActors(this.raw.cc.values(), token, callback)
|
||||
} else if (this.raw.cc && typeof this.raw.cc === 'string') {
|
||||
// Only one element in the array
|
||||
this.loadToActors([this.raw.cc].values(), token, callback)
|
||||
} else {
|
||||
callback(true, undefined)
|
||||
}
|
||||
}.bind(this))
|
||||
},
|
||||
// Load the "To" array
|
||||
loadToActors: function(iter, token, callback) {
|
||||
const next = iter.next()
|
||||
if (next.done) {
|
||||
// Finished: load the "cc" array (if possible)
|
||||
if (this.raw.cc && Array.isArray(this.raw.cc)) {
|
||||
this.loadCcActors(this.raw.cc.values(), token, callback)
|
||||
} else if (this.raw.cc && typeof this.raw.cc === 'string') {
|
||||
// Only one element in the array
|
||||
this.loadToActors([this.raw.cc].values(), token, callback)
|
||||
} else {
|
||||
callback(true, undefined)
|
||||
}
|
||||
} else {
|
||||
const profile = next.value
|
||||
KnownActors.retrieve(
|
||||
profile,
|
||||
token,
|
||||
function(load_ok, actor, failure_message) {
|
||||
if (load_ok) {
|
||||
this.to.push(actor)
|
||||
} else {
|
||||
// Collection ?
|
||||
}
|
||||
this.loadToActors(iter, token, callback)
|
||||
}.bind(this))
|
||||
}
|
||||
},
|
||||
// Load the "Cc" array
|
||||
loadCcActors: function(iter, token, callback) {
|
||||
const next = iter.next()
|
||||
if (next.done) {
|
||||
callback(true, undefined)
|
||||
} else {
|
||||
const profile = next.value
|
||||
KnownActors.retrieve(
|
||||
profile,
|
||||
token,
|
||||
function(load_ok, actor, failure_message) {
|
||||
if (load_ok) {
|
||||
this.cc.push(actor)
|
||||
} else {
|
||||
// Collection ?
|
||||
}
|
||||
this.loadCcActors(iter, token, callback)
|
||||
}.bind(this))
|
||||
}
|
||||
},
|
||||
// Load everything
|
||||
load: function(token, callback) {
|
||||
this.loadActors(token, callback)
|
||||
}
|
||||
}
|
||||
|
||||
// Exported structures
|
||||
exports.ASObject = ASObject
|
|
@ -1,5 +1,6 @@
|
|||
const {Actor} = require('./actor.js')
|
||||
const {KnownActors} = require('./known-actors.js')
|
||||
const {ASObject} = require('./activity-object.js')
|
||||
|
||||
// Activity class
|
||||
const Activity = function(raw_activity) {
|
||||
|
@ -7,40 +8,51 @@ const Activity = function(raw_activity) {
|
|||
this.id = raw_activity.id
|
||||
this.type = raw_activity.type
|
||||
this.published = raw_activity.published ? new Date(raw_activity.published) : undefined
|
||||
this.object = raw_activity.object
|
||||
// filled in loadObject
|
||||
this.object = undefined
|
||||
// actor, to, cc are filled in loadActors
|
||||
this.actor = undefined
|
||||
this.actor = new Actor()
|
||||
this.to = []
|
||||
this.cc = []
|
||||
}
|
||||
Activity.prototype = {
|
||||
// Load the actors present in the actor, to and cc fields
|
||||
loadActors: function(callback) {
|
||||
loadActors: function(token, callback) {
|
||||
// Load the actor
|
||||
const profile = this.raw.actor
|
||||
KnownActors.retrieve(
|
||||
profile,
|
||||
token,
|
||||
function(load_ok, actor, failure_message) {
|
||||
if (load_ok) {
|
||||
this.actor = actor
|
||||
}
|
||||
// Load the actors in the "to" array, skip to "cc" if there is no "to", and stop is there is no "cc" either
|
||||
if (this.raw.to) {
|
||||
this.loadToActors(this.raw.to.values(), callback)
|
||||
} else if (this.raw.cc) {
|
||||
this.loadCcActors(this.raw.cc.values(), callback)
|
||||
if (this.raw.to && Array.isArray(this.raw.to)) {
|
||||
this.loadToActors(this.raw.to.values(), token, callback)
|
||||
} else if (this.raw.to && typeof this.raw.to === 'string') {
|
||||
// Only one element in the array
|
||||
this.loadToActors([this.raw.to].values(), token, callback)
|
||||
} else if (this.raw.cc && Array.isArray(this.raw.cc)) {
|
||||
this.loadCcActors(this.raw.cc.values(), token, callback)
|
||||
} else if (this.raw.cc && typeof this.raw.cc === 'string') {
|
||||
// Only one element in the array
|
||||
this.loadToActors([this.raw.cc].values(), token, callback)
|
||||
} else {
|
||||
callback(true, undefined)
|
||||
}
|
||||
}.bind(this))
|
||||
},
|
||||
// Load the "To" array
|
||||
loadToActors: function(iter, callback) {
|
||||
loadToActors: function(iter, token, callback) {
|
||||
const next = iter.next()
|
||||
if (next.done) {
|
||||
// Finished: load the "cc" array (if possible)
|
||||
if (this.raw.cc) {
|
||||
this.loadCcActors(this.raw.cc.values(), callback)
|
||||
if (this.raw.cc && Array.isArray(this.raw.cc)) {
|
||||
this.loadCcActors(this.raw.cc.values(), token, callback)
|
||||
} else if (this.raw.cc && typeof this.raw.cc === 'string') {
|
||||
// Only one element in the array
|
||||
this.loadToActors([this.raw.cc].values(), token, callback)
|
||||
} else {
|
||||
callback(true, undefined)
|
||||
}
|
||||
|
@ -48,18 +60,19 @@ Activity.prototype = {
|
|||
const profile = next.value
|
||||
KnownActors.retrieve(
|
||||
profile,
|
||||
token,
|
||||
function(load_ok, actor, failure_message) {
|
||||
if (load_ok) {
|
||||
this.to.push(actor)
|
||||
} else {
|
||||
// Collection ?
|
||||
}
|
||||
this.loadToActors(iter, callback)
|
||||
this.loadToActors(iter, token, callback)
|
||||
}.bind(this))
|
||||
}
|
||||
},
|
||||
// Load the "Cc" array
|
||||
loadCcActors: function(iter, callback) {
|
||||
loadCcActors: function(iter, token, callback) {
|
||||
const next = iter.next()
|
||||
if (next.done) {
|
||||
callback(true, undefined)
|
||||
|
@ -67,15 +80,38 @@ Activity.prototype = {
|
|||
const profile = next.value
|
||||
KnownActors.retrieve(
|
||||
profile,
|
||||
token,
|
||||
function(load_ok, actor, failure_message) {
|
||||
if (load_ok) {
|
||||
this.cc.push(actor)
|
||||
} else {
|
||||
// Collection ?
|
||||
}
|
||||
this.loadCcActors(iter, callback)
|
||||
this.loadCcActors(iter, token, callback)
|
||||
}.bind(this))
|
||||
}
|
||||
},
|
||||
// Load the object
|
||||
loadObject: function(token, callback) {
|
||||
// Fetch the object if there is one
|
||||
if (this.raw.object) {
|
||||
ASObject.fetch(this.raw.object, token, function(load_ok, activity_object, failure_message) {
|
||||
if (load_ok) {
|
||||
this.object = activity_object
|
||||
}
|
||||
callback(load_ok, failure_message)
|
||||
}.bind(this))
|
||||
}
|
||||
},
|
||||
// Load everything
|
||||
load: function(token, callback) {
|
||||
this.loadActors(token, function(load_ok, failure_message) {
|
||||
if (load_ok) {
|
||||
this.loadObject(token, callback)
|
||||
} else {
|
||||
callback(false, failure_message)
|
||||
}
|
||||
}.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,8 @@ const KnownActivities = {
|
|||
const answer = JSON.parse(request.responseText)
|
||||
if (answer) {
|
||||
const activity = new Activity(answer)
|
||||
activity.loadActors(
|
||||
activity.load(
|
||||
token,
|
||||
function(load_ok, failure_message) {
|
||||
if (load_ok) {
|
||||
KnownActivities.set(id, activity)
|
||||
|
|
|
@ -19,7 +19,7 @@ const KnownActors = {
|
|||
},
|
||||
// Retrieve an actor
|
||||
// Callback takes three arguments: load_ok, retrieved_actor, failure_message
|
||||
retrieve: function(profile, callback) {
|
||||
retrieve: function(profile, token, callback) {
|
||||
if (KnownActors.get(profile)) {
|
||||
callback(true, KnownActors.get(profile), undefined)
|
||||
} else {
|
||||
|
|
|
@ -29,6 +29,12 @@ Timeline.prototype = {
|
|||
this.prev = answer.first.prev
|
||||
this.next = answer.first.next
|
||||
this.parseActivities(answer.first.orderedItems, token, callback)
|
||||
} else if (answer.type === 'OrderedCollection' && answer.orderedItems) {
|
||||
// Collection is not paginated
|
||||
this.activities = []
|
||||
this.prev = answer.prev
|
||||
this.next = answer.next
|
||||
this.parseActivities(answer.orderedItems, token, callback)
|
||||
} else if (answer.type === 'OrderedCollectionPage') {
|
||||
this.activities = []
|
||||
this.prev = answer.prev
|
||||
|
@ -64,7 +70,8 @@ Timeline.prototype = {
|
|||
}.bind(this))
|
||||
} else if (raw_act) {
|
||||
const act = new Activity(raw_act)
|
||||
act.loadActors(
|
||||
act.load(
|
||||
token,
|
||||
function(load_ok, failure_message) {
|
||||
if (load_ok) {
|
||||
// Push to the list of activities
|
||||
|
|
Loading…
Reference in New Issue