Update
This commit is contained in:
parent
efc4c49a7f
commit
3f69a8a94d
|
@ -38,6 +38,7 @@ var ApiSchema = {
|
|||
},
|
||||
Quoting: {
|
||||
Mastodon: "reblog",
|
||||
Misskey: "renote",
|
||||
},
|
||||
Time: {
|
||||
Mastodon: "created_at",
|
||||
|
@ -78,7 +79,16 @@ var ApiSchema = {
|
|||
},
|
||||
};
|
||||
|
||||
var ApiEndpoints = {
|
||||
FetchNotes: {
|
||||
Mastodon(Profile) {
|
||||
return `accounts/${Profile.Id}/statuses`;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function ApiTransform(Data, FromSource, DestType) {
|
||||
LogDebug([Data, 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);
|
||||
if (Attrs) {
|
||||
Object.keys(Attrs).forEach(function(Attr){
|
||||
|
@ -8,15 +8,27 @@ function HtmlEl(Tag, Attrs) {
|
|||
return El;
|
||||
};
|
||||
|
||||
function MakeWindow(Attrs) {
|
||||
var Window = HtmlEl('div', Attrs);
|
||||
function MkWindow(Attrs) {
|
||||
var Window = MkHtmlEl('div', Attrs);
|
||||
Window.className += ' Window';
|
||||
Root.appendChild(Window);
|
||||
return Window;
|
||||
};
|
||||
|
||||
function Dropdown(Attrs) {
|
||||
var Menu = HtmlEl('div', Attrs);
|
||||
Window.className += ' Dropdown';
|
||||
function MkSelectMenu(Opts, Attrs) {
|
||||
var Menu = MkHtmlEl('div', Attrs);
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -12,7 +12,7 @@ var FriendicaCredentials = 'redacted:redacted';
|
|||
|
||||
// Development
|
||||
var Debug = true;
|
||||
var UseFakeApi = true;
|
||||
var UseFakeApi = false;
|
||||
</script>
|
||||
<script data-build-json="true">
|
||||
var Assets = {
|
||||
|
@ -43,6 +43,8 @@ var Assets = {
|
|||
<span>(Pre-Alpha Development Version)</span>
|
||||
</div>
|
||||
<script>NoscriptView.remove();</script>
|
||||
<script src="./Polyfill.js"></script>
|
||||
<script src="./Lib/cleanHTML.js"></script>
|
||||
<script src="./Utils.js"></script>
|
||||
<script src="./Strings.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 Present = CopyObj(Persist);
|
||||
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) {
|
||||
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) {
|
||||
var Job = RndId();
|
||||
Tasker[Job] = {
|
||||
|
@ -46,7 +66,7 @@ function DoAsync(First, Then, Data) {
|
|||
};
|
||||
|
||||
function DisplayProfile(Profile) {
|
||||
var Window = MakeWindow({className: "Profile"});
|
||||
var Window = MkWindow({className: "Profile"});
|
||||
Window.innerHTML += `<div class="" style="display: inline-block;">
|
||||
<a href="${Profile.Url}">
|
||||
<div>
|
||||
|
@ -62,14 +82,19 @@ function DisplayProfile(Profile) {
|
|||
};
|
||||
|
||||
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) {
|
||||
if (UseFakeApi) {
|
||||
ResFetchMastodon({responseJson: [FakeApi.Mastodon.Status], Proc: Proc});
|
||||
} else {
|
||||
ApiCall({Target: "Mastodon", Method: "timelines/public", CallFine: ResFetchMastodon}, Proc);
|
||||
NetApiCall({Target: "Mastodon", Method: "timelines/public", CallFine: ResFetchMastodon}, Proc);
|
||||
};
|
||||
};
|
||||
function ResFetchMastodon(Res) {
|
||||
|
@ -80,12 +105,13 @@ function ResFetchMastodon(Res) {
|
|||
|
||||
function FillTimeline(Notes) {
|
||||
Notes.forEach(function(Note){
|
||||
ApiCache.__UrlStore__(Note.Profile);
|
||||
Root.lastChild.innerHTML += `<div class="View Note">
|
||||
<a href="${Note.Profile.Url}" onclick="DisplayProfile(ApiCache.Urls['${Note.Profile.Url}']); return false;">
|
||||
<img class="Profile Icon" src="${Note.Profile.Icon}"/>
|
||||
${Note.Profile.Name}
|
||||
</a>
|
||||
${Note.Content}
|
||||
${SanitizeHtml(Note.Content)}
|
||||
<a href="${Note.Url}">${Note.Time}</a>
|
||||
</div>`;
|
||||
});
|
||||
|
@ -106,7 +132,7 @@ function FetchFeatured(Proc) {
|
|||
};
|
||||
|
||||
function FillFeatured(Categories) {
|
||||
var Window = MakeWindow({className: "Gallery"});
|
||||
var Window = MkWindow({className: "Gallery"});
|
||||
Object.values(Categories).forEach(function(Profiles){
|
||||
Profiles.forEach(function(Profile){
|
||||
Window.innerHTML += `<div>
|
||||
|
@ -141,7 +167,7 @@ PlazasView.innerHTML = `
|
|||
*/
|
||||
|
||||
function ComposeNote() {
|
||||
var Window = MakeWindow();
|
||||
var Window = MkWindow();
|
||||
Window.innerHTML += `
|
||||
<h2>Compose</h2>
|
||||
<p>Posting in: [Channel]</p>
|
||||
|
@ -158,11 +184,11 @@ function PostNote(Text) {
|
|||
};
|
||||
|
||||
function ManageSettings() {
|
||||
MakeWindow().innerHTML = `
|
||||
MkWindow().innerHTML = `
|
||||
<h2>Settings</h2>
|
||||
<h3>Misc</h3>
|
||||
<p>
|
||||
Language: ${Dropdown()}
|
||||
Language: ${MkSelectMenu([{innerHTML: "en"}, {innerHTML: "it"},]).outerHTML}
|
||||
</p>
|
||||
<p>
|
||||
Theme:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
function ApiCall(Data, Proc) {
|
||||
function NetApiCall(Data, Proc) {
|
||||
// Data = {Target: "Friendica", Method: "...", Data: {}, Call: (), CallFine: (), CallFail: ()}
|
||||
var Req = new XMLHttpRequest();
|
||||
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;
|
||||
padding: 0;
|
||||
overflow-y: hidden;
|
||||
color: black;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
img, video {
|
||||
|
@ -33,6 +35,7 @@ img, video {
|
|||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
background-color: white;
|
||||
border: 2px solid black;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ function LogDebug(Data, Status) {
|
|||
Data[i] = JSON.parse(Data[i]);
|
||||
} 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) {
|
||||
LogDebug([TreesOld, SchemaNew, NodeNew, TypeOld]);
|
||||
if (Array.isArray(TreesOld)) {
|
||||
var ListNew = [];
|
||||
ForceList(TreesOld).forEach(function(TreeOld){
|
||||
|
@ -102,9 +103,11 @@ function JsonTransformB(TreesOld, SchemaNew, NodeNew, TypeOld) {
|
|||
});
|
||||
return ListNew;
|
||||
} else {
|
||||
if (TreesOld) {
|
||||
return JsonTransformCycleB(TreesOld, SchemaNew, NodeNew, TypeOld);
|
||||
};
|
||||
};
|
||||
};
|
||||
function JsonTransformCycleB(TreeOld, SchemaNew, NodeNew, TypeOld) {
|
||||
var TreeNew = CopyObj(NodeNew);
|
||||
Object.keys(TreeNew).forEach(function(KeyNew){
|
||||
|
|
Loading…
Reference in New Issue