Compare commits

73 Commits
Author SHA1 Message Date
NotAlexNoyle ed6b88fa34 Update canonical Gitea link 2026-11-03 10:48:16 -05:00
NotAlexNoyle 4452d640e7 Update .gitignore 2026-08-05 12:14:43 -04:00
NotAlexNoyle 295ae7d962 Update About dialog for fork attribution
Preserve Jon W copyright and MIT license; add NotAlexNoyle fork
attribution and fork source URL.
2026-05-13 08:18:30 -04:00
NotAlexNoyle 0da0c2426d AlmostSimpleChat fork: rebrand + Twimoji emoji 2026-05-13 08:18:30 -04:00
codepoet80 0d0d231fae image uploading 2026-05-12 21:47:40 -04:00
codepoet80 e4245d1970 add version note (for next time) 2023-09-20 19:26:00 -04:00
codepoet80 ec2547302c bug fix share parsing 2023-09-14 08:25:28 -04:00
codepoet80 6631e5a5aa fix a bug parsing wosa links, should go in next release 2023-06-13 21:17:46 -04:00
codepoet80 6d5b15cc73 bump version 2023-05-31 11:06:12 -04:00
codepoet80 ee1a8f2dc8 add sharing support 2023-05-31 09:58:50 -04:00
codepoet80 6699fd8c06 update additions 2022-09-18 15:47:01 -04:00
codepoet80 f4a07ab049 update attributions 2022-09-17 14:34:13 -04:00
codepoet80 143206362d update additions path 2022-09-17 10:58:51 -04:00
codepoet80 34d7edf578 migrate to common components 2022-09-11 14:06:32 -04:00
codepoet80 43db97cc2b update links 2022-09-11 12:48:34 -04:00
codepoet80 b4110b5ad7 update copyrights 2022-09-10 15:09:35 -04:00
codepoet80 c5f16ff2f7 re-set log level 2022-09-03 07:49:56 -04:00
codepoet80 678b7a3db8 add comment 2022-09-03 07:37:55 -04:00
codepoet80 21106502dc update comment 2022-08-20 15:23:26 -04:00
codepoet80 3f88c17702 add dark mode, migrate TLD 2022-08-20 15:03:32 -04:00
codepoet80 8621e204af parsing pref 2022-03-28 21:37:47 -04:00
codepoet80 09359eab76 try optional link parsing and loading spinner 2022-03-28 20:47:47 -04:00
codepoet80 ad813fa9db tidy up 2022-02-18 14:42:36 -05:00
Jon W bd3a85d163 Update README.md 2022-01-19 12:04:14 -05:00
Jon W d9fcf68f6c Update README.md 2022-01-19 12:03:33 -05:00
Jon W b0e7d8201b Update README.md 2022-01-19 11:44:35 -05:00
codepoet80 49c53c2725 sync from new pc 2021-12-18 18:57:08 -05:00
CodePoet80 8795a83a59 fix bug 2021-12-14 12:51:03 -05:00
CodePoet80 8d8d089b39 fix clientid bug 2021-12-14 12:05:15 -05:00
CodePoet80 11da674e7d bump version 2021-12-13 17:28:56 -05:00
CodePoet80 b3d1b8a9ba support downloading attachments 2021-12-13 17:11:00 -05:00
CodePoet80 da0e6a82e1 stub in menu item 2021-12-13 16:55:12 -05:00
CodePoet80 c5c1d37c17 support image attachments 2021-12-13 16:42:40 -05:00
CodePoet80 852db09107 updater 2021-12-11 14:59:32 -05:00
CodePoet80 bb6b6ed5c9 change version info 2021-12-11 13:31:38 -05:00
CodePoet80 1bff5e2bf4 send version number in update check 2021-12-11 13:00:26 -05:00
CodePoet80 5a09ebaf10 bump version 2021-11-23 20:03:01 -05:00
CodePoet80 0aad1d3f66 bug fix imgur links 2021-11-23 20:02:43 -05:00
CodePoet80 9348c8efa6 upgrade updater 2021-11-23 19:22:08 -05:00
CodePoet80 4b66dc21b1 bump version 2021-11-23 10:21:44 -05:00
CodePoet80 0002846c4a add support for imgur albums 2021-11-23 10:20:28 -05:00
CodePoet80 f51cb700bc tweak info text 2021-06-05 14:27:32 -04:00
CodePoet80 52069fdfff add wosa parsing 2021-06-05 13:49:48 -04:00
CodePoet80 8e396ada3c add markdown support, bump version 2021-04-26 18:34:09 -04:00
CodePoet80 6ff0c5af86 bump version number 2021-04-21 18:50:29 -04:00
CodePoet80 689a4e8744 add preference for enter submits 2021-04-21 18:46:56 -04:00
CodePoet80 052bd755a1 pre-check background notifications for quieter experience 2021-04-21 16:59:26 -04:00
CodePoet80 bc5484ec0a remember last message when dashboard swiped clsoed 2021-04-21 15:26:14 -04:00
CodePoet80 b8972169a3 handle unknown emojis, better tap target 2021-04-21 15:08:23 -04:00
CodePoet80 f44e6e4464 make update check config specific 2021-04-16 13:00:20 -04:00
CodePoet80 d3f48b5823 send client-id with get 2021-04-16 11:00:09 -04:00
CodePoet80 a6add5d811 add client-id to get request 2021-04-16 10:40:51 -04:00
CodePoet80 200f327f23 better approach to sound playing 2021-04-15 21:14:07 -04:00
CodePoet80 ccb8b74a50 tweak dashboard message for flexibility 2021-04-15 21:02:21 -04:00
CodePoet80 e57abfcd0c oops 2021-04-15 20:53:24 -04:00
CodePoet80 3183c069a7 bump version info 2021-04-15 20:51:56 -04:00
CodePoet80 994f0edea6 more tweaks to make forking easier 2021-04-15 20:20:33 -04:00
CodePoet80 b7c4ccff34 another tweak to simplify forking 2021-04-15 20:03:39 -04:00
CodePoet80 12df9ec788 small tweak to make forking easier 2021-04-15 19:54:51 -04:00
CodePoet80 f5d412987f hide notification stage on relaunch 2021-04-15 09:51:07 -04:00
CodePoet80 3b1a01f231 bump version 2021-04-15 09:27:23 -04:00
CodePoet80 9de7602d63 send message with enter if command menu hidden 2021-04-15 09:27:10 -04:00
CodePoet80 230334e997 tweak icons 2021-04-15 08:27:48 -04:00
CodePoet80 1ab5a4154c bump version 2021-04-14 17:35:19 -04:00
CodePoet80 37b4a356cd cleanup and code comments 2021-04-14 17:34:08 -04:00
CodePoet80 86275b9f75 better background notificiations 2021-04-14 17:22:26 -04:00
CodePoet80 430d174742 bump version 2021-04-12 14:12:57 -04:00
CodePoet80 5e522c3fb5 bump version 2021-04-12 14:12:40 -04:00
CodePoet80 2ad76ec331 more background check times 2021-04-12 14:10:49 -04:00
CodePoet80 776b7b696a handle image links with proxy 2021-04-12 13:43:31 -04:00
CodePoet80 4785694ad9 restore new notification approach 2021-04-10 14:45:35 -04:00
CodePoet80 52a3bf09ee restore 1.1.3 2021-04-10 14:45:10 -04:00
CodePoet80 70e43f02f4 work on new notification approach 2021-04-10 14:37:41 -04:00
86 changed files with 1980 additions and 817 deletions

No files matched your search

Regular → Executable
+2
View File
@@ -1,2 +1,4 @@
secrets.js
.DS_Store
framework_config.json
_bin/
Vendored Regular → Executable
View File
File mode changed.
+4 -1
View File
@@ -1,3 +1,6 @@
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
echo copying latest common components
cp $DIR/../../webos-common/Mojo/updater-model.js $DIR/../app/models/updater-model.js
cp $DIR/../../webos-common/Mojo/com.wosa.mojo.additions.js $DIR/../app/com.wosa.mojo.additions.js
mkdir -p $DIR/../_bin
palm-package $DIR/../ -o $DIR/../_bin
palm-package $DIR/../ -o $DIR/../_bin
Vendored Regular → Executable
View File
File mode changed.
+4 -4
View File
@@ -1,5 +1,5 @@
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
palm-package simplechat-app simplechat-service simplechat-package
palm-install com.jonandnic.simplechat_2.0.0_all.ipk
palm-launch com.jonandnic.simplechat
palm-log -f com.jonandnic.simplechat.app
echo copying latest common components
cp $DIR/../../webos-common/Mojo/updater-model.js $DIR/../app/models/updater-model.js
cp $DIR/../../webos-common/Mojo/com.wosa.mojo.additions.js $DIR/../app/com.wosa.mojo.additions.js
palm-run $DIR/../
Vendored Regular → Executable
View File
File mode changed.
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 Jon W
Copyright (c) 2022 Jon W
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Executable
+40
View File
@@ -0,0 +1,40 @@
# Simple Chat client app for webOS
![App Icon](icon.png)
SimpleChat client for legacy Palm/HP webOS devices, depends on back-end services:
+ [simplechat-service](https://github.com/codepoet80/simplechat-service)
+ [simplechat-discordbot](https://github.com/codepoet80/simplechat-discordbot) (Optional)
You can also self-host the services.
## What is This?
This is an app for the defunct mobile webOS platform, made by Palm and later acquired by HP. It runs on devices like the Palm Pre or Pixi, or the HP
Pre3 or TouchPad.
webOS technology was acquired by LG and repurposed for TVs and IoT devices, but they made significant changes and this app will not run on those platforms.
Releases of this app, and many other new and restored apps, can be found in the [webOS Archive App Museum](http://appcatalog.webosarchive.org).
## Why?
Aside from being a fan of the platform, the author thinks consumers have lost out now that the smart phone ecosystem has devolved into a duopoly.
Apple and Google take turns copying each other, and consumers line up to buy basically the same new phone every year. The era when webOS, Blackberry and
Windows Phone were serious competitors was marked by creativity in form factor and software development, which has been lost. This app represents a (futile)
attempt to keep webOS mobile devices useful for as long as possible.
The website [http://www.webosarchive.org](http://www.webosarchive.org) recovers, archives and maintains material related to development, and hosts services
that restore functionality to webOS devices. A small but active [community](http://www.webosarchive.org/discord) of users take advantage of these services to keep their retro devices alive.
## How?
Mobile webOS was truly a web-derived platform. Based on Linux, and able to run properly compiled Linux binaries, developers could get raw resources access (including GPU) through a PDK (Plug-in Development Kit) but most apps were written in some flavor of Javascript, interacting with a WebKit-based browser. The toolkits were heavily optimized for the devices, and web-based apps usually felt pretty close to native. Services could be written using NodeJS and talk to each other through API calls structured to look like web service calls. App front-ends were usually written in either the Mojo (pre-tablet) or Enyo (tablet and newer phones) Javascript frameworks. Enyo apps can often be run with only minor modification in any WebKit based browser (eg: Safari or Chrome).
You can learn more about these frameworks at the restored [SDK](http://sdk.webosarchive.org).
webOS devices can be found cheaply on eBay, and while the phones will cease to be useful as phones when the 3G shutdown is through, both the phones and the Touchpad can be used around the home for a variety of [fun and useful things](http://www.webosarchive.org/docs/thingstotry/).
If you have a device, instructions for activating, getting online and getting apps installed can be found in the [webOS Archive Docs section](http://www.webosarchive.org/docs/activate/).
+87
View File
@@ -0,0 +1,87 @@
/*
In the app assistant, we setup some app-wide global objects and handle different kinds of launches, creating and delegating to the main stage
*/
var appModel = null;
var updaterModel = null;
var systemModel = null;
var MainStageName = "main";
var welcomed = false;
Mojo.Additions = Additions;
function AppAssistant() {
appModel = new AppModel();
updaterModel = new UpdaterModel();
systemModel = new SystemModel();
serviceModel = new ServiceModel();
}
//This function will handle relaunching the app when an alarm goes off(see the device/alarm scene)
AppAssistant.prototype.handleLaunch = function(params) {
Mojo.Log.info("SimpleChat is Launching! Launch params: " + JSON.stringify(params));
//Load preferences
appModel.LoadSettings();
Mojo.Log.info("settings now: " + JSON.stringify(appModel.AppSettingsCurrent));
if (!appModel.AppSettingsCurrent["BackgroundUpdate"]) {
appModel.AppSettingsCurrent["BackgroundUpdate"] = "00:30:00";
appModel.SaveSettings();
}
//Reset alarms
systemModel.ClearSystemAlarm("SimpleChat");
if (appModel.AppSettingsCurrent["BackgroundUpdate"] && appModel.AppSettingsCurrent["BackgroundUpdate"] != "" && appModel.AppSettingsCurrent["BackgroundUpdate"] != -1)
systemModel.SetSystemAlarmRelative("SimpleChat", appModel.AppSettingsCurrent["BackgroundUpdate"]);
var mainStage = this.controller.getStageProxy("main"); //get the proxy for the stage if it already exists (eg: app is currently open)
if (mainStage) //If the stage exists, use it
{
Mojo.Log.info("Found existing main stage, app was already running");
var stageController = this.controller.getStageController("main");
if (!params || params["action"] == undefined) //If no action parameters was passed, this is a normal launch
{
//if there was a share action, load with that
if (params && params["newshare"] != undefined) {
appModel.LaunchQuery = decodeURI(params["newshare"]);
Mojo.Log.info("This is re-launch with share action: " + appModel.LaunchQuery);
} else {
Mojo.Log.info("This is a normal re-launch");
}
stageController.activate(); //bring existing stage into focus
return;
} else //If parameters were passed, this is a launch from a system alarm
{
Mojo.Log.warn("This is a re-launch with parameters: " + JSON.stringify(params) + ". Safe to ignore, since app is already running.");
appModel.ShowNotificationStage();
return;
}
} else //If no stage exists, determine if we should make one
{
Mojo.Log.info("Did not find existing main stage, app is not running");
if (!params || params["action"] == undefined) //If no action parameters was passed, this is a normal launch
{
var currVersion = Mojo.Controller.appInfo.version;
//if there was a share action, load with that
if (params && params["newshare"] != undefined) {
appModel.LaunchQuery = decodeURI(params["newshare"]);
Mojo.Log.info("This is launch with share action: " + appModel.LaunchQuery);
} else {
Mojo.Log.info("This is a normal launch of version " + currVersion);
}
var stageArguments = { name: MainStageName, lightweight: true };
this.controller.createStageWithCallback(stageArguments, function(stageController) {
stageController.pushScene(MainStageName);
}.bind(this));
return;
} else //If parameters were passed, this is a launch from a system alarm
{
Mojo.Log.info("This is an alarm launch: " + JSON.stringify(params));
appModel.ShowNotificationStage();
return;
}
}
};
@@ -5,11 +5,12 @@ chance of successfully changing the settings. We can then close this window. All
*/
function DashboardAssistant(argFromPusher) {
this.passedArguments = argFromPusher;
this.lastMessageCount = 0;
}
DashboardAssistant.prototype.setup = function() {
Mojo.Log.info("Notification stage setup at " + new Date());
this.displayDashboard("SimpleChat", "Checking for new messages...");
Mojo.Log.info("Dashboard notification stage setup at " + new Date());
this.displayDashboard(Mojo.Controller.appInfo.title, "Checking for new messages...");
this.serviceEndpointBase = appModel.ServiceEndpointBase;
if (appModel.AppSettingsCurrent["UseCustomEndpoint"] && appModel.AppSettingsCurrent["EndpointURL"]) {
@@ -18,42 +19,60 @@ DashboardAssistant.prototype.setup = function() {
if (!appModel.AppSettingsCurrent["AlertSound"] || appModel.AppSettingsCurrent["AlertSound"] == "") {
appModel.AppSettingsCurrent["AlertSound"] = "Subtle (short)";
}
serviceModel.getChats(this.serviceEndpointBase, function(response) {
//Event handlers
Mojo.Event.listen(this.controller.get("dashboardinfo"), Mojo.Event.tap, this.handleTap.bind(this));
}
DashboardAssistant.prototype.activate = function(event) {
Mojo.Log.info("Dashboard activating!");
this.clientId = appModel.ClientId;
if (appModel.AppSettingsCurrent["UseClientAPIKey"] && appModel.AppSettingsCurrent["ClientAPIKey"]) {
this.clientId = appModel.AppSettingsCurrent["ClientAPIKey"];
}
this.checkForMessages();
}
DashboardAssistant.prototype.checkForMessages = function() {
Mojo.Log.info("Checking for new messages in Dashboard...");
serviceModel.getChats(this.serviceEndpointBase, this.clientId, function(response) {
var appController = Mojo.Controller.getAppController();
if (response != null && response != "") {
var responseObj = JSON.parse(response);
if (responseObj.status == "error") {
Mojo.Log.error("Error message from server during background check: " + responseObj.msg);
appController.closeAllStages();
Mojo.Log.error("Error message from server during Dashboard message check: " + responseObj.msg);
appController.closeStage("dashboard");
} else {
if (responseObj.messages && responseObj.messages.length > 0) {
var newMessageCount = this.findNewMessageCount(appModel.AppSettingsCurrent["LastKnownMessage"], responseObj.messages);
if (newMessageCount > 0) {
Mojo.Log.info("Found new chat on server during background check!");
this.displayDashboard("SimpleChat", "New messages in the chat!", newMessageCount);
//If the main app window is open, it will play a sound. If not, play a sound here.
Mojo.Log.info("Found new chat on server during Dashboard message check!");
var appController = Mojo.Controller.getAppController();
var mainStage = appController.getStageController("main");
if (!mainStage) {
systemModel.PlayAlertSound(appModel.AppSettingsCurrent["AlertSound"]);
if (!mainStage) { //If the main app window is open,
if (newMessageCount > this.lastMessageCount) {
systemModel.PlayAlertSound(appModel.AppSettingsCurrent["AlertSound"]);
this.lastMessageCount = newMessageCount;
}
this.displayDashboard(Mojo.Controller.appInfo.title, "New messages have arrived!", newMessageCount);
} else {
this.displayDashboard(Mojo.Controller.appInfo.title, "New messages have arrived!");
}
} else {
Mojo.Log.info("No new chats on server during background check.");
Mojo.Log.info("No new chats on server during Dashboard message check.");
appController.closeStage("dashboard");
}
} else {
Mojo.Log.warn("Chat results were empty during scheduled background check. This is unlikely; server, API or connectivity problem possible");
Mojo.Log.warn("Chat results were empty during scheduled Dashboard message check. This is unlikely; server, API or connectivity problem possible");
appController.closeStage("dashboard");
}
}
} else {
Mojo.Log.error("No usable response from server during background check: " + response);
Mojo.Log.error("No usable response from server during Dashboard message check: " + response);
appController.closeStage("dashboard");
}
}.bind(this));
//Event handlers
Mojo.Event.listen(this.controller.get("dashboardinfo"), Mojo.Event.tap, this.handleTap.bind(this));
}
DashboardAssistant.prototype.findNewMessageCount = function(LastKnownMessage, newMessages) {
@@ -63,6 +82,7 @@ DashboardAssistant.prototype.findNewMessageCount = function(LastKnownMessage, ne
if (startCounting) {
actuallyNewMessages.push(newMessages[j].uid);
appModel.AppSettingsCurrent["LastKnownMessage"] = newMessages[j].uid;
Mojo.Log.info("Dashboard counting: " + newMessages[j].uid);
}
if (newMessages[j].uid == LastKnownMessage) {
startCounting = true;
@@ -97,8 +117,13 @@ DashboardAssistant.prototype.handleDragEnd = function(event) {
DashboardAssistant.prototype.displayDashboard = function(title, message, count) {
var infoElement = this.controller.get("dashboardinfo");
var info = { title: title, message: message, count: count };
var info = { title: title, message: message };
if (count) {
info.count = count;
info.showCount = "inline";
} else {
info.showCount = "none";
}
var renderedInfo = Mojo.View.render({ object: info, template: "dashboard/item-info" });
infoElement.innerHTML = renderedInfo;
this.controller.getSceneScroller().mojo.revealTop(true);
@@ -110,5 +135,6 @@ DashboardAssistant.prototype.deactivate = function(event) {
// Cleanup anything we did in setup function
DashboardAssistant.prototype.cleanup = function() {
Mojo.Log.info("notification stage closing at " + new Date());
Mojo.Log.info("Dashboard notification stage closing at " + new Date());
appModel.SaveSettings();
}
@@ -56,9 +56,7 @@ EmoticonPickerDialogAssistant.prototype.setup = function(widget) {
EmoticonPickerDialogAssistant.prototype.insertCell = function(row, emoticon) {
var cell = row.insertCell(0);
var MojoHTML = Mojo.Format.runTextIndexer(emoticon.escapeHTML());
MojoHTML = MojoHTML.replace("file:///usr/palm/", "");
cell.innerHTML = MojoHTML;
cell.innerHTML = Mojo.Format.runTextIndexer(emoticon.escapeHTML());
cell.setAttribute("emo", emoticon);
cell.style.height = "40px";
};
+420 -104
View File
@@ -11,10 +11,14 @@ function MainAssistant() {
this.doingMessageEdit = false;
this.serverRetries = 0;
this.serverGiveUp = 4;
this.maximized = false;
}
MainAssistant.prototype.setup = function() {
appModel.SetThemePreference(this.controller);
// title
this.controller.get('spnTitle').innerHTML = Mojo.Controller.appInfo.pageTitle;
// setup message field
this.messageFieldModel = {
'original': ''
@@ -37,14 +41,16 @@ MainAssistant.prototype.setup = function() {
this.messageFieldModel.attributes,
this.messageFieldModel
);
//Loading spinner - with global members for easy toggling later
//Spinners
// with global members for easy toggling later
this.spinnerAttrs = {
spinnerSize: Mojo.Widget.spinnerSmall
};
this.spinnerModel = {
//Posting spinner
this.spinnerWorkModel = {
spinning: false
}
this.controller.setupWidget('workingSpinner', this.spinnerAttrs, this.spinnerModel);
this.controller.setupWidget('workingSpinner', this.spinnerAttrs, this.spinnerWorkModel);
//Chat Log List (starts empty)
this.listElement = this.controller.get('chatList');
this.listInfoModel = {
@@ -69,15 +75,22 @@ MainAssistant.prototype.setup = function() {
friction: 'low'
};
this.controller.setupWidget('chatScroller', {}, this.scrollerModel);
//Detect device type for UI layout decisions (Pixi/Veer lack 480px or 800px dimension)
var isTiny = Mojo.Environment.DeviceInfo.platformVersionMajor < 3 &&
this.controller.window.screen.width !== 800 &&
this.controller.window.screen.height !== 800 &&
this.controller.window.screen.width !== 480 &&
this.controller.window.screen.height !== 480;
//Menu
this.appMenuAttributes = { omitDefaultItems: true };
this.appMenuModel = {
label: "Settings",
items: [
Mojo.Menu.editItem,
{ label: "SimpleChat Info", command: 'do-Info' },
{ label: Mojo.Controller.appInfo.title + " Info", command: 'do-Info' },
{ label: "Change Username", command: 'do-Username' },
{ label: "Preferences", command: 'do-Preferences' },
{ label: "Share Image", command: 'do-ShareImage' },
{ label: "About", command: 'do-myAbout' }
]
};
@@ -87,12 +100,13 @@ MainAssistant.prototype.setup = function() {
spacerHeight: 0,
menuClass: 'no-fade'
};
var leftButtons = [{ label: 'Emoticon', iconPath: 'assets/emoticon.png', command: 'do-Emoticon' }];
if (!isTiny)
leftButtons.push({ label: 'Photo', command: 'do-ShareImage' });
this.cmdMenuModel = {
visible: true,
items: [{
items: [
{ label: 'Emoticon', iconPath: 'assets/emoticon.png', command: 'do-Emoticon' }
]
items: leftButtons
},
{
items: [
@@ -108,6 +122,14 @@ MainAssistant.prototype.setup = function() {
Mojo.Event.listen(this.controller.get("chatList"), Mojo.Event.listTap, this.handleListClick.bind(this));
Mojo.Event.listen(this.controller.stageController.document, Mojo.Event.stageActivate, this.activateWindow.bind(this));
Mojo.Event.listen(this.controller.stageController.document, Mojo.Event.stageDeactivate, this.deactivateWindow.bind(this));
Mojo.Event.listenForFocusChanges(this.controller.get("txtMessage"), this.handleTextFocus.bind(this));
// Non-Mojo handlers
this.keyupHandler = this.handleKeyUp.bindAsEventListener(this);
this.controller.document.addEventListener("keyup", this.keyupHandler, true);
//this.controller.document.getElementById("divComposeTitle").addEventListener("click", this.toggleCommandMenu.bind(this));
this.controller.document.getElementById("spanCompose").addEventListener("click", this.toggleCommandMenu.bind(this));
this.controller.document.getElementById("imgTwisty").addEventListener("click", this.toggleCommandMenu.bind(this));
//Check for updates
this.checkForUpdates();
@@ -123,23 +145,8 @@ MainAssistant.prototype.activate = function(event) {
if (appModel.AppSettingsCurrent["UseClientAPIKey"] && appModel.AppSettingsCurrent["ClientAPIKey"]) {
this.clientId = appModel.AppSettingsCurrent["ClientAPIKey"];
}
//Set an alert sound
if (!appModel.AppSettingsCurrent["AlertSound"] || appModel.AppSettingsCurrent["AlertSound"] == "") {
appModel.AppSettingsCurrent["AlertSound"] = "Subtle (short)";
}
//Check for Emoji option
if (appModel.AppSettingsCurrent["ShowEmojis"] == null) {
Mojo.Log.info("Adding Emoji setting with default value!")
appModel.AppSettingsCurrent["ShowEmojis"] = appModel.AppSettingsDefaults["ShowEmojis"];
appModel.SaveSettings();
}
//Init Message memory
if (!appModel.AppSettingsCurrent["MyMessages"]) {
appModel.AppSettingsCurrent["MyMessages"] = [];
}
if (appModel.AppSettingsCurrent["LastKnownMessages"]) { //This was the old way, clean it up
appModel.AppSettingsCurrent["LastKnownMessages"] = null;
}
//Get settings or load defaults
this.initDefaultSettings();
//Find out what kind of device this is
if (Mojo.Environment.DeviceInfo.platformVersionMajor >= 3) {
@@ -157,14 +164,21 @@ MainAssistant.prototype.activate = function(event) {
Mojo.Log.warn("Device detected as Pixi or Veer");
}
}
//systemModel.PreventDisplaySleep();
//Set some variables
this.maximized = true;
this.serverRetries = 0;
this.pendingMessages = [];
this.firstPoll = true;
this.firstPoll = this.listInfoModel.items.length === 0;
//Disable background sync
Mojo.Log.info("Switching to foreground sync")
this.startPollingServer();
systemModel.ClearSystemAlarm("SimpleChat");
//Get ready for input
this.handleTextFocus();
//Determine if we should show any startup messages
var currVersion = Mojo.Controller.appInfo.version;
if (!welcomed && (!appModel.AppSettingsCurrent["LastVersionRun"] || appModel.AppSettingsCurrent["LastVersionRun"] != currVersion)) {
this.pausePollingServer();
@@ -177,11 +191,6 @@ MainAssistant.prototype.activate = function(event) {
this.controller.window.addEventListener('resize', this.orientationChanged.bind(this)); //we have to do this for TouchPad because it does not get orientationChanged events
this.orientationChanged();
//Add event handlers
Mojo.Event.listen(this.controller.get("chatList"), Mojo.Event.listTap, this.handleListClick.bind(this));
Mojo.Event.listenForFocusChanges(this.controller.get("txtMessage"), this.handleTextFocus.bind(this));
this.controller.document.getElementById("divComposeTitle").addEventListener("click", this.toggleCommandMenu.bind(this));
//Figure out if this is our first time
if (appModel.AppSettingsCurrent["FirstRun"] || !appModel.AppSettingsCurrent["SenderName"] || (appModel.AppSettingsCurrent["SenderName"] && appModel.AppSettingsCurrent["SenderName"].toLowerCase() == "webos user")) {
appModel.AppSettingsCurrent["FirstRun"] = false;
@@ -211,7 +220,7 @@ MainAssistant.prototype.orientationChanged = function() {
this.scaleScroller("wide");
}
}
};
}
MainAssistant.prototype.scaleScroller = function(orientation) {
if (!this.lastOrientation || orientation != this.lastOrientation) {
@@ -241,9 +250,8 @@ MainAssistant.prototype.adustScrollerForKeyboard = function(orientation) {
else
this.scaledHeight = Math.floor(Mojo.Environment.DeviceInfo.screenWidth / this.scalingFactor) - bottomBuffer;
Mojo.Log.info(this.DeviceType + " orientation is " + orientation + " bottom buffer is: " + bottomBuffer + " scaled height: " + this.scaledHeight);
//Mojo.Log.info(this.DeviceType + " orientation is " + orientation + " bottom buffer is: " + bottomBuffer + " scaled height: " + this.scaledHeight);
this.chatScroller.style.height = this.scaledHeight + "px";
//this.scrollToBottom();
setTimeout(fixScroll = function() {
this.controller.getSceneScroller().mojo.revealTop(true);
@@ -266,32 +274,51 @@ MainAssistant.prototype.pausePollingServer = function() {
}
/* UI Handlers */
//Handles the enter key
MainAssistant.prototype.handleKeyUp = function(event) {
if (event && Mojo.Char.isEnterKey(event.keyCode)) {
if (event.srcElement.parentElement.id == "txtMessage") {
if (appModel.AppSettingsCurrent["EnterSubmits"] == "always") {
Mojo.Log.info("Doing submit on enter because Preference is always");
this.handleSendMessage();
} else if (!this.menuOn && appModel.AppSettingsCurrent["EnterSubmits"] == "collapsed") {
Mojo.Log.info("Doing submit on enter because collapsed and Preference is collapsed");
this.handleSendMessage();
}
}
}
}
MainAssistant.prototype.handleListClick = function(event) {
appModel.LastMessageSelected = event.item;
this.listTarget = event.originalEvent.target;
//Decide what items to put in pop-up menu
var popupMenuItems = [];
var isMine = false;
for (var m = 0; m < appModel.AppSettingsCurrent["MyMessages"].length; m++) {
if (appModel.AppSettingsCurrent["MyMessages"][m].uid == appModel.LastMessageSelected.uid)
isMine = true;
}
if (isMine)
popupMenuItems.push({ label: 'Edit Message', command: 'do-editMessage' });
else {
if (event.item.links != null) {
popupMenuItems.push({ label: 'Copy Link', command: 'do-copyLink' });
popupMenuItems.push({ label: 'Follow Link', command: 'do-followLink' });
if (this.listTarget.tagName != "A" && this.listTarget.tagName != "IMG") { //don't pop-up menu if they tapped a hyperlink or image
//Decide what items to put in pop-up menu
var popupMenuItems = [];
var isMine = false;
for (var m = 0; m < appModel.AppSettingsCurrent["MyMessages"].length; m++) {
if (appModel.AppSettingsCurrent["MyMessages"][m].uid == appModel.LastMessageSelected.uid)
isMine = true;
}
popupMenuItems.push({ label: 'Copy Message', command: 'do-copy' });
popupMenuItems.push({ label: 'Like', command: 'do-like' });
if (isMine)
popupMenuItems.push({ label: 'Edit Message', command: 'do-editMessage' });
else {
if (event.item.links != null) {
popupMenuItems.push({ label: 'Copy Link', command: 'do-copyLink' });
popupMenuItems.push({ label: 'Follow Link', command: 'do-followLink' });
}
popupMenuItems.push({ label: 'Copy Message', command: 'do-copy' });
if (event.item.attachments && event.item.attachments.length > 0)
popupMenuItems.push({ label: 'Save Attachments', command: 'do-saveAttachments' });
popupMenuItems.push({ label: 'Like', command: 'do-like' });
}
this.controller.popupSubmenu({
onChoose: this.handlePopupChoose.bind(this, event.item),
placeNear: this.listTarget,
items: popupMenuItems
});
}
this.controller.popupSubmenu({
onChoose: this.handlePopupChoose.bind(this, event.item),
placeNear: this.listTarget,
items: popupMenuItems
});
return true;
}
@@ -325,6 +352,14 @@ MainAssistant.prototype.handlePopupChoose = function(message, command) {
}
systemModel.LaunchApp("com.palm.app.browser", parameters);
break;
case "do-saveAttachments":
var attachments = appModel.LastMessageSelected.attachments;
for (var i=0; i< attachments.length; i++) {
var attach = attachments[i];
var url = serviceModel.makeServiceUrl(this.serviceEndpointBase, "attachment.php?") + attach.filename;
systemModel.DownloadFile(url, "image/" + attach.extension, "downloads", attach.filename, true);
}
break;
case "do-editMessage":
this.doEditMessage(message);
break;
@@ -362,11 +397,14 @@ MainAssistant.prototype.handleCommand = function(event) {
stageController.pushScene({ name: "version", disableSceneScroller: false });
break;
case 'do-myAbout':
Mojo.Additions.ShowDialogBox("SimpleChat - " + Mojo.Controller.appInfo.version, "SimpleChat client for webOS. Copyright 2021, Jon Wise. Distributed under an MIT License.<br>Source code available at: https://github.com/codepoet80/webos-simplechat");
Mojo.Additions.ShowDialogBox(Mojo.Controller.appInfo.title + " - " + Mojo.Controller.appInfo.version, "AlmostSimpleChat client for webOS. Fork by NotAlexNoyle. Based on SimpleChat, Copyright 2022, Jon W. Distributed under an MIT License.<br>Fork source: https://git.trueog.net/NotAlexNoyle/AlmostSimpleChat<br>Upstream source: https://github.com/codepoet80/webos-simplechat");
break;
case 'do-ShareImage':
this.handleShareImage();
break;
}
}
};
}
MainAssistant.prototype.doEditMessage = function(message) {
this.controller.get('txtMessage').mojo.setValue(appModel.LastMessageSelected.message);
@@ -394,6 +432,56 @@ MainAssistant.prototype.doLikeMessage = function(message) {
}
/* Server Interactions */
MainAssistant.prototype.handleShareImage = function() {
var self = this;
Mojo.FilePicker.pickFile({
kind: 'image',
actionName: 'Share',
onSelect: function(file) {
Mojo.Log.info("Photo selected: " + file.fullPath);
Mojo.Controller.getAppController().showBanner({ messageText: 'Uploading image...' }, { source: 'notification' });
self.disableUI();
serviceModel.uploadPhoto(
file.fullPath,
appModel.AppSettingsCurrent["SenderName"],
self.serviceEndpointBase,
self.clientId,
function(attachment) {
self.postPhotoToService(attachment);
},
function(errorText) {
self.enableUI();
Mojo.Log.error("Photo upload failed: " + errorText);
Mojo.Controller.getAppController().showBanner({ messageText: 'Upload failed' }, { source: 'notification' });
}
);
}
}, this.controller.stageController);
};
MainAssistant.prototype.postPhotoToService = function(attachment) {
serviceModel.postChatWithAttachment(
appModel.AppSettingsCurrent["SenderName"],
[attachment],
this.serviceEndpointBase,
this.clientId,
function(response) {
this.enableUI();
try {
var responseObj = JSON.parse(response);
if (responseObj.error) {
Mojo.Log.error("Server error posting photo: " + responseObj.error);
Mojo.Controller.getAppController().showBanner({ messageText: 'Server error posting image' }, { source: 'notification' });
} else {
Mojo.Log.info("Photo posted: " + responseObj.posted);
}
} catch (e) {
Mojo.Log.error("Failed to parse photo post response");
}
}.bind(this)
);
};
MainAssistant.prototype.handleSendMessage = function(event) {
this.controller.get('txtMessage').mojo.blur();
var newMessage = this.controller.get('txtMessage').mojo.getValue();
@@ -421,14 +509,11 @@ MainAssistant.prototype.postMessageToService = function(newMessage) {
Mojo.Log.info("Message accepted by server: " + responseObj.posted); //{ "posted": "604529e19fb7f", "senderKey": "604529e19fb99" }
if (responseObj.posted) {
Mojo.Log.info("responseObj: " + JSON.stringify(responseObj));
var MojoHTML = Mojo.Format.runTextIndexer(newMessage);
MojoHTML = MojoHTML.replace("file:///usr/palm/", "");
var newMsg = {
uid: responseObj.posted,
sender: appModel.AppSettingsCurrent["SenderName"],
message: newMessage,
formattedMessage: MojoHTML,
formattedMessage: Mojo.Format.runTextIndexer(newMessage),
timestamp: this.convertTimeStamp(new Date(), false),
color: "gray"
};
@@ -475,11 +560,7 @@ MainAssistant.prototype.editMessageToService = function(newMessage) {
for (var i = 0; i < thisWidgetSetup.model.items.length; i++) {
if (thisWidgetSetup.model.items[i].uid == appModel.LastMessageSelected.uid) {
thisWidgetSetup.model.items[i].message = newMessage;
var MojoHTML = Mojo.Format.runTextIndexer(newMessage);
MojoHTML = MojoHTML.replace("file:///usr/palm/", "");
thisWidgetSetup.model.items[i].formattedMessage = MojoHTML;
thisWidgetSetup.model.items[i].formattedMessage = Mojo.Format.runTextIndexer(newMessage);
thisWidgetSetup.model.items[i].color = "gray";
this.controller.get('chatList').mojo.noticeUpdatedItems(i, [thisWidgetSetup.model.items[i]]);
}
@@ -526,8 +607,11 @@ MainAssistant.prototype.likeMessageToService = function() {
//Send a request to Service to get chat messages
MainAssistant.prototype.getChats = function() {
//show transmitting
this.controller.get('imgTransmit').style.display = "block";
if (this.serverRetries <= this.serverGiveUp) {
serviceModel.getChats(this.serviceEndpointBase, function(response) {
serviceModel.getChats(this.serviceEndpointBase, this.clientId, function(response) {
//Mojo.Log.info("getChat response: " + response);
if (response != null && response != "") {
var responseObj = JSON.parse(response);
@@ -539,8 +623,7 @@ MainAssistant.prototype.getChats = function() {
this.serverRetries = 0;
this.updateChatsList(responseObj.messages);
} else {
Mojo.Log.warn("Search results were empty. This is unlikely; server, API or connectivity problem possible");
Mojo.Additions.ShowDialogBox("No results", "The server did not report any matches for the search.");
Mojo.Log.warn("Server returned empty message list.");
}
}
} else {
@@ -551,6 +634,9 @@ MainAssistant.prototype.getChats = function() {
}
}
}.bind(this));
} else {
//hide transmitting
this.controller.get('imgTransmit').style.display = "none";
}
}
@@ -563,18 +649,25 @@ MainAssistant.prototype.updateChatsList = function(results) {
//now make the new list
var newMessages = [];
for (var i = 0; i < results.length; i++) {
var MojoHTML = Mojo.Format.runTextIndexer(results[i].message);
MojoHTML = MojoHTML.replace("file:///usr/palm/", "");
var useLinks;
var formattedMessage = results[i].message;
if (!appModel.AppSettingsCurrent["SkipParsing"]) {
formattedMessage = Mojo.Format.runTextIndexer(results[i].message);
if (formattedMessage.length < 1)
formattedMessage = results[i].message;
formattedMessage = this.expandAttachments(formattedMessage, results[i]);
useLinks = this.detectURLs(results[i].message);
}
newMessages.push({
uid: results[i].uid,
sender: results[i].sender,
message: results[i].message,
formattedMessage: MojoHTML,
formattedMessage: formattedMessage,
postedFrom: results[i].postedFrom,
links: this.detectURLs(results[i].message),
links: useLinks,
timestamp: this.convertTimeStamp(results[i].timestamp, true),
likes: results[i].likes,
attachments: results[i].attachments,
color: "black"
});
}
@@ -601,21 +694,14 @@ MainAssistant.prototype.updateChatsList = function(results) {
for (var m = 0; m < thisWidgetSetup.model.items.length; m++) {
for (var n = 0; n < newMessages.length; n++) {
if (newMessages[n].uid == thisWidgetSetup.model.items[m].uid) {
var changed = false;
if (newMessages[n].message != thisWidgetSetup.model.items[m].message ||
newMessages[n].likes != thisWidgetSetup.model.items[m].likes ||
newMessages[n].color != thisWidgetSetup.model.items[m].color) {
Mojo.Log.info("Updating message from list at position " + m);
Mojo.Log.info("Old Message: " + JSON.stringify(thisWidgetSetup.model.items[m]));
thisWidgetSetup.model.items[m] = newMessages[n];
this.controller.get('chatList').mojo.noticeUpdatedItems(m, [thisWidgetSetup.model.items[m]]);
changed = true;
Mojo.Log.info("New Message should be: " + JSON.stringify(newMessages[n]));
Mojo.Log.info("New Message is : " + JSON.stringify(thisWidgetSetup.model.items[m]));
updated = true;
}
}
if (changed)
updated = true;
}
}
//Insert new messages
@@ -641,7 +727,7 @@ MainAssistant.prototype.updateChatsList = function(results) {
if (inserted.length > 0 || updated || deleted) {
if (inserted.length > 0) {
if (!this.firstPoll) {
systemModel.PlayAlertSound(appModel.AppSettingsCurrent["AlertSound"]);
this.doNotification();
var offset = thisWidgetSetup.model.items.length - 1;
for (var i = 0; i < inserted.length; i++) {
thisWidgetSetup.model.items.push(inserted[i]);
@@ -649,7 +735,6 @@ MainAssistant.prototype.updateChatsList = function(results) {
this.controller.get('chatList').mojo.noticeAddedItems(offset, inserted);
} else {
thisWidgetSetup.model.items = inserted;
}
this.controller.modelChanged(thisWidgetSetup.model);
this.scrollToBottom();
@@ -658,6 +743,8 @@ MainAssistant.prototype.updateChatsList = function(results) {
Mojo.Log.info("No changes to apply to list.")
}
this.firstPoll = false;
//hide transmitting
this.controller.get('imgTransmit').style.display = "none";
//Also clean-up MyMessage history
for (var m = 0; m < appModel.AppSettingsCurrent["MyMessages"].length; m++) {
@@ -672,9 +759,21 @@ MainAssistant.prototype.updateChatsList = function(results) {
}
}
//Called by Mojo once the list has been painted, gives us an opportunity to force HTML into the message
//Called by Mojo once the list has been painted, gives us an opportunity to force HTML changes in the message
MainAssistant.prototype.handleItemRendered = function(listWidget, itemModel, itemNode) {
itemNode.innerHTML = this.unescapeEntities(itemNode.innerHTML);
if (!appModel.AppSettingsCurrent["SkipParsing"]) {
itemNode.innerHTML = this.replaceImageLinks(itemNode.innerHTML);
itemNode.innerHTML = this.parseShareSpaceLinks(itemNode.innerHTML);
itemNode.innerHTML = this.parseImgurLinks(itemNode.innerHTML);
}
if (appModel.AppSettingsCurrent["ShowEmojis"]) {
itemNode.innerHTML = this.renderTwimoji(itemNode.innerHTML);
}
if (itemNode.innerHTML.indexOf("</span> </div>") != -1) {
itemNode.innerHTML = itemModel.message;
Mojo.Log.warn("**** EMPTY MESSAGE RENDERED! " + JSON.stringify(itemModel));
}
//Hide emojis if app settings say to do so
if (!appModel.AppSettingsCurrent["ShowEmojis"]) {
@@ -687,7 +786,7 @@ MainAssistant.prototype.handleItemRendered = function(listWidget, itemModel, ite
//Used to show or hide command menu buttons
MainAssistant.prototype.toggleCommandMenu = function(show) {
Mojo.Log.warn("Toggling command menu");
Mojo.Log.info("Toggling command menu");
var stageController = Mojo.Controller.getAppController().getActiveStageController();
if (stageController) {
this.controller = stageController.activeScene();
@@ -708,17 +807,39 @@ MainAssistant.prototype.toggleCommandMenu = function(show) {
}
/* Helper Functions */
MainAssistant.prototype.doNotification = function() {
systemModel.PlayAlertSound(appModel.AppSettingsCurrent["AlertSound"]);
if (!this.maximized)
appModel.ShowNotificationStage();
else {}
}
MainAssistant.prototype.checkForUpdates = function() {
if (!appModel.UpdateCheckDone) {
appModel.UpdateCheckDone = true;
updaterModel.CheckForUpdate("webOS SimpleChat", function(responseObj) {
if (responseObj && responseObj.updateFound) {
updaterModel.PromptUserForUpdate(function(response) {
if (response)
updaterModel.InstallUpdate();
//First check for old version
var oldFound = false;
systemModel.GetInstalledApps(function(response) {
if (response && response.apps) {
for (var a = 0; a < response.apps.length; a++) {
if (response.apps[a].id == "com.notalexnoyle.simplechat") {
oldFound = true;
}
}
}
if (oldFound) {
Mojo.Additions.ShowDialogBox("Deprecated App Found", "It looks like you have both the old and the new SimpleChat apps installed. This will cause problems with notifications. It is strongly recommended that you delete the old version of SimpleChat -- the old icon looks like this:<p align='center' style='margin:0px'><img src='assets/oldicon.png'></p>");
} else {
appModel.UpdateCheckDone = true;
updaterModel.CheckForUpdate(Mojo.Controller.appInfo.appMuseumTitle, function(responseObj) {
if (responseObj && responseObj.updateFound) {
updaterModel.PromptUserForUpdate(function(response) {
if (response)
updaterModel.InstallUpdate();
}.bind(this));
}
}.bind(this));
}
}.bind(this));
}.bind(this))
}
}
@@ -752,6 +873,157 @@ MainAssistant.prototype.scrollToPosition = function(scrollPos) {
MainAssistant.prototype.unescapeEntities = function(str) {
str = str.replace(/&gt;/g, ">");
str = str.replace(/&lt;/g, "<");
str = str
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
.replace(/^~~ (.*$)/gim, '<strike>$1</strike>')
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
.replace(/^\> (.*$)/gim, '<blockquote>$1</blockquote>')
.replace(/\*\*(.*)\*\*/gim, '<b>$1</b>')
.replace(/\*(.*?)\*/gim, '<i>$1</i>')
.replace(/\`(.*)\`/gim, '<code>$1</code>')
.replace(/\[(.*?)\]\((.*?)\)/gim, "<a href='$2'>$1</a>")
return str;
}
MainAssistant.prototype.renderTwimoji = function(html) {
if (!html) return html;
var parts = html.split(/(<[^>]+>)/g);
for (var i = 0; i < parts.length; i++) {
if (parts[i].indexOf("<") !== 0) {
parts[i] = this.replaceEmojis(parts[i]);
}
}
return parts.join("");
}
MainAssistant.prototype.replaceEmojis = function(text) {
if (!text) return text;
return text.replace(this.getEmojiRegex(), function(match) {
return this.buildTwimojiSpan(match);
}.bind(this));
}
MainAssistant.prototype.buildTwimojiSpan = function(emojiText) {
var codepoints = [];
for (var i = 0; i < emojiText.length; i++) {
var code = emojiText.charCodeAt(i);
if (code >= 0xD800 && code <= 0xDBFF && i + 1 < emojiText.length) {
var high = code;
var low = emojiText.charCodeAt(i + 1);
if (low >= 0xDC00 && low <= 0xDFFF) {
code = ((high - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
i++;
}
}
codepoints.push(code.toString(16));
}
var className = "emoji" + codepoints.join("");
return "<span name=\"emoji\" class=\"emoji-outer emoji-sizer\"><span class=\"emoji-inner " + className + "\">" + emojiText + "</span></span>";
}
MainAssistant.prototype.getEmojiRegex = function() {
if (this.emojiRegex) {
return this.emojiRegex;
}
var basicEmoji = "[\\u203C\\u2049\\u2122\\u2139\\u2194-\\u2199\\u21A9-\\u21AA\\u231A-\\u231B\\u2328\\u23CF\\u23E9-\\u23F3\\u23F8-\\u23FA\\u24C2\\u25AA-\\u25FE\\u2600-\\u27BF\\u2934-\\u2935\\u2B05-\\u2B55\\u3030\\u303D\\u3297\\u3299]";
var surrogateEmoji = "[\\uD83C-\\uDBFF][\\uDC00-\\uDFFF]";
var emojiChar = "(?:" + basicEmoji + "|" + surrogateEmoji + ")";
var variation = "[\\uFE0E\\uFE0F]?";
var modifier = "(?:\\uD83C[\\uDFFB-\\uDFFF])?";
var zwj = "(?:\\u200D" + emojiChar + variation + modifier + ")*";
var keycap = "[#*0-9]\\uFE0F?\\u20E3";
var flag = "\\uD83C[\\uDDE6-\\uDDFF]\\uD83C[\\uDDE6-\\uDDFF]";
this.emojiRegex = new RegExp(flag + "|" + keycap + "|" + emojiChar + variation + modifier + zwj, "g");
return this.emojiRegex;
}
MainAssistant.prototype.replaceImageLinks = function(str) {
if (typeof btoa === 'undefined')
return str;
var urlBase = serviceModel.makeServiceUrl(this.serviceEndpointBase, "image.php") + "?";
var pattern = /href=\"(http)?:?(\/\/[^"']*\.(?:png|jpg|jpeg|gif|png|svg))/g;
str = str.replace(pattern, function(match, protocol, url) {
return "href=\"" + urlBase + btoa("http:" + url);
});
var pattern = /href=\"(https)?:?(\/\/[^"']*\.(?:png|jpg|jpeg|gif|png|svg))/g;
str = str.replace(pattern, function(match, protocol, url) {
return "href=\"" + urlBase + btoa("https:" + url);
});
return str;
}
MainAssistant.prototype.parseShareSpaceLinks = function(str) {
if (appModel.AppSettingsCurrent["ShowWOSAThumbs"] == undefined) {
Mojo.Log.warn("Thumbnail setting missing, adding with default value: " + appModel.AppSettingsDefaults["ShowWOSAThumbs"]);
appModel.AppSettingsCurrent["ShowWOSAThumbs"] = appModel.AppSettingsDefaults["ShowWOSAThumbs"];
appModel.SaveSettings();
}
if (appModel.AppSettingsCurrent["ShowWOSAThumbs"] == true) {
if (str.indexOf("share.wosa.link") != -1) {
Mojo.Log.info("Found WOSA link to parse! " + str);
if (str.indexOf("download.php")) {
str = str.replace("download.php", "image.php");
var linkPortion = str.split("image.php?");
if (Array.isArray(linkPortion) && linkPortion.length > 1) {
linkPortion = linkPortion[1];
linkPortion = linkPortion.replace("\"", " ");
linkPortion = linkPortion.replace("'", " ");
linkPortion = linkPortion.replace("<", " ");
linkPortion = linkPortion.replace(">", " ");
linkPortion = linkPortion.split(" ");
linkPortion = linkPortion[0];
if (str.indexOf("t.php") != -1) {
linkPortion = "http://share.wosa.link/tthumb.php?" + linkPortion;
} else {
linkPortion = "http://share.wosa.link/ithumb.php?" + linkPortion;
}
var thumbnail = "<img src=\"" + linkPortion + "\" style=\"float:right; width: 64px; height: 64px; margin-top: -2px;\"> "
str = thumbnail + str;
Mojo.Log.warn("Parsed WOSA link html: " + str);
}
}
}
}
return str;
}
MainAssistant.prototype.parseImgurLinks = function(str) {
var newStr = str;
if (str.indexOf("https://imgur.com") != -1) {
Mojo.Log.error("Found Imgur link to parse!");
var urlBase = serviceModel.makeServiceUrl(this.serviceEndpointBase, "imgurproxy.php");
urlBase = urlBase + "?maxwidth=" + Mojo.Environment.DeviceInfo.screenWidth + "&album=";
links = this.detectURLs(str);
for (var l=0;l<links.length;l++) {
if (links[l].indexOf("imgur.com") != -1 && links[l].indexOf("imgurproxy.php") == -1) {
var newLink = links[l].replace(links[l], urlBase + links[l]);
Mojo.Log.error("Replaced Imgur link " + links[l] + " with " + newLink);
newStr = newStr.replace(links[l], newLink);
}
}
}
return newStr;
}
MainAssistant.prototype.expandAttachments = function(str, itemData) {
var linkBase = serviceModel.makeServiceUrl(this.serviceEndpointBase, "attachment.php?");
var imgBase = serviceModel.makeServiceUrl(this.serviceEndpointBase, "thumb.php") + "?image=";
var supportedAttachments = ['jpg', 'jpeg', 'png', 'gif'];
if (itemData.attachments) {
str += "<br>";
Mojo.Log.info("Attachments found: " + itemData.attachments.length);
for (var i=0;i<itemData.attachments.length;i++) {
if (supportedAttachments.indexOf(itemData.attachments[i].extension.toLowerCase()) != -1) {
str += "<a href='" + linkBase + itemData.attachments[i].filename + "'>";
str += "<img src='" + imgBase + itemData.attachments[i].filename + "&size=64'></a>";
}
}
}
return str;
}
@@ -774,19 +1046,45 @@ MainAssistant.prototype.detectURLs = function(message) {
MainAssistant.prototype.disableUI = function(statusValue) {
//start spinner
if (!this.spinnerModel.spinning) {
this.spinnerModel.spinning = true;
this.controller.modelChanged(this.spinnerModel);
if (!this.spinnerWorkModel.spinning) {
this.spinnerWorkModel.spinning = true;
this.controller.modelChanged(this.spinnerWorkModel);
}
}
MainAssistant.prototype.enableUI = function() {
//stop spinner
this.spinnerModel.spinning = false;
this.controller.modelChanged(this.spinnerModel);
this.spinnerWorkModel.spinning = false;
this.controller.modelChanged(this.spinnerWorkModel);
this.controller.get('txtMessage').mojo.focus();
}
MainAssistant.prototype.initDefaultSettings = function() {
//Set an alert sound
if (!appModel.AppSettingsCurrent["AlertSound"] || appModel.AppSettingsCurrent["AlertSound"] == "") {
appModel.AppSettingsCurrent["AlertSound"] = "Subtle (short)";
}
//Check for Enter option
if (appModel.AppSettingsCurrent["EnterSubmits"] == null) {
Mojo.Log.info("Adding EnterSubmits setting with default value!")
appModel.AppSettingsCurrent["EnterSubmits"] = appModel.AppSettingsDefaults["EnterSubmits"];
appModel.SaveSettings();
}
//Check for Emoji option
if (appModel.AppSettingsCurrent["ShowEmojis"] == null) {
Mojo.Log.info("Adding Emoji setting with default value!")
appModel.AppSettingsCurrent["ShowEmojis"] = appModel.AppSettingsDefaults["ShowEmojis"];
appModel.SaveSettings();
}
//Init Message memory
if (!appModel.AppSettingsCurrent["MyMessages"]) {
appModel.AppSettingsCurrent["MyMessages"] = [];
}
if (appModel.AppSettingsCurrent["LastKnownMessages"]) { //This was the old way, clean it up
appModel.AppSettingsCurrent["LastKnownMessages"] = null;
}
}
MainAssistant.prototype.rememberLastMessage = function() {
var thisWidgetSetup = this.controller.getWidgetSetup("chatList");
if (thisWidgetSetup.model.items.length > 0) {
@@ -797,26 +1095,44 @@ MainAssistant.prototype.rememberLastMessage = function() {
/* Mojo Lifecycle Stuff */
MainAssistant.prototype.activateWindow = function(event) {
Mojo.Log.info("SimpleChat being maximized!");
this.maximized = true;
this.rememberLastMessage();
appModel.CloseNotificationStage();
//If we were re-launched with some share text, insert that
if (appModel.LaunchQuery != "") {
Mojo.Log.info("using launch action: " + appModel.LaunchQuery);
this.controller.get('txtMessage').mojo.setValue(appModel.LaunchQuery);
appModel.LaunchQuery = "";
}
};
MainAssistant.prototype.deactivateWindow = function(event) {
Mojo.Log.info("SimpleChat being minimized!");
this.maximized = false;
this.rememberLastMessage();
};
MainAssistant.prototype.deactivate = function(event) {
/* remove any event handlers you added in activate and do any other cleanup that should happen before
this scene is popped or another scene is pushed on top */
Mojo.Log.info("Main scene deactivated " + Mojo.Controller.appInfo.id);
Mojo.Log.info("Main scene deactivating " + Mojo.Controller.appInfo.id);
this.maximized = false;
//Remember last known messages
//Get ready for background sync
Mojo.Log.info("Switching to background sync");
this.pausePollingServer();
this.rememberLastMessage();
systemModel.ClearSystemAlarm("SimpleChat");
if (appModel.AppSettingsCurrent["BackgroundUpdate"] && appModel.AppSettingsCurrent["BackgroundUpdate"] != "" && appModel.AppSettingsCurrent["BackgroundUpdate"] != -1)
systemModel.SetSystemAlarmRelative("SimpleChat", appModel.AppSettingsCurrent["BackgroundUpdate"]);
//Detach UI
//Detach UI handlers
Mojo.Event.stopListening(this.controller.get("chatList"), Mojo.Event.listTap, this.handleListClick);
this.controller.window.removeEventListener('resize', this.orientationChanged);
this.controller.document.getElementById("divComposeTitle").removeEventListener("click", this.toggleCommandMenu.bind(this));
//this.controller.document.getElementById("divComposeTitle").removeEventListener("click", this.toggleCommandMenu.bind(this));
this.controller.document.getElementById("spanCompose").removeEventListener("click", this.toggleCommandMenu.bind(this));
this.controller.document.getElementById("imgTwisty").removeEventListener("click", this.toggleCommandMenu.bind(this));
Mojo.Event.stopListening(this.controller.stageController.document, Mojo.Event.stageActivate, this.activateWindow);
Mojo.Event.stopListening(this.controller.stageController.document, Mojo.Event.stageDeactivate, this.deactivateWindow);
@@ -827,4 +1143,4 @@ MainAssistant.prototype.cleanup = function(event) {
a result of being popped off the scene stack */
var appController = Mojo.Controller.getAppController();
appController.closeAllStages();
};
};
@@ -8,6 +8,16 @@ function PreferencesAssistant() {
PreferencesAssistant.prototype.setup = function() {
/* setup widgets here */
//Theme picker
this.controller.setupWidget("listThemePreference",
{label: $L({value:"Theme", key:"theme"}),
labelPlacement: Mojo.Widget.labelPlacementLeft,
choices: [
{label: $L({value:"Light", key:"themeLight"}), value: "palm-default"},
{label: $L({value:"Dark", key:"themeDark"}), value: "palm-dark"},
{label: $L({value:"System Pref", key:"themeSystem"}), value: "system-theme"}
]},
{ value: appModel.AppSettingsCurrent["ThemePreference"] });
//Timeout picker
this.controller.setupWidget("listForegroundUpdate",
this.attributes = {
@@ -15,6 +25,7 @@ PreferencesAssistant.prototype.setup = function() {
choices: [
{ label: "5 seconds", value: 5000 },
{ label: "10 seconds", value: 10000 },
{ label: "20 seconds", value: 20000 },
{ label: "30 second", value: 30000 },
{ label: "1 minute", value: 60000 },
{ label: "2 minutes", value: 120000 },
@@ -36,9 +47,14 @@ PreferencesAssistant.prototype.setup = function() {
label: $L("Background Update"),
choices: [
{ label: "Off", value: -1 },
{ label: "5 minutes", value: "00:05:00" },
{ label: "10 minutes", value: "00:10:00" },
{ label: "15 minutes", value: "00:15:00" },
{ label: "30 minutes", value: "00:30:00" },
{ label: "45 minutes", value: "00:45:00" },
{ label: "1 Hour", value: "01:00:00" },
{ label: "2 Hours", value: "02:00:00" },
{ label: "3 Hours", value: "02:00:00" },
{ label: "5 Hours", value: "05:00:00" },
]
},
@@ -47,6 +63,21 @@ PreferencesAssistant.prototype.setup = function() {
disabled: false
}
);
//Enter Action listEnterSubmits
this.controller.setupWidget("listEnterSubmits",
this.attributes = {
label: $L("Enter Submits"),
choices: [
{ label: "Never", value: "never" },
{ label: "Collapsed", value: "collapsed" },
{ label: "Always", value: "always" }
]
},
this.model = {
value: appModel.AppSettingsCurrent["EnterSubmits"],
disabled: false
}
);
//Emoji toggle
this.controller.setupWidget("toggleEmojis",
this.attributes = {
@@ -58,6 +89,28 @@ PreferencesAssistant.prototype.setup = function() {
disabled: false
}
);
//Link toggle
this.controller.setupWidget("toggleParsing",
this.attributes = {
trueValue: true,
falseValue: false
},
this.model = {
value: appModel.AppSettingsCurrent["SkipParsing"],
disabled: false
}
);
//Thumbnail toggle
this.controller.setupWidget("toggleThumbs",
this.attributes = {
trueValue: true,
falseValue: false
},
this.model = {
value: appModel.AppSettingsCurrent["ShowWOSAThumbs"],
disabled: false
}
);
//Setup sound picker
this.soundChoices = {
choices: [
@@ -144,9 +197,13 @@ PreferencesAssistant.prototype.setup = function() {
this.controller.setupWidget(Mojo.Menu.appMenu, this.appMenuAttributes, this.appMenuModel);
/* add event handlers to listen to events from widgets */
Mojo.Event.listen(this.controller.get("listThemePreference"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.listen(this.controller.get("listForegroundUpdate"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.listen(this.controller.get("listBackgroundUpdate"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.listen(this.controller.get("toggleEmojis"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.listen(this.controller.get("toggleParsing"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.listen(this.controller.get("toggleThumbs"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.listen(this.controller.get("listEnterSubmits"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.listen(this.controller.get("listAlertSound"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.listen(this.controller.get("txtClientAPI"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
Mojo.Event.listen(this.controller.get("txtEndpointURL"), Mojo.Event.propertyChange, this.handleValueChange.bind(this));
@@ -169,8 +226,17 @@ PreferencesAssistant.prototype.handleValueChange = function(event) {
Mojo.Log.info(event.srcElement.id + " value changed to " + event.value);
switch (event.srcElement.id) {
case "listThemePreference":
appModel.AppSettingsCurrent["ThemePreference"] = event.value;
appModel.SetThemePreference(this.controller);
break;
case "toggleParsing":
Mojo.Controller.getAppController().showBanner({ messageText: "Re-launch app to apply this setting" }, "", "");
break;
case "toggleEmojis":
Mojo.Log.info("Should show banner!");
Mojo.Controller.getAppController().showBanner({ messageText: "Re-launch app to apply this setting" }, "", "");
break;
case "toggleThumbs":
Mojo.Controller.getAppController().showBanner({ messageText: "Re-launch app to apply this setting" }, "", "");
break;
case "listAlertSound":
@@ -274,17 +340,17 @@ PreferencesAssistant.prototype.okClick = function(event) {
PreferencesAssistant.prototype.deactivate = function(event) {
/* remove any event handlers you added in activate and do any other cleanup that should happen before
this scene is popped or another scene is pushed on top */
systemModel.ClearSystemAlarm("SimpleChat");
if (appModel.AppSettingsCurrent["BackgroundUpdate"] && appModel.AppSettingsCurrent["BackgroundUpdate"] != "" && appModel.AppSettingsCurrent["BackgroundUpdate"] != -1)
systemModel.SetSystemAlarmRelative("SimpleChat", appModel.AppSettingsCurrent["BackgroundUpdate"]);
Mojo.Event.stopListening(this.controller.get("listForegroundUpdate"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("listThemePreference"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("listBackgroundUpdate"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("toggleEmojis"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("toggleParsing"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("toggleThumbs"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("listEnterSubmits"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("listAlertSound"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("txtClientAPI"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("toggleClientAPI"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("toggleCustomEndPoint"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("txtClientAPI"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("txtEndpointURL"), Mojo.Event.propertyChange, this.handleValueChange);
Mojo.Event.stopListening(this.controller.get("btnOK"), Mojo.Event.tap, this.okClick.bind(this));
};
File renamed without changes.
File renamed without changes.
+178
View File
@@ -0,0 +1,178 @@
function VersionAssistant(startup) {
this.isStartup = startup;
// on first start, this message is displayed, along with the current version message from below
this.startupMessage = Mojo.Controller.appInfo.startupMessage;
//New Features
this.changelog = [
{
version: "Version 1.9.0",
detail: [
"Share images in the chat, right from your webOS device!",
]
},
{
version: "Version 1.8.6",
detail: [
"Fix a bug parsing certain kinds of links.",
]
},
{
version: "Version 1.8.5",
detail: [
"Add support for sharing from JustType and other apps.",
]
},
{
version: "Version 1.8.0",
detail: [
"Dark theme now available in Preferences!",
]
},
{
version: "Version 1.7.1",
detail: [
"Fix a bug where hardware mute switch wasn't respected. Show transmit indicator. Add ability to disable link and image parsing to speed things up on tiny devices.",
]
},{
version: "Version 1.7.0",
detail: [
"Images from Discord are now supported! Inline rendering uses a thumbnail for efficiency; tap the image to load the fullsize, or tap elsewhere on the row to pull up the menu that lets you download the image to your device (it will be saved to /internal/downloads)",
]
},{
version: "Version 1.6.0",
detail: [
"Added ability to parse Imgur album links and render via a service proxy.",
]
},{
version: "Version 1.5.0",
detail: [
"Added ability to parse Share Services links and show thumbnails, configurable in Preferences.",
]
},{
version: "Version 1.4.1",
detail: [
"Add support for some basic markdown",
]
},
{
version: "Version 1.4.0",
detail: [
"Enter key behavior as a Preference",
"Even more silent background sync, won't sync if offline",
"Swiping notifications resets missed message count",
"Adjust tap target for collapsing controls"
]
},
{
version: "Version 1.3.2",
detail: [
"More sync timing options.",
"Refactored some code to make forking easier."
]
}, {
version: "Version 1.3.1",
detail: [
"Enter key now sends message when Command menu buttons are hidden."
]
},
{
version: "Version 1.3.0",
detail: [
"Drastically improved background update and notifications."
]
}, {
version: "Version 1.2.0",
detail: [
"Converts image links to an image proxy so they can be viewed and scaled properly on tiny devices",
"Improved background update, using a notification launch, no longer interupts Exhibition, video playback or One Night Stand"
]
},
{
version: "Version 1.1.3",
detail: [
"Bug fixes and general maintenance"
]
}, {
version: "Version 1.1.2",
detail: [
"Show/Hide the Command menu buttons by tapping the Compose area title bar",
"Enable/Disable Discord Emojis in Preferences",
]
}, {
version: "Version 1.1.1",
detail: [
"Improved Discord integration, including showing Emojis!<span class='emoji-outer emoji-sizer'><span class='emoji-inner emoji1f388'></span></span>",
"Now with a smaller emoji file!",
"Improved timeout logic.",
"Trying different background sync logic."
]
}
];
// setup command menu
this.cmdMenuModel = {
visible: false,
items: [
{},
{
label: $L("OK! Let's Go..."),
command: 'do-continue'
},
{}
]
};
};
VersionAssistant.prototype.setup = function() {
this.titleElement = this.controller.get('title');
this.dataElement = this.controller.get('data');
this.titleElement.innerHTML = $L('Version Info');
var html = '';
html += '<div style="margin: 2px 12px 12px 12px">' + this.startupMessage + '</div>';
for (var i = 0; i < this.changelog.length; i++) {
html += Mojo.View.render({ object: { title: this.changelog[i].version }, template: 'version/rowDivider' });
html += '<ul>';
for (var j = 0; j < this.changelog[i].detail.length; j++) {
html += '<li>' + this.changelog[i].detail[j] + '</li>';
}
html += '</ul>';
}
// set data
this.dataElement.innerHTML = html;
// setup menu
this.controller.setupWidget(Mojo.Menu.appMenu, { omitDefaultItems: true }, { visible: false });
//if (this.isStartup) {
// set command menu
this.controller.setupWidget(Mojo.Menu.commandMenu, { menuClass: 'no-fade' }, this.cmdMenuModel);
//}
};
VersionAssistant.prototype.activate = function(event) {
this.timer = this.controller.window.setTimeout(this.showContinue.bind(this), 2 * 1000);
};
VersionAssistant.prototype.deactivate = function(event) {};
VersionAssistant.prototype.cleanup = function(event) {};
VersionAssistant.prototype.showContinue = function() {
// show the command menu
this.controller.setMenuVisible(Mojo.Menu.commandMenu, true);
};
VersionAssistant.prototype.handleCommand = function(event) {
if (event.type == Mojo.Event.command) {
switch (event.command) {
case 'do-continue':
this.controller.stageController.popScene();
break;
}
}
};
@@ -2,14 +2,18 @@
Mojo Additions
Version 0.3e
Created: 2018
Author: Jonathan Wise
Author: Jon W
License: MIT
Description: Functions I use often, that probably should have been part of Mojo.
They either fix bugs in Mojo or make it easier to use.
*/
//** Note: If you synced this file from a common repository, local edits may be over-written! */
this.Additions = function() {}
console.log("** Mojo Additions loaded **");
Additions.ShowDialogBox = function(title, message) {
var stageController = Mojo.Controller.getAppController().getActiveStageController();
if (stageController) {
@@ -20,7 +24,6 @@ Additions.ShowDialogBox = function(title, message) {
title: title,
message: message,
choices: [{ label: 'OK', value: 'OK' }],
preventCancel: true,
allowHTMLMessage: true
});
}
@@ -62,6 +65,16 @@ Additions.SetWidgetLabel = function(widgetName, newvalue) {
}
}
Additions.GetWidgetLabel = function(widgetName) {
var stageController = Mojo.Controller.getAppController().getActiveStageController();
if (stageController) {
this.controller = stageController.activeScene();
var thisWidgetModel = this.controller.getWidgetSetup(widgetName).model;
return thisWidgetModel.label;
}
}
Additions.SetToggleState = function(widgetName, toggledValue) {
var stageController = Mojo.Controller.getAppController().getStageController("");
if (stageController) {
+224
View File
@@ -0,0 +1,224 @@
/*
App Model
Version 0.4
Created: 2018
Author: Jon W
License: MIT
Description: Common functions for webOS apps, particularly for managing persisted options in cookies
*/
var AppModel = function() {
//Define your default scene (needed for settings management)
this.DefaultScene = "main";
//Define your app-wide, non-persisted settings here
this.UpdateCheckDone = false;
this.LaunchQuery = "";
this.ServiceEndpointBase = Mojo.Controller.appInfo.serviceURL;
this.ClientId = secrets.clientid;
this.LastMessageSelected = null;
//Define your app preferences (to be saved by OS)
this.AppSettingsCurrent = null;
this.AppSettingsDefaults = {
ThemePreference: "palm-default",
ForegroundUpdate: 10000,
BackgroundUpdate: "00:30:00",
AlertSound: "Subtle (short)",
ShowEmojis: true,
SkipParsing: false,
ShowWOSAThumbs: true,
EnterSubmits: "collapsed",
UseClientAPIKey: false,
UseCustomEndpoint: false,
ClientAPIKey: "",
EndpointURL: "",
SenderName: "webOS User",
FirstRun: true,
MyMessages: null,
LastKnownMessage: null,
LastVersionRun: 0
};
}
AppModel.prototype.SetThemePreference = function(theController) {
if (appModel.AppSettingsCurrent["ThemePreference"] != "system-theme") {
theController.document.body.className = appModel.AppSettingsCurrent["ThemePreference"];
Mojo.Log.info("Using local theme pref: " + appModel.AppSettingsCurrent["ThemePreference"]);
} else {
systemModel.LoadWOSAPrefs(function(response) {
if (response) {
Mojo.Log.error("Using system theme pref: " + systemModel.WOSAPrefs.theme);
theController.document.body.className = systemModel.WOSAPrefs.theme;
}
}.bind(this))
}
}
AppModel.prototype.ShowNotificationStage = function() {
Mojo.Log.info("App Model checking if we need to show Dashboard stage...");
this.serviceEndpointBase = appModel.ServiceEndpointBase;
if (appModel.AppSettingsCurrent["UseCustomEndpoint"] && appModel.AppSettingsCurrent["EndpointURL"]) {
this.serviceEndpointBase = appModel.AppSettingsCurrent["EndpointURL"];
}
if (appModel.AppSettingsCurrent["UseClientAPIKey"] && appModel.AppSettingsCurrent["ClientAPIKey"]) {
this.ClientId = appModel.AppSettingsCurrent["ClientAPIKey"];
}
//Check for connectivity, then check for messages
systemModel.GetInternetConnectionState(this.checkForMessages.bind(this));
}
AppModel.prototype.checkForMessages = function(connMgrResponse) {
Mojo.Log.info("App Model got response from connection manager: " + JSON.stringify(connMgrResponse));
if (connMgrResponse && connMgrResponse.isInternetConnectionAvailable) {
Mojo.Log.info("Checking for new messages in App Model...");
serviceModel.getChats(this.serviceEndpointBase, this.ClientId, function(response) {
Mojo.Log.info("App Model got response from getChats: " + response);
if (response != null && response != "") {
var responseObj = JSON.parse(response);
if (responseObj.status != "error") {
if (responseObj.messages && responseObj.messages.length > 0) {
var newMessageCount = this.findNewMessageCount(appModel.AppSettingsCurrent["LastKnownMessage"], responseObj.messages);
if (newMessageCount > 0) {
Mojo.Log.info("Found new chat on server during App Model message check!");
this.actuallyShowNotificationStage();
}
}
}
}
}.bind(this));
}
}
AppModel.prototype.findNewMessageCount = function(LastKnownMessage, newMessages) {
var actuallyNewMessages = [];
var startCounting = false;
for (var j = 0; j < newMessages.length; j++) {
if (startCounting) {
actuallyNewMessages.push(newMessages[j].uid);
}
if (newMessages[j].uid == LastKnownMessage) {
startCounting = true;
}
}
return actuallyNewMessages.length;
}
AppModel.prototype.actuallyShowNotificationStage = function() {
this.controller = Mojo.Controller.getAppController();
var dashboardStage = this.controller.getStageProxy("dashboard");
var pushDashScene = function(stageController) {
stageController.pushScene('dashboard', "dashboard/dashboard-scene");
}.bind(this);
if (!dashboardStage) {
Mojo.Log.info("Dashboard stage not open, creating it...");
this.controller.createStageWithCallback({
name: 'dashboard',
lightweight: true,
height: 100,
soundclass: "assets/silent.mp3"
}, pushDashScene, 'dashboard');
} else {
Mojo.Log.info("Dashboard stage already open, activating it...");
var dashboardStageController = this.controller.getStageController("dashboard");
if (dashboardStageController) {
dashboardStageController.activate();
dashboardStageController.delegateToSceneAssistant("activate");
}
}
}
AppModel.prototype.CloseNotificationStage = function() {
this.controller = Mojo.Controller.getAppController();
var dashboardStage = this.controller.getStageProxy("dashboard");
if (!dashboardStage) {
Mojo.Log.info("Dashboard stage not open, nothing to do");
} else {
Mojo.Log.info("Dashboard stage open, closing it...");
this.controller.closeStage("dashboard");
}
}
//#region Inherited from common App Model
//You probably don't need to change the below functions since they all work against the Cookie defaults you defined above.
// LoadSettings: call when your app starts, or you want to load previously persisted options.
// SaveSettings: call any time you want to persist an option.
// ResetSettings: call if you want to forget stored settings and return to defaults. Your default scene will be popped and re-pushed.
AppModel.prototype.LoadSettings = function(safe) {
this.AppSettingsCurrent = this.AppSettingsDefaults;
var loadSuccess = false;
var settingsCookie = new Mojo.Model.Cookie("settings");
try {
appSettings = settingsCookie.get();
if ((typeof appSettings == "undefined" || appSettings == null) || (safe && !this.checkSettingsValid(appSettings))) {
Mojo.Log.warn("** Using first run default settings");
} else {
Mojo.Log.info("** Using cookie settings!");
this.loadCookieIntoCurrent(appSettings);
loadSuccess = true;
}
} catch (ex) {
settingsCookie.put(null);
Mojo.Log.error("** Settings cookie were corrupt and have been purged!");
Mojo.Log.error(ex);
}
return loadSuccess;
}
AppModel.prototype.loadCookieIntoCurrent = function(cookieSettings) {
this.AppSettingsCurrent = cookieSettings;
}
AppModel.prototype.checkSettingsValid = function(loadedSettings) {
var retValue = true;
for (var key in this.AppSettingsDefaults) {
if (typeof loadedSettings[key] === undefined || loadedSettings[key] == null) {
Mojo.Log.warn("** An expected saved setting, " + key + ", was null or undefined.");
retValue = false;
}
if (typeof loadedSettings[key] !== typeof this.AppSettingsDefaults[key]) {
Mojo.Log.warn("** A saved setting, " + key + ", was of type " + typeof(loadedSettings[key]) + " but expected type " + typeof(this.AppSettingsDefaults[key]));
retValue = false;
}
if (typeof this.AppSettingsDefaults[key] === "string" && this.AppSettingsDefaults[key].indexOf(this.BaseDateString) != -1 && loadedSettings[key].indexOf(this.BaseDateString)) {
Mojo.Log.warn("** A saved setting could not be compared to an expected date value.");
retValue = false;
}
if (typeof this.AppSettingsDefaults[key] === "string" && (this.AppSettingsDefaults[key] == "false" || this.AppSettingsDefaults[key] == "true")) {
if (loadedSettings[key] != "false" && loadedSettings[key] != "true") {
Mojo.Log.warn("** A saved setting did not have the expected boolean value.");
retValue = false;
}
}
}
return retValue;
}
AppModel.prototype.SaveSettings = function() {
var settingsCookie = new Mojo.Model.Cookie("settings");
Mojo.Log.info("Saving settings as: " + JSON.stringify(appModel.AppSettingsCurrent));
Mojo.Log.info("Default settings at Save time are: " + JSON.stringify(appModel.AppSettingsDefaults));
settingsCookie.put(appModel.AppSettingsCurrent);
}
AppModel.prototype.ResetSettings = function() {
Mojo.Log.info("resetting settings");
//Tell main scene to drop settings
this.AppSettingsCurrent = this.AppSettingsDefaults;
this.SaveSettings();
Mojo.Log.info("settings have been reset");
var stageController = Mojo.Controller.getAppController().getActiveStageController();
stageController.popScene(this.DefaultScene);
Mojo.Log.info("closed default scene");
//Restart main scene
stageController.pushScene(this.DefaultScene);
Mojo.Log.info("re-opened default scene");
}
//#endregion
+175
View File
@@ -0,0 +1,175 @@
var ServiceModel = function() {
};
ServiceModel.prototype.makeServiceUrl = function(serviceBase, endPoint) {
if (endPoint.indexOf(".php") == -1)
endPoint = endPoint + ".php";
return serviceBase + endPoint;
}
ServiceModel.prototype.getChats = function(serviceBase, clientId, callback) {
var serviceURL = serviceBase + "get-chat.php";
Mojo.Log.info("Trying to get chats from: " + serviceURL);
this.retVal = "";
if (callback)
callback = callback.bind(this);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", serviceURL);
xmlhttp.setRequestHeader("Client-Id", clientId);
xmlhttp.send();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if (callback)
callback(xmlhttp.responseText);
}
}.bind(this);
}
ServiceModel.prototype.postChat = function(useSender, useMessage, serviceBase, clientId, callback) {
var serviceURL = serviceBase + "post-chat.php";
var msgToPost = {
sender: useSender,
message: useMessage
}
Mojo.Log.info("Posting chat to: " + serviceURL);
Mojo.Log.info(JSON.stringify(msgToPost));
this.retVal = "";
if (callback)
callback = callback.bind(this);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", serviceURL);
xmlhttp.setRequestHeader("Client-Id", clientId);
xmlhttp.timeout = 2000;
xmlhttp.send(JSON.stringify(msgToPost));
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if (callback)
callback(xmlhttp.responseText);
}
}.bind(this);
xmlhttp.ontimeout = function() {
callback();
}.bind(this);
}
ServiceModel.prototype.editChat = function(useSender, useMessage, uid, editKey, serviceBase, clientId, callback) {
var serviceURL = serviceBase + "edit-chat.php";
var msgToPost = {
sender: useSender,
message: useMessage,
uid: uid,
editKey: editKey
}
Mojo.Log.info("Edit chat to: " + serviceURL);
Mojo.Log.info(JSON.stringify(msgToPost));
this.retVal = "";
if (callback)
callback = callback.bind(this);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", serviceURL);
xmlhttp.setRequestHeader("Client-Id", clientId);
xmlhttp.send(JSON.stringify(msgToPost));
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if (callback)
callback(xmlhttp.responseText);
}
}.bind(this);
}
ServiceModel.prototype.uploadPhoto = function(fullFilePath, sender, serviceBase, clientId, callback, errorhandler) {
var useURL = serviceBase + 'upload-attachment.php';
Mojo.Log.info("Uploading photo: " + fullFilePath + " to " + useURL);
return new Mojo.Service.Request('palm://com.palm.downloadmanager/', {
method: 'upload',
parameters: {
fileName: fullFilePath,
url: useURL,
fileLabel: 'image',
contentType: (function(p) {
var e = p.split('.').pop().toLowerCase();
return e === 'png' ? 'image/png' : e === 'gif' ? 'image/gif' : 'image/jpeg';
}(fullFilePath)),
postParameters: [
{ key: 'sender', data: sender }
],
customHttpHeaders: ['Client-Id: ' + clientId],
subscribe: true
},
onSuccess: function(e) {
if (!e.completed) return;
if (e.completionCode !== 0 || e.httpCode !== 200) {
if (errorhandler) errorhandler('Upload failed: HTTP ' + e.httpCode + ', code ' + e.completionCode);
return;
}
try {
var result = JSON.parse(e.responseString);
if (callback) callback(result);
} catch (ex) {
if (errorhandler) errorhandler('Bad response from upload server');
}
},
onFailure: function(e) {
if (errorhandler) errorhandler('Upload error: ' + JSON.stringify(e));
}
});
};
ServiceModel.prototype.postChatWithAttachment = function(useSender, attachments, serviceBase, clientId, callback) {
var serviceURL = serviceBase + 'post-chat.php';
var msgToPost = {
sender: useSender,
message: '',
attachments: attachments
};
Mojo.Log.info("Posting chat with attachment to: " + serviceURL);
if (callback)
callback = callback.bind(this);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", serviceURL);
xmlhttp.setRequestHeader("Client-Id", clientId);
xmlhttp.timeout = 5000;
xmlhttp.send(JSON.stringify(msgToPost));
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if (callback)
callback(xmlhttp.responseText);
}
}.bind(this);
xmlhttp.ontimeout = function() {
if (callback) callback();
}.bind(this);
};
ServiceModel.prototype.likeChat = function(uid, serviceBase, clientId, callback) {
var serviceURL = serviceBase + "like-chat.php";
var msgToPost = {
uid: uid,
like: "+1"
}
Mojo.Log.info("Like chat to: " + serviceURL);
Mojo.Log.info(JSON.stringify(msgToPost));
this.retVal = "";
if (callback)
callback = callback.bind(this);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", serviceURL);
xmlhttp.setRequestHeader("Client-Id", clientId);
xmlhttp.send(JSON.stringify(msgToPost));
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if (callback)
callback(xmlhttp.responseText);
}
}.bind(this);
}
+345 -24
View File
@@ -1,16 +1,42 @@
/*
System Model
Version 1.2
Created: 2021
Author: Jonathan Wise
Version 1.6
Created: 2022
Author: Jon W
License: MIT
Description: A generic and re-usable model for accessing webOS system features more easily
Privileged functions can only be called if your App ID starts with com.palm
*/
var SystemModel = function() {
//** Note: If you synced this file from a common repository, local edits may be over-written! */
var SystemModel = function() {
};
SystemModel.WOSAPrefs = {};
//You should probably use Mojo.Environment.DeviceInfo for this
// http://sdk.webosarchive.org/docs/docs.html#reference/mojo/classes/mojo-environment.html#summary
SystemModel.prototype.DetectDevice = function() {
//find out what kind of device this is
var deviceType;
if (Mojo.Environment.DeviceInfo.platformVersionMajor >= 3) {
deviceType = "TouchPad";
Mojo.Log.info("Device detected as TouchPad");
} else {
if (window.screen.width == 800 || window.screen.height == 800) {
deviceType = "Pre3";
Mojo.Log.info("Device detected as Pre3");
} else if ((window.screen.width == 480 || window.screen.height == 480) && (window.screen.width == 320 || window.screen.height == 320)) {
deviceType = "Pre";
Mojo.Log.info("Device detected as Pre or Pre2");
} else {
deviceType = "Tiny";
Mojo.Log.info("Device detected as Pixi or Veer");
}
}
return deviceType;
}
//Create a named System Alarm using relative time ("in")
SystemModel.prototype.SetSystemAlarmRelative = function(alarmName, alarmTime) {
@@ -82,15 +108,20 @@ SystemModel.prototype.ClearSystemAlarm = function(alarmName) {
//Allow the display to sleep
SystemModel.prototype.AllowDisplaySleep = function(stageController) {
if (!stageController)
stageController = Mojo.Controller.getAppController().getActiveStageController();
try {
if (!stageController)
stageController = Mojo.Controller.getAppController().getActiveStageController();
//Tell the System it doesn't have to stay awake any more
Mojo.Log.info("Allowing display sleep");
//Tell the System it doesn't have to stay awake any more
Mojo.Log.info("Allowing display sleep");
stageController.setWindowProperties({
blockScreenTimeout: false
});
stageController.setWindowProperties({
blockScreenTimeout: false
});
} catch(e) {
//If the stage has already gone away, this function will return an error
// But it doesn't matter because webOS will perform this function anyway during stage clean-up
}
}
//Prevent the display from sleeping
@@ -108,6 +139,7 @@ SystemModel.prototype.PreventDisplaySleep = function(stageController) {
//Show a notification window in its own small stage
// Launches with sound: pass true for default, false for no sound, or pass the path to a specific sound file
// Note: This implementation will not work for pure background notifications as it requires the app to be active
SystemModel.prototype.ShowNotificationStage = function(stageName, sceneName, heightToUse, sound, vibrate) {
Mojo.Log.info("Showing notification stage.");
//Determine what sound to use
@@ -153,14 +185,34 @@ SystemModel.prototype.PlaySound = function(soundName) {
}
//Play an alert sound
//Note: If you want more control, include an HTML5 audio tag with the id "audioPlayer"
// Otherwise a system function will be used
SystemModel.prototype.PlayAlertSound = function(sound) {
if (!sound || sound == "") {
sound = "Subtle (short)";
}
if (sound != "off") {
var soundPath = "/media/internal/ringtones/" + sound + ".mp3";
Mojo.Log.info("trying to play: " + soundPath);
Mojo.Controller.getAppController().playSoundNotification("media", soundPath, 2500);
if (sound.indexOf("/") != -1)
soundPath = sound;
var audioPlayer = document.getElementById("audioPlayer");
if (audioPlayer) {
Mojo.Log.info("Playing notification sound " + soundPath + " using audioPlayer element");
if (soundPath) {
Mojo.Log.info("trying to play audio: " + soundPath);
audioPlayer.src = soundPath;
audioPlayer.load();
}
audioPlayer.play();
} else {
Mojo.Log.warn("No audio element named audioPlayer, using notification sound to play " + soundPath);
if (!sound || sound == "") {
sound = "Subtle (short)";
}
Mojo.Log.info("trying to play: " + soundPath);
Mojo.Controller.getAppController().playSoundNotification("media", soundPath, 2500);
}
}
}
@@ -176,11 +228,26 @@ SystemModel.prototype.Vibrate = function(vibrate) {
} else
vibeMax = Number(vibrate);
if (vibeMax > 0)
vibeInterval = setInterval(doVibrate, 500);
vibeInterval = window.setInterval(doVibrate, 500);
return success;
}
//Lock the physical volume buttons to the media volume (instead of default, notification volume)
SystemModel.prototype.LockVolumeKeys = function() {
Mojo.Log.info("Locking media volume to hardware buttons");
this.wakeupRequest = new Mojo.Service.Request("palm://com.palm.audio/media", {
method: "lockVolumeKeys",
onSuccess: function(response) {
Mojo.Log.info("Lock Volume Keys Success", JSON.stringify(response));
},
onFailure: function(response) {
Mojo.Log.error("Lock Volume Keys Failure: ", JSON.stringify(response));
}
});
return true;
}
//Launch an app
SystemModel.prototype.LaunchApp = function(appName, params) {
if (!params)
@@ -194,12 +261,148 @@ SystemModel.prototype.LaunchApp = function(appName, params) {
Mojo.Log.info("App Launch Success", appName, JSON.stringify(response));
},
onFailure: function(response) {
Mojo.Log.error("Alarm Launch Failure", appName, JSON.stringify(response));
Mojo.Log.error("App Launch Failure", appName, JSON.stringify(response));
}
});
return true;
}
//Use in combination with a Touch2Share launch to send a URI to a tapped device
SystemModel.prototype.SendDataForTouch2Share = function(url, callback) {
if (!url) {
Mojo.Log.error("Share URL not supplied");
return false;
}
if (callback)
callback = callback.bind(this);
var params = {data: { target: url, type: "rawdata", mimetype: "text/html" }};
Mojo.Log.info("Touch2Share payload is ", JSON.stringify(params));
this.shareRequest = new Mojo.Service.Request("palm://com.palm.stservice", {
method: "shareData",
parameters: params,
subscribe: true,
onSuccess: function(response) {
Mojo.Log.info("Touch2Share Success!", JSON.stringify(response));
if (callback) {
callback(response);
return true;
}
},
onFailure: function(response) {
Mojo.Log.error("Touch2Share Failure: ", JSON.stringify(response));
if (callback) {
callback(response);
return false;
}
}
});
return true;
}
//Download a file
//A fire-and-forget way to interact with the DownloadManager. The user will get a generic notification when the download is complete.
SystemModel.prototype.DownloadFile = function (url, mimetype, pathFromInteral, fileName, subscribe, callback) {
if (!url) {
Mojo.Log.error("Download URL not supplied");
return false;
}
if (!pathFromInteral){
Mojo.Log.error("Save path not supplied");
return false;
}
if (callback)
callback = callback.bind(this);
//Figure out what extension to use
var ext = "";
mimetype = mimetype.toLowerCase();
if (mimetype.indexOf("image/") != -1) {
if (mimetype == "image/jpeg")
ext = "jpg"
else {
ext = mimetype.split("/");
ext = ext[ext.length - 1];
}
} else if (mimetype == "text/plain") {
ext = "txt";
} else if (mimetype == "application/json") {
ext = "json";
} else {
if (mimetype.indexOf("/") != -1) {
ext = mimetype.split("/");
ext = ext[1];
}
}
ext = "." + ext;
this.downloadRequest = new Mojo.Service.Request('palm://com.palm.downloadmanager/', {
method: 'download',
parameters: {
target: url,
mime: mimetype,
targetDir : "/media/internal/" + pathFromInteral,
targetFilename : fileName + ext,
keepFilenameOnRedirect: true,
subscribe: subscribe
},
onSuccess: function(response) {
Mojo.Log.info("Download Success!", JSON.stringify(response));
if (callback) {
callback(response);
return true;
} else {
if (response.completed == true) {
Mojo.Controller.getAppController().showBanner({ messageText: "Content downloaded!" }, "", "");
}
}
},
onFailure: function(response) {
Mojo.Log.error("Download Failure: ", JSON.stringify(response));
if (callback) {
callback(response);
return false;
} else {
Mojo.Controller.getAppController().showBanner({ messageText: "Download error:" + JSON.stringify(response) }, "", "");
}
}
});
}
SystemModel.prototype.LoadWOSAPrefs = function (callback) {
if (callback)
callback.bind(this);
var req = new Ajax.Request("/media/internal/.wosaprefs", {
method: 'get',
onFailure: function() {
Mojo.Log.warn("Could not load .wosaprefs file. Preferences cannot be opened.");
callback(null);
},
on404: function() {
Mojo.Log.warn("Could not find .wosaprefs file. Preferences cannot be opened.");
callback(null);
},
onSuccess: function(response) {
if (response && response.responseText) {
Mojo.Log.info("Loaded .wosaprefs file: " + JSON.stringify(response.responseText));
try {
prefsObj = JSON.parse(response.responseText);
this.WOSAPrefs = prefsObj.preferences;
} catch(ex) {
Mojo.Log.warn("Could not parse .wosaprefs file. Preferences cannot be opened. " + ex);
callback(null);
return;
}
callback(prefsObj);
return;
} else {
Mojo.Log.warn("Could not read .wosaprefs file. Preferences cannot be opened.");
}
callback(null);
}.bind(this)
});
}
//Helper Functions
var vibeInterval;
var vibeCount = 0;
@@ -356,7 +559,7 @@ SystemModel.prototype.SetDisplayState = function(state) {
}
//Set the Notifications-When-Locked state
SystemModel.prototype.setShowNotificationsWhenLocked = function(value) {
SystemModel.prototype.SetShowNotificationsWhenLocked = function(value) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm") != -1) {
Mojo.Log.info("Setting Notifications When Locked to " + value);
this.service_identifier = 'palm://com.palm.systemservice';
@@ -372,7 +575,7 @@ SystemModel.prototype.setShowNotificationsWhenLocked = function(value) {
}
//Set the LED Notification state
SystemModel.prototype.setLEDLightNotifications = function(value) {
SystemModel.prototype.SetLEDLightNotifications = function(value) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm") != -1) {
Mojo.Log.info("Setting LED Notifications to " + value);
this.service_identifier = 'palm://com.palm.systemservice';
@@ -394,7 +597,7 @@ SystemModel.prototype.GetInternetConnectionState = function(callback) {
throw ("Privileged system service call not allowed for this App ID!");
}
Mojo.Log.info("Requesting Internet connection state from Connection Manager");
this.connectedRequest = new Mojo.Service.Request("palm://com.palm.connectionamanager", {
this.connectedRequest = new Mojo.Service.Request("palm://com.palm.connectionmanager", {
method: "getStatus",
parameters: {
subscribe: false
@@ -405,7 +608,7 @@ SystemModel.prototype.GetInternetConnectionState = function(callback) {
}
//Set the WAN state
SystemModel.prototype.setWANEnabled = function(value) {
SystemModel.prototype.SetWANEnabled = function(value) {
var state = value ? 'off' : 'on';
if (Mojo.Controller.appInfo.id.indexOf("com.palm") != -1) {
Mojo.Log.info("Setting WAN State to " + value);
@@ -424,7 +627,7 @@ SystemModel.prototype.setWANEnabled = function(value) {
}
//Set the WIFI state
SystemModel.prototype.setWifiEnabled = function(value) {
SystemModel.prototype.SetWifiEnabled = function(value) {
var state = value ? 'enabled' : 'disabled';
if (Mojo.Controller.appInfo.id.indexOf("com.palm") != -1) {
Mojo.Log.info("Setting WIFI State to " + state);
@@ -457,6 +660,7 @@ SystemModel.prototype.SetBluetoothEnabled = function(value) {
}
}
//Helper for Bluetooth functions, do not call directly
SystemModel.prototype.bluetoothControlService = function(url, params, cb) {
return new Mojo.Service.Request(url, {
onSuccess: cb,
@@ -465,14 +669,16 @@ SystemModel.prototype.bluetoothControlService = function(url, params, cb) {
});
}
SystemModel.prototype.GetInstalledApps = function(callBack) {
SystemModel.prototype.GetInstalledApps = function(callback) {
if (callback)
callback.bind(this);
if (Mojo.Controller.appInfo.id.indexOf("com.palm") != -1) {
Mojo.Log.info("Getting list of running apps.");
Mojo.Log.info("Getting list of installed apps.");
this.appRequest = new Mojo.Service.Request("palm://com.palm.applicationManager/listApps", {
method: "",
parameters: {},
onSuccess: callBack,
onFailure: callBack
onSuccess: callback,
onFailure: callback
});
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm'!");
@@ -505,6 +711,121 @@ SystemModel.prototype.KillApp = function(appId) {
onFailure: function(response) { Mojo.Log.warn("App was not killed!", JSON.stringify(response)); }
});
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
SystemModel.prototype.ListHandlersForURL = function(url, callback) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm") != -1) {
if (callback)
callback.bind(this);
Mojo.Log.info("Listing handlers for URL: " + url);
this.serviceRequest = new Mojo.Service.Request("palm://com.palm.applicationManager", {
method: "listAllHandlersForUrl",
parameters: {
"url": url
},
onSuccess: function(response) {
//Mojo.Log.info("Handler list success: " + JSON.stringify(response));
if (response && response.redirectHandlers) {
if (callback)
callback(response.redirectHandlers);
} else {
Mojo.Log.error("Unexpected payload in system service request");
}
}.bind(this),
onFailure: function(response) {
Mojo.Log.error("Handler list failure: " + JSON.stringify(response));
if (callback)
callback(response);
}.bind(this)
});
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
SystemModel.prototype.AddHandlerForURL = function(urlPattern, appId, callback) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm") != -1) {
if (!urlPattern)
throw "No urlPattern specified";
if (callback)
callback.bind(this);
if (!appId)
appId = Mojo.Controller.appInfo.id;
Mojo.Log.info("Add handler for URL: " + urlPattern + " with app " + appId);
if (urlPattern && urlPattern != "" && appId && appId != "" && appId.indexOf(".") > -1) {
this.serviceRequest = new Mojo.Service.Request("palm://com.palm.applicationManager", {
method: "addRedirectHandler",
parameters: {
"appId": appId,
"urlPattern": urlPattern,
"schemeForm": false,
},
onSuccess: function(response) {
Mojo.Log.info("Handler registration success: " + JSON.stringify(response));
if (callback)
callback(response);
else
Mojo.Controller.getAppController().showBanner("URL Helper added: " + appId + ".", { source: 'notification' });
}.bind(this),
onFailure: function(response) {
Mojo.Log.error("Handler registration failure: " + JSON.stringify(response));
if (callback)
callback(response);
else
Mojo.Controller.getAppController().showBanner("URL Helper failed: " + appId + ".", { source: 'notification' });
}.bind(this)
});
} else {
Mojo.Log.error("Invalid parameters specified. Not attempting to add Redirect Handler!");
}
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm'!");
throw ("Privileged system service call not allowed for this App ID!");
}
}
SystemModel.prototype.RemoveHandlerForURL = function(appId, callback) {
if (Mojo.Controller.appInfo.id.indexOf("com.palm") != -1) {
if (!appId)
appId = Mojo.Controller.appInfo.id;
if (callback)
callback.bind(this);
Mojo.Log.info("Remove URL handling for app: " + appId);
this.serviceRequest = new Mojo.Service.Request("palm://com.palm.applicationManager", {
method: "removeHandlersForAppId",
parameters: {
"appId": appId
},
onSuccess: function(response) {
Mojo.Log.info("Handler remove success: " + JSON.stringify(response));
if (callback)
callback(response);
else
Mojo.Controller.getAppController().showBanner("URL Helper removed: " + appId + ".", { source: 'notification' });
}.bind(this),
onFailure: function(response) {
Mojo.Log.error("Handler list failure: " + JSON.stringify(response));
if (callback)
callback(response)
else
Mojo.Controller.getAppController().showBanner("URL Helper failed: " + appId + ".", { source: 'notification' });
}.bind(this)
});
} else {
Mojo.Log.error("Privileged system services can only be called by apps with an ID that starts with 'com.palm'!");
throw ("Privileged system service call not allowed for this App ID!");
}
+87 -66
View File
@@ -1,68 +1,36 @@
/*
Updater Model - Mojo
Version 0.3
Created: 2020
Author: Jonathan Wise
Version 1.0
Created: 2022
Author: Jon W
License: MIT
Description: A model to check for and get updates from App Museum II web service.
Does not require App Museum to be installed, but does require internet access, and Preware to do the actual install.
Source: Find the latest version of this library and clean samples of how to use it on GitHub:
https://github.com/codepoet80/webos-catalog-frontend/tree/main/Examples
Source: Find the latest version of this library and info on how to use it on GitHub:
https://github.com/webosarchive/webos-common
*/
//** Note: If you synced this file from a common repository, local edits may be over-written! */
var UpdaterModel = function() {
this.updateURL = "http://appcatalog.webosarchive.com/WebService/getLatestVersionInfo.php?app=";
this.updateURL = "http://appcatalog.webosarchive.org/WebService/getLatestVersionInfo.php?app=";
this.lastUpdateResponse = null;
};
/* "Public" Updater functions */
//Check App Museum II web service to see if there are any updates
//Gather information and Check App Museum II web service to see if there are any updates
UpdaterModel.prototype.CheckForUpdate = function(appName, callback) {
var currVersion = this.getVersionObject(Mojo.Controller.appInfo.version);
Mojo.Log.info("UpdaterModel identified current app " + appName + " version: " + JSON.stringify(currVersion));
// TODO: It would be nice to use the AppID, instead of an arbitrary name, but the performance
// implications are overwhelming since the AppID is not a part of the masterData file.
// We could get that from the app with: Mojo.Controller.appInfo.id
var updateURL = this.updateURL + encodeURI(appName);
// set scope for xmlhttp anonymous function callback
if (callback)
callBack = callback.bind(this);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", updateURL);
Mojo.Log.info("Updater calling: " + updateURL);
xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xmlhttp.send();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
Mojo.Log.info("Museum responded: " + xmlhttp.responseText);
if (xmlhttp.responseText != null && xmlhttp.responseText != "" && xmlhttp.responseText.indexOf("ERROR:") != 1) {
var updateResponse = JSON.parse(xmlhttp.responseText);
if (updateResponse.version != null) {
var museumVersion = this.getVersionObject(updateResponse.version);
//Mojo.Log.info("Museum version: " + JSON.stringify(museumVersion));
if (this.isVersionHigher(currVersion, museumVersion)) {
Mojo.Log.warn("UpdaterModel found an update in webOS App Museum II!");
updateResponse.updateFound = true;
} else {
Mojo.Log.info("UpdaterModel did not find an update in webOS App Museum II!");
updateResponse.updateFound = false;
}
}
this.lastUpdateResponse = updateResponse;
} else {
Mojo.Log.info("UpdaterModel: No useable response from App Museum II update API");
}
//Mojo.Log.info("New update response object: " + JSON.stringify(updateResponse));
if (callback) {
callBack(updateResponse);
}
}
}.bind(this);
this.deviceInfoRequest = new Mojo.Service.Request("palm://com.palm.preferences/systemProperties", {
method: "Get",
parameters:{"key": "com.palm.properties.nduid" },
onSuccess: this.performIdentifiedUpdateCheck.bind(this, appName, currVersion, callback),
onFailure: this.performIdentifiedUpdateCheck.bind(this, appName, currVersion, callback)
});
}
//You can optionally call this function if you don't want to handle the user interaction related to prompting for an update
@@ -73,7 +41,10 @@ UpdaterModel.prototype.PromptUserForUpdate = function(callback, message) {
Mojo.Log.warn("UpdaterModel: Not prompting user for update when no update has been discovered.");
} else {
if (!message)
message = "An update for " + Mojo.Controller.appInfo.title + " was found in App Museum II: <br>" + this.lastUpdateResponse.versionNote + "<br>Do you want to update now?";
message = "An update for " + Mojo.Controller.appInfo.title + " was found in App Museum II:<br>" + this.lastUpdateResponse.versionNote + "<br>Do you want to update now?";
if (callback) // set scope for xmlhttp anonymous function callback
callBack = callback.bind(this);
var stageController = Mojo.Controller.getAppController().getActiveStageController();
if (stageController) {
@@ -86,8 +57,8 @@ UpdaterModel.prototype.PromptUserForUpdate = function(callback, message) {
} else {
Mojo.Log.info("User deferred update.");
}
if (callback)
callback(value);
if (callBack)
callBack(value);
},
allowHTMLMessage: true,
title: $L("Update Available!"),
@@ -102,33 +73,83 @@ UpdaterModel.prototype.PromptUserForUpdate = function(callback, message) {
}
//Ask Preware to actually install the update...
UpdaterModel.prototype.InstallUpdate = function(callBack) {
UpdaterModel.prototype.InstallUpdate = function() {
if (!this.lastUpdateResponse) {
Mojo.Log.warn("UpdaterModel: Not performing update when no update has been discovered.");
} else {
var app = this.lastUpdateResponse.downloadURI;
Mojo.Log.info("Asking PreWare to perform update to " + app);
this.prewareRequest = new Mojo.Service.Request("palm://com.palm.applicationManager", {
method: "open",
parameters: {
"id": "org.webosinternals.preware",
params: { type: "install", file: app }
},
onSuccess: function(response) {
Mojo.Log.info("Preware launch success", JSON.stringify(response));
},
onFailure: function(response) {
Mojo.Log.error("Preware launch failure, " + app + ":",
JSON.stringify(response), response.errorText);
}
});
this.InstallViaPreware(app);
return true;
}
}
UpdaterModel.prototype.InstallViaPreware = function(app) {
//Ask webOS to launch the PreWare with install url
this.prewareRequest = new Mojo.Service.Request("palm://com.palm.applicationManager", {
method: "open",
parameters: {
"id": "org.webosinternals.preware",
params: { type: "install", file: app }
},
onSuccess: function(response) {
Mojo.Log.info("Preware launch success", JSON.stringify(response));
}.bind(this),
onFailure: function(response) {
Mojo.Log.error("Preware launch failure, " + app + ":", JSON.stringify(response), response.errorText);
}.bind(this)
});
}
/* "Private" helper functions */
//Internal Function that uses the resolved information to actually do the check
UpdaterModel.prototype.performIdentifiedUpdateCheck = function(appName, currVersion, callback, response) {
// Build appropriate URL for conditions
var updateURL = this.updateURL + encodeURI(appName + "/" + Mojo.Controller.appInfo.version);
// If we have a device identifier, use that
if(response && JSON.stringify(response).indexOf("com.palm.properties.nduid") != -1) {
updateURL = updateURL + "&clientid=" + response[Object.keys(response)[0]];
}
// Send some info about the device (could be used for compat checks)
deviceData = Mojo.Environment.DeviceInfo.modelName + "/" + Mojo.Environment.DeviceInfo.platformVersion + "/" + (Mojo.Environment.DeviceInfo.carrierName || "WiFi") + "/" + Mojo.Locale.getCurrentLocale();
updateURL = updateURL + "&device=" + encodeURIComponent(deviceData);
if (callback) // set scope for xmlhttp anonymous function callback
callBack = callback.bind(this);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", updateURL);
Mojo.Log.info("Updater calling: " + updateURL);
xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xmlhttp.send();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
Mojo.Log.info("Museum responded: " + xmlhttp.responseText);
if (xmlhttp.responseText != null && xmlhttp.responseText != "" && xmlhttp.responseText.indexOf("ERROR:") != 1) {
var updateResponse = JSON.parse(xmlhttp.responseText);
if (updateResponse.version != null) {
var museumVersion = this.getVersionObject(updateResponse.version);
if (this.isVersionHigher(currVersion, museumVersion)) {
Mojo.Log.warn("UpdaterModel found an update in webOS App Museum II!");
updateResponse.updateFound = true;
} else {
Mojo.Log.info("UpdaterModel did not find an update in webOS App Museum II!");
updateResponse.updateFound = false;
}
}
this.lastUpdateResponse = updateResponse;
} else {
Mojo.Log.info("UpdaterModel: No useable response from App Museum II update API");
}
if (callBack) {
callBack(updateResponse);
}
}
}.bind(this);
}
//Turn a version string into an object with three independent number values
UpdaterModel.prototype.getVersionObject = function(versionNum) {
versionNumParts = versionNum.split(".");
@@ -158,4 +179,4 @@ UpdaterModel.prototype.isVersionHigher = function(currVersion, compareVersion) {
return true;
return false;
}
}
}
File renamed without changes.
@@ -1,11 +1,8 @@
<div class="dashboard-notification-module">
<div class="palm-dashboard-icon-container">
<div class="dashboard-newitem">
<div class="dashboard-newitem" style="display:#{showCount}">
<span>#{count}</span>
</div>
<div id="dashboard-icon" class="palm-dashboard-icon dashboard-icon-chat">
</div>
</div>
@@ -1,4 +1,4 @@
<div class="palm-row" x-mojo-tap-highlight='momentary'>
<div class="palm-row" style="min-height: 68px;">
<div class="palm-row-wrapper">
<div id="divMsg-#{uid}" class="chat-log" style="color:#{color}"><span class="sender #{postedFrom}">#{sender}:</span> #{formattedMessage}</div>
<div class="chat-time">#{timestamp} <span class="likes">#{likes}</span></div>
File renamed without changes.
@@ -1,7 +1,8 @@
<div class="palm-page-header">
<div class="palm-page-header multi-line">
<div class="header-icon preferences"></div>
<div id="imgTransmit" style="float:right;width:32px;height:32px;margin-top:-24px;margin-right:-12px; background: url('assets/transmit.png') no-repeat; background-size: 32px 32px;"></div>
<div class="header-text">
<span x-mojo-loc='' style="text-transform:none;">webOS SimpleChat 2</span>
<span x-mojo-loc='' style="text-transform:none;" id="spnTitle">webOS SimpleChat</span>
</div>
</div>
@@ -16,7 +17,7 @@
<div class="palm-group" style="min-height:64px">
<div class="palm-group-title" id="divComposeTitle"><span x-mojo-loc='' id="spanCompose">Compose</span> &nbsp;<img src="assets/twisty-down.png" id="imgTwisty"></div>
<div x-mojo-element="TextField" id="txtMessage" class="textFieldClass" name="txtMessage" style="min-height:56px; background-color:white;border: 1px solid dimgray"></div>
<div x-mojo-element="TextField" id="txtMessage" class="textFieldClass" name="txtMessage"></div>
<div id="divSpinnerContainer" style="width: 30px;margin-left: auto;margin-right: auto; align-items: center; margin-top:-25px;">
<div x-mojo-element="Spinner" class="spinnerClass" id="workingSpinner" name="workingSpinner"></div>
</div>
@@ -11,6 +11,12 @@
<div class="palm-list" id="divTest">
<div class="palm-row first">
<div class="palm-row-wrapper">
<div x-mojo-element="ListSelector" title="ThemePreference" id="listThemePreference"></div>
</div>
</div>
<div class="palm-row">
<div class="palm-row-wrapper">
<div id="listForegroundUpdate" x-mojo-element="ListSelector" title="ForegroundUpdate" class="right"></div>
</div>
@@ -22,6 +28,13 @@
</div>
</div>
<div class="palm-row">
<div class="palm-row-wrapper">
<div x-mojo-element="ToggleButton" id="toggleParsing" title="SkipParsing"></div>
<div class="title" style="margin-left: -4px; float:left;"><span class="label">Skip Parsing</span></div>
</div>
</div>
<div class="palm-row">
<div class="palm-row-wrapper">
<div x-mojo-element="ToggleButton" id="toggleEmojis" title="ShowEmojis"></div>
@@ -29,6 +42,19 @@
</div>
</div>
<div class="palm-row">
<div class="palm-row-wrapper">
<div x-mojo-element="ToggleButton" id="toggleThumbs" title="ShowWOSAThumbs"></div>
<div class="title" style="margin-left: -4px; float:left;"><span class="label">Share Thumbnails</span></div>
</div>
</div>
<div class="palm-row">
<div class="palm-row-wrapper">
<div id="listEnterSubmits" x-mojo-element="ListSelector" title="EnterSubmits" class="right"></div>
</div>
</div>
<div class="palm-row last">
<div class="palm-row-wrapper">
<span x-mojo-loc="" id="SoundOptionLabel" width="100%" class="label truncating-text" style="float:left; min-width:120px; margin-left: 14px; margin-top: 14px;">Alert Sound</span>
@@ -1,5 +1,5 @@
<div class="palm-dialog-content">
<div id="hue-title" class="palm-dialog-title">
<div id="hue-title" class="palm-dialog-title multi-line">
Getting Acquainted
</div>
<div class="palm-dialog-seperator" style="height: 8px;"></div>
Executable
+25
View File
@@ -0,0 +1,25 @@
{
"id": "com.palm.app.notalexnoyle.almostsimplechat",
"version": "1.9.1",
"vendor": "NotAlexNoyle",
"type": "web",
"main": "index.html",
"noWindow": true,
"title": "AlmostSimpleChat",
"appMuseumTitle": "Almost webOS SimpleChat",
"icon": "icon.png",
"pageTitle": "Almost webOS SimpleChat",
"startupMessage": "<img src='icon.png' style='float:left; margin-right:8px'>Welcome to SimpleChat! We're so excited you're here chatting with us more than 10 years after this platform was supposed to have died!<br>You can also join this chat from Discord: <a href='http://www.webosarchive.org/discord'>webosarchive.org/discord</a> or view the log on the web: <a href='http://chat.webosarchive.org'>chat.webosarchive.org</a>",
"serviceURL": "http://chat.webosarchive.org/",
"splashicon": "icon-256x256.png",
"uiRevision": "2",
"universalSearch" : {
"action": {
"displayName": "New Share",
"url": "com.palm.app.notalexnoyle.almostsimplechat",
"launchParam": {
"newshare": "#{searchTerms}"
}
}
}
}
View File
File renamed without changes.
@@ -13,7 +13,7 @@ span.emoji-outer {
}
span.emoji-inner {
background: url(emoji.png);
background: url(../assets/emoji.png);
display: -moz-inline-box;
display: inline-block;
text-indent: -9999px;
View File
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
+32
View File
@@ -5,6 +5,10 @@
background-size: 32px 32px;
}
.palm-dark .header-text {
color:lightgray !important;
}
.explanation-text {
margin-top: -5px;
padding: 14px;
@@ -12,10 +16,18 @@
color: #000;
}
.palm-dark .explanation-text {
color: lightgray;
}
.chat-log {
margin-top: 8px;
}
.palm-dark .chat-log {
color:lightgray !important;
}
.sender {
font-weight: bold;
}
@@ -36,6 +48,22 @@
padding-bottom: 14px;
}
.palm-dark .chat-time {
color: white;
}
.textFieldClass {
min-height:56px;
background-color:white;
border: 1px solid dimgray;
}
.palm-dark .textFieldClass {
min-height:56px;
background-color:lightgray;
border: 1px solid dimgray;
}
.textFieldClass input {
padding: 1px;
}
@@ -48,6 +76,10 @@ textarea {
margin-top: 0px !important;
}
.palm-dark .palm-dialog-content .palm-body-text {
color: black !important;
}
.multiline-hint-text.oblique {
color: darkgray;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File
File renamed without changes.
View File
File renamed without changes.
+2 -2
View File
@@ -1,6 +1,6 @@
{
"logLevel": 99,
"debuggingEnabled": true,
"logLevel": 10,
"debuggingEnabled": false,
"timingEnabled": false,
"logEvents": false,
"escapeHTMLInTemplates": true
View File
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
Executable
+25
View File
@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta name='viewport' content='height=device-height'>
<title>Simple Chat</title>
<script src="/usr/palm/frameworks/mojo/mojo.js" type="text/javascript" x-mojo-version="1"></script>
<!-- load my additions -->
<script src="app/com.wosa.mojo.additions.js" type="text/javascript"></script>
<!-- application stylesheet should come in after the one loaded by the framework -->
<link href="assets/simplechat.css" media="screen" rel="stylesheet" type="text/css">
<link href="assets/emoji.css" rel="stylesheet" type="text/css" />
</head>
<body>
<!--
<audio id="audioPlayer">
<source src="/media/internal/ringtones/Subtle (short).mp3" type="audio/mp3">
</audio>
-->
</body>
</html>
View File
File renamed without changes.
-7
View File
@@ -1,7 +0,0 @@
# Simple Chat client app for webOS
SimpleChat client for legacy Palm/HP webOS devices, depends on back-end services listed below.
Uses [simplechat-service](https://github.com/codepoet80/simplechat-service)
You can also self-host the services.
@@ -1,133 +0,0 @@
/*
In the app assistant, we setup some app-wide global objects and handle different kinds of launches, creating and delegating to the main stage
*/
var appModel = null;
var updaterModel = null;
var systemModel = null;
var MainStageName = "main";
var welcomed = false;
Mojo.Additions = Additions;
function AppAssistant() {
appModel = new AppModel();
updaterModel = new UpdaterModel();
systemModel = new SystemModel();
serviceModel = new ServiceModel();
}
//This function will handle relaunching the app when an alarm goes off(see the device/alarm scene)
AppAssistant.prototype.handleLaunch = function(params) {
//Load preferences
appModel.LoadSettings();
Mojo.Log.info("settings now: " + JSON.stringify(appModel.AppSettingsCurrent));
if (!appModel.AppSettingsCurrent["BackgroundUpdate"]) {
appModel.AppSettingsCurrent["BackgroundUpdate"] = "00:30:00";
appModel.SaveSettings();
}
//get the proxy for the stage in the event it already exists (eg: app is currently open)
var mainStage = this.controller.getStageProxy("main");
Mojo.Log.info("SimpleChat is Launching! Launch params: " + JSON.stringify(params));
//Reset alarms
systemModel.ClearSystemAlarm("SimpleChat");
if (appModel.AppSettingsCurrent["BackgroundUpdate"] && appModel.AppSettingsCurrent["BackgroundUpdate"] != "" && appModel.AppSettingsCurrent["BackgroundUpdate"] != -1)
systemModel.SetSystemAlarmRelative("SimpleChat", appModel.AppSettingsCurrent["BackgroundUpdate"]);
var AppRunning = false;
if (mainStage) {
Mojo.Log.info("Found existing stage, app was already running");
AppRunning = true;
} else {
Mojo.Log.info("Did not find existing stage, this is a new app launch!");
}
if (AppRunning) //If the stage exists, use it
{
var stageController = this.controller.getStageController("main");
if (!params || params["action"] == undefined) //If no parameters were passed, this is a normal launch
{
Mojo.Log.info("This is a normal re-launch");
stageController.activate(); //bring existing stage into focus
return;
} else //If parameters were passed, this is a launch from a system alarm
{
Mojo.Log.info("This is a re-launch with parameters: " + JSON.stringify(params) + ". Safe to ignore, since app is already running.");
systemModel.ShowNotificationStage("dashboard", "dashboard/dashboard-scene", 60, false, false);
return;
}
} else //If not, determine if we should make one
{
if (!params || params["action"] == undefined) //If no parameters were passed, this is a normal launch
{
var currVersion = Mojo.Controller.appInfo.version;
Mojo.Log.info("This is a normal launch of version " + currVersion);
var stageArguments = { name: MainStageName, lightweight: true };
this.controller.createStageWithCallback(stageArguments, function(stageController) {
stageController.pushScene(MainStageName);
}.bind(this));
return;
} else //If parameters were passed, this is a launch from a system alarm
{
Mojo.Log.info("This is an alarm launch: " + JSON.stringify(params));
/*
return;*/
var pushClass0AlertScene = function(stageController) {
stageController.pushScene('dashboard', "dashboard/dashboard-scene");
}.bind(this);
Mojo.Controller.getAppController().showBanner("New messages in the chat!", { source: 'notification' });
//var stageArguments = { name: MainStageName, lightweight: true };
//this.controller.createStageWithCallback(stageArguments, function(stageController) {
// Mojo.Controller.appController.closeStage("main");
//}.bind(this));
this.controller.createStageWithCallback({
name: 'dashboard',
lightweight: true,
height: 100,
soundclass: "assets/silent.mp3"
}, pushClass0AlertScene, 'popupalert');
//systemModel.ShowNotificationStage("dashboard", "dashboard/dashboard-scene", 60, false, false);
}
}
};
/*
SystemModel.prototype.ShowNotificationStage = function(stageName, sceneName, heightToUse, sound, vibrate) {
Mojo.Log.info("Showing notification stage.");
//Determine what sound to use
var soundToUse;
if (!sound)
soundToUse = "assets/silent.mp3";
else if (sound == true || sound == "")
soundToUse = "/media/internal/ringtones/Dulcimer (short).mp3"
else
soundToUse = sound;
if (vibrate)
this.Vibrate(vibrate);
var stageCallBack = function(stageController) {
stageController.pushScene({ name: stageName, sceneTemplate: sceneName });
}.bind(this);
var appController = Mojo.Controller.getAppController();
var stageController = appController.getStageController(stageName);
if (stageController) {
stageCallBack(stageController);
} else {
Mojo.Controller.getAppController().createStageWithCallback({
name: stageName,
lightweight: true,
height: heightToUse,
sound: soundToUse,
clickableWhenLocked: true
}, stageCallBack, 'dashboard');
}
}
*/
@@ -1,93 +0,0 @@
function VersionAssistant(startup) {
this.isStartup = startup;
// on first start, this message is displayed, along with the current version message from below
this.startupMessage = $L("<img src='icon.png' style='float:left; margin-right:8px'>Welcome to SimpleChat! We're so excited you're here chatting with us 10 years after this platform was supposed to have died!<br>You can also join this chat from Discord: <a href='discord.gg/7NrrT8exrn'>discord.gg/7NrrT8exrn</a> or view the log on the web: <a href='http://chat.webosarchive.com'>chat.webosarchive.com</a>");
//New Features
this.changelog = [{
version: "Version 1.1.3",
detail: [
"Bug fixes and general maintenance"
]
}, {
version: "Version 1.1.2",
detail: [
"Show/Hide the Command menu buttons by tapping the Compose area title bar",
"Enable/Disable Discord Emojis in Preferences",
]
}, {
version: "Version 1.1.1",
detail: [
"Improved Discord integration, including showing Emojis!<span class='emoji-outer emoji-sizer'><span class='emoji-inner emoji1f388'></span></span>",
"Now with a smaller emoji file!",
"Improved timeout logic.",
"Trying different background sync logic."
]
}];
// setup command menu
this.cmdMenuModel = {
visible: false,
items: [
{},
{
label: $L("OK! Let's Go..."),
command: 'do-continue'
},
{}
]
};
};
VersionAssistant.prototype.setup = function() {
this.titleElement = this.controller.get('title');
this.dataElement = this.controller.get('data');
this.titleElement.innerHTML = $L('Version Info');
var html = '';
html += '<div style="margin: 2px 12px 12px 12px">' + this.startupMessage + '</div>';
for (var i = 0; i < this.changelog.length; i++) {
html += Mojo.View.render({ object: { title: this.changelog[i].version }, template: 'version/rowDivider' });
html += '<ul>';
for (var j = 0; j < this.changelog[i].detail.length; j++) {
html += '<li>' + this.changelog[i].detail[j] + '</li>';
}
html += '</ul>';
}
// set data
this.dataElement.innerHTML = html;
// setup menu
this.controller.setupWidget(Mojo.Menu.appMenu, { omitDefaultItems: true }, { visible: false });
//if (this.isStartup) {
// set command menu
this.controller.setupWidget(Mojo.Menu.commandMenu, { menuClass: 'no-fade' }, this.cmdMenuModel);
//}
};
VersionAssistant.prototype.activate = function(event) {
this.timer = this.controller.window.setTimeout(this.showContinue.bind(this), 2 * 1000);
};
VersionAssistant.prototype.deactivate = function(event) {};
VersionAssistant.prototype.cleanup = function(event) {};
VersionAssistant.prototype.showContinue = function() {
// show the command menu
this.controller.setMenuVisible(Mojo.Menu.commandMenu, true);
};
VersionAssistant.prototype.handleCommand = function(event) {
if (event.type == Mojo.Event.command) {
switch (event.command) {
case 'do-continue':
this.controller.stageController.popScene();
break;
}
}
};
-116
View File
@@ -1,116 +0,0 @@
/*
App Model
Version 0.4
Created: 2018
Author: Jonathan Wise
License: MIT
Description: Common functions for webOS apps, particularly for managing persisted options in cookies
*/
var AppModel = function() {
//Define your default scene (needed for settings management)
this.DefaultScene = "main";
//Define your app-wide, non-persisted settings here
this.UpdateCheckDone = false;
this.LaunchQuery = "";
this.ServiceEndpointBase = "http://chat.webosarchive.com/";
this.ClientId = secrets.clientid;
this.LastMessageSelected = null;
//Define your app preferences (to be saved by OS)
this.AppSettingsCurrent = null;
this.AppSettingsDefaults = {
ForegroundUpdate: 10000,
BackgroundUpdate: "00:30:00",
AlertSound: "Subtle (short)",
ShowEmojis: true,
UseClientAPIKey: false,
UseCustomEndpoint: false,
ClientAPIKey: "",
EndpointURL: "",
SenderName: "webOS User",
FirstRun: true,
MyMessages: null,
LastKnownMessage: null,
LastVersionRun: 0
};
}
//You probably don't need to change the below functions since they all work against the Cookie defaults you defined above.
// LoadSettings: call when your app starts, or you want to load previously persisted options.
// SaveSettings: call any time you want to persist an option.
// ResetSettings: call if you want to forget stored settings and return to defaults. Your default scene will be popped and re-pushed.
AppModel.prototype.LoadSettings = function(safe) {
this.AppSettingsCurrent = this.AppSettingsDefaults;
var loadSuccess = false;
var settingsCookie = new Mojo.Model.Cookie("settings");
try {
appSettings = settingsCookie.get();
if ((typeof appSettings == "undefined" || appSettings == null) || (safe && !this.checkSettingsValid(appSettings))) {
Mojo.Log.warn("** Using first run default settings");
} else {
Mojo.Log.info("** Using cookie settings!");
this.loadCookieIntoCurrent(appSettings);
loadSuccess = true;
}
} catch (ex) {
settingsCookie.put(null);
Mojo.Log.error("** Settings cookie were corrupt and have been purged!");
Mojo.Log.error(ex);
}
return loadSuccess;
}
AppModel.prototype.loadCookieIntoCurrent = function(cookieSettings) {
this.AppSettingsCurrent = cookieSettings;
}
AppModel.prototype.checkSettingsValid = function(loadedSettings) {
var retValue = true;
for (var key in this.AppSettingsDefaults) {
if (typeof loadedSettings[key] === undefined || loadedSettings[key] == null) {
Mojo.Log.warn("** An expected saved setting, " + key + ", was null or undefined.");
retValue = false;
}
if (typeof loadedSettings[key] !== typeof this.AppSettingsDefaults[key]) {
Mojo.Log.warn("** A saved setting, " + key + ", was of type " + typeof(loadedSettings[key]) + " but expected type " + typeof(this.AppSettingsDefaults[key]));
retValue = false;
}
if (typeof this.AppSettingsDefaults[key] === "string" && this.AppSettingsDefaults[key].indexOf(this.BaseDateString) != -1 && loadedSettings[key].indexOf(this.BaseDateString)) {
Mojo.Log.warn("** A saved setting could not be compared to an expected date value.");
retValue = false;
}
if (typeof this.AppSettingsDefaults[key] === "string" && (this.AppSettingsDefaults[key] == "false" || this.AppSettingsDefaults[key] == "true")) {
if (loadedSettings[key] != "false" && loadedSettings[key] != "true") {
Mojo.Log.warn("** A saved setting did not have the expected boolean value.");
retValue = false;
}
}
}
return retValue;
}
AppModel.prototype.SaveSettings = function() {
var settingsCookie = new Mojo.Model.Cookie("settings");
Mojo.Log.info("Saving settings as: " + JSON.stringify(appModel.AppSettingsCurrent));
Mojo.Log.info("Default settings at Save time are: " + JSON.stringify(appModel.AppSettingsDefaults));
settingsCookie.put(appModel.AppSettingsCurrent);
}
AppModel.prototype.ResetSettings = function() {
Mojo.Log.info("resetting settings");
//Tell main scene to drop settings
this.AppSettingsCurrent = this.AppSettingsDefaults;
this.SaveSettings();
Mojo.Log.info("settings have been reset");
var stageController = Mojo.Controller.getAppController().getActiveStageController();
stageController.popScene(this.DefaultScene);
Mojo.Log.info("closed default scene");
//Restart main scene
stageController.pushScene(this.DefaultScene);
Mojo.Log.info("re-opened default scene");
}
-103
View File
@@ -1,103 +0,0 @@
var ServiceModel = function() {
};
ServiceModel.prototype.getChats = function(serviceBase, callback) {
serviceURL = serviceBase + "get-chat.php";
Mojo.Log.info("Trying to get chats from: " + serviceURL);
this.retVal = "";
if (callback)
callback = callback.bind(this);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", serviceURL);
xmlhttp.send();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if (callback)
callback(xmlhttp.responseText);
}
}.bind(this);
}
ServiceModel.prototype.postChat = function(useSender, useMessage, serviceBase, clientId, callback) {
serviceURL = serviceBase + "post-chat.php";
var msgToPost = {
sender: useSender,
message: useMessage
}
Mojo.Log.info("Posting chat to: " + serviceURL);
Mojo.Log.info(JSON.stringify(msgToPost));
this.retVal = "";
if (callback)
callback = callback.bind(this);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", serviceURL);
xmlhttp.setRequestHeader("Client-Id", clientId);
xmlhttp.timeout = 2000;
xmlhttp.send(JSON.stringify(msgToPost));
xmlhttp.onreadystatechange = function() {
Mojo.Log.info("ready state: " + xmlhttp.readyState);
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if (callback)
callback(xmlhttp.responseText);
}
}.bind(this);
xmlhttp.ontimeout = function() {
callback();
}.bind(this);
}
ServiceModel.prototype.editChat = function(useSender, useMessage, uid, editKey, serviceBase, clientId, callback) {
serviceURL = serviceBase + "edit-chat.php";
var msgToPost = {
sender: useSender,
message: useMessage,
uid: uid,
editKey: editKey
}
Mojo.Log.info("Edit chat to: " + serviceURL);
Mojo.Log.info(JSON.stringify(msgToPost));
this.retVal = "";
if (callback)
callback = callback.bind(this);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", serviceURL);
xmlhttp.setRequestHeader("Client-Id", clientId);
xmlhttp.send(JSON.stringify(msgToPost));
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if (callback)
callback(xmlhttp.responseText);
}
}.bind(this);
}
ServiceModel.prototype.likeChat = function(uid, serviceBase, clientId, callback) {
serviceURL = serviceBase + "like-chat.php";
var msgToPost = {
uid: uid,
like: "+1"
}
Mojo.Log.info("Like chat to: " + serviceURL);
Mojo.Log.info(JSON.stringify(msgToPost));
this.retVal = "";
if (callback)
callback = callback.bind(this);
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", serviceURL);
xmlhttp.setRequestHeader("Client-Id", clientId);
xmlhttp.send(JSON.stringify(msgToPost));
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if (callback)
callback(xmlhttp.responseText);
}
}.bind(this);
}
-12
View File
@@ -1,12 +0,0 @@
{
"id": "com.jonandnic.simplechat.app",
"version": "2.0.0",
"vendor": "Jon Wise",
"type": "web",
"main": "index.html",
"noWindow": true,
"title": "SimpleChat",
"icon": "icon.png",
"uiRevision": "2",
"theme": "light"
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

-21
View File
@@ -1,21 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name='viewport' content='height=device-height'>
<title>Simple Chat</title>
<script src="/usr/palm/frameworks/mojo/mojo.js" type="text/javascript" x-mojo-version="1"></script>
<!-- load my additions -->
<script src="app/com.jonandnic.mojo.additions.js" type="text/javascript"></script>
<!-- application stylesheet should come in after the one loaded by the framework -->
<link href="stylesheets/simplechat.css" media="screen" rel="stylesheet" type="text/css">
<link href="emoji.css" rel="stylesheet" type="text/css" />
</head>
<body>
</body>
</html>
-12
View File
@@ -1,12 +0,0 @@
{
"id": "com.jonandnic.simplechat",
"package_format_version": 2,
"loc_name": "SimpleChat",
"version": "2.0.0",
"icon": "icon.png",
"miniicon": "icon.png",
"vendor": "Jon Wise",
"vendorurl": "www.jonandnic.com",
"app": "com.jonandnic.simplechat.app",
"services": ["com.jonandnic.simplechat.service"]
}
-52
View File
@@ -1,52 +0,0 @@
var libraries = MojoLoader.require({ name: "foundations", version: "1.0" });
var Future = libraries["foundations"].Control.Future;
var PalmCall = libraries["foundations"].Comms.PalmCall;
var AjaxCall = libraries["foundations"].Comms.AjaxCall;
var AlarmAssistant = function() {}
AlarmAssistant.prototype.run = function(future) {
console.log("***************Hello " + this.controller.args.name);
if (this.controller.args.name == "silent") {
var outputVal = 0;
for (var i = 0; i < 1000; i++) {
outputVal = outputVal++;
}
future.result = { reply: "*********** Hello quietly! Output val is:" + outputVal };
} else {
console.log("********* Trying to get data");
/*var options = { "bodyEncoding": "utf8" };
var future1 = AjaxCall.get("http://chat.webosarchive.com/get-chat.php", options);
future1.then(function(future2) {
console.log("********* Ajax result " + JSON.stringify(future2.result))
if (future2.result.status == 200) { // 200 = Success
console.log('********* Ajax get success ' + JSON.stringify(future2.result));
} else console.log('Ajax get fail');
});*/
var options = { "bodyEncoding": "utf8" };
var future1 = AjaxCall.get("http://chat.webosarchive.com/get-chat.php", options);
future1.then(function(future) {
var result = future.result;
//if (result.returnValue == true) {
console.log("********* Launch Success = " + JSON.stringify(result));
//} else Mojo.Log.info("********* Launch Failure = " + JSON.stringify(result));
future.result = { reply: "*********** Hello noisily!" };
});
/*
var future1 = PalmCall.call("palm://com.palm.applicationManager", "open", { "id": "com.jonandnic.simplechat.app", "params": { "action": "alarm" } });
future1.then(function(future) {
var result = future.result;
if (result.returnValue == true) {
Mojo.Log.info("********* Launch Success = " + JSON.stringify(result));
} else Mojo.Log.info("********* Launch Failure = " + JSON.stringify(result));
});
*/
}
}
-14
View File
@@ -1,14 +0,0 @@
{
"id": "com.jonandnic.simplechat.service",
"description": "SimpleChat Background Service",
"activityTimeout": 30,
"services": [{
"name": "com.jonandnic.simplechat.service",
"description": "helloworld example",
"commands": [{
"name": "alarm",
"assistant": "AlarmAssistant",
"public": true
}]
}]
}
-10
View File
@@ -1,10 +0,0 @@
[{
"library": {
"name": "foundations",
"version": "1.0"
}
},
{
"source": "AlarmAssistant.js"
}
]
View File
File renamed without changes.
+160
View File
@@ -0,0 +1,160 @@
function VersionAssistant(startup) {
this.isStartup = startup;
// on first start, this message is displayed, along with the current version message from below
this.startupMessage = Mojo.Controller.appInfo.startupMessage;
//New Features
this.changelog = [
{
version: "Version 1.8.0",
detail: [
"Dark theme now available in Preferences!",
]
},
{
version: "Version 1.7.1",
detail: [
"Fix a bug where hardware mute switch wasn't respected. Show transmit indicator. Add ability to disable link and image parsing to speed things up on tiny devices.",
]
},{
version: "Version 1.7.0",
detail: [
"Images from listThemePreference are now supported! Inline rendering uses a thumbnail for efficiency; tap the image to load the fullsize, or tap elsewhere on the row to pull up the menu that lets you download the image to your device (it will be saved to /internal/downloads)",
]
},{
version: "Version 1.6.0",
detail: [
"Added ability to parse Imgur album links and render via a service proxy.",
]
},{
version: "Version 1.5.0",
detail: [
"Added ability to parse Share Services links and show thumbnails, configurable in Preferences.",
]
},{
version: "Version 1.4.1",
detail: [
"Add support for some basic markdown",
]
},
{
version: "Version 1.4.0",
detail: [
"Enter key behavior as a Preference",
"Even more silent background sync, won't sync if offline",
"Swiping notifications resets missed message count",
"Adjust tap target for collapsing controls"
]
},
{
version: "Version 1.3.2",
detail: [
"More sync timing options.",
"Refactored some code to make forking easier."
]
}, {
version: "Version 1.3.1",
detail: [
"Enter key now sends message when Command menu buttons are hidden."
]
},
{
version: "Version 1.3.0",
detail: [
"Drastically improved background update and notifications."
]
}, {
version: "Version 1.2.0",
detail: [
"Converts image links to an image proxy so they can be viewed and scaled properly on tiny devices",
"Improved background update, using a notification launch, no longer interupts Exhibition, video playback or One Night Stand"
]
},
{
version: "Version 1.1.3",
detail: [
"Bug fixes and general maintenance"
]
}, {
version: "Version 1.1.2",
detail: [
"Show/Hide the Command menu buttons by tapping the Compose area title bar",
"Enable/Disable listThemePreference Emojis in Preferences",
]
}, {
version: "Version 1.1.1",
detail: [
"Improved listThemePreference integration, including showing Emojis!<span class='emoji-outer emoji-sizer'><span class='emoji-inner emoji1f388'></span></span>",
"Now with a smaller emoji file!",
"Improved timeout logic.",
"Trying different background sync logic."
]
}
];
// setup command menu
this.cmdMenuModel = {
visible: false,
items: [
{},
{
label: $L("OK! Let's Go..."),
command: 'do-continue'
},
{}
]
};
};
VersionAssistant.prototype.setup = function() {
this.titleElement = this.controller.get('title');
this.dataElement = this.controller.get('data');
this.titleElement.innerHTML = $L('Version Info');
var html = '';
html += '<div style="margin: 2px 12px 12px 12px">' + this.startupMessage + '</div>';
for (var i = 0; i < this.changelog.length; i++) {
html += Mojo.View.render({ object: { title: this.changelog[i].version }, template: 'version/rowDivider' });
html += '<ul>';
for (var j = 0; j < this.changelog[i].detail.length; j++) {
html += '<li>' + this.changelog[i].detail[j] + '</li>';
}
html += '</ul>';
}
// set data
this.dataElement.innerHTML = html;
// setup menu
this.controller.setupWidget(Mojo.Menu.appMenu, { omitDefaultItems: true }, { visible: false });
//if (this.isStartup) {
// set command menu
this.controller.setupWidget(Mojo.Menu.commandMenu, { menuClass: 'no-fade' }, this.cmdMenuModel);
//}
};
VersionAssistant.prototype.activate = function(event) {
this.timer = this.controller.window.setTimeout(this.showContinue.bind(this), 2 * 1000);
};
VersionAssistant.prototype.deactivate = function(event) {};
VersionAssistant.prototype.cleanup = function(event) {};
VersionAssistant.prototype.showContinue = function() {
// show the command menu
this.controller.setMenuVisible(Mojo.Menu.commandMenu, true);
};
VersionAssistant.prototype.handleCommand = function(event) {
if (event.type == Mojo.Event.command) {
switch (event.command) {
case 'do-continue':
this.controller.stageController.popScene();
break;
}
}
};