This commit is contained in:
octospacc 2023-04-25 00:55:20 +02:00
parent efc4c49a7f
commit 3f69a8a94d
9 changed files with 131 additions and 19 deletions

View File

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

View File

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

View File

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

47
App/Lib/cleanHTML.js Normal file
View File

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

View File

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

View File

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

9
App/Polyfill.js Normal file
View File

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

View File

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

View File

@ -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,7 +103,9 @@ function JsonTransformB(TreesOld, SchemaNew, NodeNew, TypeOld) {
});
return ListNew;
} else {
return JsonTransformCycleB(TreesOld, SchemaNew, NodeNew, TypeOld);
if (TreesOld) {
return JsonTransformCycleB(TreesOld, SchemaNew, NodeNew, TypeOld);
};
};
};
function JsonTransformCycleB(TreeOld, SchemaNew, NodeNew, TypeOld) {