Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
{
"editor.formatOnSave": true,
"autoimport.doubleQuotes": false,
"java.configuration.updateBuildConfiguration": "disabled",
"prettier.requireConfig": true,
"javascript.format.enable": true,
"js/ts.format.enabled": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"prettier.tabWidth": 2,
"prettier.useTabs": false,
"javascript.format.semicolons": "insert",
"js/ts.format.semicolons": "insert",
"[scss]": {
"editor.defaultFormatter": "vscode.css-language-features"
},
Expand All @@ -27,6 +25,10 @@
"attr_quotes": "single"
}
},
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"biome.lsp.bin": "./node_modules/.bin/biome",
"cSpell.words": [
"abap",
"Acode",
Expand Down Expand Up @@ -126,6 +128,7 @@
"flac",
"Flix",
"floobits",
"FOXBIZ",
"Foxdebug",
"freemarker",
"gamemaker",
Expand Down Expand Up @@ -372,8 +375,5 @@
"wxss",
"xquery",
"Zeek"
],
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
}
]
}
20 changes: 8 additions & 12 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,14 @@
},
"files": {
"includes": [
"**/src/**/*",
"**/utils/**/*.js",
"!**/www/build/**/*",
"**/www/res/**/*.css",
"**/src/plugins/terminal/**",
"!**/ace-builds",
"!**/src/plugins/**/*",
"!**/plugins/**/*",
"!**/hooks/**/*",
"!**/fastlane/**/*",
"!**/res/**/*",
"!**/platforms/**/*"
"src/**/*.js",
"utils/**/*.js",
"!src/plugins/**/*.js",
"!www/**/*",
"!plugins/**/*",
"!hooks/**/*",
"!fastlane/**/*",
"!platforms/**/*"
]
}
}
3 changes: 1 addition & 2 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
{
"exclude": ["**/node_modules", "**/platforms", "**/www", "www/js/ace/**/*"],
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"*": ["*"]
"*": ["./src/*"]
}
},
"include": ["src/**/*"],
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"com.foxdebug.acode.rk.exec.terminal": {},
"com.foxdebug.acode.rk.customtabs": {},
"com.foxdebug.acode.rk.plugin.plugincontext": {},
"com.foxdebug.acode.rk.auth": {},
"cordova-plugin-system": {}
"cordova-plugin-system": {},
"com.foxdebug.acode.rk.auth": {}
},
"platforms": [
"android"
Expand Down Expand Up @@ -187,4 +187,4 @@
"@codemirror/view": "^6.40.0"
},
"browserslist": "cover 100%,not android < 5"
}
}
9 changes: 8 additions & 1 deletion src/cm/lsp/serverLauncher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const STATUS_FAILED: InstallStatus = "failed";

const AXS_BINARY = "$PREFIX/axs";

let alreadyInformed = false;

function getTerminalRequiredMessage(): string {
return (
strings?.terminal_required_message_for_lsp ??
Expand Down Expand Up @@ -1081,7 +1083,12 @@ export async function ensureServerRunning(
} catch {}
if (!isTerminalInstalled) {
const message = getTerminalRequiredMessage();
alert(strings?.error, message);

if (!alreadyInformed){
alreadyInformed = true;
alert(strings?.error, message);
}

const unavailable: LspError = new Error(message);
unavailable.code = "LSP_SERVER_UNAVAILABLE";
throw unavailable;
Expand Down
106 changes: 75 additions & 31 deletions src/components/sidebar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@ import toast from "components/toast";
import Ref from "html-tag-js/ref";
import actionStack from "lib/actionStack";
import auth, { loginEvents } from "lib/auth";
import constants from "lib/constants";
import config from "lib/config";

/**
* @typedef {object} SideBar
* @extends HTMLElement
* @property {function():void} hide
* @property {function():void} toggle
* @property {function():void} onshow
*/

/**@type {HTMLElement} */
let $sidebar;
/**@type {Array<(el:HTMLElement)=>boolean>} */
let preventSlideTests = [];
Expand All @@ -14,14 +23,6 @@ const events = {
hide: [],
};

/**
* @typedef {object} SideBar
* @extends HTMLElement
* @property {function():void} hide
* @property {function():void} toggle
* @property {function():void} onshow
*/

/**
* Create a sidebar
* @param {HTMLElement} [$container] - the element that will contain the sidebar
Expand All @@ -31,7 +32,7 @@ const events = {
function create($container, $toggler) {
let { innerWidth } = window;

const START_THRESHOLD = constants.SIDEBAR_SLIDE_START_THRESHOLD_PX; //Point where to start swipe
const START_THRESHOLD = config.SIDEBAR_SLIDE_START_THRESHOLD_PX; //Point where to start swipe
const MIN_WIDTH = 200; //Min width of the side bar
const MAX_WIDTH = () => innerWidth * 0.7; //Max width of the side bar
const resizeBar = Ref();
Expand Down Expand Up @@ -103,10 +104,15 @@ function create($container, $toggler) {

async function handleUserIconClick(e) {
try {
const isLoggedIn = await auth.isLoggedIn();

if (!isLoggedIn) {
auth.openLoginUrl();
const user = await auth.getLoggedInUser();

if (!user) {
CustomTabs.open(
`${config.BASE_URL}/login?redirect=app`,
{ showTitle: true },
() => {},
() => {},
);
} else {
toggleUserMenu();
}
Expand All @@ -121,10 +127,8 @@ function create($container, $toggler) {
const isActive = menu.classList.toggle("active");

if (isActive) {
// Populate user info
updateUserMenuInfo();

// Add click outside listener
setTimeout(() => {
document.addEventListener("click", handleClickOutside);
}, 10);
Expand All @@ -146,16 +150,19 @@ function create($container, $toggler) {

async function updateUserMenuInfo() {
try {
const userInfo = await auth.getUserInfo();
if (userInfo) {
const menuName = userContextMenu.el.querySelector(".user-menu-name");
const menuEmail = userContextMenu.el.querySelector(".user-menu-email");
menuName.textContent = userInfo.name || "Anonymous";
if (userInfo.isAdmin) {
menuName.innerHTML += ' <span class="badge">Admin</span>';
}
menuEmail.textContent = userInfo.email || "";
}
const userInfo = await auth.getLoggedInUser();
const menuName = userContextMenu.el.querySelector(".user-menu-name");
const menuEmail = userContextMenu.el.querySelector(".user-menu-email");
menuName.content = (
<div style={{ display: "flex" }}>
{Boolean(userInfo.verified) && (
<span className="icon verified"></span>
)}
{userInfo.name}
{Boolean(userInfo.acode_pro) && <span className="badge">Pro</span>}
</div>
);
menuEmail.textContent = userInfo.email || "";
} catch (error) {
console.error("Error fetching user info:", error);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Null dereference in updateUserMenuInfo when getLoggedInUser() returns null

auth.getLoggedInUser() explicitly returns null when the server responds with 401 (e.g., session expired between the handleUserIconClick guard and this call, or the token was revoked). Accessing userInfo.verified, userInfo.name, userInfo.acode_pro, and userInfo.email on a null value all throw TypeError. The catch block swallows the error silently, so the menu is rendered empty with no user feedback.

Add a null guard after the await:

const userInfo = await auth.getLoggedInUser();
if (!userInfo) return;

Expand All @@ -178,8 +185,6 @@ function create($container, $toggler) {
}

async function updateSidebarAvatar() {
const avatarUrl = await auth.getAvatar();
// Remove existing icon or avatar
const existingIcon = userAvatar.el.querySelector(".icon");
const existingAvatar = userAvatar.el.querySelector(".avatar");

Expand All @@ -190,20 +195,59 @@ function create($container, $toggler) {
existingAvatar.remove();
}

if (avatarUrl?.startsWith("data:") || avatarUrl?.startsWith("http")) {
// Create and add avatar image
const user = await auth.getLoggedInUser();

if (user) {
const avatarUrl = user.github
? `https://avatars.githubusercontent.com/${user.github}`
: generateInitialsAvatar(user.name);
const avatarImg = document.createElement("img");
avatarImg.className = "avatar";
avatarImg.src = avatarUrl;
userAvatar.append(avatarImg);
} else {
// Fallback to default icon
const defaultIcon = document.createElement("span");
defaultIcon.className = "icon account_circle";
userAvatar.append(defaultIcon);
}
}

function generateInitialsAvatar(name) {
const nameParts = name.split(" ");
const initials =
nameParts.length >= 2
? `${nameParts[0][0]}${nameParts[1][0]}`.toUpperCase()
: nameParts[0][0].toUpperCase();

const canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
const ctx = canvas.getContext("2d");

const colors = [
"#2196F3",
"#9C27B0",
"#E91E63",
"#009688",
"#4CAF50",
"#FF9800",
];
ctx.fillStyle =
colors[
name.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0) %
colors.length
];
ctx.fillRect(0, 0, 100, 100);

ctx.fillStyle = "#ffffff";
ctx.font = "bold 40px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(initials, 50, 50);

return canvas.toDataURL();
}

function onWindowResize() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
Expand Down
4 changes: 2 additions & 2 deletions src/dialogs/rateBox.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import constants from "lib/constants";
import config from "lib/config";
import template from "views/rating.hbs";
import box from "./box";

Expand Down Expand Up @@ -34,7 +34,7 @@ function rateBox() {
const stars = getStars(val);
const subject = "feedback - Acode editor";
const textBody = stars + "</br>%0A" + getFeedbackBody("</br>%0A");
const email = constants.FEEDBACK_EMAIL;
const email = config.FEEDBACK_EMAIL;
system.openInBrowser(
`mailto:${email}?subject=${subject}&body=${textBody}`,
);
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/editorFileTab.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import constants from "lib/constants";
import config from "lib/config";
import settings from "lib/settings";

const opts = { passive: false };
Expand Down Expand Up @@ -82,7 +82,7 @@ export default function startDrag(e) {
}

if (settings.value.vibrateOnTap) {
navigator.vibrate(constants.VIBRATION_TIME);
navigator.vibrate(config.VIBRATION_TIME);
}

$tab = e.target;
Expand Down
4 changes: 2 additions & 2 deletions src/handlers/quickToolsInit.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import quickTools from "components/quickTools";
import constants from "lib/constants";
import config from "lib/config";
import appSettings from "lib/settings";
import actions, { key } from "./quickTools";

Expand Down Expand Up @@ -300,7 +300,7 @@ function oncontextmenu(e) {
const { editor, activeFile } = editorManager;

if (isClickMode && appSettings.value.vibrateOnTap) {
navigator.vibrate(constants.VIBRATION_TIME_LONG);
navigator.vibrate(config.VIBRATION_TIME_LONG);
$el.classList.add("active");
}

Expand Down
2 changes: 0 additions & 2 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ declare const DATA_STORAGE: string;
declare const CACHE_STORAGE: string;
declare const PLUGIN_DIR: string;
declare const KEYBINDING_FILE: string;
declare const IS_FREE_VERSION: string;
declare const ANDROID_SDK_INT: number;
declare const DOES_SUPPORT_THEME: boolean;
declare const acode: object;
Expand All @@ -16,7 +15,6 @@ interface Window {
CACHE_STORAGE: string;
PLUGIN_DIR: string;
KEYBINDING_FILE: string;
IS_FREE_VERSION: string;
ANDROID_SDK_INT: number;
DOES_SUPPORT_THEME: boolean;
acode: object;
Expand Down
Loading