03_Add_Simpacker.patch

Takashi Kato, 2021-07-06 11:59

Download (251 KB)

View differences:

.gitignore
29 29
!/public/themes/alternate
30 30
!/public/themes/classic
31 31
!/public/themes/README
32
/public/images/jquery-ui/*
33
/public/javascripts/*.js*
34
/public/javascripts/jstoolbar/*.js*
35
/public/stylesheets/*.css*
32 36
/tmp/*
33 37
/tmp/cache/*
34 38
/tmp/pdf/*
......
45 49

  
46 50
/node_modules
47 51
yarn-error.log
52

  
53
/public/packs
Gemfile
17 17
gem "rbpdf", "~> 1.20.0"
18 18
gem 'addressable'
19 19
gem 'rubyzip', '~> 2.3.0'
20
gem 'simpacker'
20 21

  
21 22
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
22 23
gem 'tzinfo-data', platforms: [:mingw, :x64_mingw, :mswin]
......
79 80

  
80 81
group :development do
81 82
  gem "yard"
83
  gem "rack-reverse-proxy"
82 84
end
83 85

  
84 86
group :test do
app/assets/README.md
1
In Redmine, most javascripts, stylesheets, and images(hereafter called "assets") are under the control of webpack
2

  
3
webpack register assets in 'public/pack/manifest.json'. Redmine determines the actual URL based on the registered contents in manifest.json.
4

  
5
In the development environment, webpack-dev-server delivers assets, and the HMR feature automatically re-fetches the updated assets.
6

  
7
In the production environment, Redmine uses pre-compiled assets.
8

  
9
Each asset can be fine-grained to control how it is cached in the browser by file name, query strings, etc., thus reducing traffic and ensuring accurate content updates.
10

  
11
## Requirements
12

  
13
The following tools are necessary to the development and testing environment.
14

  
15
Node.js 14+
16
Yarn 1.x
17

  
18
## Preparation
19

  
20
In the development and test environments, start webpack-dev-server after installing the dependent npm packages.
21

  
22
````
23
# Install dependent packages
24
$ yarn
25

  
26
# Run this before starting Redmine.
27
# assets will be stored in memory and will not be output as a file.
28
$ yarn server &
29
$ rails server -b 0.0.0.0
30
````
31

  
32
In production, pre-compile your assets.
33

  
34
```
35
# Install the dependency packages.
36
$ yarn
37

  
38
# Compile assets (assets will be output to 'public/').
39
$ yarn release
40

  
41
# Start Redmine
42
$ rails server
43
````
44

  
45
## Notes
46

  
47
It will take some time to generate manifest.json after starting webpack-dev-server.
app/assets/javascripts/application.js
1

  
2
/*
3
  "~/" in import is an alias for "app/assets/stylesheets/". See: "https://webpack.js.org/configuration/resolve/#resolvealias"
4
*/
5

  
6
import $ from "jquery";
7
import '~/stylesheets/application.css';
8
import 'jquery-ui-dist/jquery-ui';
9
import 'jquery-ui-dist/jquery-ui.css';
10

  
11
import '~/stylesheets/responsive.css';
12

  
13
import Rails from '@rails/ujs';
14
Rails.start();
15

  
16
export {
17
    sanitizeHTML
18
  , checkAll
19
  , toggleCheckboxesBySelector
20
  , showAndScrollTo
21
  , toggleRowGroup
22
  , collapseAllRowGroups
23
  , expandAllRowGroups
24
  , toggleAllRowGroups
25
  , toggleFieldset
26
  , hideFieldset
27
  , moveOptions
28
  , moveOptionUp
29
  , moveOptionTop
30
  , moveOptionDown
31
  , moveOptionBottom
32
  , initFilters
33
  , addFilter
34
  , buildFilterRow
35
  , toggleFilter
36
  , enableValues
37
  , toggleOperator
38
  , toggleMultiSelect
39
  , showTab
40
  , showIssueHistory
41
  , getRemoteTab
42
  , replaceInHistory
43
  , moveTabRight
44
  , moveTabLeft
45
  , displayTabsButtons
46
  , setPredecessorFieldsVisibility
47
  , showModal
48
  , hideModal
49
  , collapseScmEntry
50
  , expandScmEntry
51
  , scmEntryClick
52
  , randomKey
53
  , copyTextToClipboard
54
  , updateIssueFrom
55
  , replaceIssueFormWith
56
  , updateBulkEditFrom
57
  , observeAutocompleteField
58
  , multipleAutocompleteField
59
  , observeSearchfield
60
  , beforeShowDatePicker
61
  , warnLeavingUnsavedMessage
62
  , warnLeavingUnsaved
63
  , setupAjaxIndicator
64
  , setupTabs
65
  , setupFilePreviewNavigation
66
  , hideOnLoad
67
  , addFormObserversForDoubleSubmit
68
  , defaultFocus
69
  , blockEventPropagation
70
  , toggleDisabledOnChange
71
  , toggleDisabledInit
72
  , toggleMultiSelectIconInit
73
  , toggleNewObjectDropdown
74
  , keepAnchorOnSignIn
75
  , setFilecontentContainerHeight
76
  , setupAttachmentDetail
77
  , setupWikiTableSortableHeader
78
  , inlineAutoComplete
79
} from "./base.js";
80

  
81
export {
82
    openFlyout
83
  , closeFlyout
84
  , isMobile
85
  , setupFlyout
86
} from "./responsive.js";
app/assets/javascripts/attachments.js
1 1
/* Redmine - project management software
2 2
   Copyright (C) 2006-2021  Jean-Philippe Lang */
3 3

  
4
function addFile(inputEl, file, eagerUpload) {
4
export function addFile(inputEl, file, eagerUpload) {
5 5
  var attachmentsFields = $(inputEl).closest('.attachments_form').find('.attachments_fields');
6 6
  var addAttachment = $(inputEl).closest('.attachments_form').find('.add_attachment');
7 7
  var maxFiles = ($(inputEl).attr('multiple') == 'multiple' ? 10 : 1);
......
35 35

  
36 36
addFile.nextAttachmentId = 1;
37 37

  
38
function ajaxUpload(file, attachmentId, fileSpan, inputEl) {
38
export function ajaxUpload(file, attachmentId, fileSpan, inputEl) {
39 39

  
40 40
  function onLoadstart(e) {
41 41
    fileSpan.removeClass('ajax-waiting');
......
89 89

  
90 90
ajaxUpload.uploading = 0;
91 91

  
92
function removeFile() {
92
export function removeFile() {
93 93
  $(this).closest('.attachments_form').find('.add_attachment').show();
94 94
  $(this).parent('span').remove();
95 95
  return false;
96 96
}
97 97

  
98
function uploadBlob(blob, uploadUrl, attachmentId, options) {
98
export function uploadBlob(blob, uploadUrl, attachmentId, options) {
99 99

  
100 100
  var actualOptions = $.extend({
101 101
    loadstartEventHandler: $.noop,
......
128 128
  });
129 129
}
130 130

  
131
function addInputFiles(inputEl) {
131
export function addInputFiles(inputEl) {
132 132
  var attachmentsFields = $(inputEl).closest('.attachments_form').find('.attachments_fields');
133 133
  var addAttachment = $(inputEl).closest('.attachments_form').find('.add_attachment');
134 134
  var clearedFileInput = $(inputEl).clone().val('');
......
153 153
  clearedFileInput.prependTo(addAttachment);
154 154
}
155 155

  
156
function uploadAndAttachFiles(files, inputEl) {
156
export function uploadAndAttachFiles(files, inputEl) {
157 157

  
158 158
  var maxFileSize = $(inputEl).data('max-file-size');
159 159
  var maxFileSizeExceeded = $(inputEl).data('max-file-size-message');
......
175 175
  return sizeExceeded;
176 176
}
177 177

  
178
function handleFileDropEvent(e) {
178
export function handleFileDropEvent(e) {
179 179

  
180 180
  $(this).removeClass('fileover');
181 181
  blockEventPropagation(e);
......
191 191
}
192 192
handleFileDropEvent.target = '';
193 193

  
194
function dragOverHandler(e) {
194
export function dragOverHandler(e) {
195 195
  $(this).addClass('fileover');
196 196
  blockEventPropagation(e);
197 197
  e.dataTransfer.dropEffect = 'copy';
198 198
}
199 199

  
200
function dragOutHandler(e) {
200
export function dragOutHandler(e) {
201 201
  $(this).removeClass('fileover');
202 202
  blockEventPropagation(e);
203 203
}
204 204

  
205
function setupFileDrop() {
205
export function setupFileDrop() {
206 206
  if (window.File && window.FileList && window.ProgressEvent && window.FormData) {
207 207

  
208 208
    $.event.addProp('dataTransfer');
......
226 226
  }
227 227
}
228 228

  
229
function addInlineAttachmentMarkup(file) {
229
export function addInlineAttachmentMarkup(file) {
230 230
  // insert uploaded image inline if dropped area is currently focused textarea
231 231
  if($(handleFileDropEvent.target).hasClass('wiki-edit') && $.inArray(file.type, window.wikiImageMimeTypes) > -1) {
232 232
    var $textarea = $(handleFileDropEvent.target);
......
269 269
  }
270 270
}
271 271

  
272
function copyImageFromClipboard(e) {
272
export function copyImageFromClipboard(e) {
273 273
  if (!$(e.target).hasClass('wiki-edit')) { return; }
274 274
  var clipboardData = e.clipboardData || e.originalEvent.clipboardData
275 275
  if (!clipboardData) { return; }
app/assets/javascripts/base.js
1 1
/* Redmine - project management software
2 2
   Copyright (C) 2006-2021  Jean-Philippe Lang */
3 3

  
4
function sanitizeHTML(string) {
4
import $ from "jquery";
5
import jQuery from "jquery";
6
import Tribute from "tributejs";
7
import Tablesort from "tablesort";
8

  
9
import 'tributejs/dist/tribute.css';
10

  
11
export function sanitizeHTML(string) {
5 12
  var temp = document.createElement('span');
6 13
  temp.textContent = string;
7 14
  return temp.innerHTML;
8 15
}
9 16

  
10
function checkAll(id, checked) {
17
export function checkAll(id, checked) {
11 18
  $('#'+id).find('input[type=checkbox]:enabled').prop('checked', checked);
12 19
}
13 20

  
14
function toggleCheckboxesBySelector(selector) {
21
export function toggleCheckboxesBySelector(selector) {
15 22
  var all_checked = true;
16 23
  $(selector).each(function(index) {
17 24
    if (!$(this).is(':checked')) { all_checked = false; }
......
19 26
  $(selector).prop('checked', !all_checked).trigger('change');
20 27
}
21 28

  
22
function showAndScrollTo(id, focus) {
29
export function showAndScrollTo(id, focus) {
23 30
  $('#'+id).show();
24 31
  if (focus !== null) {
25 32
    $('#'+focus).focus();
......
27 34
  $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100);
28 35
}
29 36

  
30
function toggleRowGroup(el) {
37
export function toggleRowGroup(el) {
31 38
  var tr = $(el).parents('tr').first();
32 39
  var n = tr.next();
33 40
  tr.toggleClass('open');
......
38 45
  }
39 46
}
40 47

  
41
function collapseAllRowGroups(el) {
48
export function collapseAllRowGroups(el) {
42 49
  var tbody = $(el).parents('tbody').first();
43 50
  tbody.children('tr').each(function(index) {
44 51
    if ($(this).hasClass('group')) {
......
50 57
  });
51 58
}
52 59

  
53
function expandAllRowGroups(el) {
60
export function expandAllRowGroups(el) {
54 61
  var tbody = $(el).parents('tbody').first();
55 62
  tbody.children('tr').each(function(index) {
56 63
    if ($(this).hasClass('group')) {
......
62 69
  });
63 70
}
64 71

  
65
function toggleAllRowGroups(el) {
72
export function toggleAllRowGroups(el) {
66 73
  var tr = $(el).parents('tr').first();
67 74
  if (tr.hasClass('open')) {
68 75
    collapseAllRowGroups(el);
......
71 78
  }
72 79
}
73 80

  
74
function toggleFieldset(el) {
81
export function toggleFieldset(el) {
75 82
  var fieldset = $(el).parents('fieldset').first();
76 83
  fieldset.toggleClass('collapsed');
77 84
  fieldset.children('legend').toggleClass('icon-expended icon-collapsed');
78 85
  fieldset.children('div').toggle();
79 86
}
80 87

  
81
function hideFieldset(el) {
88
export function hideFieldset(el) {
82 89
  var fieldset = $(el).parents('fieldset').first();
83 90
  fieldset.toggleClass('collapsed');
84 91
  fieldset.children('div').hide();
85 92
}
86 93

  
87 94
// columns selection
88
function moveOptions(theSelFrom, theSelTo) {
95
export function moveOptions(theSelFrom, theSelTo) {
89 96
  $(theSelFrom).find('option:selected').detach().prop("selected", false).appendTo($(theSelTo));
90 97
}
91 98

  
92
function moveOptionUp(theSel) {
99
export function moveOptionUp(theSel) {
93 100
  $(theSel).find('option:selected').each(function(){
94 101
    $(this).prev(':not(:selected)').detach().insertAfter($(this));
95 102
  });
96 103
}
97 104

  
98
function moveOptionTop(theSel) {
105
export function moveOptionTop(theSel) {
99 106
  $(theSel).find('option:selected').detach().prependTo($(theSel));
100 107
}
101 108

  
102
function moveOptionDown(theSel) {
109
export function moveOptionDown(theSel) {
103 110
  $($(theSel).find('option:selected').get().reverse()).each(function(){
104 111
    $(this).next(':not(:selected)').detach().insertBefore($(this));
105 112
  });
106 113
}
107 114

  
108
function moveOptionBottom(theSel) {
115
export function moveOptionBottom(theSel) {
109 116
  $(theSel).find('option:selected').detach().appendTo($(theSel));
110 117
}
111 118

  
112
function initFilters() {
119
export function initFilters() {
113 120
  $('#add_filter_select').change(function() {
114 121
    addFilter($(this).val(), '', []);
115 122
  });
......
124 131
  });
125 132
}
126 133

  
127
function addFilter(field, operator, values) {
134
export function addFilter(field, operator, values) {
128 135
  var fieldId = field.replace('.', '_');
129 136
  var tr = $('#tr_'+fieldId);
130 137

  
......
154 161
  });
155 162
}
156 163

  
157
function buildFilterRow(field, operator, values) {
164
export function buildFilterRow(field, operator, values) {
158 165
  var fieldId = field.replace('.', '_');
159 166
  var filterTable = $("#filters-table");
160 167
  var filterOptions = availableFilters[field];
......
253 260
  }
254 261
}
255 262

  
256
function toggleFilter(field) {
263
export function toggleFilter(field) {
257 264
  var fieldId = field.replace('.', '_');
258 265
  if ($('#cb_' + fieldId).is(':checked')) {
259 266
    $("#operators_" + fieldId).show().removeAttr('disabled');
......
264 271
  }
265 272
}
266 273

  
267
function enableValues(field, indexes) {
274
export function enableValues(field, indexes) {
268 275
  var fieldId = field.replace('.', '_');
269 276
  $('#tr_'+fieldId+' td.values .value').each(function(index) {
270 277
    if ($.inArray(index, indexes) >= 0) {
......
284 291
  });
285 292
}
286 293

  
287
function toggleOperator(field) {
294
export function toggleOperator(field) {
288 295
  var fieldId = field.replace('.', '_');
289 296
  var operator = $("#operators_" + fieldId);
290 297
  switch (operator.val()) {
......
331 338
  }
332 339
}
333 340

  
334
function toggleMultiSelect(el) {
341
export function toggleMultiSelect(el) {
335 342
  var isWorkflow = el.closest('.controller-workflows');
336 343
  if (el.attr('multiple')) {
337 344
    el.removeAttr('multiple');
......
347 354
  }
348 355
}
349 356

  
350
function showTab(name, url) {
357
export function showTab(name, url) {
351 358
  $('#tab-content-' + name).parent().find('.tab-content').hide();
352 359
  $('#tab-content-' + name).show();
353 360
  $('#tab-' + name).closest('.tabs').find('a').removeClass('selected');
......
358 365
  return false;
359 366
}
360 367

  
361
function showIssueHistory(journal, url) {
368
export function showIssueHistory(journal, url) {
362 369
  tab_content = $('#tab-content-history');
363 370
  tab_content.parent().find('.tab-content').hide();
364 371
  tab_content.show();
......
398 405
  return false;
399 406
}
400 407

  
401
function getRemoteTab(name, remote_url, url, load_always) {
408
export function getRemoteTab(name, remote_url, url, load_always) {
402 409
  load_always = load_always || false;
403 410
  var tab_content = $('#tab-content-' + name);
404 411

  
......
424 431

  
425 432
//replaces current URL with the "href" attribute of the current link
426 433
//(only triggered if supported by browser)
427
function replaceInHistory(url) {
434
export function replaceInHistory(url) {
428 435
  if ("replaceState" in window.history && url !== undefined) {
429 436
    window.history.replaceState(null, document.title, url);
430 437
  }
431 438
}
432 439

  
433
function moveTabRight(el) {
440
export function moveTabRight(el) {
434 441
  var lis = $(el).parents('div.tabs').first().find('ul').children();
435 442
  var bw = $(el).parents('div.tabs-buttons').outerWidth(true);
436 443
  var tabsWidth = 0;
......
450 457
  }
451 458
}
452 459

  
453
function moveTabLeft(el) {
460
export function moveTabLeft(el) {
454 461
  var lis = $(el).parents('div.tabs').first().find('ul').children();
455 462
  var i = 0;
456 463
  while (i < lis.length && !lis.eq(i).is(':visible')) { i++; }
......
463 470
  }
464 471
}
465 472

  
466
function displayTabsButtons() {
473
export function displayTabsButtons() {
467 474
  var lis;
468 475
  var tabsWidth;
469 476
  var el;
......
489 496
  });
490 497
}
491 498

  
492
function setPredecessorFieldsVisibility() {
499
export function setPredecessorFieldsVisibility() {
493 500
  var relationType = $('#relation_relation_type');
494 501
  if (relationType.val() == "precedes" || relationType.val() == "follows") {
495 502
    $('#predecessor_fields').show();
......
498 505
  }
499 506
}
500 507

  
501
function showModal(id, width, title) {
508
export function showModal(id, width, title) {
502 509
  var el = $('#'+id).first();
503 510
  if (el.length === 0 || el.is(':visible')) {return;}
504 511
  if (!title) title = el.find('h3.title').text();
......
516 523
  el.find("input[type=text], input[type=submit]").first().focus();
517 524
}
518 525

  
519
function hideModal(el) {
526
export function hideModal(el) {
520 527
  var modal;
521 528
  if (el) {
522 529
    modal = $(el).parents('.ui-dialog-content');
......
526 533
  modal.dialog("close");
527 534
}
528 535

  
529
function collapseScmEntry(id) {
536
export function collapseScmEntry(id) {
530 537
  $('.'+id).each(function() {
531 538
    if ($(this).hasClass('open')) {
532 539
      collapseScmEntry($(this).attr('id'));
......
536 543
  $('#'+id).removeClass('open');
537 544
}
538 545

  
539
function expandScmEntry(id) {
546
export function expandScmEntry(id) {
540 547
  $('.'+id).each(function() {
541 548
    $(this).show();
542 549
    if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) {
......
546 553
  $('#'+id).addClass('open');
547 554
}
548 555

  
549
function scmEntryClick(id, url) {
556
export function scmEntryClick(id, url) {
550 557
    var el = $('#'+id);
551 558
    if (el.hasClass('open')) {
552 559
        collapseScmEntry(id);
......
574 581
    return true;
575 582
}
576 583

  
577
function randomKey(size) {
584
export function randomKey(size) {
578 585
  var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
579 586
  var key = '';
580 587
  for (var i = 0; i < size; i++) {
......
583 590
  return key;
584 591
}
585 592

  
586
function copyTextToClipboard(target) {
593
export function copyTextToClipboard(target) {
587 594
  if (target) {
588 595
    var temp = document.createElement('textarea');
589 596
    temp.value = target.getAttribute('data-clipboard-text');
......
600 607
  return false;
601 608
}
602 609

  
603
function updateIssueFrom(url, el) {
610
export function updateIssueFrom(url, el) {
604 611
  $('#all_attributes input, #all_attributes textarea, #all_attributes select').each(function(){
605 612
    $(this).data('valuebeforeupdate', $(this).val());
606 613
  });
......
614 621
  });
615 622
}
616 623

  
617
function replaceIssueFormWith(html){
624
export function replaceIssueFormWith(html){
618 625
  var replacement = $(html);
619 626
  $('#all_attributes input, #all_attributes textarea, #all_attributes select').each(function(){
620 627
    var object_id = $(this).attr('id');
......
626 633
  $('#all_attributes').prepend(replacement);
627 634
}
628 635

  
629
function updateBulkEditFrom(url) {
636
export function updateBulkEditFrom(url) {
630 637
  $.ajax({
631 638
    url: url,
632 639
    type: 'post',
......
634 641
  });
635 642
}
636 643

  
637
function observeAutocompleteField(fieldId, url, options) {
644
export function observeAutocompleteField(fieldId, url, options) {
638 645
  $(document).ready(function() {
639 646
    $('#'+fieldId).autocomplete($.extend({
640 647
      source: url,
......
647 654
  });
648 655
}
649 656

  
650
function multipleAutocompleteField(fieldId, url, options) {
657
export function multipleAutocompleteField(fieldId, url, options) {
651 658
  function split(val) {
652 659
    return val.split(/,\s*/);
653 660
  }
......
687 694
  });
688 695
}
689 696

  
690
function observeSearchfield(fieldId, targetId, url) {
697
export function observeSearchfield(fieldId, targetId, url) {
691 698
  $('#'+fieldId).each(function() {
692 699
    var $this = $(this);
693 700
    $this.addClass('autocomplete');
......
792 799
  });
793 800
});
794 801

  
795
function beforeShowDatePicker(input, inst) {
802
export function beforeShowDatePicker(input, inst) {
796 803
  var default_date = null;
797 804
  switch ($(input).attr("id")) {
798 805
    case "issue_start_date" :
......
855 862
  }
856 863
}( jQuery ));
857 864

  
858
var warnLeavingUnsavedMessage;
859
function warnLeavingUnsaved(message) {
865
export let warnLeavingUnsavedMessage;
866
export function warnLeavingUnsaved(message) {
860 867
  warnLeavingUnsavedMessage = message;
861 868
  $(document).on('submit', 'form', function(){
862 869
    $('textarea').removeData('changed');
......
875 882
  };
876 883
}
877 884

  
878
function setupAjaxIndicator() {
885
export function setupAjaxIndicator() {
879 886
  $(document).bind('ajaxSend', function(event, xhr, settings) {
880 887
    if ($('.ajax-loading').length === 0 && settings.contentType != 'application/octet-stream') {
881 888
      $('#ajax-indicator').show();
......
886 893
  });
887 894
}
888 895

  
889
function setupTabs() {
896
export function setupTabs() {
890 897
  if($('.tabs').length > 0) {
891 898
    displayTabsButtons();
892 899
    $(window).resize(displayTabsButtons);
893 900
  }
894 901
}
895 902

  
896
function setupFilePreviewNavigation() {
903
export function setupFilePreviewNavigation() {
897 904
  // only bind arrow keys when preview navigation is present
898 905
  const element = $('.pagination.filepreview').first();
899 906
  if (element) {
......
938 945
});
939 946

  
940 947

  
941
function hideOnLoad() {
948
export function hideOnLoad() {
942 949
  $('.hol').hide();
943 950
}
944 951

  
945
function addFormObserversForDoubleSubmit() {
952
export function addFormObserversForDoubleSubmit() {
946 953
  $('form[method=post]').each(function() {
947 954
    if (!$(this).hasClass('multiple-submit')) {
948 955
      $(this).submit(function(form_submission) {
......
956 963
  });
957 964
}
958 965

  
959
function defaultFocus(){
966
export function defaultFocus(){
960 967
  if (($('#content :focus').length == 0) && (window.location.hash == '')) {
961 968
    $('#content input[type=text], #content textarea').first().focus();
962 969
  }
963 970
}
964 971

  
965
function blockEventPropagation(event) {
972
export function blockEventPropagation(event) {
966 973
  event.stopPropagation();
967 974
  event.preventDefault();
968 975
}
969 976

  
970
function toggleDisabledOnChange() {
977
export function toggleDisabledOnChange() {
971 978
  var checked = $(this).is(':checked');
972 979
  $($(this).data('disables')).attr('disabled', checked);
973 980
  $($(this).data('enables')).attr('disabled', !checked);
974 981
  $($(this).data('shows')).toggle(checked);
975 982
}
976
function toggleDisabledInit() {
983
export function toggleDisabledInit() {
977 984
  $('input[data-disables], input[data-enables], input[data-shows]').each(toggleDisabledOnChange);
978 985
}
979
function toggleMultiSelectIconInit() {
986
export function toggleMultiSelectIconInit() {
980 987
  $('.toggle-multiselect:not(.icon-toggle-minus), .toggle-multiselect:not(.icon-toggle-plus)').each(function(){
981 988
    if ($(this).siblings('select').find('option:selected').length > 1){
982 989
      $(this).addClass('icon-toggle-minus');
......
986 993
  });
987 994
}
988 995

  
989
function toggleNewObjectDropdown() {
996
export function toggleNewObjectDropdown() {
990 997
  var dropdown = $('#new-object + ul.menu-children');
991 998
  if(dropdown.hasClass('visible')){
992 999
    dropdown.removeClass('visible');
......
1060 1067
  });
1061 1068
});
1062 1069

  
1063
function keepAnchorOnSignIn(form){
1070
export function keepAnchorOnSignIn(form){
1064 1071
  var hash = decodeURIComponent(self.document.location.hash);
1065 1072
  if (hash) {
1066 1073
    if (hash.indexOf("#") === -1) {
......
1077 1084
  }).change();
1078 1085
});
1079 1086

  
1080
function setFilecontentContainerHeight() {
1087
export function setFilecontentContainerHeight() {
1081 1088
  var $filecontainer = $('.filecontent-container');
1082 1089
  var fileTypeSelectors = ['.image', 'video'];
1083 1090

  
......
1091 1098
  }
1092 1099
}
1093 1100

  
1094
function setupAttachmentDetail() {
1101
export function setupAttachmentDetail() {
1095 1102
  setFilecontentContainerHeight();
1096 1103
  $(window).resize(setFilecontentContainerHeight);
1097 1104
}
1098 1105

  
1099
function setupWikiTableSortableHeader() {
1106
export function setupWikiTableSortableHeader() {
1100 1107
  $('div.wiki table').each(function(i, table){
1101 1108
    if (table.rows.length < 3) return true;
1102 1109
    var tr = $(table.rows).first();
......
1120 1127
  });
1121 1128
});
1122 1129

  
1123
function inlineAutoComplete(element) {
1130
export function inlineAutoComplete(element) {
1124 1131
    'use strict';
1125 1132

  
1126 1133
    // do not attach if Tribute is already initialized
app/assets/javascripts/chart.js
1
export { Chart } from "chart.js";
app/assets/javascripts/context_menu.js
1 1
/* Redmine - project management software
2
   Copyright (C) 2006-2021  Jean-Philippe Lang */
2
   Copyright (C) 2006-2021  Jean-Philippe Lang
3 3

  
4
var contextMenuObserving;
4
  "~/" in import is an alias for "app/assets/stylesheets/". See: "https://webpack.js.org/configuration/resolve/#resolvealias"
5
*/
5 6

  
6
function contextMenuRightClick(event) {
7
import '~/stylesheets/context_menu.css';
8

  
9
export var contextMenuObserving;
10

  
11
export function contextMenuRightClick(event) {
7 12
  var target = $(event.target);
8 13
  if (target.is('a:not(.js-contextmenu)')) {return;}
9 14
  var tr = target.closest('.hascontextmenu').first();
......
17 22
  contextMenuShow(event);
18 23
}
19 24

  
20
function contextMenuClick(event) {
25
export function contextMenuClick(event) {
21 26
  var target = $(event.target);
22 27
  var lastSelected;
23 28

  
......
80 85
  }
81 86
}
82 87

  
83
function contextMenuCreate() {
88
export function contextMenuCreate() {
84 89
  if ($('#context-menu').length < 1) {
85 90
    var menu = document.createElement("div");
86 91
    menu.setAttribute("id", "context-menu");
......
89 94
  }
90 95
}
91 96

  
92
function contextMenuShow(event) {
97
export function contextMenuShow(event) {
93 98
  var mouse_x = event.pageX;
94 99
  var mouse_y = event.pageY;  
95 100
  var mouse_y_c = event.clientY;  
......
159 164
  });
160 165
}
161 166

  
162
function contextMenuSetLastSelected(tr) {
167
export function contextMenuSetLastSelected(tr) {
163 168
  $('.cm-last').removeClass('cm-last');
164 169
  tr.addClass('cm-last');
165 170
}
166 171

  
167
function contextMenuLastSelected() {
172
export function contextMenuLastSelected() {
168 173
  return $('.cm-last').first();
169 174
}
170 175

  
171
function contextMenuUnselectAll() {
176
export function contextMenuUnselectAll() {
172 177
  $('input[type=checkbox].toggle-selection').prop('checked', false);
173 178
  $('.hascontextmenu').each(function(){
174 179
    contextMenuRemoveSelection($(this));
......
176 181
  $('.cm-last').removeClass('cm-last');
177 182
}
178 183

  
179
function contextMenuHide() {
184
export function contextMenuHide() {
180 185
  $('#context-menu').hide();
181 186
}
182 187

  
183
function contextMenuToggleSelection(tr) {
188
export function contextMenuToggleSelection(tr) {
184 189
  if (contextMenuIsSelected(tr)) {
185 190
    contextMenuRemoveSelection(tr);
186 191
  } else {
......
188 193
  }
189 194
}
190 195

  
191
function contextMenuAddSelection(tr) {
196
export function contextMenuAddSelection(tr) {
192 197
  tr.addClass('context-menu-selection');
193 198
  contextMenuCheckSelectionBox(tr, true);
194 199
  contextMenuClearDocumentSelection();
195 200
}
196 201

  
197
function contextMenuRemoveSelection(tr) {
202
export function contextMenuRemoveSelection(tr) {
198 203
  tr.removeClass('context-menu-selection');
199 204
  contextMenuCheckSelectionBox(tr, false);
200 205
}
201 206

  
202
function contextMenuIsSelected(tr) {
207
export function contextMenuIsSelected(tr) {
203 208
  return tr.hasClass('context-menu-selection');
204 209
}
205 210

  
206
function contextMenuCheckSelectionBox(tr, checked) {
211
export function contextMenuCheckSelectionBox(tr, checked) {
207 212
  tr.find('input[type=checkbox]').prop('checked', checked);
208 213
}
209 214

  
210
function contextMenuClearDocumentSelection() {
215
export function contextMenuClearDocumentSelection() {
211 216
  // TODO
212 217
  if (document.selection) {
213 218
    document.selection.empty(); // IE
......
216 221
  }
217 222
}
218 223

  
219
function contextMenuInit() {
224
export function contextMenuInit() {
220 225
  contextMenuCreate();
221 226
  contextMenuUnselectAll();
222 227
  
......
228 233
  }
229 234
}
230 235

  
231
function toggleIssuesSelection(el) {
236
export function toggleIssuesSelection(el) {
232 237
  var checked = $(this).prop('checked');
233 238
  var boxes = $(this).parents('table').find('input[name=ids\\[\\]]');
234 239
  boxes.prop('checked', checked).parents('.hascontextmenu').toggleClass('context-menu-selection', checked);
235 240
}
236 241

  
237
function window_size() {
242
export function window_size() {
238 243
  var w;
239 244
  var h;
240 245
  if (window.innerWidth) {
app/assets/javascripts/gantt.js
1 1
/* Redmine - project management software
2 2
   Copyright (C) 2006-2021  Jean-Philippe Lang */
3 3

  
4
var draw_gantt = null;
5
var draw_top;
6
var draw_right;
7
var draw_left;
4
import Raphael from 'raphael';
8 5

  
9
var rels_stroke_width = 2;
6
export var draw_gantt = null;
7
export var draw_top;
8
export var draw_right;
9
export var draw_left;
10 10

  
11
function setDrawArea() {
11
export var rels_stroke_width = 2;
12

  
13
export function setDrawArea() {
12 14
  draw_top   = $("#gantt_draw_area").position().top;
13 15
  draw_right = $("#gantt_draw_area").width();
14 16
  draw_left  = $("#gantt_area").scrollLeft();
15 17
}
16 18

  
17
function getRelationsArray() {
19
export function getRelationsArray() {
18 20
  var arr = new Array();
19 21
  $.each($('div.task_todo[data-rels]'), function(index_div, element) {
20 22
    if(!$(element).is(':visible')) return true;
......
33 35
  return arr;
34 36
}
35 37

  
36
function drawRelations() {
38
export function drawRelations() {
37 39
  var arr = getRelationsArray();
38 40
  $.each(arr, function(index_issue, element_issue) {
39 41
    var issue_from = $("#task-todo-issue-" + element_issue["issue_from"]);
......
102 104
  });
103 105
}
104 106

  
105
function getProgressLinesArray() {
107
export function getProgressLinesArray() {
106 108
  var arr = new Array();
107 109
  var today_left = $('#today_line').position().left;
108 110
  arr.push({left: today_left, top: 0});
......
144 146
  return arr;
145 147
}
146 148

  
147
function drawGanttProgressLines() {
149
export function drawGanttProgressLines() {
148 150
  var arr = getProgressLinesArray();
149 151
  var color = $("#today_line")
150 152
                    .css("border-left-color");
......
163 165
  }
164 166
}
165 167

  
166
function drawSelectedColumns(){
168
export function drawSelectedColumns(){
167 169
  if ($("#draw_selected_columns").prop('checked')) {
168 170
    if(isMobile()) {
169 171
      $('td.gantt_selected_column').each(function(i) {
......
194 196
  }
195 197
}
196 198

  
197
function drawGanttHandler() {
199
export function drawGanttHandler() {
198 200
  var folder = document.getElementById('gantt_draw_area');
199 201
  if(draw_gantt != null)
200 202
    draw_gantt.clear();
......
209 211
  $('#content').addClass('gantt_content');
210 212
}
211 213

  
212
function resizableSubjectColumn(){
214
export function resizableSubjectColumn(){
213 215
  $('.issue-subject, .project-name, .version-name').each(function(){
214 216
    $(this).width($(".gantt_subjects_column").width()-$(this).position().left);
215 217
  });
......
230 232
  };
231 233
}
232 234

  
233
ganttEntryClick = function(e){
235
export function ganttEntryClick(e) {
234 236
  var icon_expander = e.target;
235 237
  var subject = $(icon_expander.parentElement);
236 238
  var subject_left = parseInt(subject.css('left')) + parseInt(icon_expander.offsetWidth);
......
296 298
  drawGanttHandler();
297 299
};
298 300

  
299
function disable_unavailable_columns(unavailable_columns) {
301
export function disable_unavailable_columns(unavailable_columns) {
300 302
  $.each(unavailable_columns, function (index, value) {
301 303
    $('#available_c, #selected_c').children("[value='" + value + "']").prop('disabled', true);
302 304
  });
app/assets/javascripts/jstoolbar/jstoolbar.js
21 21
*/
22 22

  
23 23
/* Modified by JP LANG for textile formatting */
24
let lastJstPreviewed = null;
25
const isMac = Boolean(navigator.platform.toLowerCase().match(/mac/));
24
export let lastJstPreviewed = null;
25
export const isMac = Boolean(navigator.platform.toLowerCase().match(/mac/));
26 26

  
27
function jsToolBar(textarea) {
27
export function jsToolBar(textarea) {
28 28
  if (!document.createElement) { return; }
29 29

  
30 30
  if (!textarea) { return; }
......
101 101
                       // de raccourcis vers les éléments DOM correspondants aux outils.
102 102
}
103 103

  
104
function jsTab(name, selected) {
104
export function jsTab(name, selected) {
105 105
  selected = selected || false;
106 106
  if(typeof jsToolBar.strings == 'undefined') {
107 107
    var tabName = name || null;
......
122 122

  
123 123
  return tab;
124 124
}
125
function jsButton(title, fn, scope, className) {
125
export function jsButton(title, fn, scope, className) {
126 126
  if(typeof jsToolBar.strings == 'undefined') {
127 127
    this.title = title || null;
128 128
  } else {
......
155 155
  return button;
156 156
}
157 157

  
158
function jsSpace(id) {
158
export function jsSpace(id) {
159 159
  this.id = id || null;
160 160
  this.width = null;
161 161
}
......
169 169
  return span;
170 170
}
171 171

  
172
function jsCombo(title, options, scope, fn, className) {
172
export function jsCombo(title, options, scope, fn, className) {
173 173
  this.title = title || null;
174 174
  this.options = options || null;
175 175
  this.scope = scope || null;
......
566 566
  }
567 567
});
568 568

  
569
function isToogleEditPreviewShortcut(e) {
569
export function isToogleEditPreviewShortcut(e) {
570 570
  if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === 'p') {
571 571
    return true;
572 572
  } else {
573 573
    return false;
574 574
  }
575 575
}
576
function isModifierKey(e) {
576
export function isModifierKey(e) {
577 577
  if (isMac && e.metaKey) {
578 578
    return true;
579 579
  } else if (!isMac && e.ctrlKey) {
app/assets/javascripts/project_identifier.js
3 3

  
4 4
// Automatic project identifier generation
5 5

  
6
function generateProjectIdentifier(identifier, maxlength) {
6
export function generateProjectIdentifier(identifier, maxlength) {
7 7
  var diacriticsMap = [
8 8
    {'base':'a', 'letters':/[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/g},
9 9
    {'base':'aa','letters':/[\uA733\uA732]/g},
......
61 61
  return identifier;
62 62
}
63 63

  
64
function autoFillProjectIdentifier() {
64
export function autoFillProjectIdentifier() {
65 65
  var locked = ($('#project_identifier').val() != '');
66 66
  var maxlength = parseInt($('#project_identifier').attr('maxlength'));
67 67

  
app/assets/javascripts/responsive.js
1
import $ from "jquery";
1 2
// generic layout specific responsive stuff goes here
2 3

  
3
function openFlyout() {
4
export function openFlyout() {
4 5
  $('html').addClass('flyout-is-active');
5 6
  $('#wrapper2').on('click', function(e){
6 7
    e.preventDefault();
......
9 10
  });
10 11
}
11 12

  
12
function closeFlyout() {
13
export function closeFlyout() {
13 14
  $('html').removeClass('flyout-is-active');
14 15
  $('#wrapper2').off('click');
15 16
}
16 17

  
17 18

  
18
function isMobile() {
19
export function isMobile() {
19 20
  return $('.js-flyout-menu-toggle-button').is(":visible");
20 21
}
21 22

  
22
function setupFlyout() {
23
export function setupFlyout() {
23 24
  var mobileInit = false,
24 25
    desktopInit = false;
25 26

  
app/assets/javascripts/revision_graph.js
1 1
/* Redmine - project management software
2 2
   Copyright (C) 2006-2021  Jean-Philippe Lang */
3 3

  
4
var revisionGraph = null;
4
import Raphael from 'raphael';
5 5

  
6
function drawRevisionGraph(holder, commits_hash, graph_space) {
6
export var revisionGraph = null;
7

  
8
export function drawRevisionGraph(holder, commits_hash, graph_space) {
7 9
    var XSTEP = 20,
8 10
        CIRCLE_INROW_OFFSET = 10;
9 11
    var commits_by_scmid = commits_hash,
app/assets/stylesheets/application.css
1
/*
2
"@/" in url is an alias for "public/". See: "https://webpack.js.org/configuration/resolve/#resolvealias"
3
*/
4

  
1 5
html, body { min-height: 100vh; }
2 6
html {overflow-y:scroll;}
3 7
body { font-family: Verdana, sans-serif; font-size: 12px; color:#333; margin: 0; padding: 0; min-width: 900px; }
......
127 131
#login-form input[type=text], #login-form input[type=password], #login-form input[type=submit] {display: block; width: 100%;}
128 132
#login-form input[type=text], #login-form input[type=password] {margin-bottom: 15px;}
129 133
#login-form a.lost_password {float:right; font-weight:normal;}
130
#login-form input#openid_url {background:#fff url(../images/openid-bg.gif) no-repeat 4px 50%; padding-left:24px !important;}
134
#login-form input#openid_url {background:#fff url(@/images/openid-bg.gif) no-repeat 4px 50%; padding-left:24px !important;}
131 135
#login-form h3 {text-align: center;}
132 136

  
133 137
div.modal { border-radius:5px; background:#fff; z-index:50; padding:4px;}
......
235 239
  margin:0 !important;
236 240
  vertical-align:middle;
237 241
  color:#555;
238
  background:#fff url(../images/arrow_down.png) no-repeat 97% 50%;
242
  background:#fff url(@/images/arrow_down.png) no-repeat 97% 50%;
239 243
}
240
#project-jump .drdn.expanded .drdn-trigger {background-image:url(../images/arrow_up.png);}
244
#project-jump .drdn.expanded .drdn-trigger {background-image:url(@/images/arrow_up.png);}
241 245
#project-jump .drdn-content {width:280px;}
242 246
#project-jump .drdn-items>* {color:#555 !important;}
243 247
#project-jump .drdn-items>a:hover {background-color:#759FCF; color:#fff !important;}
......
281 285
table.issues td.block_column span {font-weight: bold; display: block; margin-bottom: 4px;}
282 286
table.issues td.block_column pre {white-space:normal;}
283 287

  
284
tr.idnt td.subject, tr.idnt td.name {background: url(../images/arrow_right.png) no-repeat 2px 50%;}
288
tr.idnt td.subject, tr.idnt td.name {background: url(@/images/arrow_right.png) no-repeat 2px 50%;}
285 289
tr.idnt-1 td.subject, tr.idnt-1 td.name {padding-left: 24px; background-position: 8px 50%;}
286 290
tr.idnt-2 td.subject, tr.idnt-2 td.name {padding-left: 40px; background-position: 24px 50%;}
287 291
tr.idnt-3 td.subject, tr.idnt-3 td.name {padding-left: 56px; background-position: 40px 50%;}
......
385 389

  
386 390
a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
387 391

  
388
table.boards a.board { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
392
table.boards a.board { background: url(@/images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
389 393
table.boards td.last-message {text-align:left;font-size:80%;}
390 394

  
391 395
div.table-list.boards .table-list-cell.name {width: 30%;}
......
428 432
span#watchers_inputs {overflow:auto; display:block;}
429 433
span.search_for_watchers {display:block;}
430 434
span.search_for_watchers, span.add_attachment {font-size:80%; line-height:2.5em;}
431
span.add_attachment a {padding-left:16px; background: url(../images/bullet_add.png) no-repeat 0 50%; }
435
span.add_attachment a {padding-left:16px; background: url(@/images/bullet_add.png) no-repeat 0 50%; }
432 436

  
433 437
input:disabled, select:disabled, textarea:disabled {
434 438
  cursor: not-allowed;
......
480 484
  -o-appearance: none;
481 485
  appearance: none;
482 486
  background-color: #fff;
483
  background-image: url(../images/arrow_down.png);
487
  background-image: url(@/images/arrow_down.png);
484 488
  background-repeat: no-repeat;
485 489
  background-position: calc(100% - 7px) 50%;
486 490
  padding-right: 20px;
......
706 710
}
707 711
#projects-index a.icon-user, a.icon-bookmarked-project {padding-left:0; padding-right:20px; background-position:98% 50%;}
708 712
#projects-index a.icon-user.icon-bookmarked-project {
709
  background-image: url(../images/tag_blue.png), url(../images/user.png);
713
  background-image: url(@/images/tag_blue.png), url(@/images/user.png);
710 714
  background-position: bottom 0px right 0px, bottom 0px right 20px;
711 715
  padding-right: 40px;
712 716
  padding-top: 4px;
......
810 814
.tabular input, .tabular select {max-width:95%}
811 815
.tabular textarea {width:95%; resize:vertical;}
812 816
input#twofa_code, img#twofa_code { width: 140px; }
813
ul.twofa_backup_codes { list-style-type: none; padding: 0; display: inline-block; columns: 14em 2;}
814
ul.twofa_backup_codes code { font-size: 16px; line-height: 2em }
817
ul.twofa_backup_codes { list-style-type: none; padding: 0; display: inline-block; }
818
ul.twofa_backup_codes li { float: left; }
819
ul.twofa_backup_codes li:nth-child(odd) { float: left; clear: left; padding-right: 4em; }
815 820

  
816 821
.tabular label{
817 822
  font-weight: bold;
......
925 930
.attachments_fields input.filename, #existing-attachments .filename {border:0; width:250px; color:#555; background-color:inherit; }
926 931
.tabular input.filename {max-width:75% !important;}
927 932
.attachments_fields input.filename {height:1.8em;}
928
.attachments_fields .ajax-waiting input.filename {background:url(../images/hourglass.png) no-repeat 0px 50%;}
929
.attachments_fields .ajax-loading input.filename {background:url(../images/loading.gif) no-repeat 0px 50%;}
933
.attachments_fields .ajax-waiting input.filename {background:url(@/images/hourglass.png) no-repeat 0px 50%;}
934
.attachments_fields .ajax-loading input.filename {background:url(@/images/loading.gif) no-repeat 0px 50%;}
930 935
.attachments_fields div.ui-progressbar { width: 100px; height:14px; margin: 2px 0 -5px 8px; display: inline-block; }
931 936

  
932 937
a.remove-upload:hover {text-decoration:none !important;}
......
946 951
p.other-formats { text-align: right; font-size:0.9em; color: #666; }
947 952
.other-formats span + span:before { content: "| "; }
948 953

  
949
a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
954
a.atom { background: url(@/images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
950 955

  
951 956
em.info {font-style:normal;display:block;font-size:90%;color:#888;}
952
em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;}
957
em.info.error {padding-left:20px; background:url(@/images/exclamation.png) no-repeat 0 50%;}
953 958

  
954 959
textarea.text_cf {width:95%; resize:vertical;}
955 960
input.string_cf, input.link_cf {width:95%;}
......
970 975
.roles-selection label {display:inline-block; width:210px;}
971 976

  
972 977
input.autocomplete {
973
  background: #fff url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px !important;
978
  background: #fff url(@/images/magnifier.png) no-repeat 2px 50%; padding-left:20px !important;
974 979
}
975 980
input.autocomplete.ajax-loading {
976
  background-image: url(../images/loading.gif);
981
  background-image: url(@/images/loading.gif);
977 982
}
978 983

  
979 984
.role-visibility {padding-left:2em;}
......
1013 1018
div.flash {margin-top: 8px;}
1014 1019

  
1015 1020
div.flash.error, #errorExplanation {
1016
  background: url(../images/exclamation.png) 8px 50% no-repeat;
1021
  background: url(@/images/exclamation.png) 8px 50% no-repeat;
1017 1022
  background-color: #ffe3e3;
1018 1023
  border-color: #d88;
1019 1024
  color: #880000;
1020 1025
}
1021 1026

  
1022 1027
div.flash.notice {
1023
  background: url(../images/true.png) 8px 5px no-repeat;
1028
  background: url(@/images/true.png) 8px 5px no-repeat;
1024 1029
  background-color: #dfffdf;
1025 1030
  border-color: #9fcf9f;
1026 1031
  color: #005f00;
1027 1032
}
1028 1033

  
1029 1034
div.flash.warning, .conflict {
1030
  background: url(../images/warning.png) 8px 5px no-repeat;
1035
  background: url(@/images/warning.png) 8px 5px no-repeat;
1031 1036
  background-color: #F3EDD1;
1032 1037
  border-color: #eadbbc;
1033 1038
  color: #A6750C;
......
1066 1071
#ajax-indicator span {
1067 1072
background-position: 0% 40%;
1068 1073
background-repeat: no-repeat;
1069
background-image: url(../images/loading.gif);
1074
background-image: url(@/images/loading.gif);
1070 1075
padding-left: 26px;
1071 1076
vertical-align: bottom;
1072 1077
}
......
1084 1089
table.cal td.today {background:#ffffdd;}
1085 1090
table.cal td.today p.day-num {font-weight: bold;}
1086 1091
table.cal td.nwday:not(.odd) {background-color:#f1f1f1;}
1087
table.cal .starting a.issue, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
1088
table.cal .ending a.issue, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
1089
table.cal .starting.ending a.issue, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
1092
table.cal .starting a.issue, p.cal.legend .starting {background: url(@/images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
1093
table.cal .ending a.issue, p.cal.legend .ending {background: url(@/images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
1094
table.cal .starting.ending a.issue, p.cal.legend .starting.ending {background: url(@/images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
1090 1095
p.cal.legend span {display:block;}
1091 1096

  
1092 1097
/***** Tooltips ******/
......
1203 1208

  
1204 1209
button.tab-left {
1205 1210
  right: 28px;
1206
  background: #eeeeee url(../images/arrow_left.png) no-repeat 50% 50%;
1211
  background: #eeeeee url(@/images/arrow_left.png) no-repeat 50% 50%;
1207 1212
  border-top-left-radius:3px;
1208 1213
}
1209 1214

  
1210 1215
button.tab-right {
1211 1216
  right: 4px;
1212
  background: #eeeeee url(../images/arrow_right.png) no-repeat 50% 50%;
1217
  background: #eeeeee url(@/images/arrow_right.png) no-repeat 50% 50%;
1213 1218
  border-top-right-radius:3px;
1214 1219
}
1215 1220

  
......
1249 1254
  background-position: 0% 60%;
1250 1255
  background-repeat: no-repeat;
1251 1256
  padding-left: 12px;
1252
  background-image: url(../images/external.png);
1257
  background-image: url(@/images/external.png);
1253 1258
}
1254 1259

  
1255 1260
div.wiki a {word-wrap: break-word;}
......
1421 1426
.task.label {width:100%;}
1422 1427
.task.label.project, .task.label.version { font-weight: bold; }
1423 1428

  
1424
.task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
1425
.task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }
1426
.task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
1429
.task_late { background:#f66 url(@/images/task_late.png); border: 1px solid #f66; }
1430
.task_done { background:#00c600 url(@/images/task_done.png); border: 1px solid #00c600; }
1431
.task_todo { background:#aaa url(@/images/task_todo.png); border: 1px solid #aaa; }
1427 1432

  
1428 1433
.task_todo.parent { background: #888; border: 1px solid #888; height: 3px;}
1429 1434
.task_late.parent, .task_done.parent { height: 3px;}
1430
.task.parent.marker.starting  { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; left: 0px; top: -1px;}
1431
.task.parent.marker.ending { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; right: 0px; top: -1px;}
1435
.task.parent.marker.starting  { position: absolute; background: url(@/images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; left: 0px; top: -1px;}
1436
.task.parent.marker.ending { position: absolute; background: url(@/images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; right: 0px; top: -1px;}
1432 1437

  
1433
.version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
1434
.version.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
1435
.version.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
1436
.version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
1438
.version.task_late { background:#f66 url(@/images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
1439
.version.task_done { background:#00c600 url(@/images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
1440
.version.task_todo { background:#fff url(@/images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
1441
.version.marker { background-image:url(@/images/version_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
1437 1442

  
1438
.project.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
1439
.project.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
1440
.project.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
1441
.project.marker { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
1443
.project.task_late { background:#f66 url(@/images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
1444
.project.task_done { background:#00c600 url(@/images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
1445
.project.task_todo { background:#fff url(@/images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
1446
.project.marker { background-image:url(@/images/project_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
1442 1447

  
1443 1448
.version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
1444 1449
.version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
......
1513 1518
  content: "\a0";
1514 1519
}
1515 1520

  
1516
.icon-add { background-image: url(../images/add.png); }
1517
.icon-edit { background-image: url(../images/edit.png); }
1518
.icon-copy { background-image: url(../images/copy.png); }
1519
.icon-duplicate { background-image: url(../images/duplicate.png); }
1520
.icon-del { background-image: url(../images/delete.png); }
1521
.icon-move { background-image: url(../images/move.png); }
1522
.icon-save { background-image: url(../images/save.png); }
1523
.icon-download { background-image: url(../images/download.png); }
1524
.icon-cancel { background-image: url(../images/cancel.png); }
1525
.icon-multiple { background-image: url(../images/table_multiple.png); }
1526
.icon-folder { background-image: url(../images/folder.png); }
1527
.open .icon-folder { background-image: url(../images/folder_open.png); }
1528
.icon-package { background-image: url(../images/package.png); }
1529
.icon-user { background-image: url(../images/user.png); }
1530
.icon-project, .icon-projects { background-image: url(../images/projects.png); }
1531
.icon-help { background-image: url(../images/help.png); }
1532
.icon-attachment  { background-image: url(../images/attachment.png); }
1533
.icon-history  { background-image: url(../images/history.png); }
1534
.icon-time-entry, .icon-time  { background-image: url(../images/time.png); }
1535
.icon-time-add  { background-image: url(../images/time_add.png); }
1536
.icon-stats  { background-image: url(../images/stats.png); }
1537
.icon-warning  { background-image: url(../images/warning.png); }
1538
.icon-error { background-image: url(../images/exclamation.png); }
1539
.icon-fav  { background-image: url(../images/fav.png); }
1540
.icon-fav-off  { background-image: url(../images/fav_off.png); }
1541
.icon-reload  { background-image: url(../images/reload.png); }
1542
.icon-lock, .icon-locked  { background-image: url(../images/locked.png); }
1543
.icon-unlock  { background-image: url(../images/unlock.png); }
1544
.icon-checked  { background-image: url(../images/toggle_check.png); }
1545
.icon-report  { background-image: url(../images/report.png); }
1546
.icon-comment, .icon-comments  { background-image: url(../images/comment.png); }
1547
.icon-summary  { background-image: url(../images/lightning.png); }
1548
.icon-server-authentication { background-image: url(../images/server_key.png); }
1549
.icon-issue { background-image: url(../images/ticket.png); }
1550
.icon-zoom-in { background-image: url(../images/zoom_in.png); }
1551
.icon-zoom-out { background-image: url(../images/zoom_out.png); }
1552
.icon-magnifier { background-image: url(../images/magnifier.png); }
1553
.icon-passwd { background-image: url(../images/textfield_key.png); }
1554
.icon-arrow-right, .icon-test, .icon-sticky { background-image: url(../images/bullet_go.png); }
1555
.icon-email { background-image: url(../images/email.png); }
1556
.icon-email-disabled { background-image: url(../images/email_disabled.png); }
1557
.icon-email-add { background-image: url(../images/email_add.png); }
1558
.icon-ok { background-image: url(../images/true.png); }
1559
.icon-not-ok { background-image: url(../images/false.png); }
1560
.icon-link-break { background-image: url(../images/link_break.png); }
1561
.icon-list { background-image: url(../images/text_list_bullets.png); }
1562
.icon-close { background-image: url(../images/close.png); }
1563
.icon-close:hover { background-image: url(../images/close_hl.png); }
1564
.icon-settings { background-image: url(../images/changeset.png); }
1565
.icon-group, .icon-groupnonmember, .icon-groupanonymous { background-image: url(../images/group.png); }
1566
.icon-roles { background-image: url(../images/database_key.png); }
1567
.icon-issue-edit { background-image: url(../images/ticket_edit.png); }
1568
.icon-workflows { background-image: url(../images/ticket_go.png); }
1569
.icon-custom-fields { background-image: url(../images/textfield.png); }
1570
.icon-plugins { background-image: url(../images/plugin.png); }
... This diff was truncated because it exceeds the maximum size that can be displayed.