diff --git a/Qt/Bungloo.py b/Qt/Bungloo.py index a8ca0be..6ccfe15 100755 --- a/Qt/Bungloo.py +++ b/Qt/Bungloo.py @@ -170,13 +170,13 @@ class Controller(QtCore.QObject): except OSError: pass - @QtCore.pyqtSlot(str) + @QtCore.pyqtSlot() def openNewMessageWidow(self): - self.openNewMessageWindowInReplyTostatus(None) + self.openNewMessageWindowInReplyToStatus("") - @QtCore.pyqtSlot(str, str, str, bool) - def openNewMessageWindowInReplyTostatus(self, status_string): - new_message_window = Windows.NewPost(self.app) + @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) @@ -262,10 +262,15 @@ class Controller(QtCore.QObject): msgBox.exec_() @QtCore.pyqtSlot(result=str) - def getCachedEntities(self): - entities = self.app.timeline.evaluateJavaScript("JSON.stringify(bungloo.cache.entities);") + 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" diff --git a/Qt/Windows.py b/Qt/Windows.py index fb7d332..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) @@ -329,12 +329,9 @@ class FindEntity(QtGui.QDialog): class NewPost(Helper.RestorableWindow): - def __init__(self, app, string=None, mentions="[]", is_private=False, post_id=None): + def __init__(self, app, status_string): self.app = app - self.string = string - self.mentions = mentions - self.is_private = is_private - self.post_id = post_id + self.status_string = status_string Helper.RestorableWindow.__init__(self, "newpost", self.app) self.activateWindow() @@ -349,6 +346,8 @@ class NewPost(Helper.RestorableWindow): 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) @@ -409,18 +408,8 @@ class NewPost(Helper.RestorableWindow): helpMenu.addAction(aboutAction) helpMenu.addAction(developerExtrasAction) - def load_finished(self, widget): - is_private = "false" - if self.is_private: - is_private = "true" - - post_id = "" - if self.post_id: - post_id = self.post_id - - callback = "function() { bungloo.newpost.setString('%s'); bungloo.newpost.setIsPrivate(%s); bungloo.newpost.setMentions(%s); bungloo.newPostAction.setPostId(%s); }" % (self.string, is_private, self.mentions, post_id) - + callback = "function() { bungloo.newpost.setStatus('%s'); }" % (self.status_string) script = "function HostAppGo() { start('newpost', " + callback + "); }" self.webView.page().mainFrame().evaluateJavaScript(script) self.webView.setFocus() @@ -432,32 +421,17 @@ class NewPost(Helper.RestorableWindow): def sendMessage(self): script = "bungloo.newpost.send()" self.webView.page().mainFrame().evaluateJavaScript(script) - self.close() - - """ - 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() - """ - - 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 def developer_extras(self, widget): QtWebKit.QWebSettings.globalSettings().setAttribute(QtWebKit.QWebSettings.DeveloperExtrasEnabled, True) + def openFileDialog(self): + 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 63bd2e8..eec1a75 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 { @@ -460,4 +469,5 @@ p.noresult { #suggestions { position: absolute; left: 0; bottom: 0; } #suggestions .active { color: red; } #status_bar { height: 1em; } -#status_bar p { float: right; } \ No newline at end of file +#status_bar p { float: right; margin: 0; padding: 0; } +#status_bar span { display: inline-block; margin: 4px 5px 0 5px; } \ No newline at end of file diff --git a/WebKit/scripts/controller/NewPost.js b/WebKit/scripts/controller/NewPost.js index e37fcce..63ec2bd 100644 --- a/WebKit/scripts/controller/NewPost.js +++ b/WebKit/scripts/controller/NewPost.js @@ -1,13 +1,20 @@ define([ + "helper/APICalls", + "helper/HostApp" ], -function() { +function(APICalls, HostApp) { function NewPost() { - this.entities = JSON.parse(controller.getCachedEntities()); + 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 = []; - this.is_private = false; document.body.className = "new_post"; // Textarea @@ -30,8 +37,8 @@ function() { var buttons = $( "

" + //"" + - "" + - "" + + "" + + "" + "

"); this.buttons = { @@ -41,18 +48,26 @@ function() { } //this.buttons.images.bind("click", this.addImage.bind(this)); - //this.buttons.is_private.bind("click", this.togglePrivate.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.textarea.focus(); + this.setIsPrivate(false); } NewPost.prototype.setStatus = function(status_string) { - this.status = JSON.parse(status_string); - debug(this.status) + if (status_string && status_string.length > 0) { + debug(status_string) + 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. }; @@ -60,27 +75,67 @@ function() { this.textarea.val(string); } - NewPost.prototype.setMentions = function(mentions) { + NewPost.prototype.setMentions = function(status) { - if(mentions && mentions.length > 0) { - var mentions_string = " "; - for (var i = 0; i < mentions.length; i++) { - mentions_string += mentions[i].name + " "; + 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.textarea.val(this.textarea.val() + " " + mentions_string); - this.mentions = mentions; } - this.keyup(); + + 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.is_private = !this.is_private; - }; + this.setIsPrivate(!this.is_private); + } NewPost.prototype.keyup = function(e) { if(!e) return; @@ -152,16 +207,20 @@ function() { if(words) { var name = words[2]; - for (var key in this.entities.length) { - var item = this.entities[key]; - if(item.name.toLowerCase().indexOf(name.toLowerCase()) != -1 || item.entity.toLowerCase().indexOf(name.toLowerCase()) != -1) { + 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 + " " + item.entity + "
  • ") li.get(0).item = item; - this.suggestions.append(li) + this.suggestions.append(li); } } } + 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','
    '); @@ -188,19 +247,18 @@ function() { this.highlighter.html(text); var count = 256 - this.textarea.val().length + (this.mentions.length * 6); - this.counter.html(count) - - return true; + this.counter.html(count); } NewPost.prototype.send = function() { - debug("Send not implemented yet"); - $("textarea").focus(); + var count = 256 - this.textarea.val().length + (this.mentions.length * 6); - if(count >= 0) { - this.sentNewMessage(); + if(count >= 0 && count <= 256) { + this.sendNewMessage(); + return true; } else { debug("BEEP"); + return false; } } @@ -208,44 +266,52 @@ function() { var content = this.textarea.val(); - var url = URI(HostApp.serverUrl("new_post")); - - var type = in_reply_to_status_id.length == 0 ? "https://tent.io/types/status/v0#" : "https://tent.io/types/status/v0#reply"; - + var type = "https://tent.io/types/status/v0#"; var data = { - "type": type, - "published_at": parseInt(new Date().getTime(), 10), - "permissions": { - "public": !is_private - }, - "content": { - "text": content, + type: type, + content: { + text: content }, + permissions: { + public: !this.is_private + } }; - if (location) { - //data["content"]["location"] = { "type": "Point", "coordinates": location } + var mentions = []; + if (this.status) { + mentions.push({ + entity: this.status.entity, + post: this.status.id, + type: this.status.type + }); } - 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; - } + for (var i = 0; i < this.mentions.length; i++) { + var mention = this.mentions[i]; + mentions.push({ + entity: mention.entity + }); } - // APICalls.http_call(url.toString(), http_method, callback, JSON.stringify(data)); - APICalls.post(url.toString(), JSON.stringify(data), { + 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, - callback: callback + 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(); + } + } }); } /* diff --git a/WebKit/scripts/controller/Oauth.js b/WebKit/scripts/controller/Oauth.js index 69e1b41..9999826 100644 --- a/WebKit/scripts/controller/Oauth.js +++ b/WebKit/scripts/controller/Oauth.js @@ -178,7 +178,7 @@ function(HostApp, APICalls, Hmac) { HostApp.loggedIn(); } - Oauth.prototype.logout = function() { + Oauth.prototype.logout = function() { // FIXME var url = APICalls.mkApiRootPath("/apps/" + HostApp.stringForKey("app_id")); var http_method = "DELETE"; diff --git a/WebKit/scripts/controller/Profile.js b/WebKit/scripts/controller/Profile.js index bd42d8d..fb8125b 100644 --- a/WebKit/scripts/controller/Profile.js +++ b/WebKit/scripts/controller/Profile.js @@ -250,6 +250,7 @@ function(HostApp, Core, APICalls, URI) { APICalls.get(url, {callback: function(resp) { var json = JSON.parse(resp.responseText); + debug(json) var count = json.posts.length; if (count > 0) { @@ -273,12 +274,14 @@ function(HostApp, Core, APICalls, URI) { if(profiles.posts.length < 1) return; var profile = profiles.posts[0]; + bungloo.cache.profiles[profile.entity] = profile; + var basic = profile.content.profile; if (profile && basic) { // Find and apply avatar - if(profile.attachments.length > 0) { + if(profile.attachments) { var digest = null; for (var i = 0; i < profile.attachments.length; i++) { diff --git a/WebKit/scripts/controller/Sidebar.js b/WebKit/scripts/controller/Sidebar.js index e715f94..61d05d4 100644 --- a/WebKit/scripts/controller/Sidebar.js +++ b/WebKit/scripts/controller/Sidebar.js @@ -1,16 +1,13 @@ define([ "helper/HostApp", "helper/APICalls", - "helper/Cache" ], -function(HostApp, APICalls, Cache) { +function(HostApp, APICalls) { function Sidebar() { - this.cache = new Cache(); - this.body = document.createElement("ul"); this.body.class = "sidebar"; @@ -51,7 +48,7 @@ function(HostApp, APICalls, Cache) { document.body.className = "body-timeline"; document.body.id = "with-sidebar"; - //this.setEntityAvatar(); FIXME + this.setEntityAvatar(); this.setOnScroll(); } @@ -88,53 +85,13 @@ function(HostApp, APICalls, Cache) { var _this = this; - var profile_callback = function(p) { + var url = HostApp.serverUrl("discover"); + debug(url) - var basic = p["https://tent.io/types/info/basic/v0.1.0"]; + APICalls.get(url, { callback: function(resp) { - if (p && basic) { - if(basic.name) { - _this.menu.user.title = basic.name; - } - if(basic.avatar_url) { + }}); - img.onerror = function() { - img.src = "img/sidebar/user.png"; - img.src_inactive = img.src; - img.src_active = img.src; - } - - img.src = basic.avatar_url; - img.src_inactive = basic.avatar_url; - img.src_active = basic.avatar_url; - - } - } - - } - - var p = this.cache.profiles.getItem(entity); - - if (p && p != "null") { - - profile_callback(p); - - } else { - - APICalls.findProfileURL(entity, function(profile_url) { - - 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(entity, p); - profile_callback(p); - } - - }, null, false); // do not send auth-headers - } - }); - } } Sidebar.prototype.removeEntityAvatar = function() { diff --git a/WebKit/scripts/helper/APICalls.js b/WebKit/scripts/helper/APICalls.js index 1bdc52d..9ab43c2 100644 --- a/WebKit/scripts/helper/APICalls.js +++ b/WebKit/scripts/helper/APICalls.js @@ -35,19 +35,23 @@ function(jQuery, HostApp, Hmac, Cache) { if(options.http_method == "POST" && !options.content_type) { console.error("No content type for " + options.url); return; - } else { + } 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; type=\"" + 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( @@ -63,7 +67,7 @@ function(jQuery, HostApp, Hmac, Cache) { } else if(!options.no_auth) { console.error("No user_access_token yet - " + options.url); } - xhr.setRequestHeader("Cache-Control", "no-cache"); + xhr.setRequestHeader("Cache-Control", "no-proxy"); }, url: options.url, contentType: content_type, diff --git a/WebKit/scripts/helper/HostApp.js b/WebKit/scripts/helper/HostApp.js index 13992ad..9811fda 100644 --- a/WebKit/scripts/helper/HostApp.js +++ b/WebKit/scripts/helper/HostApp.js @@ -88,9 +88,9 @@ define(function() { HostApp.openNewMessageWidow = function(status) { if (OS_TYPE == "mac") { - controller.openNewMessageWindowInReplyToStatus(JSON.stringify(status)); + controller.openNewMessageWindowInReplyToStatus_(JSON.stringify(status)); } else { - controller.openNewMessageWindowInReplyTostatus(JSON.stringify(status)); + controller.openNewMessageWindowInReplyToStatus(JSON.stringify(status).escapeSpecialChars()); } } diff --git a/WebKit/scripts/main.js b/WebKit/scripts/main.js index 69511ef..3b48c90 100644 --- a/WebKit/scripts/main.js +++ b/WebKit/scripts/main.js @@ -64,46 +64,12 @@ function start(view, callback) { bungloo.search = new Search(); bungloo.sidebar.showContentForTimeline(); - - bungloo.cache.entities = { - "https://jeena.net" : { - name: "Jeena", - entity: "https://jeena.net", - avatar: "https://jeena.net/avatar.png" - }, - "https://ck.kennt-wayne.de": { - name: "Christian", - entity: "http://ck.kennt-wayne.de", - avatar: "http://ck.kennt-wayne.de/pavatar.png" - } - }; - }); } } -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") { @@ -195,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