1- import Multilingualization from " ./multilingualization.js" ;
2- import { getTodayString , LOG_DATA_KEY , ROUNDING_UNIT_MINUTE_KEY , fetchHourFromTime , fetchMinFromTime , escapeHtml } from " ./utils.js" ;
1+ import Multilingualization from ' ./multilingualization.js' ;
2+ import { getTodayString , LOG_DATA_KEY , ROUNDING_UNIT_MINUTE_KEY , fetchHourFromTime , fetchMinFromTime , escapeHtml } from ' ./utils.js' ;
33
44const HTML_SUMMARY = 'html_summary' ;
55const PLAINTEXT_LOG = 'plaintext_log' ;
@@ -14,39 +14,39 @@ const MARKDOWN_SUMMARY = 'markdown_summary';
1414 * @param {string } mimeType mime type
1515 */
1616export function download ( outputDataString , extension = 'html' , mimeType = 'text/html' ) {
17- const blob = new Blob ( [ outputDataString ] , { type : mimeType } ) ;
18- const url = URL . createObjectURL ( blob ) ;
19- const filename = Multilingualization . translate ( " app_name" ) + "_" + getTodayString ( ) + "." + extension ;
20-
21- // Save download information to localStorage
22- localStorage . setItem ( 'downloadUrl' , url ) ;
23- localStorage . setItem ( 'downloadFilename' , filename ) ;
24-
25- // Trigger an event to start the download
26- const event = new CustomEvent ( 'startDownload' ) ;
27- window . dispatchEvent ( event ) ;
17+ const blob = new Blob ( [ outputDataString ] , { type : mimeType } ) ;
18+ const url = URL . createObjectURL ( blob ) ;
19+ const filename = Multilingualization . translate ( ' app_name' ) + '_' + getTodayString ( ) + '.' + extension ;
20+
21+ // Save download information to localStorage
22+ localStorage . setItem ( 'downloadUrl' , url ) ;
23+ localStorage . setItem ( 'downloadFilename' , filename ) ;
24+
25+ // Trigger an event to start the download
26+ const event = new CustomEvent ( 'startDownload' ) ;
27+ window . dispatchEvent ( event ) ;
2828}
2929
3030// Add download event listener
3131window . addEventListener ( 'startDownload' , ( ) => {
32- const url = localStorage . getItem ( 'downloadUrl' ) ;
33- const filename = localStorage . getItem ( 'downloadFilename' ) ;
34-
35- if ( url && filename ) {
36- const a = document . createElement ( 'a' ) ;
37- a . href = url ;
38- a . download = filename ;
39- document . body . appendChild ( a ) ;
40- a . click ( ) ;
41- document . body . removeChild ( a ) ;
42-
43- // Release the URL after use
44- URL . revokeObjectURL ( url ) ;
45-
46- // Clear localStorage
47- localStorage . removeItem ( 'downloadUrl' ) ;
48- localStorage . removeItem ( 'downloadFilename' ) ;
49- }
32+ const url = localStorage . getItem ( 'downloadUrl' ) ;
33+ const filename = localStorage . getItem ( 'downloadFilename' ) ;
34+
35+ if ( url && filename ) {
36+ const a = document . createElement ( 'a' ) ;
37+ a . href = url ;
38+ a . download = filename ;
39+ document . body . appendChild ( a ) ;
40+ a . click ( ) ;
41+ document . body . removeChild ( a ) ;
42+
43+ // Release the URL after use
44+ URL . revokeObjectURL ( url ) ;
45+
46+ // Clear localStorage
47+ localStorage . removeItem ( 'downloadUrl' ) ;
48+ localStorage . removeItem ( 'downloadFilename' ) ;
49+ }
5050} ) ;
5151
5252/**
@@ -57,40 +57,40 @@ window.addEventListener('startDownload', () => {
5757 * @returns {string } Formatted log HTML
5858 */
5959export function generateFormattedLog ( log , mins ) {
60- const sections = [
61- { title : HTML_SUMMARY , content : toHtml ( log , mins ) } ,
62- { title : PLAINTEXT_LOG , content : log , isCode : true } ,
63- { title : MARKDOWN_SUMMARY , content : toMarkdown ( log , mins ) , isCode : true }
64- ] ;
65-
66- return `<html><head>
67- <meta charset=" UTF-8" >
68- <meta http-equiv=" X-UA-Compatible" content=" IE=edge" >
69- <meta name=" viewport" content=" width=device-width, initial-scale=1.0" >
70- <title>${ Multilingualization . translate ( " log_viewer" ) } </title>
71- <script src=" https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.3/js/bootstrap.bundle.min.js" integrity=" sha512-i9cEfJwUwViEPFKdC1enz4ZRGBj8YQo6QByFTF92YXHi7waCqyexvRD75S5NVTsSiTv7rKWqG9Y5eFxmRsOn0A==" crossorigin=" anonymous" referrerpolicy=" no-referrer" ></script>
72- <link rel=" stylesheet" href=" https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0-alpha1/css/bootstrap.min.css" integrity=" sha512-72OVeAaPeV8n3BdZj7hOkaPSEk/uwpDkaGyP4W2jSzAC8tfiO4LMEDWoL3uFp5mcZu+8Eehb4GhZWFwvrss69Q==" crossorigin=" anonymous" referrerpolicy=" no-referrer" />
73- <link rel=" stylesheet" href=" https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" integrity=" sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==" crossorigin=" anonymous" referrerpolicy=" no-referrer" />
60+ const sections = [
61+ { title : HTML_SUMMARY , content : toHtml ( log , mins ) } ,
62+ { title : PLAINTEXT_LOG , content : log , isCode : true } ,
63+ { title : MARKDOWN_SUMMARY , content : toMarkdown ( log , mins ) , isCode : true }
64+ ] ;
65+
66+ return `<html><head>
67+ <meta charset=' UTF-8' >
68+ <meta http-equiv=' X-UA-Compatible' content=' IE=edge' >
69+ <meta name=' viewport' content=' width=device-width, initial-scale=1.0' >
70+ <title>${ Multilingualization . translate ( ' log_viewer' ) } </title>
71+ <script src=' https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.3/js/bootstrap.bundle.min.js' integrity=' sha512-i9cEfJwUwViEPFKdC1enz4ZRGBj8YQo6QByFTF92YXHi7waCqyexvRD75S5NVTsSiTv7rKWqG9Y5eFxmRsOn0A==' crossorigin=' anonymous' referrerpolicy=' no-referrer' ></script>
72+ <link rel=' stylesheet' href=' https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0-alpha1/css/bootstrap.min.css' integrity=' sha512-72OVeAaPeV8n3BdZj7hOkaPSEk/uwpDkaGyP4W2jSzAC8tfiO4LMEDWoL3uFp5mcZu+8Eehb4GhZWFwvrss69Q==' crossorigin=' anonymous' referrerpolicy=' no-referrer' />
73+ <link rel=' stylesheet' href=' https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css' integrity=' sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw==' crossorigin=' anonymous' referrerpolicy=' no-referrer' />
7474</head><body>
7575${ sections . map ( section => `
7676<h2>${ Multilingualization . translate ( section . title ) } </h2>
77- <i id=" ${ section . title } -copy" class=" fa-sharp fa-regular fa-copy btn btn-outline-secondary d-none"
78- data-bs-trigger=" manual" data-bs-toggle=" tooltip" data-bs-placement=" top" title=" copy!" ></i>
77+ <i id=' ${ section . title } -copy' class=' fa-sharp fa-regular fa-copy btn btn-outline-secondary d-none'
78+ data-bs-trigger=' manual' data-bs-toggle=' tooltip' data-bs-placement=' top' title=' copy!' ></i>
7979<div>
80- ${ section . isCode ? `<pre><code id=" ${ section . title } -source" >${ section . content } </code></pre>` : section . content }
80+ ${ section . isCode ? `<pre><code id=' ${ section . title } -source' >${ section . content } </code></pre>` : section . content }
8181</div>
8282` ) . join ( '' ) }
8383<script>
84- (async()=>{const e=await(navigator?.permissions?.query({name:" clipboard-write" }));" granted" !==e?.state&&" prompt" !==e?.state||document.querySelectorAll(" #${ HTML_SUMMARY } -copy,#${ PLAINTEXT_LOG } -copy,#${ MARKDOWN_SUMMARY } -copy" ).forEach((e=>{const t=new bootstrap.Tooltip(e);e.classList.remove(" d-none" ),e.addEventListener(" click" ,(async e=>{let a;e.preventDefault(),e.stopPropagation(),(a=document.querySelector(\`#\${e.target.id.replace(" -copy"," -source" )}\`).textContent)," ${ HTML_SUMMARY } -copy" ===e.target.id&&(a=a.replace(/\\n\\n/g," <></>" ).replace(/\\n/g," \\t" ).replace(/<><\\/>/g," \\n" )),await(navigator?.clipboard?.writeText(a.trim())),t.show(),setTimeout((()=>t.hide()),1e3)}))}))})();
84+ (async()=>{const e=await(navigator?.permissions?.query({name:' clipboard-write' }));' granted' !==e?.state&&' prompt' !==e?.state||document.querySelectorAll(' #${ HTML_SUMMARY } -copy,#${ PLAINTEXT_LOG } -copy,#${ MARKDOWN_SUMMARY } -copy' ).forEach((e=>{const t=new bootstrap.Tooltip(e);e.classList.remove(' d-none' ),e.addEventListener(' click' ,(async e=>{let a;e.preventDefault(),e.stopPropagation(),(a=document.querySelector(\`#\${e.target.id.replace(' -copy',' -source' )}\`).textContent),' ${ HTML_SUMMARY } -copy' ===e.target.id&&(a=a.replace(/\\n\\n/g,' <></>' ).replace(/\\n/g,' \\t' ).replace(/<><\\/>/g,' \\n' )),await(navigator?.clipboard?.writeText(a.trim())),t.show(),setTimeout((()=>t.hide()),1e3)}))}))})();
8585</script>
8686</body></html>` ;
8787}
8888
8989export async function downloadLog ( ) {
90- const log = localStorage . getItem ( LOG_DATA_KEY ) ;
91- const mins = localStorage . getItem ( ROUNDING_UNIT_MINUTE_KEY ) ;
92- const outputStr = generateFormattedLog ( log , mins ) ;
93- download ( outputStr ) ;
90+ const log = localStorage . getItem ( LOG_DATA_KEY ) ;
91+ const mins = localStorage . getItem ( ROUNDING_UNIT_MINUTE_KEY ) ;
92+ const outputStr = generateFormattedLog ( log , mins ) ;
93+ download ( outputStr ) ;
9494}
9595
9696/**
@@ -101,54 +101,54 @@ export async function downloadLog() {
101101 * @returns {object } Category-wise summary data (json)
102102 */
103103export function parse ( text , mins ) {
104- const TIME_LENGTH = 16 ;
105- const FIELD_SEPARATOR = ";" ;
106- const RECORD_SEPARATOR = "\n" ;
107-
108- let timeStamp = [ ] , obj = { } ;
109-
110- // Convert log to JSON
111- text . split ( RECORD_SEPARATOR ) . forEach ( line => {
112- const time = line . slice ( 0 , TIME_LENGTH ) ;
113- const junction = line . indexOf ( FIELD_SEPARATOR ) ;
114- const category = junction < 0 ? line . slice ( TIME_LENGTH ) : line . slice ( TIME_LENGTH , junction ) ;
115- const detail = junction < 0 ? '' : line . slice ( junction + 1 ) ;
116-
117- timeStamp . push ( { "time" : time , "category" : category } ) ;
118- if ( ! obj [ category ] ) obj [ category ] = { } ;
119- obj [ category ] . time = 0 ;
120- if ( ! obj [ category ] . detail ) obj [ category ] . detail = [ ] ;
121- obj [ category ] . detail . push ( detail ) ;
122- } ) ;
123-
124- // Remove duplicates from work details
125- Object . keys ( obj ) . forEach ( item => {
126- obj [ item ] . detail = Array . from ( new Set ( obj [ item ] . detail ) ) . join ( ", " ) ;
127- } ) ;
128-
129- // Calculate work time in minutes
130- for ( let i = 1 ; i < timeStamp . length ; i ++ ) {
131- const after = timeStamp [ i ] . time ;
132- const before = timeStamp [ i - 1 ] . time ;
133- let hour = fetchHourFromTime ( after ) - fetchHourFromTime ( before ) ;
134- if ( hour < 0 ) {
135- hour += 24 ;
136- }
137- let min = fetchMinFromTime ( after ) - fetchMinFromTime ( before ) ;
138- // If crossing midnight
139- if ( min < 0 ) {
140- hour -= 1 ;
141- min += 60 ;
142- }
143- obj [ timeStamp [ i - 1 ] . category ] . time += hour * 60 + min ;
104+ const TIME_LENGTH = 16 ;
105+ const FIELD_SEPARATOR = ';' ;
106+ const RECORD_SEPARATOR = '\n' ;
107+
108+ let timeStamp = [ ] , obj = { } ;
109+
110+ // Convert log to JSON
111+ text . split ( RECORD_SEPARATOR ) . forEach ( line => {
112+ const time = line . slice ( 0 , TIME_LENGTH ) ;
113+ const junction = line . indexOf ( FIELD_SEPARATOR ) ;
114+ const category = junction < 0 ? line . slice ( TIME_LENGTH ) : line . slice ( TIME_LENGTH , junction ) ;
115+ const detail = junction < 0 ? '' : line . slice ( junction + 1 ) ;
116+
117+ timeStamp . push ( { 'time' : time , 'category' : category } ) ;
118+ if ( ! obj [ category ] ) obj [ category ] = { } ;
119+ obj [ category ] . time = 0 ;
120+ if ( ! obj [ category ] . detail ) obj [ category ] . detail = [ ] ;
121+ obj [ category ] . detail . push ( detail ) ;
122+ } ) ;
123+
124+ // Remove duplicates from work details
125+ Object . keys ( obj ) . forEach ( item => {
126+ obj [ item ] . detail = Array . from ( new Set ( obj [ item ] . detail ) ) . join ( ', ' ) ;
127+ } ) ;
128+
129+ // Calculate work time in minutes
130+ for ( let i = 1 ; i < timeStamp . length ; i ++ ) {
131+ const after = timeStamp [ i ] . time ;
132+ const before = timeStamp [ i - 1 ] . time ;
133+ let hour = fetchHourFromTime ( after ) - fetchHourFromTime ( before ) ;
134+ if ( hour < 0 ) {
135+ hour += 24 ;
136+ }
137+ let min = fetchMinFromTime ( after ) - fetchMinFromTime ( before ) ;
138+ // If crossing midnight
139+ if ( min < 0 ) {
140+ hour -= 1 ;
141+ min += 60 ;
144142 }
143+ obj [ timeStamp [ i - 1 ] . category ] . time += hour * 60 + min ;
144+ }
145145
146- // Convert work time to ROUNDING_UNIT time
147- Object . keys ( obj ) . forEach ( item => {
148- obj [ item ] . round = Math . floor ( obj [ item ] . time / 60 ) + Number ( ( Math . round ( obj [ item ] . time % 60 / mins ) * mins / 60 ) . toFixed ( 2 ) ) ;
149- } ) ;
146+ // Convert work time to ROUNDING_UNIT time
147+ Object . keys ( obj ) . forEach ( item => {
148+ obj [ item ] . round = Math . floor ( obj [ item ] . time / 60 ) + Number ( ( Math . round ( obj [ item ] . time % 60 / mins ) * mins / 60 ) . toFixed ( 2 ) ) ;
149+ } ) ;
150150
151- return obj ;
151+ return obj ;
152152}
153153
154154/**
@@ -159,43 +159,43 @@ export function parse(text, mins) {
159159 * @returns {string } HTML table
160160 */
161161export function toHtml ( log , mins ) {
162- const dataJson = parse ( log , mins ) ;
163- const breakMark = "^" ;
164- let sum = 0 ;
165- let total = 0 ;
166- let output =
167- `</head><body><table class=" table table-striped-columns" ><thead class=" table-light" ><thead class=" table-light" >
162+ const dataJson = parse ( log , mins ) ;
163+ const breakMark = '^' ;
164+ let sum = 0 ;
165+ let total = 0 ;
166+ let output =
167+ `</head><body><table class=' table table-striped-columns' ><thead class=' table-light' ><thead class=' table-light' >
168168<tr>
169- <th class=" text-center" >${ Multilingualization . translate ( " work_category" ) } </th>
170- <th class=" text-center" >${ Multilingualization . translate ( " work_detail" ) } </th>
171- <th class=" text-center" >${ Multilingualization . translate ( " work_time_hour" ) } </th>
172- <th class=" text-center" >${ Multilingualization . translate ( " work_time_min" ) } </th>
169+ <th class=' text-center' >${ Multilingualization . translate ( ' work_category' ) } </th>
170+ <th class=' text-center' >${ Multilingualization . translate ( ' work_detail' ) } </th>
171+ <th class=' text-center' >${ Multilingualization . translate ( ' work_time_hour' ) } </th>
172+ <th class=' text-center' >${ Multilingualization . translate ( ' work_time_min' ) } </th>
173173</tr>
174- </thead><tbody id=" ${ HTML_SUMMARY } -source" class=" table-group-divider" >` ;
174+ </thead><tbody id=' ${ HTML_SUMMARY } -source' class=' table-group-divider' >` ;
175175
176- for ( const category of Object . keys ( dataJson ) . sort ( ) ) {
177- output +=
178- `<tr>
176+ for ( const category of Object . keys ( dataJson ) . sort ( ) ) {
177+ output +=
178+ `<tr>
179179<td>${ escapeHtml ( category ) ? escapeHtml ( category ) : '-' } </td>
180180<td>${ escapeHtml ( dataJson [ category ] . detail ) ? escapeHtml ( dataJson [ category ] . detail ) : ' ' } </td>
181- <td class=" text-end" >${ dataJson [ category ] . round } </td>
182- <td class=" text-end" >${ dataJson [ category ] . time } </td>
181+ <td class=' text-end' >${ dataJson [ category ] . round } </td>
182+ <td class=' text-end' >${ dataJson [ category ] . time } </td>
183183</tr>` ;
184- if ( category [ 0 ] != breakMark ) sum += dataJson [ category ] . time ;
185- total += dataJson [ category ] . time ;
186- }
184+ if ( category [ 0 ] != breakMark ) sum += dataJson [ category ] . time ;
185+ total += dataJson [ category ] . time ;
186+ }
187187
188- const sumStr = Multilingualization . translate ( " work_time_actual" ) + ": " + ( Math . floor ( sum / 60 ) + Number ( ( Math . round ( sum % 60 / mins ) * mins / 60 ) . toFixed ( 2 ) ) ) + " h" ;
189- const totalStr = Multilingualization . translate ( " work_time_total" ) + ": " + ( Math . floor ( total / 60 ) + Number ( ( Math . round ( total % 60 / mins ) * mins / 60 ) . toFixed ( 2 ) ) ) + " h" ;
188+ const sumStr = Multilingualization . translate ( ' work_time_actual' ) + ': ' + ( Math . floor ( sum / 60 ) + Number ( ( Math . round ( sum % 60 / mins ) * mins / 60 ) . toFixed ( 2 ) ) ) + ' h' ;
189+ const totalStr = Multilingualization . translate ( ' work_time_total' ) + ': ' + ( Math . floor ( total / 60 ) + Number ( ( Math . round ( total % 60 / mins ) * mins / 60 ) . toFixed ( 2 ) ) ) + ' h' ;
190190
191- output +=
192- `</tbody></table>
191+ output +=
192+ `</tbody></table>
193193<p>
194194${ sumStr } (${ sum } ${ Multilingualization . translate ( 'mins' ) } )<br>
195195${ totalStr } (${ total } ${ Multilingualization . translate ( 'mins' ) } )</p>
196196` ;
197197
198- return output ;
198+ return output ;
199199}
200200
201201/**
@@ -206,23 +206,23 @@ ${totalStr} (${total} ${Multilingualization.translate('mins')})</p>
206206 * @returns {string } Markdown table
207207 */
208208export function toMarkdown ( log , mins ) {
209- const dataJson = parse ( log , mins ) ;
210- const breakMark = "^" ;
211- let sum = 0 ;
212- let total = 0 ;
213- let output =
214- `${ Multilingualization . translate ( " work_category" ) } | ${ Multilingualization . translate ( " work_detail" ) } | ${ Multilingualization . translate ( " work_time_hour" ) } | ${ Multilingualization . translate ( " work_time_min" ) }
209+ const dataJson = parse ( log , mins ) ;
210+ const breakMark = '^' ;
211+ let sum = 0 ;
212+ let total = 0 ;
213+ let output =
214+ `${ Multilingualization . translate ( ' work_category' ) } | ${ Multilingualization . translate ( ' work_detail' ) } | ${ Multilingualization . translate ( ' work_time_hour' ) } | ${ Multilingualization . translate ( ' work_time_min' ) }
215215--- | --- | --: | --:
216216` ;
217217
218- for ( const category of Object . keys ( dataJson ) . sort ( ) ) {
219- output += `${ escapeHtml ( category ) } | ${ escapeHtml ( dataJson [ category ] . detail ) } | ${ dataJson [ category ] . round } | ${ dataJson [ category ] . time } \n` ;
220- if ( category [ 0 ] != breakMark ) sum += dataJson [ category ] . time ;
221- total += dataJson [ category ] . time ;
222- }
218+ for ( const category of Object . keys ( dataJson ) . sort ( ) ) {
219+ output += `${ escapeHtml ( category ) } | ${ escapeHtml ( dataJson [ category ] . detail ) } | ${ dataJson [ category ] . round } | ${ dataJson [ category ] . time } \n` ;
220+ if ( category [ 0 ] != breakMark ) sum += dataJson [ category ] . time ;
221+ total += dataJson [ category ] . time ;
222+ }
223223
224- output += `\n${ Multilingualization . translate ( " work_time_actual" ) } : ` + ( Math . floor ( sum / 60 ) + Number ( ( Math . round ( sum % 60 / mins ) * mins / 60 ) . toFixed ( 2 ) ) ) + ` h (${ sum } ${ Multilingualization . translate ( 'mins' ) } )` ;
225- output += `\n${ Multilingualization . translate ( " work_time_total" ) } : ` + ( Math . floor ( total / 60 ) + Number ( ( Math . round ( total % 60 / mins ) * mins / 60 ) . toFixed ( 2 ) ) ) + ` h (${ total } ${ Multilingualization . translate ( 'mins' ) } )` ;
224+ output += `\n${ Multilingualization . translate ( ' work_time_actual' ) } : ` + ( Math . floor ( sum / 60 ) + Number ( ( Math . round ( sum % 60 / mins ) * mins / 60 ) . toFixed ( 2 ) ) ) + ` h (${ sum } ${ Multilingualization . translate ( 'mins' ) } )` ;
225+ output += `\n${ Multilingualization . translate ( ' work_time_total' ) } : ` + ( Math . floor ( total / 60 ) + Number ( ( Math . round ( total % 60 / mins ) * mins / 60 ) . toFixed ( 2 ) ) ) + ` h (${ total } ${ Multilingualization . translate ( 'mins' ) } )` ;
226226
227- return output ;
227+ return output ;
228228}
0 commit comments