From 5f379cc7a34467cc89e68bd4e4b84fb5703ac464 Mon Sep 17 00:00:00 2001 From: KKamaa Date: Thu, 23 Apr 2026 04:46:29 +0300 Subject: [PATCH 1/4] [FIX] issues #36,#37,#38 &39 --- dist/chrome/js/components/options-app.js | 19 +- dist/chrome/js/components/popup-app.js | 24 +- dist/chrome/js/templates.js | 263 +++-------- dist/chrome/manifest.json | 3 + dist/firefox/js/components/options-app.js | 19 +- dist/firefox/js/components/popup-app.js | 24 +- dist/firefox/js/templates.js | 263 +++-------- src/templates/options_app.xml | 546 +++++++++------------- src/templates/popup_app.xml | 21 +- 9 files changed, 423 insertions(+), 759 deletions(-) diff --git a/dist/chrome/js/components/options-app.js b/dist/chrome/js/components/options-app.js index 4a9cf31..3156572 100644 --- a/dist/chrome/js/components/options-app.js +++ b/dist/chrome/js/components/options-app.js @@ -51,7 +51,7 @@ function createOptionsAppTemplate(app, bdom, helpers) { const readMoreState = app.createComponent('ReadMore', true, false, false, ['text', 'limit']); const rootBlock = createBlock( - `


Description


This is a standalone Owl rewrite of the original cross-platform timer extension for posting work hours to Odoo timesheets.

Features


  • Support for both tasks and issues
  • Start and stop the timer for the selected item
  • Create Odoo timesheet lines against the linked analytic account
  • Show assigned items or everyone’s items
  • Add, remove, or clear remote hosts
  • Switch between remote sessions
  • Download current month or current item timesheets as CSV

Add Remote


Controls
` + `


Description


This is a standalone Owl rewrite of the original cross-platform timer extension for posting work hours to Odoo timesheets.

Features


  • Support for both tasks and issues
  • Start and stop the timer for the selected item
  • Create Odoo timesheet lines against the linked analytic account
  • Show assigned items or everyone’s items
  • Add, remove, or clear remote hosts
  • Switch between remote sessions
  • Download current month or current item timesheets as CSV

General Settings


Store timesheet locally each time you stop the timer on an item.


Add Remote


Controls
` ); const errorBlock = createBlock(`
`); const remotesTableBlock = createBlock( @@ -95,6 +95,9 @@ function createOptionsAppTemplate(app, bdom, helpers) { const reloadRemotesHandler = [ctx.loadRemotes, ctx]; const toggleListHandler = [() => { ctx.state.showList = !ctx.state.showList; }, ctx]; const removeAllRemotesHandler = [ctx.removeAllRemotes, ctx]; + // [FIX #38] Auto-download preference + const autoDownloadChecked = ctx.state.autoDownloadIssueTimesheet; + const autoDownloadHandler = [(ev) => { ctx.toggleAutoDownload(ev); }]; if (ctx.state.error) { errorNode = errorBlock([ctx.state.error]); @@ -155,6 +158,8 @@ function createOptionsAppTemplate(app, bdom, helpers) { reloadRemotesHandler, toggleListHandler, removeAllRemotesHandler, + autoDownloadChecked, // 21 [FIX #38] + autoDownloadHandler, // 22 [FIX #38] ], [errorNode, remoteListNode] ); @@ -174,6 +179,7 @@ class OptionsApp extends Component { remotes: [], showList: true, error: '', + autoDownloadIssueTimesheet: false, // [FIX #38] form: { remote_host: '', remote_name: '', @@ -184,6 +190,8 @@ class OptionsApp extends Component { onWillStart(async () => { await this.loadRemotes(); + const saved = await storage.get('auto_download_issue_timesheet', false); + this.state.autoDownloadIssueTimesheet = !!saved; // [FIX #38] }); } @@ -206,6 +214,15 @@ class OptionsApp extends Component { this.state.form.remote_datasrc = DEFAULT_DATA_SOURCE; } + /** + * [FIX #38] Toggle the auto-download timesheet preference and persist it. + * @param {Event} ev + */ + async toggleAutoDownload(ev) { + this.state.autoDownloadIssueTimesheet = ev.target.checked; + await storage.set('auto_download_issue_timesheet', !!this.state.autoDownloadIssueTimesheet); + } + /** * Validate current form fields and return normalized values. * diff --git a/dist/chrome/js/components/popup-app.js b/dist/chrome/js/components/popup-app.js index 64eb316..1fc8fd3 100644 --- a/dist/chrome/js/components/popup-app.js +++ b/dist/chrome/js/components/popup-app.js @@ -79,7 +79,7 @@ function createPopupAppTemplate(app, bdom, helpers) { const readMoreProject = app.createComponent('ReadMore', true, false, false, ['text', 'limit']); const rootBlock = createBlock( - `
Loading current session and projects…
Please wait — or grab a cup of coffee ☕
PriorityStage
[]
Project
` + `
Loading current session and projects…
Please wait — or grab a cup of coffee ☕
PriorityStage
[]
Project
` ); const bootErrorBlock = createBlock(`

`); const noRemotesBlock = createBlock( @@ -106,8 +106,8 @@ function createPopupAppTemplate(app, bdom, helpers) { `
Host:
` ); const activeTimerDurationBlock = createBlock(``); - const hoursSpentHeaderBlock = createBlock(`Hours Spent`); - const remainingHoursHeaderBlock = createBlock(`Remaining Hours`); + const hoursSpentHeaderBlock = createBlock(`Hours Spent`); + const remainingHoursHeaderBlock = createBlock(`Hours Left`); const issueRowBlock = createBlock( `` ); @@ -119,8 +119,8 @@ function createPopupAppTemplate(app, bdom, helpers) { ); const priorityStarBlock = createBlock(``); const priorityStarOutlineBlock = createBlock(``); - const effectiveHoursCellBlock = createBlock(``); - const remainingHoursCellBlock = createBlock(``); + const effectiveHoursCellBlock = createBlock(``); + const remainingHoursCellBlock = createBlock(``); const emptyIssuesRowBlock = createBlock( `No matching items are currently available` ); @@ -250,8 +250,6 @@ function createPopupAppTemplate(app, bdom, helpers) { const limitHandler = [(ev) => { ctx.updateLimitPreference(ev.target.value); }]; - const autoDownloadChecked = ctx.state.autoDownloadIssueTimesheet; - const autoDownloadHandler = [ctx.toggleAutoDownload, ctx]; const downloadTimesheetHandler = [ctx.downloadCurrentMonthTimesheets, ctx]; const switchRemotesHandler = [ctx.switchBetweenRemotes, ctx]; const refreshHandler = [ctx.refreshAll, ctx]; @@ -400,8 +398,6 @@ function createPopupAppTemplate(app, bdom, helpers) { searchQueryHandler, limitValue, limitHandler, - autoDownloadChecked, - autoDownloadHandler, downloadTimesheetHandler, switchRemotesHandler, refreshHandler, @@ -845,15 +841,17 @@ class PopupApp extends Component { } issueLabel(issue) { - const issueName = this.normalizeText( - issue.display_name || issue.name || issue.message_summary || issue.description || '' - ); - + // [FIX #36] For tasks, use issue.name only — display_name already + // contains the code prefix in Odoo, causing it to appear twice. if (this.state.dataSource === DATA_SOURCE_TASK) { const code = this.normalizeText(issue.code); + const issueName = this.normalizeText(issue.name || issue.description || ''); return [code, issueName].filter(Boolean).join(' - ') || `#${issue.id}`; } + const issueName = this.normalizeText( + issue.display_name || issue.name || issue.message_summary || issue.description || '' + ); return [`#${issue.id}`, issueName].filter(Boolean).join(' - '); } diff --git a/dist/chrome/js/templates.js b/dist/chrome/js/templates.js index a87b1ca..8f38a04 100644 --- a/dist/chrome/js/templates.js +++ b/dist/chrome/js/templates.js @@ -1,190 +1,77 @@ export const templates = { - "PopupApp": function PopupApp(app, bdom, helpers + "OptionsApp": function OptionsApp(app, bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { safeOutput, prepareList, withKey } = helpers; const comp1 = app.createComponent(`ReadMore`, true, false, false, ["text","limit"]); - const comp2 = app.createComponent(`ReadMore`, true, false, false, ["text","limit","href"]); + const comp2 = app.createComponent(`ReadMore`, true, false, false, ["text","limit"]); const comp3 = app.createComponent(`ReadMore`, true, false, false, ["text","limit"]); const comp4 = app.createComponent(`ReadMore`, true, false, false, ["text","limit"]); const comp5 = app.createComponent(`ReadMore`, true, false, false, ["text","limit"]); - let block1 = createBlock(`
Loading current session and projects…
Please wait — or grab a cup of coffee ☕
PriorityStage
[]
Project
`); - let block2 = createBlock(`

`); - let block4 = createBlock(`
Hello 😉, you have not configured any remotes. Open Options below and add one.
`); - let block5 = createBlock(`
`); - let block6 = createBlock(`

`); - let block8 = createBlock(``); - let block9 = createBlock(``); - let block10 = createBlock(``); - let block12 = createBlock(``); - let block14 = createBlock(``); - let block15 = createBlock(`
Host:
`); - let block18 = createBlock(` #`); - let block20 = createBlock(``); - let block25 = createBlock(`Hours Spent`); - let block26 = createBlock(`Remaining Hours`); - let block28 = createBlock(``); - let block29 = createBlock(``); - let block30 = createBlock(``); - let block32 = createBlock(``); - let block33 = createBlock(``); - let block37 = createBlock(``); - let block39 = createBlock(``); - let block42 = createBlock(` No matching items are currently available `); + let block1 = createBlock(`


Description


This is a standalone Owl rewrite of the original cross-platform timer extension for posting work hours to Odoo timesheets.

Features


  • Support for both tasks and issues
  • Start and stop the timer for the selected item
  • Create Odoo timesheet lines against the linked analytic account
  • Show assigned items or everyone's items
  • Add, remove, or clear remote hosts
  • Switch between remote sessions
  • Download current month or current item timesheets as CSV

General Settings


Store timesheet locally each time you stop the timer on an item.


Add Remote


Controls
`); + let block2 = createBlock(`
`); + let block4 = createBlock(`
List of Available Remotes
RemoteHostDatabaseSourceState
`); + let block6 = createBlock(``); return function template(ctx, node, key = "") { - let b2, b4, b5, b18, b20, b22, b23, b24, b27, b42, b43, b44, b45, b46; - let attr1 = ctx['state'].view==='loading'?'':'hide'; - let attr2 = ctx['state'].view==='login'?'login-view':'login-view hide'; - if (ctx['state'].bootError) { - const b3 = safeOutput(ctx['state'].bootError); - b2 = block2([], [b3]); - } - if (!ctx['state'].remotes.length) { - b4 = block4(); - } - if (ctx['state'].remotes.length) { - let b6, b8, b9, b10, b11, b14, b15; - let hdlr1 = ["prevent", ctx['login'], ctx]; - if (ctx['state'].loginError) { - const b7 = safeOutput(ctx['state'].loginError); - b6 = block6([], [b7]); - } - if (!ctx['state'].useExistingSession) { - let prop1 = new String((ctx['state'].username) === 0 ? 0 : ((ctx['state'].username) || "")); - const v1 = ctx['state']; - let hdlr2 = [(_ev)=>v1.username=_ev.target.value, ctx]; - b8 = block8([prop1, hdlr2]); - } - if (!ctx['state'].useExistingSession) { - let attr3 = ctx['state'].showPassword?'text':'password'; - let prop2 = new String((ctx['state'].password) === 0 ? 0 : ((ctx['state'].password) || "")); - const v2 = ctx['state']; - let hdlr3 = [(_ev)=>v2.password=_ev.target.value, ctx]; - b9 = block9([attr3, prop2, hdlr3]); - } - if (!ctx['state'].useExistingSession) { - let hdlr4 = [ctx['togglePassword'], ctx]; - let attr4 = ctx['state'].showPassword?'fa fa-eye-slash':'fa fa-eye'; - b10 = block10([hdlr4, attr4]); - } - const v3 = ctx['state']; - let hdlr5 = [(_ev)=>v3.selectedRemoteIndex=_ev.target.value, ctx]; - ctx = Object.create(ctx); - const [k_block11, v_block11, l_block11, c_block11] = prepareList(ctx['state'].remotes);; - for (let i1 = 0; i1 < l_block11; i1++) { - ctx[`remote`] = k_block11[i1]; - const key1 = ctx['remote'].database+ctx['remote'].url; - let attr5 = ctx['remote'].__index; - let prop3 = new Boolean(ctx['state'].selectedRemoteIndex===ctx['remote'].__index); - const b13 = safeOutput(ctx['remote'].name); - c_block11[i1] = withKey(block12([attr5, prop3], [b13]), key1); - } - ctx = ctx.__proto__; - b11 = list(c_block11); - let prop4 = new Boolean(ctx['state'].useExistingSession); - let hdlr6 = [ctx['toggleUseExistingSession'], ctx]; - if (ctx['state'].loginLoading) { - b14 = block14(); - } - if (ctx['currentRemote']) { - const b16 = safeOutput(ctx['currentRemote'].url); - const b17 = safeOutput(ctx['currentRemote'].datasrc||'project.issue'); - b15 = block15([], [b16, b17]); - } - b5 = block5([hdlr1, hdlr5, prop4, hdlr6], [b6, b8, b9, b10, b11, b14, b15]); - } - let attr6 = ctx['state'].view==='main'?'':'hide'; - let prop5 = new String((ctx['state'].searchQuery) === 0 ? 0 : ((ctx['state'].searchQuery) || "")); + let b2, b4; + let attr1 = ctx['state'].activePage==='about'?'selected':'notselected'; + const v1 = ctx['state']; + let hdlr1 = [()=>v1.activePage='about', ctx]; + let attr2 = ctx['state'].activePage==='options'?'selected':'notselected'; + const v2 = ctx['state']; + let hdlr2 = [()=>v2.activePage='options', ctx]; + let attr3 = ctx['state'].activePage==='about'?'active_page':'inactive_page'; + let attr4 = ctx['state'].activePage==='options'?'active_page':'inactive_page'; + let hdlr3 = ["prevent", ctx['addRemote'], ctx]; + let prop1 = new Boolean(ctx['state'].autoDownloadIssueTimesheet); + let hdlr4 = [ctx['toggleAutoDownload'], ctx]; + let prop2 = new String((ctx['state'].form.remote_host) === 0 ? 0 : ((ctx['state'].form.remote_host) || "")); + const v3 = ctx['state']; + let hdlr5 = [(_ev)=>v3.form.remote_host=_ev.target.value, ctx]; + let prop3 = new String((ctx['state'].form.remote_name) === 0 ? 0 : ((ctx['state'].form.remote_name) || "")); const v4 = ctx['state']; - let hdlr7 = [(_ev)=>v4.searchQuery=_ev.target.value, ctx]; - let prop6 = new String((ctx['state'].limitTo) === 0 ? 0 : ((ctx['state'].limitTo) || "")); - const v5 = ctx['updateLimitPreference']; - let hdlr8 = [(_ev)=>v5(_ev.target.value), ctx]; - let prop7 = new Boolean(ctx['state'].autoDownloadIssueTimesheet); - let hdlr9 = [ctx['toggleAutoDownload'], ctx]; - let hdlr10 = [ctx['downloadCurrentMonthTimesheets'], ctx]; - let hdlr11 = [ctx['switchBetweenRemotes'], ctx]; - let hdlr12 = [ctx['refreshAll'], ctx]; - let hdlr13 = [ctx['resetTimer'], ctx]; - let hdlr14 = [ctx['logout'], ctx]; - if (ctx['state'].activeTimerId&&ctx['state'].timerStartIso) { - const b19 = safeOutput(ctx['state'].activeTimerId); - b18 = block18([], [b19]); - } - if (ctx['state'].timerStartIso) { - const b21 = safeOutput(ctx['formattedTimer']); - b20 = block20([], [b21]); - } - b22 = safeOutput(ctx['itemLabelPlural']); - b23 = safeOutput(ctx['filteredIssues'].length); - let prop8 = new Boolean(ctx['state'].allIssues); - const v6 = ctx['updateShowAllPreference']; - let hdlr15 = [(_ev)=>v6(_ev.target.checked), ctx]; - if (ctx['state'].dataSource==='project.task') { - const b25 = block25(); - const b26 = block26(); - b24 = multi([b25, b26]); + let hdlr6 = [(_ev)=>v4.form.remote_name=_ev.target.value, ctx]; + let prop4 = new String((ctx['state'].form.remote_database) === 0 ? 0 : ((ctx['state'].form.remote_database) || "")); + const v5 = ctx['state']; + let hdlr7 = [(_ev)=>v5.form.remote_database=_ev.target.value, ctx]; + let prop5 = new Boolean(ctx['state'].form.remote_datasrc==='project.issue'); + const v6 = ctx['state']; + let hdlr8 = [()=>v6.form.remote_datasrc='project.issue', ctx]; + let prop6 = new Boolean(ctx['state'].form.remote_datasrc==='project.task'); + const v7 = ctx['state']; + let hdlr9 = [()=>v7.form.remote_datasrc='project.task', ctx]; + let hdlr10 = [ctx['addRemote'], ctx]; + let hdlr11 = [ctx['loadRemotes'], ctx]; + const v8 = ctx['state']; + let hdlr12 = [()=>v8.showList=!v8.showList, ctx]; + let hdlr13 = [ctx['removeAllRemotes'], ctx]; + if (ctx['state'].error) { + const b3 = safeOutput(ctx['state'].error); + b2 = block2([], [b3]); } - if (ctx['filteredIssues'].length) { + if (ctx['state'].showList&&ctx['state'].remotes.length) { ctx = Object.create(ctx); - const [k_block27, v_block27, l_block27, c_block27] = prepareList(ctx['filteredIssues']);; - for (let i1 = 0; i1 < l_block27; i1++) { - ctx[`issue`] = k_block27[i1]; - const key1 = ctx['issue'].id; - let b29, b30, b31, b33, b34, b35, b36, b41; - let attr7 = ctx['state'].activeTimerId===ctx['issue'].id?'active-row':''; - if (!ctx['state'].activeTimerId) { - const v7 = ctx['startTimer']; - const v8 = ctx['issue']; - let hdlr16 = [()=>v7(v8), ctx]; - b29 = block29([hdlr16]); - } - if (ctx['state'].activeTimerId===ctx['issue'].id) { - const v9 = ctx['stopTimer']; - const v10 = ctx['issue']; - let hdlr17 = [()=>v9(v10), ctx]; - b30 = block30([hdlr17]); - } - if (ctx['issue'].priority_level.length) { - ctx = Object.create(ctx); - const [k_block31, v_block31, l_block31, c_block31] = prepareList(ctx['issue'].priority_level);; - for (let i2 = 0; i2 < l_block31; i2++) { - ctx[`priority`] = k_block31[i2]; - const key2 = ctx['priority']+'_'+ctx['issue'].id; - c_block31[i2] = withKey(block32(), key2); - } - ctx = ctx.__proto__; - b31 = list(c_block31); - } - if (!ctx['issue'].priority_level.length) { - b33 = block33(); - } - b34 = comp1({text: ctx['relationLabel'](ctx['issue'].stage_id),limit: 15}, key + `__1__${key1}`, node, this, null); - b35 = comp2({text: ctx['issueLabel'](ctx['issue']),limit: 70,href: ctx['issueHref'](ctx['issue'])}, key + `__2__${key1}`, node, this, null); - if (ctx['state'].dataSource==='project.task') { - const b38 = comp3({text: ctx['normalizeText'](ctx['formatHours'](ctx['issue'].effective_hours)),limit: 9}, key + `__3__${key1}`, node, this, null); - const b37 = block37([], [b38]); - const b40 = comp4({text: ctx['normalizeText'](ctx['formatHours'](ctx['issue'].remaining_hours)),limit: 9}, key + `__4__${key1}`, node, this, null); - const b39 = block39([], [b40]); - b36 = multi([b37, b39]); - } - b41 = comp5({text: ctx['relationLabel'](ctx['issue'].project_id),limit: 15}, key + `__5__${key1}`, node, this, null); - c_block27[i1] = withKey(block28([attr7], [b29, b30, b31, b33, b34, b35, b36, b41]), key1); + const [k_block5, v_block5, l_block5, c_block5] = prepareList(ctx['state'].remotes);; + for (let i1 = 0; i1 < l_block5; i1++) { + ctx[`remote`] = k_block5[i1]; + const key1 = ctx['remote'].url+ctx['remote'].database; + const b7 = comp1({text: ctx['remote'].name,limit: 18}, key + `__1__${key1}`, node, this, null); + const b8 = comp2({text: ctx['remote'].url,limit: 25}, key + `__2__${key1}`, node, this, null); + const b9 = comp3({text: ctx['remote'].database,limit: 18}, key + `__3__${key1}`, node, this, null); + const b10 = comp4({text: ctx['remote'].datasrc||'project.issue',limit: 18}, key + `__4__${key1}`, node, this, null); + const b11 = comp5({text: ctx['remote'].state||'Inactive',limit: 18}, key + `__5__${key1}`, node, this, null); + const v9 = ctx['removeRemote']; + const v10 = ctx['remote']; + let hdlr14 = [()=>v9(v10), ctx]; + c_block5[i1] = withKey(block6([hdlr14], [b7, b8, b9, b10, b11]), key1); } ctx = ctx.__proto__; - b27 = list(c_block27); + const b5 = list(c_block5); + b4 = block4([], [b5]); } - if (!ctx['filteredIssues'].length) { - let attr8 = ctx['state'].dataSource==='project.task'?7:5; - b42 = block42([attr8]); - } - b43 = safeOutput(ctx['state'].serverVersion||'Unknown'); - b44 = safeOutput(ctx['state'].currentHost||'-'); - b45 = safeOutput(ctx['state'].currentDatabase||'-'); - b46 = safeOutput(ctx['state'].user?ctx['state'].user.display_name:'-'); - return block1([attr1, attr2, attr6, prop5, hdlr7, prop6, hdlr8, prop7, hdlr9, hdlr10, hdlr11, hdlr12, hdlr13, hdlr14, prop8, hdlr15], [b2, b4, b5, b18, b20, b22, b23, b24, b27, b42, b43, b44, b45, b46]); + return block1([attr1, hdlr1, attr2, hdlr2, attr3, attr4, hdlr3, prop1, hdlr4, prop2, hdlr5, prop3, hdlr6, prop4, hdlr7, prop5, hdlr8, prop6, hdlr9, hdlr10, hdlr11, hdlr12, hdlr13], [b2, b4]); } }, @@ -198,7 +85,7 @@ export const templates = { const comp4 = app.createComponent(`ReadMore`, true, false, false, ["text","limit"]); const comp5 = app.createComponent(`ReadMore`, true, false, false, ["text","limit"]); - let block1 = createBlock(`
Loading current session and projects…
Please wait — or grab a cup of coffee ☕
PriorityStage
[]
Project
`); + let block1 = createBlock(`
Loading current session and projects…
Please wait — or grab a cup of coffee ☕
PriorityStage
[]
Project
`); let block2 = createBlock(`

`); let block4 = createBlock(`
Hello 😉, you have not configured any remotes. Open Options below and add one.
`); let block5 = createBlock(`
`); @@ -210,15 +97,15 @@ export const templates = { let block14 = createBlock(``); let block15 = createBlock(`
Host:
`); let block18 = createBlock(``); - let block23 = createBlock(`Hours Spent`); - let block24 = createBlock(`Remaining Hours`); + let block23 = createBlock(`Hours Spent`); + let block24 = createBlock(`Hours Left`); let block26 = createBlock(``); let block27 = createBlock(``); let block28 = createBlock(``); let block30 = createBlock(``); let block31 = createBlock(``); - let block35 = createBlock(``); - let block37 = createBlock(``); + let block35 = createBlock(``); + let block37 = createBlock(``); let block40 = createBlock(` No matching items are currently available `); return function template(ctx, node, key = "") { @@ -293,22 +180,20 @@ export const templates = { let hdlr8 = [(ev) => { bExpr1[expr1] = ev.target.value; }]; const v5 = ctx['updateLimitPreference']; let hdlr9 = [(_ev)=>v5(_ev.target.value), ctx]; - let prop7 = new Boolean(ctx['state'].autoDownloadIssueTimesheet); - let hdlr10 = [ctx['toggleAutoDownload'], ctx]; - let hdlr11 = [ctx['downloadCurrentMonthTimesheets'], ctx]; - let hdlr12 = [ctx['switchBetweenRemotes'], ctx]; - let hdlr13 = [ctx['refreshAll'], ctx]; - let hdlr14 = [ctx['resetTimer'], ctx]; - let hdlr15 = [ctx['logout'], ctx]; + let hdlr10 = [ctx['downloadCurrentMonthTimesheets'], ctx]; + let hdlr11 = [ctx['switchBetweenRemotes'], ctx]; + let hdlr12 = [ctx['refreshAll'], ctx]; + let hdlr13 = [ctx['resetTimer'], ctx]; + let hdlr14 = [ctx['logout'], ctx]; if (ctx['state'].timerStartIso) { const b19 = safeOutput(ctx['formattedTimer']); b18 = block18([], [b19]); } b20 = safeOutput(ctx['itemLabelPlural']); b21 = safeOutput(ctx['filteredIssues'].length); - let prop8 = new Boolean(ctx['state'].allIssues); + let prop7 = new Boolean(ctx['state'].allIssues); const v6 = ctx['updateShowAllPreference']; - let hdlr16 = [(_ev)=>v6(_ev.target.checked), ctx]; + let hdlr15 = [(_ev)=>v6(_ev.target.checked), ctx]; if (ctx['state'].dataSource==='project.task') { const b23 = block23(); const b24 = block24(); @@ -325,14 +210,14 @@ export const templates = { if (!ctx['state'].activeTimerId) { const v7 = ctx['startTimer']; const v8 = ctx['issue']; - let hdlr17 = [()=>v7(v8), ctx]; - b27 = block27([hdlr17]); + let hdlr16 = [()=>v7(v8), ctx]; + b27 = block27([hdlr16]); } if (ctx['state'].activeTimerId===ctx['issue'].id) { const v9 = ctx['stopTimer']; const v10 = ctx['issue']; - let hdlr18 = [()=>v9(v10), ctx]; - b28 = block28([hdlr18]); + let hdlr17 = [()=>v9(v10), ctx]; + b28 = block28([hdlr17]); } if (ctx['issue'].priority_level.length) { ctx = Object.create(ctx); @@ -372,7 +257,7 @@ export const templates = { b43 = safeOutput(ctx['state'].currentHost||'-'); b44 = safeOutput(ctx['state'].currentDatabase||'-'); b45 = safeOutput(ctx['state'].user?ctx['state'].user.display_name:'-'); - return block1([attr1, attr2, attr6, prop5, hdlr7, prop6, hdlr8, hdlr9, prop7, hdlr10, hdlr11, hdlr12, hdlr13, hdlr14, hdlr15, prop8, hdlr16], [b2, b4, b5, b18, b20, b21, b22, b25, b40, b41, b42, b43, b44, b45]); + return block1([attr1, attr2, attr6, prop5, hdlr7, prop6, hdlr8, hdlr9, hdlr10, hdlr11, hdlr12, hdlr13, hdlr14, prop7, hdlr15], [b2, b4, b5, b18, b20, b21, b22, b25, b40, b41, b42, b43, b44, b45]); } }, diff --git a/dist/chrome/manifest.json b/dist/chrome/manifest.json index 7e98fc6..04482f0 100644 --- a/dist/chrome/manifest.json +++ b/dist/chrome/manifest.json @@ -32,6 +32,9 @@ "storage", "cookies" ], + "host_permissions": [ + "" + ], "optional_host_permissions": [ "https://*/*", "http://*/*" diff --git a/dist/firefox/js/components/options-app.js b/dist/firefox/js/components/options-app.js index 4a9cf31..3156572 100644 --- a/dist/firefox/js/components/options-app.js +++ b/dist/firefox/js/components/options-app.js @@ -51,7 +51,7 @@ function createOptionsAppTemplate(app, bdom, helpers) { const readMoreState = app.createComponent('ReadMore', true, false, false, ['text', 'limit']); const rootBlock = createBlock( - `


Description


This is a standalone Owl rewrite of the original cross-platform timer extension for posting work hours to Odoo timesheets.

Features


  • Support for both tasks and issues
  • Start and stop the timer for the selected item
  • Create Odoo timesheet lines against the linked analytic account
  • Show assigned items or everyone’s items
  • Add, remove, or clear remote hosts
  • Switch between remote sessions
  • Download current month or current item timesheets as CSV

Add Remote


Controls
` + `


Description


This is a standalone Owl rewrite of the original cross-platform timer extension for posting work hours to Odoo timesheets.

Features


  • Support for both tasks and issues
  • Start and stop the timer for the selected item
  • Create Odoo timesheet lines against the linked analytic account
  • Show assigned items or everyone’s items
  • Add, remove, or clear remote hosts
  • Switch between remote sessions
  • Download current month or current item timesheets as CSV

General Settings


Store timesheet locally each time you stop the timer on an item.


Add Remote


Controls
` ); const errorBlock = createBlock(`
`); const remotesTableBlock = createBlock( @@ -95,6 +95,9 @@ function createOptionsAppTemplate(app, bdom, helpers) { const reloadRemotesHandler = [ctx.loadRemotes, ctx]; const toggleListHandler = [() => { ctx.state.showList = !ctx.state.showList; }, ctx]; const removeAllRemotesHandler = [ctx.removeAllRemotes, ctx]; + // [FIX #38] Auto-download preference + const autoDownloadChecked = ctx.state.autoDownloadIssueTimesheet; + const autoDownloadHandler = [(ev) => { ctx.toggleAutoDownload(ev); }]; if (ctx.state.error) { errorNode = errorBlock([ctx.state.error]); @@ -155,6 +158,8 @@ function createOptionsAppTemplate(app, bdom, helpers) { reloadRemotesHandler, toggleListHandler, removeAllRemotesHandler, + autoDownloadChecked, // 21 [FIX #38] + autoDownloadHandler, // 22 [FIX #38] ], [errorNode, remoteListNode] ); @@ -174,6 +179,7 @@ class OptionsApp extends Component { remotes: [], showList: true, error: '', + autoDownloadIssueTimesheet: false, // [FIX #38] form: { remote_host: '', remote_name: '', @@ -184,6 +190,8 @@ class OptionsApp extends Component { onWillStart(async () => { await this.loadRemotes(); + const saved = await storage.get('auto_download_issue_timesheet', false); + this.state.autoDownloadIssueTimesheet = !!saved; // [FIX #38] }); } @@ -206,6 +214,15 @@ class OptionsApp extends Component { this.state.form.remote_datasrc = DEFAULT_DATA_SOURCE; } + /** + * [FIX #38] Toggle the auto-download timesheet preference and persist it. + * @param {Event} ev + */ + async toggleAutoDownload(ev) { + this.state.autoDownloadIssueTimesheet = ev.target.checked; + await storage.set('auto_download_issue_timesheet', !!this.state.autoDownloadIssueTimesheet); + } + /** * Validate current form fields and return normalized values. * diff --git a/dist/firefox/js/components/popup-app.js b/dist/firefox/js/components/popup-app.js index 64eb316..1fc8fd3 100644 --- a/dist/firefox/js/components/popup-app.js +++ b/dist/firefox/js/components/popup-app.js @@ -79,7 +79,7 @@ function createPopupAppTemplate(app, bdom, helpers) { const readMoreProject = app.createComponent('ReadMore', true, false, false, ['text', 'limit']); const rootBlock = createBlock( - `
Loading current session and projects…
Please wait — or grab a cup of coffee ☕
PriorityStage
[]
Project
` + `
Loading current session and projects…
Please wait — or grab a cup of coffee ☕
PriorityStage
[]
Project
` ); const bootErrorBlock = createBlock(`

`); const noRemotesBlock = createBlock( @@ -106,8 +106,8 @@ function createPopupAppTemplate(app, bdom, helpers) { `
Host:
` ); const activeTimerDurationBlock = createBlock(``); - const hoursSpentHeaderBlock = createBlock(`Hours Spent`); - const remainingHoursHeaderBlock = createBlock(`Remaining Hours`); + const hoursSpentHeaderBlock = createBlock(`Hours Spent`); + const remainingHoursHeaderBlock = createBlock(`Hours Left`); const issueRowBlock = createBlock( `` ); @@ -119,8 +119,8 @@ function createPopupAppTemplate(app, bdom, helpers) { ); const priorityStarBlock = createBlock(``); const priorityStarOutlineBlock = createBlock(``); - const effectiveHoursCellBlock = createBlock(``); - const remainingHoursCellBlock = createBlock(``); + const effectiveHoursCellBlock = createBlock(``); + const remainingHoursCellBlock = createBlock(``); const emptyIssuesRowBlock = createBlock( `No matching items are currently available` ); @@ -250,8 +250,6 @@ function createPopupAppTemplate(app, bdom, helpers) { const limitHandler = [(ev) => { ctx.updateLimitPreference(ev.target.value); }]; - const autoDownloadChecked = ctx.state.autoDownloadIssueTimesheet; - const autoDownloadHandler = [ctx.toggleAutoDownload, ctx]; const downloadTimesheetHandler = [ctx.downloadCurrentMonthTimesheets, ctx]; const switchRemotesHandler = [ctx.switchBetweenRemotes, ctx]; const refreshHandler = [ctx.refreshAll, ctx]; @@ -400,8 +398,6 @@ function createPopupAppTemplate(app, bdom, helpers) { searchQueryHandler, limitValue, limitHandler, - autoDownloadChecked, - autoDownloadHandler, downloadTimesheetHandler, switchRemotesHandler, refreshHandler, @@ -845,15 +841,17 @@ class PopupApp extends Component { } issueLabel(issue) { - const issueName = this.normalizeText( - issue.display_name || issue.name || issue.message_summary || issue.description || '' - ); - + // [FIX #36] For tasks, use issue.name only — display_name already + // contains the code prefix in Odoo, causing it to appear twice. if (this.state.dataSource === DATA_SOURCE_TASK) { const code = this.normalizeText(issue.code); + const issueName = this.normalizeText(issue.name || issue.description || ''); return [code, issueName].filter(Boolean).join(' - ') || `#${issue.id}`; } + const issueName = this.normalizeText( + issue.display_name || issue.name || issue.message_summary || issue.description || '' + ); return [`#${issue.id}`, issueName].filter(Boolean).join(' - '); } diff --git a/dist/firefox/js/templates.js b/dist/firefox/js/templates.js index a87b1ca..8f38a04 100644 --- a/dist/firefox/js/templates.js +++ b/dist/firefox/js/templates.js @@ -1,190 +1,77 @@ export const templates = { - "PopupApp": function PopupApp(app, bdom, helpers + "OptionsApp": function OptionsApp(app, bdom, helpers ) { let { text, createBlock, list, multi, html, toggler, comment } = bdom; let { safeOutput, prepareList, withKey } = helpers; const comp1 = app.createComponent(`ReadMore`, true, false, false, ["text","limit"]); - const comp2 = app.createComponent(`ReadMore`, true, false, false, ["text","limit","href"]); + const comp2 = app.createComponent(`ReadMore`, true, false, false, ["text","limit"]); const comp3 = app.createComponent(`ReadMore`, true, false, false, ["text","limit"]); const comp4 = app.createComponent(`ReadMore`, true, false, false, ["text","limit"]); const comp5 = app.createComponent(`ReadMore`, true, false, false, ["text","limit"]); - let block1 = createBlock(`
Loading current session and projects…
Please wait — or grab a cup of coffee ☕
PriorityStage
[]
Project
`); - let block2 = createBlock(`

`); - let block4 = createBlock(`
Hello 😉, you have not configured any remotes. Open Options below and add one.
`); - let block5 = createBlock(`
`); - let block6 = createBlock(`

`); - let block8 = createBlock(``); - let block9 = createBlock(``); - let block10 = createBlock(``); - let block12 = createBlock(``); - let block14 = createBlock(``); - let block15 = createBlock(`
Host:
`); - let block18 = createBlock(` #`); - let block20 = createBlock(``); - let block25 = createBlock(`Hours Spent`); - let block26 = createBlock(`Remaining Hours`); - let block28 = createBlock(``); - let block29 = createBlock(``); - let block30 = createBlock(``); - let block32 = createBlock(``); - let block33 = createBlock(``); - let block37 = createBlock(``); - let block39 = createBlock(``); - let block42 = createBlock(` No matching items are currently available `); + let block1 = createBlock(`


Description


This is a standalone Owl rewrite of the original cross-platform timer extension for posting work hours to Odoo timesheets.

Features


  • Support for both tasks and issues
  • Start and stop the timer for the selected item
  • Create Odoo timesheet lines against the linked analytic account
  • Show assigned items or everyone's items
  • Add, remove, or clear remote hosts
  • Switch between remote sessions
  • Download current month or current item timesheets as CSV

General Settings


Store timesheet locally each time you stop the timer on an item.


Add Remote


Controls
`); + let block2 = createBlock(`
`); + let block4 = createBlock(`
List of Available Remotes
RemoteHostDatabaseSourceState
`); + let block6 = createBlock(``); return function template(ctx, node, key = "") { - let b2, b4, b5, b18, b20, b22, b23, b24, b27, b42, b43, b44, b45, b46; - let attr1 = ctx['state'].view==='loading'?'':'hide'; - let attr2 = ctx['state'].view==='login'?'login-view':'login-view hide'; - if (ctx['state'].bootError) { - const b3 = safeOutput(ctx['state'].bootError); - b2 = block2([], [b3]); - } - if (!ctx['state'].remotes.length) { - b4 = block4(); - } - if (ctx['state'].remotes.length) { - let b6, b8, b9, b10, b11, b14, b15; - let hdlr1 = ["prevent", ctx['login'], ctx]; - if (ctx['state'].loginError) { - const b7 = safeOutput(ctx['state'].loginError); - b6 = block6([], [b7]); - } - if (!ctx['state'].useExistingSession) { - let prop1 = new String((ctx['state'].username) === 0 ? 0 : ((ctx['state'].username) || "")); - const v1 = ctx['state']; - let hdlr2 = [(_ev)=>v1.username=_ev.target.value, ctx]; - b8 = block8([prop1, hdlr2]); - } - if (!ctx['state'].useExistingSession) { - let attr3 = ctx['state'].showPassword?'text':'password'; - let prop2 = new String((ctx['state'].password) === 0 ? 0 : ((ctx['state'].password) || "")); - const v2 = ctx['state']; - let hdlr3 = [(_ev)=>v2.password=_ev.target.value, ctx]; - b9 = block9([attr3, prop2, hdlr3]); - } - if (!ctx['state'].useExistingSession) { - let hdlr4 = [ctx['togglePassword'], ctx]; - let attr4 = ctx['state'].showPassword?'fa fa-eye-slash':'fa fa-eye'; - b10 = block10([hdlr4, attr4]); - } - const v3 = ctx['state']; - let hdlr5 = [(_ev)=>v3.selectedRemoteIndex=_ev.target.value, ctx]; - ctx = Object.create(ctx); - const [k_block11, v_block11, l_block11, c_block11] = prepareList(ctx['state'].remotes);; - for (let i1 = 0; i1 < l_block11; i1++) { - ctx[`remote`] = k_block11[i1]; - const key1 = ctx['remote'].database+ctx['remote'].url; - let attr5 = ctx['remote'].__index; - let prop3 = new Boolean(ctx['state'].selectedRemoteIndex===ctx['remote'].__index); - const b13 = safeOutput(ctx['remote'].name); - c_block11[i1] = withKey(block12([attr5, prop3], [b13]), key1); - } - ctx = ctx.__proto__; - b11 = list(c_block11); - let prop4 = new Boolean(ctx['state'].useExistingSession); - let hdlr6 = [ctx['toggleUseExistingSession'], ctx]; - if (ctx['state'].loginLoading) { - b14 = block14(); - } - if (ctx['currentRemote']) { - const b16 = safeOutput(ctx['currentRemote'].url); - const b17 = safeOutput(ctx['currentRemote'].datasrc||'project.issue'); - b15 = block15([], [b16, b17]); - } - b5 = block5([hdlr1, hdlr5, prop4, hdlr6], [b6, b8, b9, b10, b11, b14, b15]); - } - let attr6 = ctx['state'].view==='main'?'':'hide'; - let prop5 = new String((ctx['state'].searchQuery) === 0 ? 0 : ((ctx['state'].searchQuery) || "")); + let b2, b4; + let attr1 = ctx['state'].activePage==='about'?'selected':'notselected'; + const v1 = ctx['state']; + let hdlr1 = [()=>v1.activePage='about', ctx]; + let attr2 = ctx['state'].activePage==='options'?'selected':'notselected'; + const v2 = ctx['state']; + let hdlr2 = [()=>v2.activePage='options', ctx]; + let attr3 = ctx['state'].activePage==='about'?'active_page':'inactive_page'; + let attr4 = ctx['state'].activePage==='options'?'active_page':'inactive_page'; + let hdlr3 = ["prevent", ctx['addRemote'], ctx]; + let prop1 = new Boolean(ctx['state'].autoDownloadIssueTimesheet); + let hdlr4 = [ctx['toggleAutoDownload'], ctx]; + let prop2 = new String((ctx['state'].form.remote_host) === 0 ? 0 : ((ctx['state'].form.remote_host) || "")); + const v3 = ctx['state']; + let hdlr5 = [(_ev)=>v3.form.remote_host=_ev.target.value, ctx]; + let prop3 = new String((ctx['state'].form.remote_name) === 0 ? 0 : ((ctx['state'].form.remote_name) || "")); const v4 = ctx['state']; - let hdlr7 = [(_ev)=>v4.searchQuery=_ev.target.value, ctx]; - let prop6 = new String((ctx['state'].limitTo) === 0 ? 0 : ((ctx['state'].limitTo) || "")); - const v5 = ctx['updateLimitPreference']; - let hdlr8 = [(_ev)=>v5(_ev.target.value), ctx]; - let prop7 = new Boolean(ctx['state'].autoDownloadIssueTimesheet); - let hdlr9 = [ctx['toggleAutoDownload'], ctx]; - let hdlr10 = [ctx['downloadCurrentMonthTimesheets'], ctx]; - let hdlr11 = [ctx['switchBetweenRemotes'], ctx]; - let hdlr12 = [ctx['refreshAll'], ctx]; - let hdlr13 = [ctx['resetTimer'], ctx]; - let hdlr14 = [ctx['logout'], ctx]; - if (ctx['state'].activeTimerId&&ctx['state'].timerStartIso) { - const b19 = safeOutput(ctx['state'].activeTimerId); - b18 = block18([], [b19]); - } - if (ctx['state'].timerStartIso) { - const b21 = safeOutput(ctx['formattedTimer']); - b20 = block20([], [b21]); - } - b22 = safeOutput(ctx['itemLabelPlural']); - b23 = safeOutput(ctx['filteredIssues'].length); - let prop8 = new Boolean(ctx['state'].allIssues); - const v6 = ctx['updateShowAllPreference']; - let hdlr15 = [(_ev)=>v6(_ev.target.checked), ctx]; - if (ctx['state'].dataSource==='project.task') { - const b25 = block25(); - const b26 = block26(); - b24 = multi([b25, b26]); + let hdlr6 = [(_ev)=>v4.form.remote_name=_ev.target.value, ctx]; + let prop4 = new String((ctx['state'].form.remote_database) === 0 ? 0 : ((ctx['state'].form.remote_database) || "")); + const v5 = ctx['state']; + let hdlr7 = [(_ev)=>v5.form.remote_database=_ev.target.value, ctx]; + let prop5 = new Boolean(ctx['state'].form.remote_datasrc==='project.issue'); + const v6 = ctx['state']; + let hdlr8 = [()=>v6.form.remote_datasrc='project.issue', ctx]; + let prop6 = new Boolean(ctx['state'].form.remote_datasrc==='project.task'); + const v7 = ctx['state']; + let hdlr9 = [()=>v7.form.remote_datasrc='project.task', ctx]; + let hdlr10 = [ctx['addRemote'], ctx]; + let hdlr11 = [ctx['loadRemotes'], ctx]; + const v8 = ctx['state']; + let hdlr12 = [()=>v8.showList=!v8.showList, ctx]; + let hdlr13 = [ctx['removeAllRemotes'], ctx]; + if (ctx['state'].error) { + const b3 = safeOutput(ctx['state'].error); + b2 = block2([], [b3]); } - if (ctx['filteredIssues'].length) { + if (ctx['state'].showList&&ctx['state'].remotes.length) { ctx = Object.create(ctx); - const [k_block27, v_block27, l_block27, c_block27] = prepareList(ctx['filteredIssues']);; - for (let i1 = 0; i1 < l_block27; i1++) { - ctx[`issue`] = k_block27[i1]; - const key1 = ctx['issue'].id; - let b29, b30, b31, b33, b34, b35, b36, b41; - let attr7 = ctx['state'].activeTimerId===ctx['issue'].id?'active-row':''; - if (!ctx['state'].activeTimerId) { - const v7 = ctx['startTimer']; - const v8 = ctx['issue']; - let hdlr16 = [()=>v7(v8), ctx]; - b29 = block29([hdlr16]); - } - if (ctx['state'].activeTimerId===ctx['issue'].id) { - const v9 = ctx['stopTimer']; - const v10 = ctx['issue']; - let hdlr17 = [()=>v9(v10), ctx]; - b30 = block30([hdlr17]); - } - if (ctx['issue'].priority_level.length) { - ctx = Object.create(ctx); - const [k_block31, v_block31, l_block31, c_block31] = prepareList(ctx['issue'].priority_level);; - for (let i2 = 0; i2 < l_block31; i2++) { - ctx[`priority`] = k_block31[i2]; - const key2 = ctx['priority']+'_'+ctx['issue'].id; - c_block31[i2] = withKey(block32(), key2); - } - ctx = ctx.__proto__; - b31 = list(c_block31); - } - if (!ctx['issue'].priority_level.length) { - b33 = block33(); - } - b34 = comp1({text: ctx['relationLabel'](ctx['issue'].stage_id),limit: 15}, key + `__1__${key1}`, node, this, null); - b35 = comp2({text: ctx['issueLabel'](ctx['issue']),limit: 70,href: ctx['issueHref'](ctx['issue'])}, key + `__2__${key1}`, node, this, null); - if (ctx['state'].dataSource==='project.task') { - const b38 = comp3({text: ctx['normalizeText'](ctx['formatHours'](ctx['issue'].effective_hours)),limit: 9}, key + `__3__${key1}`, node, this, null); - const b37 = block37([], [b38]); - const b40 = comp4({text: ctx['normalizeText'](ctx['formatHours'](ctx['issue'].remaining_hours)),limit: 9}, key + `__4__${key1}`, node, this, null); - const b39 = block39([], [b40]); - b36 = multi([b37, b39]); - } - b41 = comp5({text: ctx['relationLabel'](ctx['issue'].project_id),limit: 15}, key + `__5__${key1}`, node, this, null); - c_block27[i1] = withKey(block28([attr7], [b29, b30, b31, b33, b34, b35, b36, b41]), key1); + const [k_block5, v_block5, l_block5, c_block5] = prepareList(ctx['state'].remotes);; + for (let i1 = 0; i1 < l_block5; i1++) { + ctx[`remote`] = k_block5[i1]; + const key1 = ctx['remote'].url+ctx['remote'].database; + const b7 = comp1({text: ctx['remote'].name,limit: 18}, key + `__1__${key1}`, node, this, null); + const b8 = comp2({text: ctx['remote'].url,limit: 25}, key + `__2__${key1}`, node, this, null); + const b9 = comp3({text: ctx['remote'].database,limit: 18}, key + `__3__${key1}`, node, this, null); + const b10 = comp4({text: ctx['remote'].datasrc||'project.issue',limit: 18}, key + `__4__${key1}`, node, this, null); + const b11 = comp5({text: ctx['remote'].state||'Inactive',limit: 18}, key + `__5__${key1}`, node, this, null); + const v9 = ctx['removeRemote']; + const v10 = ctx['remote']; + let hdlr14 = [()=>v9(v10), ctx]; + c_block5[i1] = withKey(block6([hdlr14], [b7, b8, b9, b10, b11]), key1); } ctx = ctx.__proto__; - b27 = list(c_block27); + const b5 = list(c_block5); + b4 = block4([], [b5]); } - if (!ctx['filteredIssues'].length) { - let attr8 = ctx['state'].dataSource==='project.task'?7:5; - b42 = block42([attr8]); - } - b43 = safeOutput(ctx['state'].serverVersion||'Unknown'); - b44 = safeOutput(ctx['state'].currentHost||'-'); - b45 = safeOutput(ctx['state'].currentDatabase||'-'); - b46 = safeOutput(ctx['state'].user?ctx['state'].user.display_name:'-'); - return block1([attr1, attr2, attr6, prop5, hdlr7, prop6, hdlr8, prop7, hdlr9, hdlr10, hdlr11, hdlr12, hdlr13, hdlr14, prop8, hdlr15], [b2, b4, b5, b18, b20, b22, b23, b24, b27, b42, b43, b44, b45, b46]); + return block1([attr1, hdlr1, attr2, hdlr2, attr3, attr4, hdlr3, prop1, hdlr4, prop2, hdlr5, prop3, hdlr6, prop4, hdlr7, prop5, hdlr8, prop6, hdlr9, hdlr10, hdlr11, hdlr12, hdlr13], [b2, b4]); } }, @@ -198,7 +85,7 @@ export const templates = { const comp4 = app.createComponent(`ReadMore`, true, false, false, ["text","limit"]); const comp5 = app.createComponent(`ReadMore`, true, false, false, ["text","limit"]); - let block1 = createBlock(`
Loading current session and projects…
Please wait — or grab a cup of coffee ☕
PriorityStage
[]
Project
`); + let block1 = createBlock(`
Loading current session and projects…
Please wait — or grab a cup of coffee ☕
PriorityStage
[]
Project
`); let block2 = createBlock(`

`); let block4 = createBlock(`
Hello 😉, you have not configured any remotes. Open Options below and add one.
`); let block5 = createBlock(`
`); @@ -210,15 +97,15 @@ export const templates = { let block14 = createBlock(``); let block15 = createBlock(`
Host:
`); let block18 = createBlock(``); - let block23 = createBlock(`Hours Spent`); - let block24 = createBlock(`Remaining Hours`); + let block23 = createBlock(`Hours Spent`); + let block24 = createBlock(`Hours Left`); let block26 = createBlock(``); let block27 = createBlock(``); let block28 = createBlock(``); let block30 = createBlock(``); let block31 = createBlock(``); - let block35 = createBlock(``); - let block37 = createBlock(``); + let block35 = createBlock(``); + let block37 = createBlock(``); let block40 = createBlock(` No matching items are currently available `); return function template(ctx, node, key = "") { @@ -293,22 +180,20 @@ export const templates = { let hdlr8 = [(ev) => { bExpr1[expr1] = ev.target.value; }]; const v5 = ctx['updateLimitPreference']; let hdlr9 = [(_ev)=>v5(_ev.target.value), ctx]; - let prop7 = new Boolean(ctx['state'].autoDownloadIssueTimesheet); - let hdlr10 = [ctx['toggleAutoDownload'], ctx]; - let hdlr11 = [ctx['downloadCurrentMonthTimesheets'], ctx]; - let hdlr12 = [ctx['switchBetweenRemotes'], ctx]; - let hdlr13 = [ctx['refreshAll'], ctx]; - let hdlr14 = [ctx['resetTimer'], ctx]; - let hdlr15 = [ctx['logout'], ctx]; + let hdlr10 = [ctx['downloadCurrentMonthTimesheets'], ctx]; + let hdlr11 = [ctx['switchBetweenRemotes'], ctx]; + let hdlr12 = [ctx['refreshAll'], ctx]; + let hdlr13 = [ctx['resetTimer'], ctx]; + let hdlr14 = [ctx['logout'], ctx]; if (ctx['state'].timerStartIso) { const b19 = safeOutput(ctx['formattedTimer']); b18 = block18([], [b19]); } b20 = safeOutput(ctx['itemLabelPlural']); b21 = safeOutput(ctx['filteredIssues'].length); - let prop8 = new Boolean(ctx['state'].allIssues); + let prop7 = new Boolean(ctx['state'].allIssues); const v6 = ctx['updateShowAllPreference']; - let hdlr16 = [(_ev)=>v6(_ev.target.checked), ctx]; + let hdlr15 = [(_ev)=>v6(_ev.target.checked), ctx]; if (ctx['state'].dataSource==='project.task') { const b23 = block23(); const b24 = block24(); @@ -325,14 +210,14 @@ export const templates = { if (!ctx['state'].activeTimerId) { const v7 = ctx['startTimer']; const v8 = ctx['issue']; - let hdlr17 = [()=>v7(v8), ctx]; - b27 = block27([hdlr17]); + let hdlr16 = [()=>v7(v8), ctx]; + b27 = block27([hdlr16]); } if (ctx['state'].activeTimerId===ctx['issue'].id) { const v9 = ctx['stopTimer']; const v10 = ctx['issue']; - let hdlr18 = [()=>v9(v10), ctx]; - b28 = block28([hdlr18]); + let hdlr17 = [()=>v9(v10), ctx]; + b28 = block28([hdlr17]); } if (ctx['issue'].priority_level.length) { ctx = Object.create(ctx); @@ -372,7 +257,7 @@ export const templates = { b43 = safeOutput(ctx['state'].currentHost||'-'); b44 = safeOutput(ctx['state'].currentDatabase||'-'); b45 = safeOutput(ctx['state'].user?ctx['state'].user.display_name:'-'); - return block1([attr1, attr2, attr6, prop5, hdlr7, prop6, hdlr8, hdlr9, prop7, hdlr10, hdlr11, hdlr12, hdlr13, hdlr14, hdlr15, prop8, hdlr16], [b2, b4, b5, b18, b20, b21, b22, b25, b40, b41, b42, b43, b44, b45]); + return block1([attr1, attr2, attr6, prop5, hdlr7, prop6, hdlr8, hdlr9, hdlr10, hdlr11, hdlr12, hdlr13, hdlr14, prop7, hdlr15], [b2, b4, b5, b18, b20, b21, b22, b25, b40, b41, b42, b43, b44, b45]); } }, diff --git a/src/templates/options_app.xml b/src/templates/options_app.xml index 360b0ea..6f04763 100644 --- a/src/templates/options_app.xml +++ b/src/templates/options_app.xml @@ -1,378 +1,252 @@ - -
+ +
+ + + + +
-
-
- Loading current session and projects… +

+ -
- Please wait — or grab a cup of coffee ☕ +

+
+
+

Description

+
+ This is a standalone Owl rewrite of the original cross-platform timer + extension for posting work hours to Odoo timesheets. +
+

Features

+
+
+
    +
  • Support for both tasks and issues
  • +
  • Start and stop the timer for the selected item
  • +
  • Create Odoo timesheet lines against the linked analytic account
  • +
  • Show assigned items or everyone's items
  • +
  • Add, remove, or clear remote hosts
  • +
  • Switch between remote sessions
  • +
  • Download current month or current item timesheets as CSV
  • +
-
+