Update
This commit is contained in:
parent
efc4c49a7f
commit
3f69a8a94d
|
@ -38,6 +38,7 @@ var ApiSchema = {
|
||||||
},
|
},
|
||||||
Quoting: {
|
Quoting: {
|
||||||
Mastodon: "reblog",
|
Mastodon: "reblog",
|
||||||
|
Misskey: "renote",
|
||||||
},
|
},
|
||||||
Time: {
|
Time: {
|
||||||
Mastodon: "created_at",
|
Mastodon: "created_at",
|
||||||
|
@ -78,7 +79,16 @@ var ApiSchema = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var ApiEndpoints = {
|
||||||
|
FetchNotes: {
|
||||||
|
Mastodon(Profile) {
|
||||||
|
return `accounts/${Profile.Id}/statuses`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
function ApiTransform(Data, FromSource, DestType) {
|
function ApiTransform(Data, FromSource, DestType) {
|
||||||
|
LogDebug([Data, DestType, FromSource]);
|
||||||
return JsonTransformB(Data, ApiSchema, ApiSchema[DestType], FromSource);
|
return JsonTransformB(Data, ApiSchema, ApiSchema[DestType], FromSource);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
24
App/Elems.js
24
App/Elems.js
|
@ -1,4 +1,4 @@
|
||||||
function HtmlEl(Tag, Attrs) {
|
function MkHtmlEl(Tag, Attrs) {
|
||||||
var El = document.createElement(Tag);
|
var El = document.createElement(Tag);
|
||||||
if (Attrs) {
|
if (Attrs) {
|
||||||
Object.keys(Attrs).forEach(function(Attr){
|
Object.keys(Attrs).forEach(function(Attr){
|
||||||
|
@ -8,15 +8,27 @@ function HtmlEl(Tag, Attrs) {
|
||||||
return El;
|
return El;
|
||||||
};
|
};
|
||||||
|
|
||||||
function MakeWindow(Attrs) {
|
function MkWindow(Attrs) {
|
||||||
var Window = HtmlEl('div', Attrs);
|
var Window = MkHtmlEl('div', Attrs);
|
||||||
Window.className += ' Window';
|
Window.className += ' Window';
|
||||||
Root.appendChild(Window);
|
Root.appendChild(Window);
|
||||||
return Window;
|
return Window;
|
||||||
};
|
};
|
||||||
|
|
||||||
function Dropdown(Attrs) {
|
function MkSelectMenu(Opts, Attrs) {
|
||||||
var Menu = HtmlEl('div', Attrs);
|
var Menu = MkHtmlEl('div', Attrs);
|
||||||
Window.className += ' Dropdown';
|
var OptsHtml = '';
|
||||||
|
Opts.forEach(function(Opt){
|
||||||
|
OptsHtml += '<li>' + MkHtmlEl('button', Opt).outerHTML + '</li>';
|
||||||
|
});
|
||||||
|
Menu.innerHTML = `
|
||||||
|
<button onclick="var El = this.nextElementSibling; El.hidden = !El.hidden;">
|
||||||
|
Select
|
||||||
|
</button>
|
||||||
|
<ul hidden="true">
|
||||||
|
${OptsHtml}
|
||||||
|
</ul>
|
||||||
|
`;
|
||||||
|
Menu.className += ' SelectMenu';
|
||||||
return Menu;
|
return Menu;
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,7 +12,7 @@ var FriendicaCredentials = 'redacted:redacted';
|
||||||
|
|
||||||
// Development
|
// Development
|
||||||
var Debug = true;
|
var Debug = true;
|
||||||
var UseFakeApi = true;
|
var UseFakeApi = false;
|
||||||
</script>
|
</script>
|
||||||
<script data-build-json="true">
|
<script data-build-json="true">
|
||||||
var Assets = {
|
var Assets = {
|
||||||
|
@ -43,6 +43,8 @@ var Assets = {
|
||||||
<span>(Pre-Alpha Development Version)</span>
|
<span>(Pre-Alpha Development Version)</span>
|
||||||
</div>
|
</div>
|
||||||
<script>NoscriptView.remove();</script>
|
<script>NoscriptView.remove();</script>
|
||||||
|
<script src="./Polyfill.js"></script>
|
||||||
|
<script src="./Lib/cleanHTML.js"></script>
|
||||||
<script src="./Utils.js"></script>
|
<script src="./Utils.js"></script>
|
||||||
<script src="./Strings.js"></script>
|
<script src="./Strings.js"></script>
|
||||||
<script src="./ApiTransform.js"></script>
|
<script src="./ApiTransform.js"></script>
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*!
|
||||||
|
* Sanitize an HTML string
|
||||||
|
* (c) 2021 Chris Ferdinandi, MIT License, https://gomakethings.com
|
||||||
|
* https://vanillajstoolkit.com/helpers/cleanhtml/
|
||||||
|
* (Modified from original)
|
||||||
|
*/
|
||||||
|
function SanitizeHtml(str, nodes) {
|
||||||
|
function stringToHTML() {
|
||||||
|
var parser = new DOMParser();
|
||||||
|
var doc = parser.parseFromString(str, 'text/html');
|
||||||
|
return doc.body || document.createElement('body');
|
||||||
|
};
|
||||||
|
function removeScripts(html) {
|
||||||
|
Array.from(html.querySelectorAll('script')).forEach(function(script){
|
||||||
|
script.remove();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
function isPossiblyDangerous(name, value) {
|
||||||
|
var val = value.replace(/\s+/g, '').toLowerCase();
|
||||||
|
if (['src', 'href', 'xlink:href'].includes(name)) {
|
||||||
|
if (val.includes('javascript:') || val.includes('data:')) return true;
|
||||||
|
};
|
||||||
|
if (name.startsWith('on')) return true;
|
||||||
|
};
|
||||||
|
function removeAttributes(elem) {
|
||||||
|
// Loop through each attribute
|
||||||
|
// If it's dangerous, remove it
|
||||||
|
Array.from(elem.attributes).forEach(function(att){
|
||||||
|
if (isPossiblyDangerous(att.name, att.value))
|
||||||
|
elem.removeAttribute(att.name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
function clean(html) {
|
||||||
|
Array.from(html.children).forEach(function(node){
|
||||||
|
removeAttributes(node);
|
||||||
|
clean(node);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// Convert the string to HTML
|
||||||
|
var html = stringToHTML();
|
||||||
|
// Sanitize it
|
||||||
|
removeScripts(html);
|
||||||
|
clean(html);
|
||||||
|
// If the user wants HTML nodes back, return them
|
||||||
|
// Otherwise, pass a sanitized string back
|
||||||
|
return nodes ? html.childNodes : html.innerHTML;
|
||||||
|
};
|
44
App/Main.js
44
App/Main.js
|
@ -1,7 +1,16 @@
|
||||||
var Persist = {Servers: {}, Sources: {}, Identities: {},};
|
var Persist = {Servers: {}, Sources: {}, Identities: {},};
|
||||||
var Present = CopyObj(Persist);
|
var Present = CopyObj(Persist);
|
||||||
var Tasker = {};
|
var Tasker = {};
|
||||||
var ApiCache = {Urls: {},};
|
var ApiCache = {
|
||||||
|
__Store__(Data, Key, Where) {
|
||||||
|
ApiCache[Where][Key] = Data;
|
||||||
|
ApiCache[Where][Key].__Time__ = Date.now();
|
||||||
|
},
|
||||||
|
__UrlStore__(Data) {
|
||||||
|
ApiCache.__Store__(Data, Data.Url, 'Urls');
|
||||||
|
},
|
||||||
|
Urls: {},
|
||||||
|
};
|
||||||
|
|
||||||
Assets._ = function _(Name) {
|
Assets._ = function _(Name) {
|
||||||
if (Name in Assets) {
|
if (Name in Assets) {
|
||||||
|
@ -13,6 +22,17 @@ Assets._ = function _(Name) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Strings._ = function _(Name) {
|
||||||
|
// TODO: Handle arbitrary nestation
|
||||||
|
if (Name in Strings) {
|
||||||
|
if (Strings[Name]['en']) { // TODO{ Select this language from user config
|
||||||
|
return Strings[Name]['en'];
|
||||||
|
} else {
|
||||||
|
return Strings[Name].en;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
function DoAsync(First, Then, Data) {
|
function DoAsync(First, Then, Data) {
|
||||||
var Job = RndId();
|
var Job = RndId();
|
||||||
Tasker[Job] = {
|
Tasker[Job] = {
|
||||||
|
@ -46,7 +66,7 @@ function DoAsync(First, Then, Data) {
|
||||||
};
|
};
|
||||||
|
|
||||||
function DisplayProfile(Profile) {
|
function DisplayProfile(Profile) {
|
||||||
var Window = MakeWindow({className: "Profile"});
|
var Window = MkWindow({className: "Profile"});
|
||||||
Window.innerHTML += `<div class="" style="display: inline-block;">
|
Window.innerHTML += `<div class="" style="display: inline-block;">
|
||||||
<a href="${Profile.Url}">
|
<a href="${Profile.Url}">
|
||||||
<div>
|
<div>
|
||||||
|
@ -62,14 +82,19 @@ function DisplayProfile(Profile) {
|
||||||
};
|
};
|
||||||
|
|
||||||
function FetchNotes(Profile, Proc) {
|
function FetchNotes(Profile, Proc) {
|
||||||
|
var Soft = Profile.__Software__;
|
||||||
|
NetApiCall({Target: Soft, Method: ApiEndpoints.FetchNotes[Soft](Profile), CallFine: function(Res){
|
||||||
|
var Notes = ApiTransform(Res.responseJson, 'Mastodon', 'Note');
|
||||||
|
LogDebug(Notes, 'l');
|
||||||
|
Tasker[Res.Proc[0]].Return(Notes);
|
||||||
|
}}, Proc);
|
||||||
};
|
};
|
||||||
|
|
||||||
function FetchMastodon(Proc) {
|
function FetchMastodon(Proc) {
|
||||||
if (UseFakeApi) {
|
if (UseFakeApi) {
|
||||||
ResFetchMastodon({responseJson: [FakeApi.Mastodon.Status], Proc: Proc});
|
ResFetchMastodon({responseJson: [FakeApi.Mastodon.Status], Proc: Proc});
|
||||||
} else {
|
} else {
|
||||||
ApiCall({Target: "Mastodon", Method: "timelines/public", CallFine: ResFetchMastodon}, Proc);
|
NetApiCall({Target: "Mastodon", Method: "timelines/public", CallFine: ResFetchMastodon}, Proc);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
function ResFetchMastodon(Res) {
|
function ResFetchMastodon(Res) {
|
||||||
|
@ -80,12 +105,13 @@ function ResFetchMastodon(Res) {
|
||||||
|
|
||||||
function FillTimeline(Notes) {
|
function FillTimeline(Notes) {
|
||||||
Notes.forEach(function(Note){
|
Notes.forEach(function(Note){
|
||||||
|
ApiCache.__UrlStore__(Note.Profile);
|
||||||
Root.lastChild.innerHTML += `<div class="View Note">
|
Root.lastChild.innerHTML += `<div class="View Note">
|
||||||
<a href="${Note.Profile.Url}" onclick="DisplayProfile(ApiCache.Urls['${Note.Profile.Url}']); return false;">
|
<a href="${Note.Profile.Url}" onclick="DisplayProfile(ApiCache.Urls['${Note.Profile.Url}']); return false;">
|
||||||
<img class="Profile Icon" src="${Note.Profile.Icon}"/>
|
<img class="Profile Icon" src="${Note.Profile.Icon}"/>
|
||||||
${Note.Profile.Name}
|
${Note.Profile.Name}
|
||||||
</a>
|
</a>
|
||||||
${Note.Content}
|
${SanitizeHtml(Note.Content)}
|
||||||
<a href="${Note.Url}">${Note.Time}</a>
|
<a href="${Note.Url}">${Note.Time}</a>
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
|
@ -106,7 +132,7 @@ function FetchFeatured(Proc) {
|
||||||
};
|
};
|
||||||
|
|
||||||
function FillFeatured(Categories) {
|
function FillFeatured(Categories) {
|
||||||
var Window = MakeWindow({className: "Gallery"});
|
var Window = MkWindow({className: "Gallery"});
|
||||||
Object.values(Categories).forEach(function(Profiles){
|
Object.values(Categories).forEach(function(Profiles){
|
||||||
Profiles.forEach(function(Profile){
|
Profiles.forEach(function(Profile){
|
||||||
Window.innerHTML += `<div>
|
Window.innerHTML += `<div>
|
||||||
|
@ -141,7 +167,7 @@ PlazasView.innerHTML = `
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function ComposeNote() {
|
function ComposeNote() {
|
||||||
var Window = MakeWindow();
|
var Window = MkWindow();
|
||||||
Window.innerHTML += `
|
Window.innerHTML += `
|
||||||
<h2>Compose</h2>
|
<h2>Compose</h2>
|
||||||
<p>Posting in: [Channel]</p>
|
<p>Posting in: [Channel]</p>
|
||||||
|
@ -158,11 +184,11 @@ function PostNote(Text) {
|
||||||
};
|
};
|
||||||
|
|
||||||
function ManageSettings() {
|
function ManageSettings() {
|
||||||
MakeWindow().innerHTML = `
|
MkWindow().innerHTML = `
|
||||||
<h2>Settings</h2>
|
<h2>Settings</h2>
|
||||||
<h3>Misc</h3>
|
<h3>Misc</h3>
|
||||||
<p>
|
<p>
|
||||||
Language: ${Dropdown()}
|
Language: ${MkSelectMenu([{innerHTML: "en"}, {innerHTML: "it"},]).outerHTML}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Theme:
|
Theme:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
function ApiCall(Data, Proc) {
|
function NetApiCall(Data, Proc) {
|
||||||
// Data = {Target: "Friendica", Method: "...", Data: {}, Call: (), CallFine: (), CallFail: ()}
|
// Data = {Target: "Friendica", Method: "...", Data: {}, Call: (), CallFine: (), CallFail: ()}
|
||||||
var Req = new XMLHttpRequest();
|
var Req = new XMLHttpRequest();
|
||||||
Req.Proc = Proc;
|
Req.Proc = Proc;
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
if (!Array.prototype.from) {
|
||||||
|
Array.prototype.from = function from(List) {
|
||||||
|
var Arr = [];
|
||||||
|
for (var i=0; i<List.length; i++) {
|
||||||
|
Arr.push(List[i]);
|
||||||
|
};
|
||||||
|
return Arr;
|
||||||
|
};
|
||||||
|
};
|
|
@ -10,6 +10,8 @@ body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
color: black;
|
||||||
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
img, video {
|
img, video {
|
||||||
|
@ -33,6 +35,7 @@ img, video {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
||||||
|
background-color: white;
|
||||||
border: 2px solid black;
|
border: 2px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ function LogDebug(Data, Status) {
|
||||||
Data[i] = JSON.parse(Data[i]);
|
Data[i] = JSON.parse(Data[i]);
|
||||||
} catch(_){};
|
} catch(_){};
|
||||||
};
|
};
|
||||||
console[{l: "log", e: "error"}[Status.toLowerCase()]](Data);
|
console[{l: "log", e: "error"}[Status.toLowerCase()]](LogDebug.caller, Data);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ function JsonTransformCycleA(TreeOld, SchemaCurr, SchemaRoot) {
|
||||||
};
|
};
|
||||||
|
|
||||||
function JsonTransformB(TreesOld, SchemaNew, NodeNew, TypeOld) {
|
function JsonTransformB(TreesOld, SchemaNew, NodeNew, TypeOld) {
|
||||||
|
LogDebug([TreesOld, SchemaNew, NodeNew, TypeOld]);
|
||||||
if (Array.isArray(TreesOld)) {
|
if (Array.isArray(TreesOld)) {
|
||||||
var ListNew = [];
|
var ListNew = [];
|
||||||
ForceList(TreesOld).forEach(function(TreeOld){
|
ForceList(TreesOld).forEach(function(TreeOld){
|
||||||
|
@ -102,7 +103,9 @@ function JsonTransformB(TreesOld, SchemaNew, NodeNew, TypeOld) {
|
||||||
});
|
});
|
||||||
return ListNew;
|
return ListNew;
|
||||||
} else {
|
} else {
|
||||||
return JsonTransformCycleB(TreesOld, SchemaNew, NodeNew, TypeOld);
|
if (TreesOld) {
|
||||||
|
return JsonTransformCycleB(TreesOld, SchemaNew, NodeNew, TypeOld);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
function JsonTransformCycleB(TreeOld, SchemaNew, NodeNew, TypeOld) {
|
function JsonTransformCycleB(TreeOld, SchemaNew, NodeNew, TypeOld) {
|
||||||
|
|
Loading…
Reference in New Issue