Display objects of activities. Rework how activities are displayed.

This commit is contained in:
Feufochmar 2020-03-30 17:51:02 +02:00
parent 9431963b13
commit 86a84b287b
8 changed files with 272 additions and 106 deletions

View File

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

View File

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

View File

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

153
src/activity-object.js Normal file
View File

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

View File

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

View File

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

View File

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

View File

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