diff --git a/src/zabapgit_css_common.w3mi.data.css b/src/zabapgit_css_common.w3mi.data.css index 2528aa62f..45d35dc88 100644 --- a/src/zabapgit_css_common.w3mi.data.css +++ b/src/zabapgit_css_common.w3mi.data.css @@ -24,12 +24,12 @@ img { border: 0px; vertical-align: middle; } table { border-collapse: collapse; } pre { display: inline; } -form input, textarea, select { - border: 1px solid #DDD; +input, textarea, select { padding: 3px 6px; + border: 1px solid #ddd; } -form input:focus, textarea:focus { +input:focus, textarea:focus { border: 1px solid #8cadd9; } @@ -373,7 +373,6 @@ table.repo_tab { } .stage_tab th { color: #BBB; - font-size: 10pt; text-align: left; font-weight: normal; background-color: #edf2f9; @@ -382,22 +381,23 @@ table.repo_tab { .stage_tab td.status { width: 2em; text-align: center; + color: #ccc; + background-color: #fafafa; } +.stage_tab td.highlight { + color: #444 !important; + font-weight: bold; +} +.stage_tab td.cmd a { padding: 0px 4px; } +.stage_tab th.cmd a { padding: 0px 4px; } +.stage_tab td.method { color: #ccc; } +.stage_tab td.user { color: #aaa; } +.stage_tab td.type { color: #aaa; } .stage_tab tbody tr:first-child td { padding-top: 0.5em; } .stage_tab tbody tr:last-child td { padding-bottom: 0.5em; } -.stage_tab td.cmd a { padding: 0px 4px; } - -/* STAGE */ -.stage_tab td.method { - color: #ccc; -} -.stage_tab tr.firstrow td { border-top: 0px; } -.stage_tab tr.title td { - color: #BBB; - font-size: 10pt; - background-color: #edf2f9; - padding: 4px 0.5em; - text-align: center; +.stage_tab mark { + color: white; + background-color: #79a0d2; } /* COMMIT */ diff --git a/src/zabapgit_html_chunks.prog.abap b/src/zabapgit_html_chunks.prog.abap index 0b5780749..2cb693cfe 100644 --- a/src/zabapgit_html_chunks.prog.abap +++ b/src/zabapgit_html_chunks.prog.abap @@ -32,6 +32,10 @@ CLASS lcl_gui_chunk_lib DEFINITION FINAL. RETURNING VALUE(ro_html) TYPE REF TO lcl_html RAISING lcx_exception. + CLASS-METHODS render_js_error_banner + RETURNING VALUE(ro_html) TYPE REF TO lcl_html + RAISING lcx_exception. + ENDCLASS. "lcl_gui_chunk_lib CLASS lcl_gui_chunk_lib IMPLEMENTATION. @@ -196,10 +200,17 @@ CLASS lcl_gui_chunk_lib IMPLEMENTATION. lv_error = iv_error. ENDIF. - ro_html->add( '
' ). + ro_html->add( '
' ). ro_html->add( |{ lcl_html=>icon( 'alert/red' ) } Error: { lv_error }| ). ro_html->add( '
' ). ENDMETHOD. "render_error + METHOD render_js_error_banner. + CREATE OBJECT ro_html. + ro_html->add( '
' ). + ro_html->add( |{ lcl_html=>icon( 'alert/red' ) } JS init error, please log an issue| ). + ro_html->add( '
' ). + ENDMETHOD. "render_js_error_stub + ENDCLASS. "lcl_gui_chunk_lib diff --git a/src/zabapgit_js_common.w3mi.data.js b/src/zabapgit_js_common.w3mi.data.js index b8556c7e7..45496a49a 100644 --- a/src/zabapgit_js_common.w3mi.data.js +++ b/src/zabapgit_js_common.w3mi.data.js @@ -71,6 +71,22 @@ function submitFormById(id) { document.getElementById(id).submit(); } +// JS error stub +function errorStub(event) { + var element = event.target || event.srcElement; + var targetName = element.id || element.name || "???"; + alert("JS Error, please log an issue (@" + targetName + ")"); +} + +// confirm JS initilization +function confirmInitialized() { + var errorBanner = document.getElementById("js-error-banner"); + if (errorBanner) { + errorBanner.style.display = "none"; + } + debugOutput("js: OK"); // Final final confirmation :) +} + /********************************************************** * STAGE PAGE Logic **********************************************************/ @@ -78,131 +94,219 @@ function submitFormById(id) { // Stage helper constructor function StageHelper(params) { this.pageSeed = params.seed; - this.tabId = params.stageTabId; this.formAction = params.formAction; - this.commitNodeId = params.commitNodeId; - this.commitAllNodeId = params.commitAllNodeId; this.choiseCount = 0; - this.setHook(); + this.lastSearchValue = ""; + + // DOM nodes + this.dom = { + stageTab: document.getElementById(params.ids.stageTab), + commitBtn: document.getElementById(params.ids.commitBtn), + commitAllBtn: document.getElementById(params.ids.commitAllBtn), + objectSearch: document.getElementById(params.ids.objectSearch), + fileCounter: document.getElementById(params.ids.fileCounter) + }; + + // Table columns (autodetection) + this.col = this.detectColumns(); + + // Constants + this.HIGHLIGHT_STYLE = "highlight"; + this.STATUS = { + "add": "A", + "remove": "R", + "ignore": "I", + "reset": "?" + }; + + this.setHooks(); } // Hook global click listener on table, load/unload actions -StageHelper.prototype.setHook = function() { - var stageTab = document.getElementById(this.tabId); +StageHelper.prototype.setHooks = function() { + this.dom.stageTab.onclick = this.onTableClick.bind(this); + this.dom.commitBtn.onclick = this.submit.bind(this); + this.dom.objectSearch.oninput = this.onSearch.bind(this); + this.dom.objectSearch.onkeypress = this.onSearch.bind(this); + window.onbeforeunload = this.onPageUnload.bind(this); + window.onload = this.onPageLoad.bind(this); +} - if (stageTab.addEventListener) { - stageTab.addEventListener("click", this.onEvent.bind(this)); - } else { - stageTab.attachEvent("onclick", this.onEvent.bind(this)); +// Detect column index +StageHelper.prototype.detectColumns = function() { + var dataRow = this.dom.stageTab.tBodies[0].rows[0]; + var cols = {}; + + for (var i = dataRow.cells.length - 1; i >= 0; i--) { + if (dataRow.cells[i].className) cols[dataRow.cells[i].className] = i; } - window.onbeforeunload = this.onPageUnload.bind(this); - window.onload = this.onPageLoad.bind(this); + return cols; } // Store table state on leaving the page StageHelper.prototype.onPageUnload = function() { + if (!window.sessionStorage) return; + var data = this.collectData(); window.sessionStorage.setItem(this.pageSeed, JSON.stringify(data)); } // Re-store table state on entering the page StageHelper.prototype.onPageLoad = function() { - var data = JSON.parse(window.sessionStorage.getItem(this.pageSeed)); - var stage = document.getElementById(this.tabId); - for (var i = stage.rows.length - 1; i >= 0; i--) { - var tr = stage.rows[i]; - if (tr.parentNode.tagName == "THEAD") continue; - var context = tr.parentNode.className; - var cmd = data[tr.cells[1].innerText]; - if (!cmd) continue; - - this.formatTR(tr, cmd, context); - this.choiseCount += (this.countChoiceImpact(cmd) > 0) ? 1 : 0; + var data = window.sessionStorage && JSON.parse(window.sessionStorage.getItem(this.pageSeed)); + + if (data) { + this.iterateStageTab(function (row) { + var status = data[row.cells[this.col.name].innerText]; + this.updateRow(row, status || this.STATUS.reset); + }); + debugOutput("StageHelper.onPageLoad from Storage"); + + } else { // Render initial commands + + this.iterateStageTab(function (row) { + this.updateRow(row, this.STATUS.reset); + }); + debugOutput("StageHelper.onPageLoad initial state"); } this.updateMenu(); } -// Event handler, change status -StageHelper.prototype.onEvent = function (event) { - if (!event.target) { - if (event.srcElement) { event.target = event.srcElement; } - else { return; } +// Table event handler, change status +StageHelper.prototype.onTableClick = function (event) { + var target = event.target || event.srcElement; + if (!target || target.tagName != "A") return; + + var td = target.parentNode; + if (!td || ["TD","TH"].indexOf(td.tagName) == -1 || td.className != "cmd") return; + + var status = this.STATUS[target.innerText]; // Convert anchor text to status + var targetRow = td.parentNode; + + if (td.tagName === "TD") { + this.updateRow(targetRow, status); + } else { // TH + this.iterateStageTab(function (row) { + if (row.style.display !== "none" // Not filtered out + && row.className === targetRow.className // Same context as header + ) { + this.updateRow(row, status); + } + }); } - if (event.target.tagName != "A") return; - - var td = event.target.parentNode; - if (!td || td.tagName != "TD" || td.className != "cmd") return; - - var cmd = event.target.innerText; - var tr = td.parentNode; - var context = tr.parentNode.className; - - switch (cmd) { - case "add": cmd = "A"; break; - case "remove": cmd = "R"; break; - case "ignore": cmd = "I"; break; - case "reset": cmd = "?"; break; - } - - this.formatTR(tr, cmd, context); - this.choiseCount += this.countChoiceImpact(cmd); this.updateMenu(); } -// Update action counter -> affects menu update after -StageHelper.prototype.countChoiceImpact = function (cmd) { - if ("ARI".indexOf(cmd) > -1) { return 1; } - else if ("?".indexOf(cmd) > -1) { return -1; } - else { alert("Unknown command"); } +// Search object +StageHelper.prototype.onSearch = function (e) { + if ( // Enter hit or clear, IE SUCKS ! + e.type === "input" && !e.target.value && this.lastSearchValue + || e.type === "keypress" && e.which === 13 ) { + + this.lastSearchValue = e.target.value; + this.iterateStageTab(this.applyFilterToRow, e.target.value); + } } -// Re-format table line -StageHelper.prototype.formatTR = function (tr, cmd, context) { - var cmdReset = "reset"; - var cmdLocal = "add"; - var cmdRemote = "ignoreremove"; +StageHelper.prototype.applyFilterToRow = function (row, filter) { + var td = row.cells[this.col.name]; + var origTxt = td.innerText; // without tags + var newTxt = ""; - tr.cells[0].innerText = cmd; - if (cmd == "?") { - tr.cells[0].style.color = "#CCC"; //grey - tr.cells[2].innerHTML = (context == "local") ? cmdLocal : cmdRemote; + if (filter) { + newTxt = origTxt.replace(filter, ""+filter+""); + if (newTxt !== origTxt) { // fits filter + row.style.display = "table-row"; + } else { + row.style.display = "none"; + } + } else { // No filter -> just reset the value + newTxt = origTxt; + row.style.display = "table-row"; + } + + if (td.firstChild.tagName === "A") { + td.firstChild.innerHTML = newTxt; } else { - tr.cells[0].style.color = ""; - tr.cells[2].innerHTML = cmdReset; + td.innerHTML = newTxt; + } +} + +// Get how status should affect object counter +StageHelper.prototype.getStatusImpact = function (status) { + if (typeof status !== "string" + || status.length !== 1 + || "ARI?".indexOf(status) == -1) { + alert("Unknown status"); + } else { + return (status !== this.STATUS.reset) ? 1 : 0; + } +} + +// Update table line +StageHelper.prototype.updateRow = function (row, newStatus) { + var oldStatus = row.cells[this.col.status].innerText; + + if (oldStatus !== newStatus) { + row.cells[this.col.status].innerText = newStatus; + if (newStatus === this.STATUS.reset) { + row.cells[this.col.status].classList.remove(this.HIGHLIGHT_STYLE); + } else { + row.cells[this.col.status].classList.add(this.HIGHLIGHT_STYLE); + } + this.updateRowCommand(row, newStatus); + } else if (!row.cells[this.col.cmd].innerText) { + this.updateRowCommand(row, newStatus); // For initial run + } + + this.choiseCount += this.getStatusImpact(newStatus) - this.getStatusImpact(oldStatus); +} + +// Update command cell (render set of commands) +StageHelper.prototype.updateRowCommand = function (row, status) { + var CMD_RESET = "reset"; + var CMD_LOCAL = "add"; + var CMD_REMOTE = "ignoreremove"; + + if (status === this.STATUS.reset) { + row.cells[this.col.cmd].innerHTML = (row.className == "local") ? CMD_LOCAL : CMD_REMOTE; + } else { + row.cells[this.col.cmd].innerHTML = CMD_RESET; } } // Update menu items visibility StageHelper.prototype.updateMenu = function () { - if (this.choiseCount > 0) { - document.getElementById(this.commitNodeId).style.display = "inline"; - document.getElementById(this.commitAllNodeId).style.display = "none"; - } else { - document.getElementById(this.commitNodeId).style.display = "none"; - document.getElementById(this.commitAllNodeId).style.display = "inline"; - } + this.dom.commitBtn.style.display = (this.choiseCount > 0) ? "inline" : "none"; + this.dom.commitAllBtn.style.display = (this.choiseCount > 0) ? "none" : "inline"; + this.dom.fileCounter.innerHTML = this.choiseCount.toString(); } -// Submin stage state to the server +// Submit stage state to the server StageHelper.prototype.submit = function () { - var data = this.collectData(); - submitSapeventForm(data, this.formAction); + submitSapeventForm(this.collectData(), this.formAction); } // Extract data from the table StageHelper.prototype.collectData = function () { - var stage = document.getElementById(this.tabId); var data = {}; - - for (var i = 0; i < stage.rows.length; i++) { - var row = stage.rows[i]; - if (row.parentNode.tagName == "THEAD") continue; - data[row.cells[1].innerText] = row.cells[0].innerText; - } - + this.iterateStageTab(function (row) { + data[row.cells[this.col.name].innerText] = row.cells[this.col.status].innerText; + }); return data; } + +// table iteration helper +StageHelper.prototype.iterateStageTab = function (cb /*, ...*/) { + for (var b = 0, bN = this.dom.stageTab.tBodies.length; b < bN; b++) { + var tbody = this.dom.stageTab.tBodies[b]; + for (var r = 0, rN = tbody.rows.length; r < rN; r++) { + args = [tbody.rows[r]].concat(Array.prototype.slice.call(arguments, 1)); + cb.apply(this, args); // callback + } + } +} diff --git a/src/zabapgit_object_smim.prog.abap b/src/zabapgit_object_smim.prog.abap index ac30efc6b..1b44f81b5 100644 --- a/src/zabapgit_object_smim.prog.abap +++ b/src/zabapgit_object_smim.prog.abap @@ -55,7 +55,7 @@ CLASS lcl_object_smim IMPLEMENTATION. SELECT SINGLE chng_user FROM smimloio INTO rv_user WHERE loio_id = lv_loio. "#EC CI_GENBUFF - IF sy-subrc <> 0. + IF sy-subrc <> 0 OR rv_user IS INITIAL. rv_user = c_user_unknown. ENDIF. diff --git a/src/zabapgit_page.prog.abap b/src/zabapgit_page.prog.abap index c61eb0a0f..c8be3f69c 100644 --- a/src/zabapgit_page.prog.abap +++ b/src/zabapgit_page.prog.abap @@ -176,7 +176,7 @@ CLASS lcl_gui_page IMPLEMENTATION. IF lo_script IS BOUND AND lo_script->is_empty( ) = abap_false. ro_html->add( '' ). ENDIF. diff --git a/src/zabapgit_page_commit.prog.abap b/src/zabapgit_page_commit.prog.abap index c9550a045..2a752d362 100644 --- a/src/zabapgit_page_commit.prog.abap +++ b/src/zabapgit_page_commit.prog.abap @@ -101,10 +101,13 @@ CLASS lcl_gui_page_commit IMPLEMENTATION. lt_stage = mo_stage->get_all( ). ro_html->add( '' ). - ro_html->add( ''). - ro_html->add( ''). + ro_html->add( '' ). + ro_html->add( ''). + ro_html->add( ''). ro_html->add( '' ). + ro_html->add( '' ). + ro_html->add( '' ). LOOP AT lt_stage ASSIGNING . ro_html->add( '' ). ro_html->add( '' ). ro_html->add( '' ). ENDLOOP. + ro_html->add( '' ). ro_html->add( '
Staged files
Staged files
' ). @@ -115,6 +118,7 @@ CLASS lcl_gui_page_commit IMPLEMENTATION. ro_html->add( '
' ). diff --git a/src/zabapgit_page_stage.prog.abap b/src/zabapgit_page_stage.prog.abap index 2b4f85f7a..4151272a7 100644 --- a/src/zabapgit_page_stage.prog.abap +++ b/src/zabapgit_page_stage.prog.abap @@ -32,8 +32,9 @@ CLASS lcl_gui_page_stage DEFINITION FINAL INHERITING FROM lcl_gui_page. render_list RETURNING VALUE(ro_html) TYPE REF TO lcl_html, render_file - IMPORTING is_file TYPE ty_file - iv_context TYPE string + IMPORTING iv_context TYPE string + is_file TYPE ty_file + is_item TYPE ty_item OPTIONAL RETURNING VALUE(ro_html) TYPE REF TO lcl_html, render_menu RETURNING VALUE(ro_html) TYPE REF TO lcl_html, @@ -44,6 +45,8 @@ CLASS lcl_gui_page_stage DEFINITION FINAL INHERITING FROM lcl_gui_page. METHODS process_stage_list IMPORTING it_postdata TYPE cnht_post_data_tab RAISING lcx_exception. + METHODS build_menu + RETURNING VALUE(ro_menu) TYPE REF TO lcl_html_toolbar. ENDCLASS. @@ -52,10 +55,10 @@ CLASS lcl_gui_page_stage IMPLEMENTATION. METHOD constructor. super->constructor( ). - ms_control-page_title = 'STAGE'. - mo_repo = io_repo. - ms_files = lcl_stage_logic=>get( mo_repo ). + ms_control-page_title = 'STAGE'. + mo_repo = io_repo. + ms_files = lcl_stage_logic=>get( mo_repo ). CREATE OBJECT mo_stage EXPORTING @@ -64,8 +67,21 @@ CLASS lcl_gui_page_stage IMPLEMENTATION. GET TIME STAMP FIELD mv_ts. + ms_control-page_menu = build_menu( ). + ENDMETHOD. + METHOD build_menu. + + CREATE OBJECT ro_menu. + + IF lines( ms_files-local ) > 0. + ro_menu->add( iv_txt = |All diffs| + iv_act = |{ gc_action-go_diff }?key={ mo_repo->get_key( ) }| ). + ENDIF. + + ENDMETHOD. "build_menu + METHOD lif_gui_page~on_event. FIELD-SYMBOLS: LIKE LINE OF ms_files-local. @@ -146,25 +162,25 @@ CLASS lcl_gui_page_stage IMPLEMENTATION. CREATE OBJECT ro_html. - ro_html->add( '' ). + ro_html->add( '
' ). " Local changes LOOP AT ms_files-local ASSIGNING . AT FIRST. - ro_html->add(''). - ro_html->add('' ). - ro_html->add(''). - ro_html->add(''). + ro_html->add(''). + ro_html->add(''). + ro_html->add('' ). + ro_html->add(''). + ro_html->add('' ). " Status + ro_html->add('' ). ro_html->add(''). - ro_html->add(''). + ro_html->add(''). ENDAT. - ro_html->add( render_file( is_file = -file iv_context = 'local' ) ). + ro_html->add( render_file( + iv_context = 'local' + is_file = -file + is_item = -item ) ). " TODO Refactor, unify structure AT LAST. ro_html->add(''). @@ -174,13 +190,19 @@ CLASS lcl_gui_page_stage IMPLEMENTATION. " Remote changes LOOP AT ms_files-remote ASSIGNING . AT FIRST. - ro_html->add(''). - ro_html->add('' ). - ro_html->add(''). - ro_html->add(''). + ro_html->add( '' ). + ro_html->add( '' ). " Type + ro_html->add( '' ). + ro_html->add( '' ). " Status + ro_html->add( '' ). + ro_html->add( '' ). + ro_html->add( '' ). ENDAT. - ro_html->add( render_file( is_file = iv_context = 'remote' ) ). + ro_html->add( render_file( + iv_context = 'remote' + is_file = ) ). AT LAST. ro_html->add(''). @@ -193,31 +215,32 @@ CLASS lcl_gui_page_stage IMPLEMENTATION. METHOD render_file. - DATA: lv_param TYPE string, - lv_user TYPE xubname. + DATA: lv_param TYPE string, + lv_filename TYPE string. CREATE OBJECT ro_html. + lv_filename = is_file-path && is_file-filename. + ro_html->add( || ). - ro_html->add( || ). - ro_html->add( || ). CASE iv_context. WHEN 'local'. - lv_param = lcl_html_action_utils=>file_encode( iv_key = mo_repo->get_key( ) - ig_file = is_file ). - ro_html->add( '' ). - ro_html->add( '' ). - - lv_user = read_last_changed_by( is_file ). - ro_html->add( | | ). + lv_param = lcl_html_action_utils=>file_encode( iv_key = mo_repo->get_key( ) + ig_file = is_file ). + lv_filename = lcl_html=>a( iv_txt = lv_filename + iv_act = |{ gc_action-go_diff }?{ lv_param }| ). + ro_html->add( || ). + ro_html->add( || ). + ro_html->add( || ). WHEN 'remote'. - ro_html->add( '' ). - ro_html->add( || ). + ro_html->add( '' ). " Dummy for object type + ro_html->add( || ). + ro_html->add( '' ). " Dummy for changed-by ENDCASE. + ro_html->add( || ). + ro_html->add( '' ). " Command added in JS ro_html->add( '' ). ENDMETHOD. "render_file @@ -228,6 +251,7 @@ CLASS lcl_gui_page_stage IMPLEMENTATION. ro_html->add( '
' ). ro_html->add( lcl_gui_chunk_lib=>render_repo_top( mo_repo ) ). + ro_html->add( lcl_gui_chunk_lib=>render_js_error_banner( ) ). ro_html->add( render_menu( ) ). ro_html->add( render_list( ) ). ro_html->add( '
' ). @@ -236,22 +260,29 @@ CLASS lcl_gui_page_stage IMPLEMENTATION. METHOD render_menu. + DATA lv_local_count TYPE i. + CREATE OBJECT ro_html. + lv_local_count = lines( ms_files-local ). ro_html->add( '
' ). - ro_html->add_a( iv_act = 'gHelper.submit();' + ro_html->add_a( iv_act = 'errorStub(event)' " Will be reinit by JS iv_typ = gc_action_type-onclick - iv_id = 'act_commit' + iv_id = 'commitButton' iv_style = 'display: none' - iv_txt = 'Commit' + iv_txt = 'Commit ()' iv_opt = gc_html_opt-strong ) ##NO_TEXT. - IF lines( ms_files-local ) > 0. + IF lv_local_count > 0. ro_html->add_a( iv_act = |{ c_action-stage_all }| - iv_id = 'act_commit_all' - iv_txt = 'Add all and commit') ##NO_TEXT. + iv_id = 'commitAllButton' + iv_txt = |Add all and commit ({ lv_local_count })| ) ##NO_TEXT. ENDIF. ro_html->add( '
' ). + ro_html->add( '
' ). + ro_html->add( '' ). + ro_html->add( '
' ). + ENDMETHOD. "render_menu METHOD scripts. @@ -260,10 +291,16 @@ CLASS lcl_gui_page_stage IMPLEMENTATION. ro_html->add( 'var gStageParams = {' ). ro_html->add( | seed: "stage{ mv_ts }",| ). - ro_html->add( ' stageTabId: "stage_tab",' ). ro_html->add( ' formAction: "stage_commit",' ). - ro_html->add( ' commitNodeId: "act_commit",' ). - ro_html->add( ' commitAllNodeId: "act_commit_all"' ). + + ro_html->add( ' ids: {' ). + ro_html->add( ' stageTab: "stageTab",' ). + ro_html->add( ' commitBtn: "commitButton",' ). + ro_html->add( ' commitAllBtn: "commitAllButton",' ). + ro_html->add( ' objectSearch: "objectSearch",' ). + ro_html->add( ' fileCounter: "fileCounter"' ). + ro_html->add( ' }' ). + ro_html->add( '}' ). ro_html->add( 'var gHelper = new StageHelper(gStageParams);' ). @@ -281,6 +318,8 @@ CLASS lcl_gui_page_stage IMPLEMENTATION. CATCH lcx_exception. CLEAR rv_user. "Should not raise errors if user last changed by was not found ENDTRY. + + rv_user = to_lower( rv_user ). ENDMETHOD. ENDCLASS.
LOCAL' ). - IF lines( ms_files-local ) > 1. - ro_html->add_a( iv_txt = |{ lines( ms_files-local ) } diffs| - iv_act = |{ gc_action-go_diff }?key={ mo_repo->get_key( ) }| ). - ENDIF. - ro_html->add('Last changed by
TypeFiles to add (click to see diff)Changed byadd/reset
REMOTE
Files to remove or non-code' && + '↓ignoreremovereset
?{ is_file-path && is_file-filename }add' ). - ro_html->add_a( iv_txt = 'diff' iv_act = |{ gc_action-go_diff }?{ lv_param }| ). - ro_html->add( '{ lv_user }{ is_item-obj_type }{ lv_filename }{ read_last_changed_by( is_file ) }ignoreremove--{ lv_filename }?