diff --git a/Qt/Bungloo.py b/Qt/Bungloo.py index 4545b5d..aa0f5d4 100755 --- a/Qt/Bungloo.py +++ b/Qt/Bungloo.py @@ -107,15 +107,12 @@ class Controller(QtCore.QObject): QtCore.QObject.__init__(self) self.app = app - oldpath = os.path.expanduser('~/.bungloo/') - if os.path.isdir(oldpath): - shutil.copytree(oldpath, os.path.expanduser('~/.config/bungloo/')) - shutil.rmtree(os.path.expanduser('~/.bungloo/')) + name = "bungloo2" - if not os.path.exists(os.path.expanduser("~/.config/bungloo/")): - os.makedirs(os.path.expanduser("~/.config/bungloo/")) + if not os.path.exists(os.path.expanduser("~/.config/" + name + "/")): + os.makedirs(os.path.expanduser("~/.config/" + name + "/")) - self.config_path = os.path.expanduser('~/.config/bungloo/bungloo.cfg') + self.config_path = os.path.expanduser('~/.config/' + name + '/bungloo.cfg') if os.access(self.config_path, os.R_OK): with open(self.config_path, 'r') as f: @@ -173,22 +170,18 @@ class Controller(QtCore.QObject): except OSError: pass - @QtCore.pyqtSlot(str) - def openNewMessageWidow(self, is_private=False, string=""): - string = str(string) - self.openNewMessageWindowInReplyTostatusIdwithStringIsPrivate(None, None, string, is_private) + @QtCore.pyqtSlot() + def openNewMessageWidow(self): + self.openNewMessageWindowInReplyToStatus("") - @QtCore.pyqtSlot(str, str, str, bool) - def openNewMessageWindowInReplyTostatusIdwithStringIsPrivate(self, entity, status_id, string, is_private): - new_message_window = Windows.NewPost(self.app) - new_message_window.inReplyToStatusIdWithString(entity, status_id, string) - new_message_window.setIsPrivate(is_private) + @QtCore.pyqtSlot(str) + def openNewMessageWindowInReplyToStatus(self, status_string): + new_message_window = Windows.NewPost(self.app, status_string) new_message_window.show() new_message_window.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.app.new_message_windows.append(new_message_window) new_message_window.activateWindow() new_message_window.setFocus() - new_message_window.textInput.setFocus() new_message_window.show() new_message_window.raise_() @@ -268,6 +261,16 @@ class Controller(QtCore.QObject): msgBox.setInformativeText(message) msgBox.exec_() + @QtCore.pyqtSlot(result=str) + def getCachedProfiles(self): + entities = self.app.timeline.evaluateJavaScript("JSON.stringify(bungloo.cache.profiles);") + return entities.toString() + + @QtCore.pyqtSlot() + def getNewData(self): + func = "bungloo.timeline.getNewData()" + self.app.timeline.evaluateJavaScript(func) + def logout(self, sender): print "logout is not implemented yet" @@ -297,7 +300,7 @@ class Console(QtCore.QObject): if __name__ == "__main__": - key = 'BUNGLOO' + key = 'BUNGLOO2' if len(sys.argv) > 1 and sys.argv[1] == "--help": print """ diff --git a/Qt/Windows.py b/Qt/Windows.py index b47d8ae..762a02a 100644 --- a/Qt/Windows.py +++ b/Qt/Windows.py @@ -9,7 +9,7 @@ class Preferences: # window self.window = QtGui.QMainWindow() - self.window.setWindowTitle("Preferences") + self.window.setWindowTitle("Login") self.window.resize(480, 186) self.window.setMinimumSize(480, 186) self.window.setMaximumSize(480, 186) @@ -242,7 +242,7 @@ class Oauth: new_manager.sslErrors.connect(lambda reply, errors: self.handleSslErrors(reply, errors)) self.auth_view.page().setNetworkAccessManager(new_manager) self.auth_view.show() - + print url self.auth_view.load_url(url) return False @@ -329,25 +329,29 @@ class FindEntity(QtGui.QDialog): class NewPost(Helper.RestorableWindow): - def __init__(self, app): + def __init__(self, app, status_string): self.app = app + self.status_string = status_string + Helper.RestorableWindow.__init__(self, "newpost", self.app) + self.activateWindow() + self.raise_() self.setWindowIcon(QtGui.QIcon(self.app.resources_path() + "/images/Icon.png")) - self.textInput = QtGui.QPlainTextEdit(self) - self.setCentralWidget(self.textInput) - self.textInput.textChanged.connect(self.onChanged) + self.webView = Helper.WebViewCreator(self.app, True, self) + self.webView.load_local(self.load_finished) + self.setCentralWidget(self.webView) + + self.initUI() + + self.webView.triggerPageAction(QtWebKit.QWebPage.InspectElement) + frame = self.webView.page().mainFrame() + frame.addToJavaScriptWindowObject("new_post_window", self) self.setWindowTitle("New Post") self.resize(300, 150) self.setMinimumSize(100, 100) - self.initUI() - - self.setIsPrivate(False) - self.status_id = None - self.reply_to_entity = None - self.imageFilePath = None def initUI(self): newPostAction = QtGui.QAction("&New Post", self) @@ -396,89 +400,38 @@ class NewPost(Helper.RestorableWindow): aboutAction.setStatusTip("Open about page in Webbrowser") aboutAction.triggered.connect(self.app.open_about) + developerExtrasAction = QtGui.QAction("&Developer Extras", self) + developerExtrasAction.setStatusTip("Activate webkit inspector") + developerExtrasAction.triggered.connect(self.developer_extras) + helpMenu = menubar.addMenu("&Help") helpMenu.addAction(aboutAction) + helpMenu.addAction(developerExtrasAction) - - self.statusBar().showMessage('256') - - self.addButton = QtGui.QToolButton() - self.addButton.setToolTip("Add photo") - self.addButton.clicked.connect(self.openFileDialog) - self.addButton.setAutoRaise(True) - #addIcon = QtGui.QIcon.fromTheme("insert-image", QtGui.QIcon(self.app.resources_path() + "/images/Actions-insert-image-icon.png")) - addIcon = QtGui.QIcon(self.app.resources_path() + "/images/glyphicons_138_picture.png") - self.addButton.setIcon(addIcon) - self.statusBar().addPermanentWidget(self.addButton) - - self.isPrivateButton = QtGui.QToolButton() - self.isPrivateButton.setToolTip("Make private") - self.isPrivateButton.clicked.connect(self.toggleIsPrivate) - self.isPrivateButton.setAutoRaise(True) - #self.isPrivateIcon = QtGui.QIcon(self.app.resources_path() + "/images/Lock-Lock-icon.png") - self.isPrivateIcon = QtGui.QIcon(self.app.resources_path() + "/images/glyphicons_203_lock.png") - #self.isNotPrivateIcon = QtGui.QIcon(self.app.resources_path() + "/images/Lock-Unlock-icon.png") - self.isNotPrivateIcon = QtGui.QIcon(self.app.resources_path() + "/images/glyphicons_204_unlock.png") - self.isPrivateButton.setIcon(self.isNotPrivateIcon) - self.statusBar().addPermanentWidget(self.isPrivateButton) - - self.sendButton = QtGui.QToolButton() - self.sendButton.setToolTip("Send") - self.sendButton.clicked.connect(self.sendMessage) - self.sendButton.setAutoRaise(True) - #sendIcon = QtGui.QIcon.fromTheme("mail-send", QtGui.QIcon(self.app.resources_path() + "/images/send-icon.png")) - sendIcon = QtGui.QIcon(self.app.resources_path() + "/images/glyphicons_123_message_out.png") - self.sendButton.setIcon(sendIcon) - self.statusBar().addPermanentWidget(self.sendButton) - - def setIsPrivate(self, is_private): - self.isPrivate = is_private - icon = self.isNotPrivateIcon - if self.isPrivate: - icon = self.isPrivateIcon - - self.isPrivateButton.setIcon(icon) + def load_finished(self, widget): + callback = "function() { bungloo.newpost.setStatus('%s'); }" % (self.status_string) + script = "function HostAppGo() { start('newpost', " + callback + "); }" + self.webView.page().mainFrame().evaluateJavaScript(script) + self.webView.setFocus() def toggleIsPrivate(self): - self.setIsPrivate(not self.isPrivate) - - def setString(self, string): - self.inReplyToStatusIdWithString(None, None, string) - - def inReplyToStatusIdWithString(self, reply_to, status_id, string): - self.reply_to_entity = reply_to - self.status_id = status_id - self.textInput.setPlainText(string) - - cursor = self.textInput.textCursor() - cursor.movePosition(QtGui.QTextCursor.End, QtGui.QTextCursor.MoveAnchor) - cursor.movePosition(QtGui.QTextCursor.Start, QtGui.QTextCursor.KeepAnchor) - cursor.movePosition(QtGui.QTextCursor.EndOfLine, QtGui.QTextCursor.KeepAnchor) - self.textInput.setTextCursor(cursor) - - def onChanged(self): - count = 256 - len(self.textInput.toPlainText()) - self.statusBar().showMessage(str(count)) + script = "bungloo.newpost.toggleIsPrivate();" + self.webView.page().mainFrame().evaluateJavaScript(script) def sendMessage(self): - count = len(self.textInput.toPlainText()) - if count > 0 and count <= 256: - message = Helper.PostModel() - message.text = unicode(self.textInput.toPlainText().toUtf8(), "utf-8") - message.inReplyTostatusId = self.status_id - message.inReplyToEntity = self.reply_to_entity - message.location = None - message.imageFilePath = self.imageFilePath - message.isPrivate = self.isPrivate - self.app.controller.sendMessage(message) - self.close() - else: - QtGui.qApp.beep() + script = "bungloo.newpost.send()" + self.webView.page().mainFrame().evaluateJavaScript(script) + + def developer_extras(self, widget): + QtWebKit.QWebSettings.globalSettings().setAttribute(QtWebKit.QWebSettings.DeveloperExtrasEnabled, True) def openFileDialog(self): - fileNamePath = QtGui.QFileDialog.getOpenFileName(self, "Choose a image", "", "Images (*.png *.gif *.jpg *.jpeg)") - if len(fileNamePath) > 0: - self.imageFilePath = str(fileNamePath) - else: - self.imageFilePath = None + print "openFileDialog Not implemented yet" + @QtCore.pyqtSlot() + def closeWindow(self): + self.close() + + @QtCore.pyqtSlot() + def beep(self): + QtGui.qApp.beep() \ No newline at end of file diff --git a/WebKit/css/default.css b/WebKit/css/default.css index ff8bbfd..76013fd 100644 --- a/WebKit/css/default.css +++ b/WebKit/css/default.css @@ -18,6 +18,15 @@ a { text-decoration: none; color: #00317a; outline: 0; + outline : none; +} + +button { + background: transparent; + border: 0; + margin: 0; + padding: 4px 5px 0 5px; + outline : none; } #sidebar { @@ -446,4 +455,23 @@ form.search input { p.noresult { padding : 10px; text-align : center; -} \ No newline at end of file +} + +.new_post #sidebar, .new_post #content { display: none; } +.new_post { height: 100%; } +#new_post_container { position: absolute; border-collapse: collapse; height: 100%; width: 100%; } +#new_post_container td { position: relative; height: 90%; } +#new_post_container .text td { background: white; } +#new_post_container textarea { resize: none; box-sizing: border-box; position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0; background: transparent; outline: none; } +#new_post_container div { box-sizing: border-box; position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0; background: white; color: white; padding: 2px; } + +#new_post_container div span { background: #D8DFEA; } +#suggestions { width: 100%; position: absolute; left: 0; bottom: 0; background: #efefef; list-style-type: none; padding: 0; margin: 0; border-top: 1px solid #ccc; } +#suggestions li { border-top: 1px solid #fefefe; border-bottom: #c9c9c9; padding: 0 0.5em; } +#suggestions strong { font-weight: normal; color: #555; } +#suggestions .active { background: #dedede; } +#suggestions .active strong { color: black; } +#status_bar { height: 1em; border-top: 1px solid #ccc; } +#status_bar p { float: right; margin: 0; padding: 0; } +#status_bar span { display: inline-block; margin: 4px 5px 0 5px; } + diff --git a/images/glyphicons_138_picture.png b/WebKit/img/images.png similarity index 100% rename from images/glyphicons_138_picture.png rename to WebKit/img/images.png diff --git a/images/glyphicons_203_lock.png b/WebKit/img/private.png similarity index 100% rename from images/glyphicons_203_lock.png rename to WebKit/img/private.png diff --git a/images/glyphicons_204_unlock.png b/WebKit/img/public.png similarity index 100% rename from images/glyphicons_204_unlock.png rename to WebKit/img/public.png diff --git a/images/glyphicons_123_message_out.png b/WebKit/img/send.png similarity index 100% rename from images/glyphicons_123_message_out.png rename to WebKit/img/send.png diff --git a/WebKit/scripts/controller/Conversation.js b/WebKit/scripts/controller/Conversation.js index cb49aeb..817cf01 100644 --- a/WebKit/scripts/controller/Conversation.js +++ b/WebKit/scripts/controller/Conversation.js @@ -1,11 +1,11 @@ define([ "helper/HostApp", "helper/Core", - "helper/Paths", + "helper/APICalls", "lib/URI" ], -function(HostApp, Core, Paths, URI) { +function(HostApp, Core, APICalls, URI) { function Conversation(standalone) { @@ -86,15 +86,15 @@ function(HostApp, Core, Paths, URI) { function getRemoteStatus(profile) { var server = profile["https://tent.io/types/info/core/v0.1.0"].servers[0]; - Paths.getURL(URI(server + "/posts/" + id).toString(), "GET", callback, null, false); + APICalls.http_call(URI(server + "/posts/" + id).toString(), "GET", callback, null, false); } var profile = this.cache.profiles.getItem(entity); if (entity == HostApp.stringForKey("entity")) { - var url = URI(Paths.mkApiRootPath("/posts/" + id)); - Paths.getURL(url.toString(), "GET", callback, null); + var url = URI(APICalls.mkApiRootPath("/posts/" + id)); + APICalls.http_call(url.toString(), "GET", callback, null); } else if(profile) { @@ -102,7 +102,7 @@ function(HostApp, Core, Paths, URI) { } else { - Paths.findProfileURL(entity, function(profile_url) { + APICalls.findProfileURL(entity, function(profile_url) { if (profile_url) { @@ -113,7 +113,7 @@ function(HostApp, Core, Paths, URI) { } else { - Paths.getURL(profile_url, "GET", function(resp) { + APICalls.http_call(profile_url, "GET", function(resp) { var profile = JSON.parse(resp.responseText) this.cache.profiles.setItem(entity, profile); @@ -128,7 +128,7 @@ function(HostApp, Core, Paths, URI) { Conversation.prototype.appendMentioned = function(id, entity, node) { - var url = URI(Paths.mkApiRootPath("/posts")); + var url = URI(APICalls.mkApiRootPath("/posts")); url.addSearch("mentioned_post", id); url.addSearch("post_types", "https%3A%2F%2Ftent.io%2Ftypes%2Fpost%2Fstatus%2Fv0.1.0"); @@ -147,7 +147,7 @@ function(HostApp, Core, Paths, URI) { } } - Paths.getURL(url.toString(), "GET", callback); + APICalls.http_call(url.toString(), "GET", callback); } diff --git a/WebKit/scripts/controller/Mentions.js b/WebKit/scripts/controller/Mentions.js index a0cc51c..80f1ab8 100644 --- a/WebKit/scripts/controller/Mentions.js +++ b/WebKit/scripts/controller/Mentions.js @@ -2,11 +2,11 @@ define([ "helper/HostApp", "controller/Timeline", "lib/URI", - "helper/Paths", + "helper/APICalls", "helper/Core" ], -function(HostApp, Timeline, URI, Paths, Core) { +function(HostApp, Timeline, URI, APICalls, Core) { function Mentions() { @@ -36,7 +36,7 @@ function(HostApp, Timeline, URI, Paths, Core) { Mentions.prototype.newStatus = function(statuses, append) { Timeline.prototype.newStatus.call(this, statuses, append); - +/* if(this.is_not_init) { for (var i = 0; i < statuses.length; i++) { @@ -51,21 +51,21 @@ function(HostApp, Timeline, URI, Paths, Core) { if(!append) HostApp.notificateUserAboutMention(status.content.text, name || status.entity, status.id, status.entity); } } - +*/ this.is_not_init = true; } - Mentions.prototype.getNewData = function(add_to_search, append) { + Mentions.prototype.getNewData = function(add_to_search, append, query) { add_to_search = add_to_search || {}; - if (!add_to_search["mentioned_entity"]) { - add_to_search["mentioned_entity"] = HostApp.stringForKey("entity"); + if (!add_to_search["mentions"]) { + add_to_search["mentions"] = HostApp.stringForKey("entity"); } - Timeline.prototype.getNewData.call(this, add_to_search, append); + Timeline.prototype.getNewData.call(this, add_to_search, append, query); - this.getLatestMentionRead(); + //this.getLatestMentionRead(); } Mentions.prototype.mentionRead = function(id, entity) { @@ -90,7 +90,7 @@ function(HostApp, Timeline, URI, Paths, Core) { if (!status.__repost) { if (status && status.type == "https://tent.io/types/post/status/v0.1.0") { - var url = URI(Paths.mkApiRootPath("/profile/" + encodeURIComponent("https://tent.io/types/info/cursor/v0.1.0"))); + var url = URI(APICalls.mkApiRootPath("/profile/" + encodeURIComponent("https://tent.io/types/info/cursor/v0.1.0"))); var body = { "mentions": { "https://tent.io/types/post/status/v0.1.0": { @@ -104,7 +104,7 @@ function(HostApp, Timeline, URI, Paths, Core) { } - Paths.getURL(url.toString(), "PUT", callback, JSON.stringify(body)); + APICalls.http_call(url.toString(), "PUT", callback, JSON.stringify(body)); } break; @@ -115,11 +115,11 @@ function(HostApp, Timeline, URI, Paths, Core) { Mentions.prototype.getLatestMentionRead = function() { - var cursor_url = URI(Paths.mkApiRootPath("/profile/" + encodeURIComponent("https://tent.io/types/info/cursor/v0.1.0"))); + var cursor_url = URI(APICalls.mkApiRootPath("/profile/" + encodeURIComponent("https://tent.io/types/info/cursor/v0.1.0"))); - Paths.getURL(cursor_url.toString(), "GET", function(resp) { + APICalls.http_call(cursor_url.toString(), "GET", function(resp) { - var url = URI(Paths.mkApiRootPath("/posts/count")); + var url = URI(APICalls.mkApiRootPath("/posts/count")); var post_types = [ "https://tent.io/types/post/status/v0.1.0", ]; @@ -139,7 +139,7 @@ function(HostApp, Timeline, URI, Paths, Core) { HostApp.unreadMentions(this.unread_mentions); } - Paths.getURL(url.toString(), "GET", callback); // FIXME: error callback + APICalls.http_call(url.toString(), "GET", callback); // FIXME: error callback }); } diff --git a/WebKit/scripts/controller/NewPost.js b/WebKit/scripts/controller/NewPost.js new file mode 100644 index 0000000..cd33d35 --- /dev/null +++ b/WebKit/scripts/controller/NewPost.js @@ -0,0 +1,395 @@ +define([ + "helper/APICalls", + "helper/HostApp" +], + +function(APICalls, HostApp) { + + function NewPost() { + + this.profiles = JSON.parse(controller.getCachedProfiles()); + for (var key in this.profiles) { + var item = this.profiles[key]; + if(!item.entity) item.entity = key; + if(!item.name) item.name = key; + } + + this.mentions = []; + document.body.className = "new_post"; + this.is_private = false; + + // Textarea + + this.container = $("
"); + this.textarea = this.container.find("textarea"); + this.highlighter = this.container.find("div"); + + $(document.body).append(this.container); + + this.textarea.keyup(this.keyup.bind(this)); + this.textarea.keydown(this.keydown.bind(this)); + + this.suggestions = $(""); + + $(document.body).append(this.suggestions); + + // Status bar + this.counter = $("256"); + var buttons = $( + "

" + + //"" + + "" + + "" + + "

"); + + this.buttons = { + images: buttons.find("#images"), + is_private: buttons.find("#private"), + send: buttons.find("#send") + } + + //this.buttons.images.bind("click", this.addImage.bind(this)); + this.buttons.is_private.bind("click", this.toggleIsPrivate.bind(this)); + this.buttons.send.bind("click", this.send.bind(this)); + + this.container.find("#status_bar").append(this.counter); + this.container.find("#status_bar").append(buttons); + + this.textarea.focus(); + this.setIsPrivate(false); + } + + NewPost.prototype.setStatus = function(status_string) { + if (status_string && status_string.length > 0) { + this.status = JSON.parse(status_string); + this.setIsPrivate(this.status.permissions && !this.status.permissions.public); + this.setMentions(this.status); + } else { + this.status = null; + } + + // FIXME set string, private, mentions, etc. + }; + + NewPost.prototype.setString = function(string) { + this.textarea.val(string); + } + + NewPost.prototype.setMentions = function(status) { + + var mentions = [this.profiles[status.entity]]; + var text = this.profiles[status.entity].name + " "; + var start = text.length; + + if(status.mentions && status.mentions.length > 0) { + + var mentions_text = "" + for (var i = 0; i < status.mentions.length; i++) { + + var entity = status.mentions[i].entity; + + // Sometimes there are mentions without entity, don't know why + if(entity) { + // fix broken profiles + var profile = this.profiles[entity]; + if(!profile) { + profile = {}; + this.profiles[entity] = profile; + } + if(!profile.entity) profile.entity = entity; + if(!profile.name) profile.name = entity; + + // add profile to mentions and textarea + mentions.push(profile); + mentions_text += profile.name; + + // add space after mention + if(i < status.mentions.length) { + mentions_text += " "; + } + } + } + if (mentions_text.length > 0) { + text += "\n\n/cc " + mentions_text; + }; + + } + + this.mentions = mentions; + this.textarea.val(text); + this.parseText(text); + + // Select other mentions so user can start writing and removing them + var end = text.length; + this.textarea.get(0).setSelectionRange(start, end); + } + + NewPost.prototype.setIsPrivate = function(is_private) { + this.is_private = is_private; + if (this.is_private) { + this.buttons.is_private.find("img").attr("src", "img/private.png"); + } else { + this.buttons.is_private.find("img").attr("src", "img/public.png"); + } + } + + NewPost.prototype.toggleIsPrivate = function() { + this.setIsPrivate(!this.is_private); + } + + NewPost.prototype.keyup = function(e) { + if(!e) return; + + var key = e.which; + if(key != 38 && key != 40 && key != 13) { + + this.applyText($(this.textarea).val()); + + } else { + + var lis = this.suggestions.find("li"); + + if (lis.length > 0) { + e.preventDefault(); + var active = this.suggestions.find(".active"); + if(key == 38) { // up + var prev = active.prev(); + if(active.lentgh == 0) { + lis.last().addClass("active"); + } else if(prev) { + active.removeClass("active"); + prev.addClass("active"); + } + } else if(key == 40) { // down + var next = active.next(); + if(active.length == 0) { + lis.first().addClass("active"); + } else if(next) { + active.removeClass("active"); + next.addClass("active"); + } + } else if(key == 13) { // enter + if(active.length > 0) { + this.replaceWithName(this.textarea.val(), this.suggestions.find("li.active").get(0).item); + } + } + } + } + } + + NewPost.prototype.keydown = function(e) { + var key = e.which; + var lis = this.suggestions.find("li"); + if(lis.length > 0 && (key == 38 || key == 40 || key == 13)) { + e.preventDefault(); + } + } + + NewPost.prototype.replaceAll = function(txt, replace, with_this) { + return txt.replace(new RegExp(replace, 'g'),with_this); + } + + NewPost.prototype.replaceWithName = function(txt, with_item) { + var words = txt.match(/(^|\s)\^([^\s]+)/); + var replace = words[2]; + + var original = txt.replace("^" + replace, with_item.name + " "); + this.textarea.val(original); + + this.mentions.push(with_item); + + this.applyText(original); + } + + NewPost.prototype.applyText = function (text) { + var words = text.match(/(^|\s)\^([^\s]+)/); + this.suggestions.html(""); + + if(words) { + var name = words[2]; + for (var key in this.profiles) { + var item = this.profiles[key]; + if((item.name.toLowerCase().indexOf(name.toLowerCase()) != -1) || item.entity.toLowerCase().indexOf(name.toLowerCase()) != -1) { + var li = $("
  • " + item.name + "
  • ") + li.get(0).item = item; + this.suggestions.append(li); + } + } + this.suggestions.find("li:first-child").addClass("active"); + } + + this.parseText(text); + } + + NewPost.prototype.parseText = function(text) { + // parse the text: + // replace all the line braks by
    , and all the double spaces by the html version   + text = this.replaceAll(text,'\n','
    '); + text = this.replaceAll(text,' ','  '); + + // replace the words by a highlighted version of the words + + var remove = []; + + for (var i=0;i' + name + ''); + } else { + remove.push(this.mentions[i]); + } + } + + for (var i = 0; i < remove.length; i++) { + this.mentions.splice(this.mentions.indexOf(remove[i]), 1); + } + + // re-inject the processed text into the div + this.highlighter.html(text); + + var count = 256 - this.textarea.val().length + (this.mentions.length * 6); + this.counter.html(count); + } + + NewPost.prototype.send = function() { + + var count = 256 - this.textarea.val().length + (this.mentions.length * 6); + if(count >= 0 && count <= 256) { + this.sendNewMessage(); + return true; + } else { + debug("BEEP"); + return false; + } + } + + NewPost.prototype.sendNewMessage = function() { + + var content = this.textarea.val(); + + var type = "https://tent.io/types/status/v0#"; + var data = { + type: type, + content: { + text: content + }, + permissions: { + public: !this.is_private + } + }; + + var mentions = []; + for (var i = 0; i < this.mentions.length; i++) { + var mention = this.mentions[i]; + if(this.status && this.status.entity == mention.entity) { + mentions.push({ + entity: this.status.entity, + post: this.status.id, + type: this.status.type + }); + } else { + mentions.push({ + entity: mention.entity + }); + } + } + + data.mentions = mentions; + + // Make tent flavored markdown mentions + for (var i = 0; i < this.mentions.length; i++) { + var mention = this.mentions[i]; + data.content.text = this.replaceAll(data.content.text, mention.name, "^[" + mention.name + "](" + i + ")") + } + + APICalls.post(HostApp.serverUrl("new_post"), JSON.stringify(data), { + content_type: data.type, + accept: 'application/vnd.tent.post.v0+json; type="https://tent.io/types/status/v0#"', + callback: function(resp) { + if (resp.status >= 200 < 300) { + new_post_window.closeWindow(); + controller.getNewData(); + } else { + new_post_window.beep(); + } + } + }); + } +/* + NewPost.prototype.sendNewMessageWithImage = function(content, in_reply_to_status_id, in_reply_to_entity, location, image_data_uri, is_private, callback) { + + var url = URI(APICalls.mkApiRootPath("/posts")); + + var data = { + "type": "https://tent.io/types/post/photo/v0.1.0", + "published_at": parseInt(new Date().getTime() / 1000, 10), + "permissions": { + "public": !is_private + }, + "content": { + "caption": content, + }, + }; + + if (location) { + data["content"]["location"] = { "type": "Point", "coordinates": location } + } + + var mentions = this.parseMentions(content, in_reply_to_status_id, in_reply_to_entity); + if (mentions.length > 0) { + data["mentions"] = mentions; + if (is_private) { + var entities = {}; + for (var i = 0; i < mentions.length; i++) { + var entity = mentions[i]["entity"] + entities[entity] = true; + }; + + data["permissions"]["entities"] = entities; + } + } + + var data_string = JSON.stringify(data); + + var boundary = "TentAttachment----------TentAttachment"; + var post = "--" + boundary + "\r\n"; + + post += 'Content-Disposition: form-data; name="post"; filename="post.json"\r\n'; + post += 'Content-Length: ' + data_string.length + '\r\n'; + post += 'Content-Type: application/vnd.tent.v0+json\r\n'; + post += 'Content-Transfer-Encoding: binary\r\n\r\n'; + post += data_string; + + post += "\r\n--" + boundary + "\r\n"; + + var blob_string = image_data_uri.split(',')[1]; + var mime_type = image_data_uri.split(',')[0].split(':')[1].split(';')[0]; + var ext = "png"; + if (mime_type == "image/jpeg") { + ext = "jpeg"; + } else if (mime_type == "image/gif") { + ext = "gif"; + } + + + post += 'Content-Disposition: form-data; name="photos[0]"; filename="photo.' + ext + '"\r\n'; + post += 'Content-Length: ' + blob_string.length + "\r\n"; + post += 'Content-Type: ' + mime_type + "\r\n"; + post += 'Content-Transfer-Encoding: base64\r\n\r\n'; + post += blob_string; + post += "\r\n--" + boundary + "--\r\n"; + + var newCallback = function(resp) { + if (resp.status == 403) { + var err = JSON.parse(resp.responseText); + HostApp.alertTitleWithMessage(resp.statusText, err.error); + } + callback(resp); + } + + APICalls.postMultipart(url.toString(), newCallback, post, boundary); + } +*/ + + return NewPost; +}) \ No newline at end of file diff --git a/WebKit/scripts/controller/Oauth.js b/WebKit/scripts/controller/Oauth.js index 0cebc2e..7967c12 100644 --- a/WebKit/scripts/controller/Oauth.js +++ b/WebKit/scripts/controller/Oauth.js @@ -1,38 +1,46 @@ define([ "helper/HostApp", - "helper/Paths", + "helper/APICalls", "helper/Hmac" ], -function(HostApp, Paths, Hmac) { +function(HostApp, APICalls, Hmac) { function Oauth() { this.app_info = { - "id": null, - "name": "Bungloo on " + HostApp.osType(), - "description": "A small TentStatus client.", - "url": "http://jabs.nu/bungloo/", - "icon": "http://jabs.nu/bungloo/icon.png", - "redirect_uris": [ - "bungloo://oauthtoken" - ], - "scopes": { - "read_posts": "Uses posts to show them in a list", - "write_posts": "Posts on users behalf", - "read_profile": "Displays your own profile", - "write_profile": "Updating profile and mentions pointer", - "read_followers": "Display a list of people who follow you", - "write_followers": "Be able to block people who follow you", - "read_followings": "Display following list and their older posts in conversations", - "write_followings": "Follow ne entities" + "type": "https://tent.io/types/app/v0#", + "content": { + "name": "Bungloo on " + HostApp.osType(), + "url": "http://jabs.nu/bungloo/", + "description": "A desktop Tent client.", + "redirect_uri": "bungloo://oauthtoken", + "types": { + "read": [ + "https://tent.io/types/meta/v0", + "https://tent.io/types/relationship/v0", + "https://tent.io/types/subscription/v0", + "https://tent.io/types/delete/v0", + "https://tent.io/types/status/v0", + "https://tent.io/types/repost/v0", + "https://tent.io/types/photo/v0", + "https://tent.io/types/cursor/v0", + "https://tent.io/types/basic-profile/v0" + ], + "write": [ + "https://tent.io/types/relationship/v0", + "https://tent.io/types/subscription/v0", + "https://tent.io/types/delete/v0", + "https://tent.io/types/status/v0", + "https://tent.io/types/repost/v0", + "https://tent.io/types/photo/v0", + "https://tent.io/types/cursor/v0" + ] + }, + "scopes": ["permissions"] }, - "tent_profile_info_types": [ "all" ], - "tent_post_types": [ - "https://tent.io/types/post/status/v0.1.0", - "https://tent.io/types/post/photo/v0.1.0", - "https://tent.io/types/post/repost/v0.1.0", - "https://tent.io/types/post/delete/v0.1.0" - ] + "permissions": { + "public": false + } }; this.register_data = null; this.profile = null; @@ -61,13 +69,9 @@ function(HostApp, Paths, Hmac) { } } - Oauth.prototype.apiRoot = function() { - return this.profile["https://tent.io/types/info/core/v0.1.0"]["servers"][0]; - } - Oauth.prototype.requestProfileURL = function (entity) { var those = this; - Paths.findProfileURL(entity, + APICalls.findProfileURL(entity, function(profile_url) { if (profile_url && (profile_url.startsWith("http://") || profile_url.startsWith("https://"))) { those.register(profile_url); @@ -76,6 +80,7 @@ function(HostApp, Paths, Hmac) { } }, function(errorMessage) { // error callback + HostApp.authentificationDidNotSucceed(errorMessage); HostApp.authentificationDidNotSucceed("Could not find profile for: " + entity); } ); @@ -84,72 +89,76 @@ function(HostApp, Paths, Hmac) { Oauth.prototype.register = function (url) { var those = this; - Paths.getURL(url, "GET", function(resp) { + APICalls.get(url, { + no_auth: true, + callback: function(resp) { - those.profile = JSON.parse(resp.responseText); - those.entity = those.profile["https://tent.io/types/info/core/v0.1.0"].entity; + those.profile = JSON.parse(resp.responseText).post; + those.entity = those.profile.content.entity; HostApp.setStringForKey(those.entity, "entity") - HostApp.setStringForKey(those.apiRoot(), "api_root"); + HostApp.setServerUrls(those.profile.content.servers[0].urls); + APICalls.post(HostApp.serverUrl("new_post"), JSON.stringify(those.app_info), { + content_type: "https://tent.io/types/app/v0#", + no_auth: true, + callback: function(resp) { + var app_id = JSON.parse(resp.responseText).post.id; + var header_string = resp.getAllResponseHeaders(); + var regexp = /https:\/\/tent.io\/rels\/credentials/i + var url = APICalls.parseHeaderForLink(header_string, regexp); - var callback = function(resp) { - var data = JSON.parse(resp.responseText); - those.authRequest(data); - } - Paths.getURL(Paths.mkApiRootPath("/apps"), "POST", callback, JSON.stringify(those.app_info), false); - }, null, false); + APICalls.get(url, { + content_type: "https://tent.io/types/app/v0#", + no_auth: true, + callback: function(resp) { + var data = JSON.parse(resp.responseText); + those.authRequest(data.post, app_id); + } + }); + }}); + + }}); } - Oauth.prototype.authRequest = function(register_data) { - // id - // mac_key_id - // mac_key - // mac_algorithm - this.register_data = register_data; - - // Needed for later App Registration Modification - HostApp.setStringForKey(register_data["mac_key"], "app_mac_key"); - HostApp.setStringForKey(register_data["mac_key_id"], "app_mac_key_id"); - HostApp.setStringForKey(register_data["id"], "app_id"); - HostApp.setStringForKey(register_data["mac_algorithm"], "app_mac_algorithm"); - + Oauth.prototype.authRequest = function(credentials, app_id) { + + HostApp.setStringForKey(app_id, "app_id"); + HostApp.setStringForKey(credentials.id, "app_hawk_id"); + HostApp.setStringForKey(credentials.content.hawk_key, "app_hawk_key"); + HostApp.setStringForKey(credentials.content.hawk_algorithm, "app_hawk_algorithm"); + this.state = Hmac.makeid(19); - var auth = "/oauth/authorize?client_id=" + register_data["id"] - + "&redirect_uri=" + this.app_info["redirect_uris"][0] - + "&scope=" + Object.keys(this.app_info["scopes"]).join(",") - + "&state=" + this.state - + "&tent_post_types=" + this.app_info["tent_post_types"].join(",") - + "&tent_profile_info_types=" + this.app_info["tent_profile_info_types"].join(","); - - HostApp.openAuthorizationURL(this.apiRoot() + auth); + var url = HostApp.serverUrl("oauth_auth") + "?client_id=" + app_id + "&state=" + this.state; + HostApp.openAuthorizationURL(url); } Oauth.prototype.requestAccessToken = function(responseBody) { // /oauthtoken?code=51d0115b04d1ed94001dde751c5b360f&state=aQfH1VEohYsQr86qqyv + // https://app.example.com/oauth?code=K4m2J2bGI9rcICBqmUCYuQ&state=d173d2bb868a - var urlVars = Paths.getUrlVars(responseBody); + var urlVars = APICalls.getUrlVars(responseBody); if(this.state && this.state != "" && urlVars["state"] == this.state) { - var url = Paths.mkApiRootPath("/apps/") + this.register_data["id"] + "/authorizations"; + var url = HostApp.serverUrl("oauth_token"); var requestBody = JSON.stringify({ 'code' : urlVars["code"], - 'token_type' : "mac" + 'token_type': "https://tent.io/oauth/hawk-token" }); var those = this; - var http_method = "POST"; - var callback = function(resp) { - those.requestAccessTokenTicketFinished(resp.responseText); - }; - - var auth_header = Hmac.makeAuthHeader( + var auth_header = Hmac.makeHawkAuthHeader( url, - http_method, - HostApp.stringForKey("app_mac_key"), - HostApp.stringForKey("app_mac_key_id") + "POST", + HostApp.stringForKey("app_hawk_id"), + HostApp.stringForKey("app_hawk_key") ); - Paths.getURL(url, http_method, callback, requestBody, auth_header); + APICalls.post(url, requestBody, { + content_type: "application/json", + auth_header: auth_header, + callback: function(resp) { + those.requestAccessTokenTicketFinished(resp.responseText); + }}); } else { console.error("State is not the same: {" + this.state + "} vs {" + urlVars["state"] + "}") @@ -163,16 +172,16 @@ function(HostApp, Paths, Hmac) { var access = JSON.parse(responseBody); HostApp.setStringForKey(access["access_token"], "user_access_token"); - HostApp.setSecret(access["mac_key"]); - HostApp.setStringForKey(access["mac_algorithm"], "user_mac_algorithm"); + HostApp.setSecret(access["hawk_key"]); + HostApp.setStringForKey(access["hawk_algorithm"], "user_hawk_algorithm"); HostApp.setStringForKey(access["token_type"], "user_token_type"); HostApp.loggedIn(); } - Oauth.prototype.logout = function() { + Oauth.prototype.logout = function() { // FIXME - var url = Paths.mkApiRootPath("/apps/" + HostApp.stringForKey("app_id")); + var url = APICalls.mkApiRootPath("/apps/" + HostApp.stringForKey("app_id")); var http_method = "DELETE"; var auth_header = Hmac.makeAuthHeader( url, @@ -181,7 +190,7 @@ function(HostApp, Paths, Hmac) { HostApp.stringForKey("app_mac_key_id") ); - Paths.getURL(url, http_method, function(resp) { + APICalls.http_call(url, http_method, function(resp) { HostApp.setStringForKey(null, "app_mac_key"); HostApp.setStringForKey(null, "app_mac_key_id"); HostApp.setStringForKey(null, "app_id"); diff --git a/WebKit/scripts/controller/Profile.js b/WebKit/scripts/controller/Profile.js index 584a046..840eed9 100644 --- a/WebKit/scripts/controller/Profile.js +++ b/WebKit/scripts/controller/Profile.js @@ -1,667 +1,667 @@ define([ - "helper/HostApp", - "helper/Core", - "helper/Paths", - "lib/URI" + "helper/HostApp", + "helper/Core", + "helper/APICalls", + "lib/URI", + "controller/Timeline" ], -function(HostApp, Core, Paths, URI) { +function(HostApp, Core, APICalls, URI, Timeline) { - function Profile() { + function Profile() { + + Timeline.call(this); - Core.call(this); + this.action = "profile"; - this.action = "profile"; + this.container = document.createElement("div"); + this.container.className = this.action; + document.getElementById("content").appendChild(this.container); - this.posts_limit = 25; + this.initProfileTemplate(); + this.hide(); + } - this.container = document.createElement("div"); - this.container.className = this.action; - document.getElementById("content").appendChild(this.container); + Profile.prototype = Object.create(Timeline.prototype); + - this.initProfileTemplate(); - this.hide(); + Profile.prototype.show = function() { + Core.prototype.show.call(this, this.container); + } + + Profile.prototype.hide = function() { + Core.prototype.hide.call(this, this.container); + } + + Profile.prototype.logout = function() { + this.container = ""; + } + + Profile.prototype.showList = function(list) { + $(this.body).hide(); + $(this.followingsBody).hide(); + $(this.followersBody).hide(); + $(list).show(); + } - var _this = this; - setTimeout(function() { _this.showProfileForEntity() }, 5000); // Load users profile on start - } + Profile.prototype.showEntity = function(a, i) { + var entity = $(a).closest("li").get(0).status.mentions[i].entity; + this.showProfileForEntity(entity); + bungloo.sidebar.onEntityProfile(); + }; + + Profile.prototype.showProfileForEntity = function(entity) { + + if (!entity) { + entity = HostApp.stringForKey("entity"); + } + + this.clear(); + this.entity = entity; + this.following = null; + this.following_id = null; + this.profile_template.entity.innerHTML = this.entity; + this.profile_template.entity.href = this.entity; + + this.getProfile(); + this.getFollowing(); + this.getStatuses(); + } + + Profile.prototype.initProfileTemplate = function() { + + var _this = this; + + var header = document.createElement("header"); + header.className = "profile"; - Profile.prototype = Object.create(Core.prototype); - + this.container.appendChild(header); - Profile.prototype.show = function() { - Core.prototype.show.call(this, this.container); - } + this.profile_template = { + avatar: document.createElement("img"), + name: document.createElement("h1"), + entity: document.createElement("a"), + bio: document.createElement("p"), + relationships: document.createElement("td"), + posts: document.createElement("a"), + following: document.createElement("a"), + followed: document.createElement("a"), + birthdate: document.createElement("td"), + location: document.createElement("td"), + gender: document.createElement("td"), + url: document.createElement("a"), + following_button: document.createElement("button"), + mention_button: document.createElement("button") + }; - Profile.prototype.hide = function() { - Core.prototype.hide.call(this, this.container); - } + header.appendChild(this.profile_template.avatar); + this.profile_template.avatar.src = "img/default-avatar.png"; + + var div = document.createElement("div"); + header.appendChild(div); + + this.profile_template.following_button.onclick = function(e) { + _this.toggleFollow() + } + div.appendChild(this.profile_template.following_button); + + this.profile_template.mention_button.onclick = function() { + HostApp.openNewMessageWidow({entity:_this.entity}); + } + div.appendChild(this.profile_template.mention_button); + this.profile_template.mention_button.innerHTML = "Mention"; + + div.appendChild(this.profile_template.name); + + var p = document.createElement("p"); + p.appendChild(this.profile_template.entity); + div.appendChild(p); + + div.appendChild(this.profile_template.bio); + + var table = document.createElement("table"); + div.appendChild(table); + + function mkLi(name, template) { + var tr = document.createElement("tr"); + var th = document.createElement("th"); + tr.style.display = "none"; + th.innerText = name + ": "; + tr.appendChild(th); + tr.appendChild(template); + table.appendChild(tr); + } + + mkLi("Birth date", this.profile_template.birthdate); + mkLi("Location", this.profile_template.location); + mkLi("Gender", this.profile_template.gender); + + var td = document.createElement("td"); + td.appendChild(this.profile_template.url); + mkLi("Homepage", td); + + mkLi("Relationships", this.profile_template.relationships); + + td = document.createElement("td"); + td.appendChild(this.profile_template.posts); + this.profile_template.posts.href = "#"; + this.profile_template.posts.onclick = function() { _this.showPosts(); return false; }; + mkLi("Posts", td); + + td = document.createElement("td"); + td.appendChild(this.profile_template.following); + this.profile_template.following.href = "#"; + this.profile_template.following.onclick = function() { _this.showFollowings(); return false; }; + mkLi("Following", td); + + td = document.createElement("td"); + td.appendChild(this.profile_template.followed); + this.profile_template.followed.href = "#"; + this.profile_template.followed.onclick = function() { _this.showFollowers(); return false; }; + mkLi("Followed by", td); + + + this.body = document.createElement("ol"); + this.body.className = this.action; + this.container.appendChild(this.body); + + this.followingsBody = document.createElement("ol"); + this.followingsBody.className = this.action + " followings"; + this.container.appendChild(this.followingsBody); + + this.followersBody = document.createElement("ol"); + this.followersBody.className = this.action + " folloewds"; + this.container.appendChild(this.followersBody); + + } + + Profile.prototype.clear = function() { + + this.server = null; + this.before = {id: null, entity: null, loading: false}; + + + this.profile_template.avatar.src = "img/default-avatar.png"; + + this.relationships = { + following_you: false, + followed_by_you: false, + it_is_you: false + } + + this.profile_template.name.innerText = ""; + this.profile_template.entity.innerText = ""; + this.profile_template.bio.innerText = ""; + this.profile_template.relationships.innerText = ""; + this.profile_template.posts.innerText = ""; + this.profile_template.following.innerText = ""; + this.profile_template.followed.innerText = ""; + this.profile_template.birthdate.innerText = ""; + this.profile_template.location.innerText = ""; + this.profile_template.gender.innerText = ""; + this.profile_template.url.innerText = ""; + this.profile_template.url.href = ""; + + this.profile_template.posts.parentNode.parentNode.style.display = "none"; + this.profile_template.following.parentNode.parentNode.style.display = "none"; + this.profile_template.followed.parentNode.parentNode.style.display = "none"; + this.profile_template.birthdate.parentNode.style.display = "none"; + this.profile_template.location.parentNode.style.display = "none"; + this.profile_template.gender.parentNode.style.display = "none"; + this.profile_template.url.parentNode.parentNode.style.display = "none"; + + this.profile_template.following_button.style.display = ""; + this.setFollowingButton(false); + + this.body.innerHTML = ""; + this.followingsBody.innerHTML = ""; + this.followersBody.innerHTML = ""; + + this.showList(this.body); + }; + + Profile.prototype.getProfile = function() { + + var _this = this; + + if (HostApp.stringForKey("entity") == this.entity) { + this.relationships.it_is_you = true; + this.profile_template.following_button.style.display = "none"; + } + + var url = HostApp.serverUrl("posts_feed") + "?types=" + encodeURIComponent("https://tent.io/types/meta/v0") + "&entities=" + encodeURIComponent(this.entity); + APICalls.get(url, { + callback: function(resp) { + var profile = JSON.parse(resp.responseText); + _this.showProfile(profile); + _this.profile = profile; + }}); + } + + Profile.prototype.getFollowing = function() { + if(this.entity != HostApp.stringForKey("entity")) { + + var url = HostApp.serverUrl("posts_feed") + "?mentions=" + encodeURIComponent(this.entity) + "&types=" + encodeURIComponent("https://tent.io/types/subscription/v0#https://tent.io/types/status/v0"); + var _this = this; + + APICalls.get(url, {callback: function(resp) { + + var json = JSON.parse(resp.responseText); + var count = json.posts.length; + + if (count > 0) { + _this.setFollowingButton(true); + _this.following_id = json.posts[0].id; + } else { + _this.setFollowingButton(false); + delete _this.following_id; + } + + }}); + + } else { + + this.setFollowingButton(false); + this.following_id = null; + } + } + + Profile.prototype.showProfile = function(profiles) { + + if(profiles.posts.length < 1) return; + var profile = profiles.posts[0]; + bungloo.cache.profiles[profile.entity] = profile.content.profile; + + var basic = profile.content.profile; + + if (profile && basic) { + + // Find and apply avatar + if(profile.attachments) { + + var digest = null; + for (var i = 0; i < profile.attachments.length; i++) { + var attachment = profile.attachments[i]; + if(attachment.category == "avatar") { + digest = attachment.digest; + break; + } + } + + if(digest) { + var _this = this; + this.profile_template.avatar.onerror = function() { _this.profile_template.avatar.src = 'img/default-avatar.png' }; + var avatar_url = profile.content.servers[0].urls.attachment.replace(/\{entity\}/, encodeURIComponent(profile.entity)); + this.profile_template.avatar.src = avatar_url.replace(/\{digest\}/, digest); + } + } + + this.populate(this.profile_template.name, basic.name); + this.populate(this.profile_template.birthdate, basic.birthdate); + this.populate(this.profile_template.location, basic.location); + this.populate(this.profile_template.gender, basic.gender); + this.populate(this.profile_template.bio, basic.bio); + + if(basic.website) { + + var url = basic.website; + this.profile_template.url.innerText = url; + this.profile_template.url.parentNode.parentNode.style.display = ""; + + if (!url.startsWith("http")) { + url = "http://" + url; + } + + this.profile_template.url.href = url; + } + } + + if (profile) { + this.profile = profile; + + // FIXME + this.getMeta(this.profile); + this.getStatuses(); + } + } + + Profile.prototype.populate = function(t, v) { + if (v) { + t.innerText = v; + t.parentNode.style.display = ""; + t.parentNode.parentNode.style.display = ""; + } + } + + Profile.prototype.getMeta = function(profile) { + + // FIXME! + + var _this = this; +/* + var url = HostApp.serverUrl("posts_feed") + "?entities=" + encodeURIComponent(this.entity) + "&types=" + encodeURIComponent("https://tent.io/types/subscription/v0#"); + APICalls.head(url, { + callback: function(resp) { + _this.populate(_this.profile_template.followed, APICalls.getCount(resp) + " "); + } + }); + + var url = HostApp.serverUrl("posts_feed") + "?entities=" + encodeURIComponent(this.entity) + "&types=" + encodeURIComponent("https://tent.io/types/relationship/v0#following"); + APICalls.head(url, { + callback: function(resp) { + _this.populate(_this.profile_template.following, APICalls.getCount(resp) + " "); + } + }); + + var url = HostApp.serverUrl("posts_feed") + "?entities=" + encodeURIComponent(this.entity) + "&types=" + encodeURIComponent("https://tent.io/types/status/v0#"); + APICalls.head(url, { + callback: function(resp) { + _this.populate(_this.profile_template.posts, APICalls.getCount(resp) + " "); + } + }); +*/ + + // is following you + // FIXME: should use HEAD + var url = HostApp.serverUrl("posts_feed") + "?entities=" + encodeURIComponent(this.entity) + "&types=" + encodeURIComponent("https://tent.io/types/subscription/v0#https://tent.io/types/status/v0") + "&mentions=" + encodeURIComponent(HostApp.stringForKey("entity")); + APICalls.get(url, { + callback: function(resp) { + var json = JSON.parse(resp.responseText); + if (json.posts.length > 0) { + _this.relationships.following_you = true; + } else { + _this.relationships.following_you = false; + } + _this.setRelationships(); + } + }); + + // is followed by you + // FIXME: should use HEAD + var url = HostApp.serverUrl("posts_feed") + "?mentions=" + encodeURIComponent(this.entity) + "&types=" + encodeURIComponent("https://tent.io/types/subscription/v0#https://tent.io/types/status/v0"); + APICalls.get(url, { + callback: function(resp) { + var json = JSON.parse(resp.responseText); + if (json.posts.length > 0) { + _this.relationships.followed_by_you = true; + } else { + _this.relationships.followed_by_you = false; + } + _this.setRelationships(); + } + }); + + return; +/* + + + if (this.entity != HostApp.stringForKey("entity")) { + APICalls.http_call(URI(root_url + "/followers/" + encodeURIComponent(HostApp.stringForKey("entity"))).toString(), "GET", function(resp) { + if (resp.status == 200) { + _this.relationships.following_you = true; + } + _this.setRelationships(); + + }, null, false); + + APICalls.http_call(URI(APICalls.mkApiRootPath("/followings/" + encodeURIComponent(this.entity))), "GET", function(resp) { + if (resp.status == 200) { + _this.relationships.followed_by_you = true; + } + _this.setRelationships(); + }); + + } else { + this.setRelationships(); + } + + + var url = URI(root_url + "/posts/count"); + var post_types = [ + "https://tent.io/types/post/repost/v0.1.0", + "https://tent.io/types/post/status/v0.1.0", + "https://tent.io/types/post/photo/v0.1.0" + ]; + url.addSearch("post_types", post_types.join(",")); + + APICalls.http_call(url.toString(), "GET", function(resp) { + + _this.populate(_this.profile_template.posts, resp.responseText); + }, null, false);*/ + } + + Profile.prototype.setRelationships = function() { + var relation = "none"; + if (HostApp.stringForKey("entity") == this.entity) { + relation = "it's you"; + } else { + if (this.relationships.following_you && !this.relationships.followed_by_you) { + relation = "is following you"; + } else if (this.relationships.following_you && this.relationships.followed_by_you) { + relation = "you both follow each other"; + } else if (!this.relationships.following_you && this.relationships.followed_by_you) { + relation = "being followed by you"; + } + } + this.populate(this.profile_template.relationships, relation); + } + + + Profile.prototype.getStatuses = function() { + + Timeline.prototype.getNewData.call(this, {entities: this.entity}); + } + + Profile.prototype.setFollowingButton = function(following) { + + this.following = following; + + if (following) { + this.profile_template.following_button.className = "following"; + this.profile_template.following_button.innerText = "Unfollow"; + } else { + this.profile_template.following_button.className = ""; + this.profile_template.following_button.innerText = "Follow"; + } + } + + Profile.prototype.toggleFollow = function() { + + var _this = this; + + if (this.following_id) { + + this.setFollowingButton(false); + + var url = HostApp.serverUrl("post").replace(/\{entity\}/, encodeURIComponent(HostApp.stringForKey("entity"))).replace(/\{post\}/, this.following_id); + APICalls.delete(url, { callback: function(resp) { + if (resp.status >= 200 && resp.status < 300) { + _this.setFollowingButton(false); + delete _this.following_id; + } else { + _this.setFollowingButton(true); + } + _this.getMeta(); + }}); + + } else { + + this.setFollowingButton(true); + + var url = HostApp.serverUrl("new_post"); + + var data = { + content: { + type: "https://tent.io/types/status/v0" + }, + mentions: [{ + entity: this.entity + }], + type: "https://tent.io/types/subscription/v0#https://tent.io/types/status/v0" + }; + + debug(data) + debug(url) + + APICalls.post(url, JSON.stringify(data), { + content_type: data.type, + callback: function(resp) { + if (resp.status >= 200 && resp.status < 300) { + _this.setFollowingButton(true); + var json = JSON.parse(resp.responseText); + _this.following_id = json.post.id; + } else { + _this.setFollowingButton(false); + } + _this.getMeta(); + } + }); + } + } + + Profile.prototype.showPosts = function() { + this.showList(this.body); + } + + Profile.prototype.showFollowings = function() { + + this.showList(this.followingsBody); + this.followingsBody.innerHTML = ""; + + var _this = this; + var callback = function(resp) { + var followings = JSON.parse(resp.responseText); + for (var i = 0; i < followings.length; i++) { + var li = _this.getDOMSmallProfile(followings[i]); + _this.followingsBody.appendChild(li); + } + } + + var url = URI(this.server + "/followings"); + url.addSearch("limit", 200); + APICalls.http_call(url.toString(), "GET", callback, null, false); + } + + Profile.prototype.showFollowers = function() { + + this.showList(this.followersBody); + this.followersBody.innerHTML = ""; + + var _this = this; + var callback = function(resp) { + var followers = JSON.parse(resp.responseText); + for (var i = 0; i < followers.length; i++) { + var li = _this.getDOMSmallProfile(followers[i]); + _this.followersBody.appendChild(li); + } + } + + var url = URI(this.server + "/followers"); + url.addSearch("limit", 200); + APICalls.http_call(url.toString(), "GET", callback, null, false); + } + + Profile.prototype.getDOMSmallProfile = function(profile) { + + var li = document.createElement("li"); + + var image = document.createElement("img"); + image.title = profile.entity; + image.className = "image"; + image.src = 'img/default-avatar.png'; + li.appendChild(image); + image.onclick = function(e) { + HostApp.showProfileForEntity(e.target.title); + return false; + } + + var div = document.createElement("div"); + div.className = "data" + + var h1 = document.createElement("h1"); + var username = document.createElement("a"); + username.title = profile.entity; + username.className = "name"; + username.href = profile.entity; + username.onclick = function(e) { + HostApp.showProfileForEntity(profile.entity); + return false; + } + + h1.appendChild(username) + div.appendChild(h1); + li.appendChild(div); + + var p = document.createElement("p"); + p.className = "message"; + + var entity_tag = document.createElement("a"); + entity_tag.innerText = profile.entity; + entity_tag.href = profile.entity; + entity_tag.title = profile.entity; + + var new_line = document.createElement("br"); + var follows_since = document.createTextNode("follows since "); + var follows_since_time = document.createElement("span"); + follows_since_time.innerText = this.ISODateString(new Date(profile.created_at * 1000)); + follows_since_time.title = follows_since_time.innerText; + follows_since_time.className = "timeago"; + jQuery(follows_since_time).timeago(); + + p.appendChild(entity_tag); + p.appendChild(new_line); + p.appendChild(follows_since); + p.appendChild(follows_since_time); + div.appendChild(p); + + var profile_callback = function(p) { + + var basic = p["https://tent.io/types/info/basic/v0.1.0"]; + + if (p && basic) { + if(basic.name) { + username.title = username.innerText; + username.innerText = basic.name; + } + if(basic.avatar_url) { + image.onerror = function() { image.src = 'img/default-avatar.png'; }; + image.src = basic.avatar_url; + } + } + + } + + var p = this.cache.profiles.getItem(profile.entity); + + if (p && p != "null") { + + profile_callback(p); - Profile.prototype.logout = function() { - this.container = ""; - } + } else { + + var _this = this; + APICalls.findProfileURL(profile.entity, function(profile_url) { - Profile.prototype.showList = function(list) { - $(this.body).hide(); - $(this.followingsBody).hide(); - $(this.followersBody).hide(); - $(list).show(); - }; - - Profile.prototype.showProfileForEntity = function(entity) { + if (profile_url) { + APICalls.http_call(profile_url, "GET", function(resp) { + var p = JSON.parse(resp.responseText); + if (p && p != "null") { + _this.cache.profiles.setItem(profile.entity, p); + profile_callback(p); + } + + }, null, false); // do not send auth-headers + } + }); + } - if (!entity) { - entity = HostApp.stringForKey("entity"); - }; - - this.clear(); - this.entity = entity; - this.following = null; - this.following_id = null; - this.profile_template.entity.innerHTML = this.entity; - this.profile_template.entity.href = this.entity; - - this.getProfile(); - this.getFollowing(); - } - - Profile.prototype.initProfileTemplate = function() { + return li; + } - var _this = this; - - var header = document.createElement("header"); - header.className = "profile"; - - this.container.appendChild(header); - this.profile_template = { - avatar: document.createElement("img"), - name: document.createElement("h1"), - entity: document.createElement("a"), - bio: document.createElement("p"), - relationships: document.createElement("td"), - posts: document.createElement("a"), - following: document.createElement("a"), - followed: document.createElement("a"), - birthdate: document.createElement("td"), - location: document.createElement("td"), - gender: document.createElement("td"), - url: document.createElement("a"), - following_button: document.createElement("button"), - mention_button: document.createElement("button") - }; - header.appendChild(this.profile_template.avatar); - this.profile_template.avatar.src = "img/default-avatar.png"; - var div = document.createElement("div"); - header.appendChild(div); - - this.profile_template.following_button.onclick = function(e) { - _this.toggleFollow() - } - div.appendChild(this.profile_template.following_button); - - this.profile_template.mention_button.onclick = function() { - var e = _this.entity; - if (e.startsWith("https://")) { - e = e.substr(8, e.length); - } - HostApp.openNewMessageWidow(null, null, "^" + e + " ", false); - } - div.appendChild(this.profile_template.mention_button); - this.profile_template.mention_button.innerHTML = "Mention"; - - div.appendChild(this.profile_template.name); - - var p = document.createElement("p"); - p.appendChild(this.profile_template.entity); - div.appendChild(p); - - div.appendChild(this.profile_template.bio); - - var table = document.createElement("table"); - div.appendChild(table); - - function mkLi(name, template) { - var tr = document.createElement("tr"); - var th = document.createElement("th"); - tr.style.display = "none"; - th.innerText = name + ": "; - tr.appendChild(th); - tr.appendChild(template); - table.appendChild(tr); - } - - mkLi("Birth date", this.profile_template.birthdate); - mkLi("Location", this.profile_template.location); - mkLi("Gender", this.profile_template.gender); - - var td = document.createElement("td"); - td.appendChild(this.profile_template.url); - mkLi("Homepage", td); - - mkLi("Relationships", this.profile_template.relationships); - - td = document.createElement("td"); - td.appendChild(this.profile_template.posts); - this.profile_template.posts.href = "#"; - this.profile_template.posts.onclick = function() { _this.showPosts(); return false; }; - mkLi("Posts", td); - - td = document.createElement("td"); - td.appendChild(this.profile_template.following); - this.profile_template.following.href = "#"; - this.profile_template.following.onclick = function() { _this.showFollowings(); return false; }; - mkLi("Following", td); - - td = document.createElement("td"); - td.appendChild(this.profile_template.followed); - this.profile_template.followed.href = "#"; - this.profile_template.followed.onclick = function() { _this.showFollowers(); return false; }; - mkLi("Followed by", td); - - - this.body = document.createElement("ol"); - this.body.className = this.action; - this.container.appendChild(this.body); - - this.followingsBody = document.createElement("ol"); - this.followingsBody.className = this.action + " followings"; - this.container.appendChild(this.followingsBody); - - this.followersBody = document.createElement("ol"); - this.followersBody.className = this.action + " folloewds"; - this.container.appendChild(this.followersBody); - - } - - Profile.prototype.clear = function() { - - this.server = null; - this.before = {id: null, entity: null, loading: false}; - - - this.profile_template.avatar.src = "img/default-avatar.png"; - - this.relationships = { - following_you: false, - followed_by_you: false, - it_is_you: false - } - - this.profile_template.name.innerText = ""; - this.profile_template.entity.innerText = ""; - this.profile_template.bio.innerText = ""; - this.profile_template.relationships.innerText = ""; - this.profile_template.posts.innerText = ""; - this.profile_template.following.innerText = ""; - this.profile_template.followed.innerText = ""; - this.profile_template.birthdate.innerText = ""; - this.profile_template.location.innerText = ""; - this.profile_template.gender.innerText = ""; - this.profile_template.url.innerText = ""; - this.profile_template.url.href = ""; - - this.profile_template.posts.parentNode.parentNode.style.display = "none"; - this.profile_template.following.parentNode.parentNode.style.display = "none"; - this.profile_template.followed.parentNode.parentNode.style.display = "none"; - this.profile_template.birthdate.parentNode.style.display = "none"; - this.profile_template.location.parentNode.style.display = "none"; - this.profile_template.gender.parentNode.style.display = "none"; - this.profile_template.url.parentNode.parentNode.style.display = "none"; - - this.profile_template.following_button.style.display = ""; - this.setFollowingButton(false); - - this.body.innerHTML = ""; - this.followingsBody.innerHTML = ""; - this.followersBody.innerHTML = ""; - - this.showList(this.body); - }; - - Profile.prototype.getProfile = function() { - - var _this = this; - - if (HostApp.stringForKey("entity") == this.entity) { - this.relationships.it_is_you = true; - this.profile_template.following_button.style.display = "none"; - } - - var profile = this.cache.profiles.getItem(this.entity); - - if (profile && profile != "null") { - - this.showProfile(profile); - this.profile = profile; - - } else { - Paths.findProfileURL(this.entity, function(profile_url) { - - if (profile_url) { - - Paths.getURL(profile_url, "GET", function(resp) { - - profile = JSON.parse(resp.responseText); - _this.showProfile(profile); - _this.profile = profile; - - }, null, false); // do not send auth-headers - } - }); - - } - } - - Profile.prototype.getFollowing = function() { - if(this.entity != HostApp.stringForKey("entity")) { - var url = Paths.mkApiRootPath("/followings") + "/" + encodeURIComponent(this.entity); - var _this = this; - Paths.getURL(url, "GET", function(resp) { - if (resp.status >= 200 && resp.status < 400) { - var following = JSON.parse(resp.responseText); - _this.following_id = following.id - _this.setFollowingButton(true); - } else { - _this.setFollowingButton(false); - _this.following_id = null; - } - }) - } else { - this.setFollowingButton(false); - this.following_id = null; - } - } - - Profile.prototype.showProfile = function(profile) { - - var basic = profile["https://tent.io/types/info/basic/v0.1.0"]; - - if (profile && basic) { - - if(basic.avatar_url) { - this.profile_template.avatar.onerror = function() { this.profile_template.avatar.src = 'img/default-avatar.png' }; - this.profile_template.avatar.src = basic.avatar_url; - } - - this.populate(this.profile_template.name, basic.name); - this.populate(this.profile_template.birthdate, basic.birthdate); - this.populate(this.profile_template.location, basic.location); - this.populate(this.profile_template.gender, basic.gender); - this.populate(this.profile_template.bio, basic.bio); - - if(basic.website_url) { - - var url = basic.website_url; - this.profile_template.url.innerText = url; - this.profile_template.url.parentNode.parentNode.style.display = ""; - - if (!url.startsWith("http")) { - url = "http://" + url; - } - - this.profile_template.url.href = url; - } - } - - if (profile) { - this.server = profile["https://tent.io/types/info/core/v0.1.0"]["servers"][0]; - this.getMeta(this.server); - this.getStatuses(this.server); - } - } - - Profile.prototype.populate = function(t, v) { - if (v) { - t.innerText = v; - t.parentNode.style.display = ""; - t.parentNode.parentNode.style.display = ""; - } - } - - Profile.prototype.getMeta = function(root_url) { - - var _this = this; - Paths.getURL(URI(root_url + "/followings/count").toString(), "GET", function(resp) { - - _this.populate(_this.profile_template.following, resp.responseText); - }, null, false); - - Paths.getURL(URI(root_url + "/followers/count").toString(), "GET", function(resp) { - - _this.populate(_this.profile_template.followed, resp.responseText); - }, null, false); - - if (this.entity != HostApp.stringForKey("entity")) { - Paths.getURL(URI(root_url + "/followers/" + encodeURIComponent(HostApp.stringForKey("entity"))).toString(), "GET", function(resp) { - if (resp.status == 200) { - _this.relationships.following_you = true; - } - _this.setRelationships(); - - }, null, false); - - Paths.getURL(URI(Paths.mkApiRootPath("/followings/" + encodeURIComponent(this.entity))), "GET", function(resp) { - if (resp.status == 200) { - _this.relationships.followed_by_you = true; - } - _this.setRelationships(); - }); - - } else { - this.setRelationships(); - } - - var url = URI(root_url + "/posts/count"); - var post_types = [ - "https://tent.io/types/post/repost/v0.1.0", - "https://tent.io/types/post/status/v0.1.0", - "https://tent.io/types/post/photo/v0.1.0" - ]; - url.addSearch("post_types", post_types.join(",")); - - Paths.getURL(url.toString(), "GET", function(resp) { - - _this.populate(_this.profile_template.posts, resp.responseText); - }, null, false); - } - - Profile.prototype.setRelationships = function() { - var relation = "none"; - if (this.relationships.it_is_you) { - relation = "it's you"; - } else { - if (this.relationships.following_you && !this.relationships.followed_by_you) { - relation = "is following you"; - } else if (this.relationships.following_you && this.relationships.followed_by_you) { - relation = "you both follow each other"; - } else if (!this.relationships.following_you && this.relationships.followed_by_you) { - relation = "being followed by you"; - } - } - this.populate(this.profile_template.relationships, relation); - } - - - Profile.prototype.getStatuses = function(root_url, add_search, append) { - var _this = this; - - add_search = add_search || {}; - - var url = URI(root_url + "/posts"); - url.addSearch("limit", this.posts_limit); - - var post_types = [ - "https://tent.io/types/post/repost/v0.1.0", - "https://tent.io/types/post/status/v0.1.0", - "https://tent.io/types/post/photo/v0.1.0" - ]; - url.addSearch("post_types", post_types.join(",")); - - for(var key in add_search) { - url.addSearch(key, add_search[key]); - } - - Paths.getURL(url.toString(), "GET", function(resp) { - - var statuses = JSON.parse(resp.responseText); - - _this.newStatus(statuses, append); - - }, null, false); - } - - - Profile.prototype.newStatus = function(statuses, append) { - - if(statuses != null && statuses.length > 0) { - - this.before.loading = false; - - if (append) statuses = statuses.reverse(); - - for(var i = statuses.length-1, c=0; i>=c; --i) { - - var status = statuses[i]; - - if (status.type == "https://tent.io/types/post/status/v0.1.0" || status.type == "https://tent.io/types/post/photo/v0.1.0") { - - var new_node = this.getStatusDOMElement(status); - - if(!append && this.body.childNodes.length > 0) { - - if(this.body.childNodes.length > this.max_length) { - - this.body.removeChild(this.body.lastChild); - } - - this.body.insertBefore(new_node, this.body.firstChild); - - } else { - - this.body.appendChild(new_node); - } - - } else if (status.type == "https://tent.io/types/post/delete/v0.1.0") { - - var li = document.getElementById("post-" + status.content.id + "-" + this.action); - if (li) { - this.body.removeChild(li); - } - } else if (status.type == "https://tent.io/types/post/repost/v0.1.0") { - - this.getRepost(status, this.body.firstChild); - } - - } - } - } - - Profile.prototype.getMoreStatusPosts = function() { - if (!this.before.loading) { - this.before.loading = true; - var add_search = { - "before_id": this.body.lastChild.status.id, - "before_id_entity": this.body.lastChild.status.entity - } - this.getStatuses(this.server, add_search, true); - } - } - - Profile.prototype.mention = function() { - - } - - Profile.prototype.setFollowingButton = function(following) { - - this.following = following; - - if (following) { - this.profile_template.following_button.className = "following"; - this.profile_template.following_button.innerText = "Unfollow"; - } else { - this.profile_template.following_button.className = ""; - this.profile_template.following_button.innerText = "Follow"; - } - } - - Profile.prototype.toggleFollow = function() { - - var _this = this; - - if (this.following_id) { - - this.setFollowingButton(false); - var url = Paths.mkApiRootPath("/followings/") + this.following_id; - Paths.getURL(url, "DELETE", function(resp) { - if (resp.status >= 200 && resp.status < 300) { - _this.setFollowingButton(false); - _this.following_id = null; - } else { - _this.setFollowingButton(true); - } - }); - - } else { - - this.setFollowingButton(true); - var url = URI(Paths.mkApiRootPath("/followings")); - var data = JSON.stringify({"entity": this.entity }); - - Paths.getURL(url.toString(), "POST", function(resp) { - if (resp.status >= 200 && resp.status < 300) { - _this.following_id = JSON.parse(resp.responseText).id - _this.setFollowingButton(true); - } else { - _this.setFollowingButton(false); - } - }, data); - } - } - - Profile.prototype.showPosts = function() { - this.showList(this.body); - } - - Profile.prototype.showFollowings = function() { - - this.showList(this.followingsBody); - this.followingsBody.innerHTML = ""; - - var _this = this; - var callback = function(resp) { - var followings = JSON.parse(resp.responseText); - for (var i = 0; i < followings.length; i++) { - var li = _this.getDOMSmallProfile(followings[i]); - _this.followingsBody.appendChild(li); - } - } - - var url = URI(this.server + "/followings"); - url.addSearch("limit", 200); - Paths.getURL(url.toString(), "GET", callback, null, false); - } - - Profile.prototype.showFollowers = function() { - - this.showList(this.followersBody); - this.followersBody.innerHTML = ""; - - var _this = this; - var callback = function(resp) { - var followers = JSON.parse(resp.responseText); - for (var i = 0; i < followers.length; i++) { - var li = _this.getDOMSmallProfile(followers[i]); - _this.followersBody.appendChild(li); - } - } - - var url = URI(this.server + "/followers"); - url.addSearch("limit", 200); - Paths.getURL(url.toString(), "GET", callback, null, false); - } - - Profile.prototype.getDOMSmallProfile = function(profile) { - - var li = document.createElement("li"); - - var image = document.createElement("img"); - image.title = profile.entity; - image.className = "image"; - image.src = 'img/default-avatar.png'; - li.appendChild(image); - image.onclick = function(e) { - HostApp.showProfileForEntity(e.target.title); - return false; - } - - var div = document.createElement("div"); - div.className = "data" - - var h1 = document.createElement("h1"); - var username = document.createElement("a"); - username.title = profile.entity; - username.className = "name"; - username.href = profile.entity; - username.onclick = function(e) { - HostApp.showProfileForEntity(profile.entity); - return false; - } - - h1.appendChild(username) - div.appendChild(h1); - li.appendChild(div); - - var p = document.createElement("p"); - p.className = "message"; - - var entity_tag = document.createElement("a"); - entity_tag.innerText = profile.entity; - entity_tag.href = profile.entity; - entity_tag.title = profile.entity; - - var new_line = document.createElement("br"); - var follows_since = document.createTextNode("follows since "); - var follows_since_time = document.createElement("span"); - follows_since_time.innerText = this.ISODateString(new Date(profile.created_at * 1000)); - follows_since_time.title = follows_since_time.innerText; - follows_since_time.className = "timeago"; - jQuery(follows_since_time).timeago(); - - p.appendChild(entity_tag); - p.appendChild(new_line); - p.appendChild(follows_since); - p.appendChild(follows_since_time); - div.appendChild(p); - - var profile_callback = function(p) { - - var basic = p["https://tent.io/types/info/basic/v0.1.0"]; - - if (p && basic) { - if(basic.name) { - username.title = username.innerText; - username.innerText = basic.name; - } - if(basic.avatar_url) { - image.onerror = function() { image.src = 'img/default-avatar.png'; }; - image.src = basic.avatar_url; - } - } - - } - - var p = this.cache.profiles.getItem(profile.entity); - - if (p && p != "null") { - - profile_callback(p); - - } else { - - var _this = this; - Paths.findProfileURL(profile.entity, function(profile_url) { - - if (profile_url) { - Paths.getURL(profile_url, "GET", function(resp) { - var p = JSON.parse(resp.responseText); - if (p && p != "null") { - _this.cache.profiles.setItem(profile.entity, p); - profile_callback(p); - } - - }, null, false); // do not send auth-headers - } - }); - } - - return li; - } - - - return Profile; + return Profile; }); diff --git a/WebKit/scripts/controller/Search.js b/WebKit/scripts/controller/Search.js index 51ce153..b083362 100644 --- a/WebKit/scripts/controller/Search.js +++ b/WebKit/scripts/controller/Search.js @@ -1,11 +1,11 @@ define([ "helper/HostApp", "helper/Core", - "helper/Paths", + "helper/APICalls", "lib/URI" ], -function(HostApp, Core, Paths, URI) { +function(HostApp, Core, APICalls, URI) { function Search() { @@ -79,7 +79,7 @@ function(HostApp, Core, Paths, URI) { var _this = this; - Paths.getURL(url.toString(), "GET", function(resp) { + APICalls.http_call(url.toString(), "GET", function(resp) { var results = JSON.parse(resp.responseText).results; if (results && results.length > 0) { diff --git a/WebKit/scripts/controller/Sidebar.js b/WebKit/scripts/controller/Sidebar.js index 5b453b8..845946c 100644 --- a/WebKit/scripts/controller/Sidebar.js +++ b/WebKit/scripts/controller/Sidebar.js @@ -1,16 +1,13 @@ define([ "helper/HostApp", - "helper/Paths", - "helper/Cache" + "helper/APICalls", ], -function(HostApp, Paths, Cache) { +function(HostApp, APICalls) { function Sidebar() { - this.cache = new Cache(); - this.body = document.createElement("ul"); this.body.class = "sidebar"; @@ -84,57 +81,39 @@ function(HostApp, Paths, Cache) { var entity = HostApp.stringForKey("entity"); this.menu.user.title = entity; - var img = this.menu.user.getElementsByTagName("img")[0]; - + var avatar = this.menu.user.getElementsByTagName("img")[0]; var _this = this; - var profile_callback = function(p) { + var url = HostApp.serverUrl("posts_feed") + "?types=" + encodeURIComponent("https://tent.io/types/meta/v0") + "&entities=" + encodeURIComponent(entity); + APICalls.get(url, { callback: function(resp) { + var profiles = JSON.parse(resp.responseText); - var basic = p["https://tent.io/types/info/basic/v0.1.0"]; + if(profiles.posts.length < 1) return; + var profile = profiles.posts[0]; + bungloo.cache.profiles[entity] = profile; - if (p && basic) { - if(basic.name) { - _this.menu.user.title = basic.name; - } - if(basic.avatar_url) { + // Find and apply avatar + if(profile.attachments) { - img.onerror = function() { - img.src = "img/sidebar/user.png"; - img.src_inactive = img.src; - img.src_active = img.src; + var digest = null; + for (var i = 0; i < profile.attachments.length; i++) { + var attachment = profile.attachments[i]; + if(attachment.category == "avatar") { + digest = attachment.digest; + break; } + } - img.src = basic.avatar_url; - img.src_inactive = basic.avatar_url; - img.src_active = basic.avatar_url; - + if(digest) { + var _this = this; + avatar.onerror = function() { avatar.src = 'img/default-avatar.png' }; + var avatar_url = profile.content.servers[0].urls.attachment.replace(/\{entity\}/, encodeURIComponent(profile.entity)); + avatar.src = avatar_url.replace(/\{digest\}/, digest); + avatar.src_inactive = avatar.src; + avatar.src_active = avatar.src; } } - - } - - var p = this.cache.profiles.getItem(entity); - - if (p && p != "null") { - - profile_callback(p); - - } else { - - Paths.findProfileURL(entity, function(profile_url) { - - if (profile_url) { - Paths.getURL(profile_url, "GET", function(resp) { - var p = JSON.parse(resp.responseText); - if (p && p != "null") { - _this.cache.profiles.setItem(entity, p); - profile_callback(p); - } - - }, null, false); // do not send auth-headers - } - }); - } + }}); } Sidebar.prototype.removeEntityAvatar = function() { diff --git a/WebKit/scripts/controller/Timeline.js b/WebKit/scripts/controller/Timeline.js index fbd1383..c7d9331 100644 --- a/WebKit/scripts/controller/Timeline.js +++ b/WebKit/scripts/controller/Timeline.js @@ -1,11 +1,11 @@ define([ "helper/Core", - "helper/Paths", + "helper/APICalls", "helper/HostApp", "lib/URI" ], -function(Core, Paths, HostApp, URI) { +function(Core, APICalls, HostApp, URI) { function Timeline() { @@ -21,6 +21,8 @@ function(Core, Paths, HostApp, URI) { this.since_id_entity = null; this.since_time = 0; + this.pages = {}; + this.before = {id: null, entity: null, loading: false}; this.container = document.createElement("div"); @@ -46,8 +48,19 @@ function(Core, Paths, HostApp, URI) { } - Timeline.prototype.newStatus = function(statuses, append) { + Timeline.prototype.newStatus = function(_statuses, append) { + for (var entity in _statuses.profiles) { + if (_statuses.profiles[entity] != null) { + bungloo.cache.profiles[entity] = _statuses.profiles[entity]; + } else { + bungloo.cache.profiles[entity] = {}; + } + } + + this.pages = _statuses.pages; + + statuses = _statuses.posts; if(statuses != null && statuses.length > 0) { this.before.loading = false; @@ -62,7 +75,7 @@ function(Core, Paths, HostApp, URI) { this.since_id_entity = status.entity; } - if (status.type == "https://tent.io/types/post/status/v0.1.0" || status.type == "https://tent.io/types/post/photo/v0.1.0") { + if (status.type == "https://tent.io/types/status/v0#") { var new_node = this.getStatusDOMElement(status); @@ -95,68 +108,69 @@ function(Core, Paths, HostApp, URI) { } } - Timeline.prototype.getNewData = function(add_to_search, append) { + Timeline.prototype.getNewData = function(add_to_search, append, query) { add_to_search = add_to_search || {}; var those = this; - var url = URI(Paths.mkApiRootPath("/posts")); + var url = HostApp.serverUrl("posts_feed"); - var post_types = [ - "https://tent.io/types/post/repost/v0.1.0", - "https://tent.io/types/post/status/v0.1.0", - "https://tent.io/types/post/delete/v0.1.0", - "https://tent.io/types/post/photo/v0.1.0" - ]; - url.addSearch("post_types", post_types.join(",")); - //url.addSearch("sort_by", "published_at"); - url.addSearch("limit", this.posts_limit); + if(!query) { - if(this.since_id && !append) { - url.addSearch("since_id", this.since_id); - url.addSearch("since_id_entity", this.since_id_entity); - } + var uri = URI(url); - for (key in add_to_search) { - url.addSearch(key, add_to_search[key]); - } + var post_types = [ + "https://tent.io/types/status/v0#", + "https://tent.io/types/status/v0#reply", + "https://tent.io/types/repost/v0#", + "https://tent.io/types/delete/v0#", + //"https://tent.io/types/post/photo/v0.1.0" + ]; + uri.addSearch("types", post_types.join(",")); + //uri.addSearch("sort_by", "published_at"); + uri.addSearch("limit", this.posts_limit); + uri.addSearch("max_refs", 20); + uri.addSearch("profiles", "entity"); - var http_method = "GET"; - var callback = function(resp) { - - those.reload_blocked = false; - - try { - - var json = JSON.parse(resp.responseText); - those.newStatus(json, append); - - } catch (e) { - console.error(url + " JSON parse error"); - throw e; + for (key in add_to_search) { + uri.addSearch(key, add_to_search[key]); } - } - var data = null; + url = uri.toString(); + + } else { + url += query; + } if (HostApp.stringForKey("user_access_token")) { if (!this.reload_blocked) { this.reload_blocked = true; - Paths.getURL(url.toString(), http_method, callback, data); // FIXME: error callback + + APICalls.get(url, { callback: function(resp) { + // FIXME this is getting data when it shouldn't debug(resp.responseText) + + those.reload_blocked = false; + + try { + var json = JSON.parse(resp.responseText); + those.newStatus(json, append); + + } catch (e) { + console.error(url + " JSON parse error"); + throw e; + } + } }); } } } Timeline.prototype.getMoreStatusPosts = function() { if (!this.before.loading) { - this.before.loading = true; - var add_search = { - "before_id": this.body.lastChild.status.id, - "before_id_entity": this.body.lastChild.status.entity + if (this.pages.next) { + this.before.loading = true; + this.getNewData({}, true, this.pages.next); } - - this.getNewData(add_search, true); } } diff --git a/WebKit/scripts/helper/APICalls.js b/WebKit/scripts/helper/APICalls.js new file mode 100644 index 0000000..7d97790 --- /dev/null +++ b/WebKit/scripts/helper/APICalls.js @@ -0,0 +1,319 @@ +define([ + "jquery", + "helper/HostApp", + "helper/Hmac", + "helper/Cache" +], + +function(jQuery, HostApp, Hmac, Cache) { + var APICalls = {}; + + APICalls.cache = new Cache(); + + APICalls.getUrlVars = function(url) { + var vars = [], hash; + if(url.indexOf("#") > -1) url = url.slice(0, url.indexOf("#")); + var hashes = url.slice(url.indexOf('?') + 1).split('&'); + for(var i = 0; i < hashes.length; i++) + { + hash = hashes[i].split('='); + vars.push(hash[0]); + vars[hash[0]] = hash[1]; + } + return vars; + } + + APICalls.http_call = function(options) { + + if (typeof options === "string") { + console.error(options + " not implemented yet") + return; + } + + var content_type = null; + + if((options.http_method == "POST" || options.http_method == "PUT") && !options.content_type) { + console.error("No content type for " + options.url); + return; + } else if(options.content_type != "AAA") { + if(options.content_type == "application/json") { + content_type = "application/json"; + } else if(options.content_type) { + content_type = "application/vnd.tent.post.v0+json; charset=UTF-8; type=\"" + options.content_type + "\""; + } + } else { + content_type = 'application/vnd.tent.post.v0+json; charset=UTF-8; type="https://tent.io/types/status/v0#"'; + } + + var settings = { + beforeSend: function(xhr) { + if (options.data) xhr.setRequestHeader("Content-Length", options.data.length); + + if (options.accept) xhr.setRequestHeader("Accept", options.accept); + else xhr.setRequestHeader("Accept", "application/vnd.tent.post.v0+json"); + + var user_access_token = HostApp.stringForKey("user_access_token"); + if (!options.auth_header && !options.no_auth && user_access_token) { + var auth_header = Hmac.makeHawkAuthHeader( + options.url, + options.http_method, + user_access_token, + HostApp.secret()//, + //HostApp.stringForKey("app_id") + ); + xhr.setRequestHeader("Authorization", auth_header); + } else if(options.auth_header) { + xhr.setRequestHeader("Authorization", options.auth_header); + } else if(!options.no_auth) { + console.error("No user_access_token yet - " + options.url); + } + xhr.setRequestHeader("Cache-Control", "no-proxy"); + }, + url: options.url, + contentType: content_type, + type: options.http_method, + complete: options.callback, + data: options.data, + processData: false, + error: function(xhr, ajaxOptions, thrownError) { + console.error("HTTP CALL (" + xhr.status + ") " + xhr.statusText + " " + options.http_method + " URL(" + options.url + "): '" + xhr.responseText + "'"); + } + }; + + jQuery.ajax(settings); + } + + APICalls.head = function(url, options) { + var settings = { + url: url, + http_method: "HEAD", + }; + + for (var key in options) { + settings[key] = options[key]; + } + + APICalls.http_call(settings); + } + + APICalls.get = function(url, options) { + var settings = { + url: url, + http_method: "GET", + }; + + for (var key in options) { + settings[key] = options[key]; + } + + APICalls.http_call(settings); + } + + APICalls.post = function(url, data, options) { + var settings = { + url: url, + http_method: "POST", + data: data + }; + + for (var key in options) { + settings[key] = options[key]; + } + + APICalls.http_call(settings); + } + + APICalls.delete = function(url, options) { + var settings = { + url: url, + http_method: "DELETE" + }; + + for (var key in options) { + settings[key] = options[key]; + } + + APICalls.http_call(settings); + } + + APICalls.put = function(url, data, options) { + var settings = { + url: url, + http_method: "PUT", + data: data + }; + + for (var key in options) { + settings[key] = options[key]; + } + + APICalls.http_call(settings); + } + + + APICalls.postMultipart = function(url, callback, data, boundary, accepts) { + + accepts = accepts || "application/vnd.tent.v0+json"; + + jQuery.ajax({ + + beforeSend: function(xhr) { + xhr.setRequestHeader("Accept", accepts); + + if (data) xhr.setRequestHeader("Content-Length", data.length); + + var user_access_token = HostApp.stringForKey("user_access_token"); + + if (user_access_token) { + + auth_header = Hmac.makeAuthHeader( + url, + "POST", + HostApp.secret(), + user_access_token + ); + + xhr.setRequestHeader("Authorization", auth_header); + } + }, + url: url, + contentType: "multipart/form-data;boundary=" + boundary, + type: "POST", + complete: callback, + data: data, + processData: false, + error: function(xhr, ajaxOptions, thrownError) { + console.error("postMultipart (" + xhr.status + ")" + xhr.statusText + " (" + url + "): '" + xhr.responseText + "'"); + } + }); + } + + APICalls.findProfileURL = function(entity, callback, errorCallback) { + var profile_url = APICalls.cache.profile_urls.getItem(entity); + + if (profile_url && profile_url != "null") { + + callback(profile_url); + + } else { + + jQuery.ajax({ + url: entity, + type: "HEAD", + complete: function(resp) { + if(resp) { + var headers = resp.getAllResponseHeaders(); + + var profile_urls = APICalls.parseHeaderForProfiles(headers); + var profile_url = null; + if(profile_urls.length > 0) { + var profile_url = profile_urls[0]; + if (!profile_url.startsWith("http")) { + profile_url = entity + profile_url; + } + } + + if (profile_url) { + APICalls.cache.profile_urls.setItem(entity, profile_url); + callback(profile_url); + } else { + APICalls.http_call(entity, "GET", function(resp) { + + if (resp.status >= 200 && resp.status < 300) { + var doc = document.implementation.createHTMLDocument(""); + doc.documentElement.innerHTML = resp.responseText; + var links = $(doc).find("link[rel='https://tent.io/rels/meta-post']"); + + if (links.length > 0) { + var href = links.get(0).href; + APICalls.cache.profile_urls.setItem(entity, href); + if (!href.startsWith("http")) { + href = entity + href; + } + callback(href); + + } else { + if(errorCallback) errorCallback(entity + " has no profile URL"); + } + } else { + if(errorCallback) errorCallback(entity + " has no profile URL"); + } + + }, null, false, false); + + //if(errorCallback) errorCallback(entity + " has no profile URL"); + } + } + }, + error: function(xhr, ajaxOptions, thrownError) { + console.error("findProfileURL " + xhr.statusText + " (" + entity + "): " + xhr.responseText); + if (errorCallback) errorCallback(xhr.statusText + " - " + xhr.responseText) + } + }); + } + } + + APICalls.mkApiRootPath = function(path) { + + var api_root = HostApp.stringForKey("api_root"); + + if((api_root.substring(api_root.length - 1, api_root.length) != "/") && (path.substring(0, 1) != "/")) { + api_root += "/"; + } else if((api_root.substring(api_root.length - 1, api_root.length) == "/") && (path.substring(0, 1) == "/")) { + api_root = api_root.substring(0, api_root.length -1); + } + return api_root + path; + } + + APICalls.parseHeaderForProfiles = function(header_string) { + var regexp = /https:\/\/tent.io\/rels\/meta-post/i; + return APICalls.parseHeaderForLink(header_string, regexp); + } + + APICalls.parseHeader = function(header_string) { + var header_strings = header_string.split(/\n/); + var headers = {}; + for (var i = 0; i < header_strings.length; i++) { + var hs = header_strings[i].split(/:(.+)?/); + headers[hs[0]] = hs[1]; + } + return headers; + } + + APICalls.getCount = function(resp) { + var count = 0; + var headers = APICalls.parseHeader(resp.getAllResponseHeaders()); + if(headers["Count"]) count = parseInt(headers["Count"], 10); + return count; + } + + APICalls.parseHeaderForLink = function(header_string, match) { + var headers = header_string.split(/\n/); + var links = []; + for (var i = 0; i < headers.length; i++) { + var header = headers[i]; + if (header.match(/^Link:(.*)/i)) { + links.push(header.replace(/\r/, "").substr(5).trim()); + } + } + + var items = []; + for (var i = 0; i < links.length; i++) { + items = items.concat(links[i].split(",")); + } + var things = []; + for (var i = 0; i < items.length; i++) { + var item = items[i]; + if (item.match(match)) { + var n = item.match(/<([^>]*)>/); + if (n) { + things.push(n[1]); + } + } + } + + return things; + } + + return APICalls; +}); \ No newline at end of file diff --git a/WebKit/scripts/helper/Cache.js b/WebKit/scripts/helper/Cache.js index eb79fe7..2b35364 100644 --- a/WebKit/scripts/helper/Cache.js +++ b/WebKit/scripts/helper/Cache.js @@ -40,12 +40,12 @@ function(URI, CacheStorage, require) { } } - var url = URI(require("helper/Paths").mkApiRootPath("/followings")); + var url = URI(require("helper/APICalls").mkApiRootPath("/followings")); if (this.followings_before_id) { url.addSearch("before_id", this.followings_before_id); } - require("helper/Paths").getURL(url, "GET", callback); + require("helper/APICalls").getURL(url, "GET", callback); } Cache.prototype.periodicallyGetFollowings = function() { diff --git a/WebKit/scripts/helper/Core.js b/WebKit/scripts/helper/Core.js index 7f3b350..4b8983e 100644 --- a/WebKit/scripts/helper/Core.js +++ b/WebKit/scripts/helper/Core.js @@ -1,17 +1,15 @@ define([ "jquery", - "helper/Paths", + "helper/APICalls", "lib/URI", "helper/HostApp", - "helper/Cache", "lib/Timeago", "lib/SingleDoubleClick" ], -function(jQuery, Paths, URI, HostApp, Cache) { +function(jQuery, APICalls, URI, HostApp) { function Core() { - this.cache = new Cache(); this.saveScrollTop = 0; } @@ -69,6 +67,7 @@ function(jQuery, Paths, URI, HostApp, Cache) { image.className = "image"; image.src = "img/default-avatar.png"; image.onmousedown = function(e) { e.preventDefault(); }; + image.onerror = function() { this.src = 'img/default-avatar.png' }; item.appendChild(image); var image_username = a.cloneNode(); @@ -162,7 +161,28 @@ function(jQuery, Paths, URI, HostApp, Cache) { } Core.prototype.getStatusDOMElement = function(status) { - + /* +{ + "app": { + "id": "P8FJjaiRv0AKXfjUMd_4YQ", + "name": "Bungloo on Linux", + "url": "http:\/\/jabs.nu\/bungloo\/" + }, + "content": { + "text": "jeena test" + }, + "entity": "http:\/\/155969d81672.alpha.attic.is", + "id": "HlSXe8MREzU4h2fGLGSnCA", + "published_at": 1369566009, + "received_at": 1369566008799, + "type": "https:\/\/tent.io\/types\/status\/v0#", + "version": { + "id": "a2f702b4615c7d7dd0f98c73d7b55749880bf6e437a77349454ff10745d134c6", + "published_at": 1369566009, + "received_at": 1369566008799 + } +} + */ var _this = this; var template = this.getTemplate(); @@ -191,7 +211,8 @@ function(jQuery, Paths, URI, HostApp, Cache) { template.reply_to.onclick = function() { var mentions = []; - var status_mentions = status.mentions.slice(0); + var status_mentions = []; + if(status.mentions) status_mentions = status.mentions.slice(0); if (typeof status.__repost != "undefined") { status_mentions.push({entity:status.__repost.entity}); @@ -202,7 +223,7 @@ function(jQuery, Paths, URI, HostApp, Cache) { mentions.push(mention); } - _this.replyTo(status.entity, status.id, mentions, (status && status.permissions && !status.permissions.public)); + _this.replyTo(status); return false; } @@ -212,7 +233,8 @@ function(jQuery, Paths, URI, HostApp, Cache) { return false; } - template.username.innerText = status.entity; + if(bungloo.cache.profiles[status.entity].name) template.username.innerText = bungloo.cache.profiles[status.entity].name; + else template.username.innerText = status.entity; template.username.href = status.entity; template.username.title = status.entity; template.username.onclick = function() { @@ -220,47 +242,12 @@ function(jQuery, Paths, URI, HostApp, Cache) { return false; } + if(bungloo.cache.profiles[status.entity].avatar_digest) { + template.image.src = HostApp.serverUrl("attachment").replace(/\{entity\}/, encodeURIComponent(status.entity)).replace(/\{digest\}/, bungloo.cache.profiles[status.entity].avatar_digest); + } + template.image.onclick = template.username.onclick; - var profile_callback = function(p) { - - var basic = p["https://tent.io/types/info/basic/v0.1.0"]; - - if (p && basic) { - if(basic.name) { - template.username.title = template.username.innerText; - template.username.innerText = basic.name; - } - if(basic.avatar_url) { - template.image.onerror = function() { template.image.src = 'img/default-avatar.png' }; - template.image.src = basic.avatar_url; - } - } - - } - - var p = this.cache.profiles.getItem(status.entity); - - if (p && p != "null") { - - profile_callback(p); - - } else { - - Paths.findProfileURL(status.entity, function(profile_url) { - - if (profile_url) { - Paths.getURL(profile_url, "GET", function(resp) { - var p = JSON.parse(resp.responseText); - if (p && p != "null") { - _this.cache.profiles.setItem(status.entity, p); - profile_callback(p); - } - - }, null, false); // do not send auth-headers - } - }); - } if (status && status.permissions && !status.permissions.public) { template.is_private.style.display = ''; @@ -290,6 +277,7 @@ function(jQuery, Paths, URI, HostApp, Cache) { template.message.innerHTML = this.replaceURLWithHTMLLinks(text, entities, template.message); this.afterChangingTextinMessageHTML(template.message) + /* if (status.type == "https://tent.io/types/post/photo/v0.1.0") { for (var i = 0; i < status.attachments.length; i++) { @@ -308,18 +296,19 @@ function(jQuery, Paths, URI, HostApp, Cache) { } if (status.entity == HostApp.stringForKey("entity")) { - var url = Paths.mkApiRootPath("/posts/" + status.id + "/attachments/" + attachment.name); - Paths.getURL(url, "GET", callback, null, null, attachment.type); + var url = APICalls.mkApiRootPath("/posts/" + status.id + "/attachments/" + attachment.name); + APICalls.http_call(url, "GET", callback, null, null, attachment.type); } else { - var url = Paths.mkApiRootPath("/posts/" + encodeURIComponent(status.entity) + "/" + status.id + "/attachments/" + attachment.name); - Paths.getURL(url, "GET", callback, null, null, attachment.type); + var url = APICalls.mkApiRootPath("/posts/" + encodeURIComponent(status.entity) + "/" + status.id + "/attachments/" + attachment.name); + APICalls.http_call(url, "GET", callback, null, null, attachment.type); } })(); } } - + */ this.findMentions(template.message, status.mentions); +/* for (var i = 0; i < status.mentions.length; i++) { var mention = status.mentions[i]; if (mention.entity == HostApp.stringForKey("entity")) { @@ -327,10 +316,10 @@ function(jQuery, Paths, URI, HostApp, Cache) { break; } } - - var published_at = typeof status.__repost == "undefined" ? status.published_at : status.__repost.published_at; +*/ + var published_at = typeof status.__repost == "undefined" ? status.version.published_at : status.__repost.published_at; var time = document.createElement("abbr"); - time.innerText = this.ISODateString(new Date(published_at * 1000)); + time.innerText = this.ISODateString(new Date(published_at)); time.title = time.innerText; time.className = "timeago"; jQuery(time).timeago(); @@ -366,9 +355,11 @@ function(jQuery, Paths, URI, HostApp, Cache) { template.source.innerHTML = status.__repost.app.name; template.source.title = status.__repost.app.url; } else { - template.source.href = status.app.url; - template.source.innerHTML = status.app.name; - template.source.title = status.app.url; + if(status.app) { + template.source.href = status.app.url; + template.source.innerHTML = status.app.name; + template.source.title = status.app.url; + } } return template.item; @@ -431,9 +422,9 @@ function(jQuery, Paths, URI, HostApp, Cache) { }); var _this = this; - Paths.findProfileURL(repost.entity, function(profile_url) { + APICalls.findProfileURL(repost.entity, function(profile_url) { if (profile_url) { - Paths.getURL(profile_url, "GET", function(resp) { + APICalls.http_call(profile_url, "GET", function(resp) { if (resp.status >= 200 && resp.status < 400) { var _p = JSON.parse(resp.responseText); _this.cache.profiles.setItem(repost.entity, _p); @@ -460,14 +451,14 @@ function(jQuery, Paths, URI, HostApp, Cache) { } } - Paths.findProfileURL(repost.content.entity, function(profile_url) { + APICalls.findProfileURL(repost.content.entity, function(profile_url) { if (profile_url) { - Paths.getURL(profile_url, "GET", function(resp) { + APICalls.http_call(profile_url, "GET", function(resp) { var profile = JSON.parse(resp.responseText); var server = profile["https://tent.io/types/info/core/v0.1.0"].servers[0]; - Paths.getURL(URI(server + "/posts/" + repost.content.id).toString(), "GET", callback, null, false); + APICalls.http_call(URI(server + "/posts/" + repost.content.id).toString(), "GET", callback, null, false); }, null, false); // do not send auth-headers } @@ -475,55 +466,8 @@ function(jQuery, Paths, URI, HostApp, Cache) { } } - Core.prototype.sendNewMessage = function(content, in_reply_to_status_id, in_reply_to_entity, location, image_data_uri, is_private, callback) { - - if (image_data_uri) { - - this.sendNewMessageWithImage(content, in_reply_to_status_id, in_reply_to_entity, location, image_data_uri, is_private, callback); - - } else { - - var url = URI(Paths.mkApiRootPath("/posts")); - - var http_method = "POST"; - - var data = { - "type": "https://tent.io/types/post/status/v0.1.0", - "published_at": parseInt(new Date().getTime() / 1000, 10), - "permissions": { - "public": !is_private - }, - "content": { - "text": content, - }, - }; - - if (location) { - data["content"]["location"] = { "type": "Point", "coordinates": location } - } - - var mentions = this.parseMentions(content, in_reply_to_status_id, in_reply_to_entity); - - if (mentions.length > 0) { - data["mentions"] = mentions; - if (is_private) { - var entities = {}; - for (var i = 0; i < mentions.length; i++) { - var entity = mentions[i]["entity"] - entities[entity] = true; - }; - - data["permissions"]["entities"] = entities; - } - } - - Paths.getURL(url.toString(), http_method, callback, JSON.stringify(data)); - } - } - - Core.prototype.repost = function(id, entity, callback) { - var url = URI(Paths.mkApiRootPath("/posts")); + var url = URI(APICalls.mkApiRootPath("/posts")); var data = { "type": "https://tent.io/types/post/repost/v0.1.0", @@ -549,88 +493,14 @@ function(jQuery, Paths, URI, HostApp, Cache) { _this.highlight(id); } - Paths.getURL(url.toString(), "POST", new_callback, JSON.stringify(data)); - } - - Core.prototype.sendNewMessageWithImage = function(content, in_reply_to_status_id, in_reply_to_entity, location, image_data_uri, is_private, callback) { - - var url = URI(Paths.mkApiRootPath("/posts")); - - var data = { - "type": "https://tent.io/types/post/photo/v0.1.0", - "published_at": parseInt(new Date().getTime() / 1000, 10), - "permissions": { - "public": !is_private - }, - "content": { - "caption": content, - }, - }; - - if (location) { - data["content"]["location"] = { "type": "Point", "coordinates": location } - } - - var mentions = this.parseMentions(content, in_reply_to_status_id, in_reply_to_entity); - if (mentions.length > 0) { - data["mentions"] = mentions; - if (is_private) { - var entities = {}; - for (var i = 0; i < mentions.length; i++) { - var entity = mentions[i]["entity"] - entities[entity] = true; - }; - - data["permissions"]["entities"] = entities; - } - } - - var data_string = JSON.stringify(data); - - var boundary = "TentAttachment----------TentAttachment"; - var post = "--" + boundary + "\r\n"; - - post += 'Content-Disposition: form-data; name="post"; filename="post.json"\r\n'; - post += 'Content-Length: ' + data_string.length + '\r\n'; - post += 'Content-Type: application/vnd.tent.v0+json\r\n'; - post += 'Content-Transfer-Encoding: binary\r\n\r\n'; - post += data_string; - - post += "\r\n--" + boundary + "\r\n"; - - var blob_string = image_data_uri.split(',')[1]; - var mime_type = image_data_uri.split(',')[0].split(':')[1].split(';')[0]; - var ext = "png"; - if (mime_type == "image/jpeg") { - ext = "jpeg"; - } else if (mime_type == "image/gif") { - ext = "gif"; - } - - - post += 'Content-Disposition: form-data; name="photos[0]"; filename="photo.' + ext + '"\r\n'; - post += 'Content-Length: ' + blob_string.length + "\r\n"; - post += 'Content-Type: ' + mime_type + "\r\n"; - post += 'Content-Transfer-Encoding: base64\r\n\r\n'; - post += blob_string; - post += "\r\n--" + boundary + "--\r\n"; - - var newCallback = function(resp) { - if (resp.status == 403) { - var err = JSON.parse(resp.responseText); - HostApp.alertTitleWithMessage(resp.statusText, err.error); - } - callback(resp); - } - - Paths.postMultipart(url.toString(), newCallback, post, boundary); + APICalls.http_call(url.toString(), "POST", new_callback, JSON.stringify(data)); } Core.prototype.remove = function(id, callback, type) { type = type || "post"; if (confirm("Really delete this " + type + "?")) { - var url = URI(Paths.mkApiRootPath("/posts/" + id)); - Paths.getURL(url.toString(), "DELETE", callback); + var url = URI(APICalls.mkApiRootPath("/posts/" + id)); + APICalls.http_call(url.toString(), "DELETE", callback); } } @@ -738,9 +608,9 @@ function(jQuery, Paths, URI, HostApp, Cache) { } else { - Paths.findProfileURL(mention.entity, function(profile_url) { + APICalls.findProfileURL(mention.entity, function(profile_url) { if (profile_url) { - Paths.getURL(profile_url, "GET", function(resp) { + APICalls.http_call(profile_url, "GET", function(resp) { if (resp.status >= 200 && resp.status < 400) { var p = JSON.parse(resp.responseText); _this.cache.profiles.setItem(mention.entity, p); @@ -796,27 +666,14 @@ function(jQuery, Paths, URI, HostApp, Cache) { } Core.prototype.replaceURLWithHTMLLinks = function(text, entities, message_node) { - - var callback = function(url) { - - var result; - - if (entities && entities.some(function(x) { return x == url })) { - result = url; - } else { - - result = url; - if (url.startsWith("http://") || url.startsWith("https://")) { - result = '' + url + ''; - } - } - - return result; - } - - var hash = /(^|\s)(#)(\w+)/ig; - - return URI.withinString(text, callback).replace(hash, "$1$2$3"); + // FIXME: this has to be done better so one can nest that stuff and escape with \ + return text.replace(/_([^_]+)_/g, "$1") + .replace(/\*([^\*]+)\*/g, "$1") + .replace(/`([^`]+)`/g, "$1") + .replace(/~([^~]+)~/g, "$1") + .replace(/\#([^\s]+)/g, "#$1") + .replace(/(^|[^\^])\[([^\]]+)\]\(([^\)]+)\)/g, "$2") + .replace(/\^\[([^\]]+)\]\((\d+)\)/g, "$1"); } Core.prototype.parseForMedia = function(text, images) { @@ -838,7 +695,7 @@ function(jQuery, Paths, URI, HostApp, Cache) { } else if(word.startsWith("http://youtube.com/") || word.startsWith("http://www.youtube.com/") || word.startsWith("https://youtube.com/") || word.startsWith("https://www.youtube.com/")) { - var v = Paths.getUrlVars(word)["v"]; + var v = APICalls.getUrlVars(word)["v"]; this.addYouTube(v, images); } else if (word.startsWith("http://youtu.be/") || word.startsWith("https://youtu.be/")) { @@ -890,19 +747,8 @@ function(jQuery, Paths, URI, HostApp, Cache) { } } - Core.prototype.replyTo = function(entity, status_id, mentions, is_private) { - - var string = "^" + entity.replace("https://", "") + " "; - - var ms = ""; - for (var i = 0; i < mentions.length; i++) { - var e = mentions[i].entity.replace("https://", ""); - if(string.indexOf(e) == -1) ms += " ^" + e; - } - - if(ms.length > 0) string += "\n\n/cc" + ms; - - HostApp.openNewMessageWidow(entity, status_id, string, is_private); + Core.prototype.replyTo = function(status) { + HostApp.openNewMessageWidow(status); } Core.prototype.postDeleted = function(post_id, entity) { @@ -1001,21 +847,22 @@ function(jQuery, Paths, URI, HostApp, Cache) { Core.prototype.afterChangingTextinMessageHTML = function(message_node) { // adding show search on click hash + /* $(message_node).find("a.hash").click(function(e) { if(bungloo.search) bungloo.search.searchFor(e.target.innerHTML); return false; }); - + */ // adding show profile on click + /* $(message_node).find("a.name").click(function(e) { HostApp.showProfileForEntity(e.target.title); return false; - }); + });*/ } - return Core; }); \ No newline at end of file diff --git a/WebKit/scripts/helper/Hmac.js b/WebKit/scripts/helper/Hmac.js index b22a2f3..be4db38 100644 --- a/WebKit/scripts/helper/Hmac.js +++ b/WebKit/scripts/helper/Hmac.js @@ -7,7 +7,7 @@ function(URI, CryptoJS) { var Hmac = {}; - Hmac.makeAuthHeader = function(url, http_method, mac_key, mac_key_id) { + Hmac.makeHawkAuthHeader = function(url, http_method, hawk_id, key, app_id) { url = URI(url); var nonce = Hmac.makeid(8); @@ -18,26 +18,47 @@ function(URI, CryptoJS) { port = url.protocol() == "https" ? "443" : "80"; } - var normalizedRequestString = "" - + time_stamp + '\n' - + nonce + '\n' - + http_method + '\n' - + url.path() + url.search() + url.hash() + '\n' - + url.hostname() + '\n' - + port + '\n' - + '\n' ; + var normalizedRequestString = "hawk.1.header\n" // header + + time_stamp + '\n' // ts + + nonce + '\n' // nonce + + http_method.toUpperCase() + '\n' // method + + url.path() + url.search() + url.hash() + '\n' // request uri + + url.hostname().toLowerCase() + '\n' // host + + port + '\n' // port + + '\n' // Hmac.calculatePayloadHash(payload) + '\n' // hash // FIXME implement payload validation + + '\n' // ext (we don't use it) - var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, mac_key); + var app = ""; + + if(app_id) { + app = ', app="' + app_id + "'"; + normalizedRequestString += app_id + "\n" + // app + '\n'; // dlg should be empty + } + + var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key); hmac.update(normalizedRequestString); var hash = hmac.finalize(); var mac = hash.toString(CryptoJS.enc.Base64); - return 'MAC id="' + mac_key_id + + return 'Hawk id="' + hawk_id + + '", mac="' + mac + '", ts="' + time_stamp + - '", nonce="' + nonce + - '", mac="' + mac + '"'; + '", nonce="' + nonce + '"' + + app } + Hmac.calculatePayloadHash = function (payload) { + if (!payload) return ""; + + var hash = CryptoJS.algo.SHA256.create(); + hash.update('hawk.1.payload\n'); + hash.update('application/vnd.tent.post.v0+json\n'); + hash.update(payload || ''); + hash.update('\n'); + return hash.finalize().toString(CryptoJS.enc.Base64); + }, + Hmac.makeid = function(len) { var text = ""; var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; diff --git a/WebKit/scripts/helper/HostApp.js b/WebKit/scripts/helper/HostApp.js index 60cf9e5..9811fda 100644 --- a/WebKit/scripts/helper/HostApp.js +++ b/WebKit/scripts/helper/HostApp.js @@ -37,6 +37,14 @@ define(function() { } } + HostApp.setServerUrls = function(server_urls) { + HostApp.setStringForKey(JSON.stringify(server_urls), "server_urls"); + } + + HostApp.serverUrl = function(key) { + return JSON.parse(HostApp.stringForKey("server_urls"))[key]; + } + HostApp.openURL = function(url) { if (OS_TYPE == "mac") { @@ -77,13 +85,12 @@ define(function() { } } - HostApp.openNewMessageWidow = function(entity, status_id, string, is_private) { + HostApp.openNewMessageWidow = function(status) { if (OS_TYPE == "mac") { - controller.openNewMessageWindowInReplyTo_statusId_withString_isPrivate_(entity, status_id, string, is_private); + controller.openNewMessageWindowInReplyToStatus_(JSON.stringify(status)); } else { - is_private = is_private == true - controller.openNewMessageWindowInReplyTostatusIdwithStringIsPrivate(entity, status_id, string, is_private); + controller.openNewMessageWindowInReplyToStatus(JSON.stringify(status).escapeSpecialChars()); } } diff --git a/WebKit/scripts/helper/Paths.js b/WebKit/scripts/helper/Paths.js index 5f477a7..af1fb75 100644 --- a/WebKit/scripts/helper/Paths.js +++ b/WebKit/scripts/helper/Paths.js @@ -25,7 +25,7 @@ function(jQuery, HostApp, Hmac, Cache) { Paths.getURL = function(url, http_method, callback, data, auth_header, accepts) { - if(accepts !== false) accepts = accepts || "application/vnd.tent.v0+json; charset=utf-8"; + if(accepts !== false) accepts = accepts || "application/vnd.tent.post.v0+json"; var options = { @@ -56,7 +56,7 @@ function(jQuery, HostApp, Hmac, Cache) { } }, url: url, - contentType: "application/vnd.tent.v0+json", + contentType: 'application/vnd.tent.post.v0+json; type="https://tent.io/types/app/v0#"', type: http_method, complete: callback, data: data, @@ -65,7 +65,7 @@ function(jQuery, HostApp, Hmac, Cache) { console.error("getURL (" + xhr.status + ")" + xhr.statusText + " " + http_method + " (" + url + "): '" + xhr.responseText + "'"); } } - + jQuery.ajax(options); } @@ -127,7 +127,7 @@ function(jQuery, HostApp, Hmac, Cache) { if(profile_urls.length > 0) { var profile_url = profile_urls[0]; if (!profile_url.startsWith("http")) { - profile_url = entity + "/profile"; + profile_url = entity + profile_url; } } @@ -140,13 +140,13 @@ function(jQuery, HostApp, Hmac, Cache) { if (resp.status >= 200 && resp.status < 300) { var doc = document.implementation.createHTMLDocument(""); doc.documentElement.innerHTML = resp.responseText; - var links = $(doc).find("link[rel='https://tent.io/rels/profile']"); + var links = $(doc).find("link[rel='https://tent.io/rels/meta-post']"); if (links.length > 0) { var href = links.get(0).href; Paths.cache.profile_urls.setItem(entity, href); if (!href.startsWith("http")) { - href = entity + "/profile"; + href = entity + href; } callback(href); @@ -184,6 +184,11 @@ function(jQuery, HostApp, Hmac, Cache) { } Paths.parseHeaderForProfiles = function(header_string) { + var regexp = /https:\/\/tent.io\/rels\/meta-post/i; + return Paths.parseHeaderForLink(header_string, regexp); + } + + Paths.parseHeaderForLink = function(header_string, match) { var headers = header_string.split(/\n/); var links = []; for (var i = 0; i < headers.length; i++) { @@ -197,18 +202,18 @@ function(jQuery, HostApp, Hmac, Cache) { for (var i = 0; i < links.length; i++) { items = items.concat(links[i].split(",")); } - var profiles = []; + var things = []; for (var i = 0; i < items.length; i++) { var item = items[i]; - if (item.match(/https:\/\/tent.io\/rels\/profile/i)) { + if (item.match(match)) { var n = item.match(/<([^>]*)>/); if (n) { - profiles.push(n[1]); + things.push(n[1]); } } } - return profiles; + return things; } return Paths; diff --git a/WebKit/scripts/main.js b/WebKit/scripts/main.js index 7e30518..3b48c90 100644 --- a/WebKit/scripts/main.js +++ b/WebKit/scripts/main.js @@ -7,7 +7,8 @@ var bungloo = { entityProfile: null, conversation: null, search: null, - cache: {} + cache: { profiles: {}}, + newpost: null }; requirejs.config({ @@ -33,6 +34,15 @@ function start(view, callback) { }); + } else if (view == "newpost") { + + require(["controller/NewPost"], function(NewPost) { + + bungloo.newpost = new NewPost(); + if(callback) callback(); + + }); + } else { @@ -54,33 +64,12 @@ function start(view, callback) { bungloo.search = new Search(); bungloo.sidebar.showContentForTimeline(); - }); } } -String.prototype.startsWith = function(prefix) { - return this.indexOf(prefix) === 0; -} - -String.prototype.endsWith = function(suffix) { - return this.match(suffix+"$") == suffix; -}; - -var __entityMap = { - "&": "&", - "<": "<", - ">": ">" -}; - -String.prototype.escapeHTML = function() { - return String(this).replace(/[&<>]/g, function (s) { - return __entityMap[s]; - }); -} - var console = { log: function(s) { if (OS_TYPE == "mac") { @@ -172,4 +161,42 @@ function go() { // wait untill everything is loaded }, 500); } -go(); \ No newline at end of file +go(); + + +// String stuff +String.prototype.startsWith = function(prefix) { + return this.indexOf(prefix) === 0; +} + +String.prototype.endsWith = function(suffix) { + return this.match(suffix+"$") == suffix; +}; + +var __entityMap = { + "&": "&", + "<": "<", + ">": ">" +}; + +String.prototype.escapeHTML = function() { + return String(this).replace(/[&<>]/g, function (s) { + return __entityMap[s]; + }); +} + +String.prototype.hasArabicCharacter = function() { + var arregex = /[\u0600-\u06FF]/; + return arregex.test(this); +} + +String.prototype.escapeSpecialChars = function() { + return this.replace(/[\\]/g, '\\\\') + .replace(/[\"]/g, '\\\"') + .replace(/[\/]/g, '\\/') + .replace(/[\b]/g, '\\b') + .replace(/[\f]/g, '\\f') + .replace(/[\n]/g, '\\n') + .replace(/[\r]/g, '\\r') + .replace(/[\t]/g, '\\t'); +} \ No newline at end of file diff --git a/images/Actions-insert-image-icon.png b/images/Actions-insert-image-icon.png deleted file mode 100644 index 05adf9a..0000000 Binary files a/images/Actions-insert-image-icon.png and /dev/null differ diff --git a/images/Lock-Lock-icon.png b/images/Lock-Lock-icon.png deleted file mode 100644 index 45079c9..0000000 Binary files a/images/Lock-Lock-icon.png and /dev/null differ diff --git a/images/Lock-Unlock-icon.png b/images/Lock-Unlock-icon.png deleted file mode 100644 index 627d59f..0000000 Binary files a/images/Lock-Unlock-icon.png and /dev/null differ