<commit_before>var wav_1_minute = 'RFPT-WW13-20111229213002-540-60-KR8.wav';
var wav_15_minute = "RFPT-WW10A-2013-02-14T02.00.10-KR5.wav";
var URL = wav_1_minute;

var COLOUR_LUT = {
    k: "#0ff",
    m: "#f00",
    f: "#0f0",
    e: "#000"
};

var audio_context;

function parse_wav(raw){
    /*   https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ */
    var header = new DataView(raw, 0, 44);
    var data = raw.slice(44);
    if (header.getUint32(0, 0) != 0x52494646 || //'RIFF'
        header.getUint32(8, 0) != 0x57415645 || //'WAVE'
        header.getUint16(20, 1) != 1         || //format (pcm == 1)
        header.getUint32(40, 1) != data.byteLength  //length should match data
       ){
       message("I can't understand this file. Is it really a WAV?" +
               " (Continuing regardless...)");
       console.log(header.getUint32(0, 0), 0x52494646,
       header.getUint32(8, 0), 0x57415645,
       header.getUint16(16, 1), 1,
       header.getUint32(40, 1), data.byteLength);
    }
    var samplerate = header.getUint32(24, 1);
    /*if there are more than one channel, just silently grab the first one */
    var channels = header.getUint16(22, 1);
    var bits_per_sample = header.getUint16(34, 1);
    var samples, scale;
    if (bits_per_sample == 8){
        samples = new Int8Array(data);
        scale = 1.0 / 256;
    }
    else if (bits_per_sample == 16){
        samples = new Int16Array(data);
        scale = 1.0 / 32768;
    }
    var normalised_samples = new Float32Array(samples.length / channels);
    for (var i = 0; i < normalised_samples.length; i++){
        normalised_samples[i] = samples[i * channels] * scale;
    }
    return {
        audio: normalised_samples,
        samplerate: samplerate
    };
}

function fill_canvas(audio, samplerate, native_audio){
    var canvas = document.getElementById('fft');
    var context = canvas.getContext('2d');
    var width = canvas.width;
    var spacing = audio.length / width;
    var pixel2sec = native_audio.duration / width;
    var audio_source;
    var window_size = 1024;
    var fft = new FFT(window_size, samplerate);
    //context.fillRect(50, 25, 150, 100);
    var imgdata = context.createImageData(canvas.width, canvas.height);
    var pixels = imgdata.data;
    var i;
    var left, col;
    var mask_window = new Float32Array(window_size);
    var data_window = new Float32Array(window_size);
    var tau_norm = Math.PI * 2 / window_size;
    for (i = 0; i < window_size; i++){
        mask_window[i] = 0.5 - 0.5 * Math.cos(tau_norm * i);
    }

    for (left = 0, col = 0; left + window_size < audio.length; left += spacing, col++){
        var square_window = audio.subarray(left, left + window_size);
        for (i = 0; i < window_size; i++){
            data_window[i] = square_window[i] * mask_window[i];
        }
        fft.forward(data_window);
        var s = fft.spectrum;
        for (i = canvas.height - 1; i >= 0; i--){
            var o = ((canvas.height - i - 1) * width + col) * 4;
            var v = s[i] * s[i] + s[i + 1] * s[i + 1];
            pixels[o] = v * 3e8;
            pixels[o + 1] = Math.sqrt(v) * 1e5;
            pixels[o + 2] = Math.pow(v, 0.25) * 1e3;
            pixels[o + 3] = 255;
        }
        //console.log(col, v, s[200], spacing, pixels[o]);
    }
    context.putImageData(imgdata, 0, 0);
    function refill_background(){
        var data = context.getImageData(0, 0, canvas.width, canvas.height);
        var pix = data.data;
        for (i = 0; i < 4 * canvas.width * canvas.height; i += 4){
            if (pix[i] == 0 && pix[i + 1] == 0 && pix[i + 2] == 0){
                pix[i] = pixels[i];
                pix[i + 1] = pixels[i + 1];
                pix[i + 2] = pixels[i + 2];
            }
        }
        context.putImageData(data, 0, 0);
    }
    var drawing = 0;
    var playing_column = -1;
    var hidden_data;
    var playing_column_interval;
    var colour = COLOUR_LUT['m'];
    var colour_label = document.getElementById("colour-m");
    colour_label.style.background = "#fc0";
    function switch_colour(c){
        if (colour === '#000'){
            refill_background();
        }
        var new_colour = COLOUR_LUT[c];
        if (new_colour !== undefined){
            colour_label.style.background = "#fff";
            colour_label = document.getElementById("colour-" + c);
            colour_label.style.background = "#fc4";
            colour = new_colour;
        }
    }

    for (var c in COLOUR_LUT){
        var label = document.getElementById("colour-" + c);
        label.onclick = function(x){
            return function(){switch_colour(x);};
        }(c);
    }
    function advance_playing_line(){
        context.putImageData(hidden_data, playing_column, 0);
        playing_column++;
        if (playing_column >= canvas.width){
            playing_column = 0;
        }
        hidden_data = context.getImageData(playing_column, 0, 1, canvas.height);
        context.fillRect(playing_column, 0, 1, canvas.height);
    }
    function stop_playing(){
        if (audio_source !== undefined){
            audio_source.stop(0);
        }
        if (playing_column_interval !== undefined){
            window.clearInterval(playing_column_interval);
        }
        audio_source = undefined;
    }

    function start_playing_at_column(x){
        stop_playing();
        audio_source = audio_context.createBufferSource();
        audio_source.buffer = native_audio;
        audio_source.connect(audio_context.destination);
        audio_source.start(0, x * pixel2sec);
        if (hidden_data !== undefined && playing_column !== undefined){
            context.putImageData(hidden_data, playing_column, 0);
        }
        playing_column = x;
        context.fillStyle = "#ff3";
        hidden_data = context.getImageData(x, 0, 1, canvas.height);
        context.fillRect(x, 0, 1, canvas.height);
        playing_column_interval = window.setInterval(advance_playing_line,
                                                     pixel2sec * 1000);
        audio_source.onended = function(id){
            return function(){
                window.clearInterval(id);
            };
        }(playing_column_interval);
    }

    canvas.onclick = function(e){
        if (e.shiftKey){
            var x = e.pageX - this.offsetLeft;
            start_playing_at_column(x);
        }
    };

    function draw_to(x, y, colour){
        context.lineTo(x, y);
        context.stroke();
    }
    canvas.onmousedown = function(e){
        if (! e.shiftKey){
            drawing = 1;
            var x = e.pageX - this.offsetLeft;
            var y = e.pageY - this.offsetTop;
            context.beginPath();
            context.lineWidth = 5;
	    context.lineJoin = 'round';
            context.strokeStyle = colour;
            context.moveTo(x, y);
            draw_to(x, y);
        }
    };
    document.onmouseup = function(e){
        drawing = 0;
    };
    canvas.onmousemove = function(e){
        if (drawing){
            draw_to(e.pageX - this.offsetLeft,
                    e.pageY - this.offsetTop);
        }
    };
    document.onkeypress = function(e){
        var c = String.fromCharCode(e.charCode);
        if (COLOUR_LUT[c] !== undefined){
            switch_colour(c);
        }
        else if (c == ' '){
            if (audio_source === undefined){
                start_playing_at_column(playing_column);
            }
            else {
                stop_playing();
            }
        }
    };
}

function message(m){
    var el = document.getElementById('message');
    el.innerHTML += "
" +  m + "
";
}

function load_audio(url) {
    var AudioContext = window.AudioContext || window.webkitAudioContext;
    if (! AudioContext){
        message("Web API seems to be missing from this browser.
" +
               "It could almost work like that, but I can't be bothered" +
               " maintaining it. Expect errors. Sorry!");
    }
    /*audio_context is global*/
    audio_context = new AudioContext();
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.responseType = 'arraybuffer';
    request.onload = function(){
        var wav = request.response;
        audio_context.decodeAudioData(wav,
            function(audio) {
                var norm = parse_wav(wav);
                fill_canvas(norm.audio, norm.samplerate, audio);
            });
    };
    request.send();
}

function on_page_load() {
    if (document.location.protocol == 'file:'){
        message("Warning: this probably won't work from the local filesystem " +
                "(<tt>file://</tt> protocol), due to browser security settings. " +
                "
Use a local webserver, like webfsd.");
    }
    load_audio(URL);
}

window.addEventListener('load', on_page_load);
<commit_msg>Remove some confusion around FFT spectrum -- it is already magnitude<commit_after>var wav_1_minute = 'RFPT-WW13-20111229213002-540-60-KR8.wav';
var wav_15_minute = "RFPT-WW10A-2013-02-14T02.00.10-KR5.wav";
var URL = wav_1_minute;

var COLOUR_LUT = {
    k: "#0ff",
    m: "#f00",
    f: "#0f0",
    e: "#000"
};

var audio_context;

function parse_wav(raw){
    /*   https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ */
    var header = new DataView(raw, 0, 44);
    var data = raw.slice(44);
    if (header.getUint32(0, 0) != 0x52494646 || //'RIFF'
        header.getUint32(8, 0) != 0x57415645 || //'WAVE'
        header.getUint16(20, 1) != 1         || //format (pcm == 1)
        header.getUint32(40, 1) != data.byteLength  //length should match data
       ){
       message("I can't understand this file. Is it really a WAV?" +
               " (Continuing regardless...)");
       console.log(header.getUint32(0, 0), 0x52494646,
       header.getUint32(8, 0), 0x57415645,
       header.getUint16(16, 1), 1,
       header.getUint32(40, 1), data.byteLength);
    }
    var samplerate = header.getUint32(24, 1);
    /*if there are more than one channel, just silently grab the first one */
    var channels = header.getUint16(22, 1);
    var bits_per_sample = header.getUint16(34, 1);
    var samples, scale;
    if (bits_per_sample == 8){
        samples = new Int8Array(data);
        scale = 1.0 / 256;
    }
    else if (bits_per_sample == 16){
        samples = new Int16Array(data);
        scale = 1.0 / 32768;
    }
    var normalised_samples = new Float32Array(samples.length / channels);
    for (var i = 0; i < normalised_samples.length; i++){
        normalised_samples[i] = samples[i * channels] * scale;
    }
    return {
        audio: normalised_samples,
        samplerate: samplerate
    };
}

function fill_canvas(audio, samplerate, native_audio){
    var canvas = document.getElementById('fft');
    var context = canvas.getContext('2d');
    var width = canvas.width;
    var spacing = audio.length / width;
    var pixel2sec = native_audio.duration / width;
    var audio_source;
    var window_size = 1024;
    var fft = new FFT(window_size, samplerate);
    var imgdata = context.createImageData(canvas.width, canvas.height);
    var pixels = imgdata.data;
    var i;
    var left, col;
    var mask_window = new Float32Array(window_size);
    var data_window = new Float32Array(window_size);
    var tau_norm = Math.PI * 2 / window_size;
    for (i = 0; i < window_size; i++){
        mask_window[i] = 0.5 - 0.5 * Math.cos(tau_norm * i);
    }

    for (left = 0, col = 0; left + window_size < audio.length; left += spacing, col++){
        var square_window = audio.subarray(left, left + window_size);
        for (i = 0; i < window_size; i++){
            data_window[i] = square_window[i] * mask_window[i];
        }
        fft.forward(data_window);
        var s = fft.spectrum; /*power spectrum, normalised somehow*/
        for (i = canvas.height - 1; i >= 0; i --){
            var o = ((canvas.height - i - 1) * width + col) * 4;
            var v = s[i];
            pixels[o] = v * v * 3e8;
            pixels[o + 1] = v * 1e5;
            pixels[o + 2] = Math.sqrt(v) * 3e3;
            pixels[o + 3] = 255;
        }
    }
    context.putImageData(imgdata, 0, 0);
    function refill_background(){
        var data = context.getImageData(0, 0, canvas.width, canvas.height);
        var pix = data.data;
        for (i = 0; i < 4 * canvas.width * canvas.height; i += 4){
            if (pix[i] == 0 && pix[i + 1] == 0 && pix[i + 2] == 0){
                pix[i] = pixels[i];
                pix[i + 1] = pixels[i + 1];
                pix[i + 2] = pixels[i + 2];
            }
        }
        context.putImageData(data, 0, 0);
    }
    var drawing = 0;
    var playing_column = -1;
    var hidden_data;
    var playing_column_interval;
    var colour = COLOUR_LUT['m'];
    var colour_label = document.getElementById("colour-m");
    colour_label.style.background = "#fc0";
    function switch_colour(c){
        if (colour === '#000'){
            refill_background();
        }
        var new_colour = COLOUR_LUT[c];
        if (new_colour !== undefined){
            colour_label.style.background = "#fff";
            colour_label = document.getElementById("colour-" + c);
            colour_label.style.background = "#fc4";
            colour = new_colour;
        }
    }

    for (var c in COLOUR_LUT){
        var label = document.getElementById("colour-" + c);
        label.onclick = function(x){
            return function(){switch_colour(x);};
        }(c);
    }
    function advance_playing_line(){
        context.putImageData(hidden_data, playing_column, 0);
        playing_column++;
        if (playing_column >= canvas.width){
            playing_column = 0;
        }
        hidden_data = context.getImageData(playing_column, 0, 1, canvas.height);
        context.fillRect(playing_column, 0, 1, canvas.height);
    }
    function stop_playing(){
        if (audio_source !== undefined){
            audio_source.stop(0);
        }
        audio_source = undefined;
    }

    function start_playing_at_column(x){
        stop_playing();
        audio_source = audio_context.createBufferSource();
        audio_source.buffer = native_audio;
        audio_source.connect(audio_context.destination);
        audio_source.start(0, x * pixel2sec);
        if (hidden_data !== undefined && playing_column !== undefined){
            context.putImageData(hidden_data, playing_column, 0);
        }
        playing_column = x;
        context.fillStyle = "#ff3";
        hidden_data = context.getImageData(x, 0, 1, canvas.height);
        context.fillRect(x, 0, 1, canvas.height);
        playing_column_interval = window.setInterval(advance_playing_line,
                                                     pixel2sec * 1000);
        audio_source.onended = function(id){
            return function(){
                window.clearInterval(id);
            };
        }(playing_column_interval);
    }

    canvas.onclick = function(e){
        if (e.shiftKey){
            var x = e.pageX - this.offsetLeft;
            start_playing_at_column(x);
        }
    };

    function draw_to(x, y, colour){
        context.lineTo(x, y);
        context.stroke();
    }
    canvas.onmousedown = function(e){
        if (! e.shiftKey){
            drawing = 1;
            var x = e.pageX - this.offsetLeft;
            var y = e.pageY - this.offsetTop;
            context.beginPath();
            context.lineWidth = 5;
	    context.lineJoin = 'round';
            context.strokeStyle = colour;
            context.moveTo(x, y);
            draw_to(x, y);
        }
    };
    document.onmouseup = function(e){
        drawing = 0;
    };
    canvas.onmousemove = function(e){
        if (drawing){
            draw_to(e.pageX - this.offsetLeft,
                    e.pageY - this.offsetTop);
        }
    };
    document.onkeypress = function(e){
        var c = String.fromCharCode(e.charCode);
        if (COLOUR_LUT[c] !== undefined){
            switch_colour(c);
        }
        else if (c == ' '){
            if (audio_source === undefined){
                start_playing_at_column(playing_column);
            }
            else {
                stop_playing();
            }
        }
    };
}

function message(m){
    var el = document.getElementById('message');
    el.innerHTML += "
" +  m + "
";
}

function load_audio(url) {
    var AudioContext = window.AudioContext || window.webkitAudioContext;
    if (! AudioContext){
        message("Web API seems to be missing from this browser.
" +
               "It could almost work like that, but I can't be bothered" +
               " maintaining it. Expect errors. Sorry!");
    }
    /*audio_context is global*/
    audio_context = new AudioContext();
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.responseType = 'arraybuffer';
    request.onload = function(){
        var wav = request.response;
        audio_context.decodeAudioData(wav,
            function(audio) {
                var norm = parse_wav(wav);
                fill_canvas(norm.audio, norm.samplerate, audio);
            });
    };
    request.send();
}

function on_page_load() {
    if (document.location.protocol == 'file:'){
        message("Warning: this probably won't work from the local filesystem " +
                "(<tt>file://</tt> protocol), due to browser security settings. " +
                "
Use a local webserver, like webfsd.");
    }
    load_audio(URL);
}

window.addEventListener('load', on_page_load);
<|endoftext|>


<commit_before>import prefixProperties from './prefixProps'
import capitalizeString from './utils/capitalizeString'
import assign from './utils/assign'

import calc from './plugins/calc'
import cursor from './plugins/cursor'
import flex from './plugins/flex'
import sizing from './plugins/sizing'
import gradient from './plugins/gradient'
import transition from './plugins/transition'
// special flexbox specifications
import flexboxIE from './plugins/flexboxIE'
import flexboxOld from './plugins/flexboxOld'

const plugins = [
  calc,
  cursor,
  sizing,
  gradient,
  transition,
  flexboxIE,
  flexboxOld,
  flex
]

/**
 * Returns a prefixed version of the style object using all vendor prefixes
 * @param {Object} styles - Style object that gets prefixed properties added
 * @returns {Object} - Style object with prefixed properties and values
 */
export default function prefixAll(styles) {
  return Object.keys(styles).reduce((prefixedStyles, property) => {
    const value = styles[property]
    if (value instanceof Object && !Array.isArray(value)) {
      // recurse through nested style objects
      prefixedStyles[property] = prefixAll(value)
    } else if (Array.isArray(value)) {
      // prefix fallback arrays
      assign(prefixedStyles, prefixArray(property, value));
    } else {
      Object.keys(prefixProperties).forEach(prefix => {
        const properties = prefixProperties[prefix]
        // add prefixes if needed
        if (properties[property]) {
          prefixedStyles[prefix + capitalizeString(property)] = value
        }
      })
      // resolve every special plugins
      plugins.forEach(plugin => assign(prefixedStyles, plugin(property, value)))
    }

    return prefixedStyles
  }, styles)
}

function prefixArray(property, valueArray) {
  let result = {};
  valueArray.forEach(value => {
    plugins.forEach(plugin => {
      let prefixed = plugin(property, value);
      if (prefixed) {
        Object.keys(prefixed).forEach(prop => {
          const entry = prefixed[prop];
          result[prop] = result[prop] ? mergeValues(result[prop], entry) : entry;
        });
      }
    });
    if (!result[property]) {
      result[property] = value;
    }
  });
  return result;
}

function mergeValues(existing, toMerge) {
  let merged = existing;
  let valuesToMerge = Array.isArray(toMerge) ? toMerge : [toMerge];
  valuesToMerge.forEach(value => {
    if (Array.isArray(merged) && merged.indexOf(value) === -1) {
      merged.push(value);
    } else if (merged !== value) {
      merged = [merged, value];
    }
  });
  return merged;
}
<commit_msg>Remove semicolons<commit_after>import prefixProperties from './prefixProps'
import capitalizeString from './utils/capitalizeString'
import assign from './utils/assign'

import calc from './plugins/calc'
import cursor from './plugins/cursor'
import flex from './plugins/flex'
import sizing from './plugins/sizing'
import gradient from './plugins/gradient'
import transition from './plugins/transition'
// special flexbox specifications
import flexboxIE from './plugins/flexboxIE'
import flexboxOld from './plugins/flexboxOld'

const plugins = [
  calc,
  cursor,
  sizing,
  gradient,
  transition,
  flexboxIE,
  flexboxOld,
  flex
]

/**
 * Returns a prefixed version of the style object using all vendor prefixes
 * @param {Object} styles - Style object that gets prefixed properties added
 * @returns {Object} - Style object with prefixed properties and values
 */
export default function prefixAll(styles) {
  return Object.keys(styles).reduce((prefixedStyles, property) => {
    const value = styles[property]
    if (value instanceof Object && !Array.isArray(value)) {
      // recurse through nested style objects
      prefixedStyles[property] = prefixAll(value)
    } else if (Array.isArray(value)) {
      // prefix fallback arrays
      assign(prefixedStyles, prefixArray(property, value))
    } else {
      Object.keys(prefixProperties).forEach(prefix => {
        const properties = prefixProperties[prefix]
        // add prefixes if needed
        if (properties[property]) {
          prefixedStyles[prefix + capitalizeString(property)] = value
        }
      })
      // resolve every special plugins
      plugins.forEach(plugin => assign(prefixedStyles, plugin(property, value)))
    }

    return prefixedStyles
  }, styles)
}

function prefixArray(property, valueArray) {
  let result = {}
  valueArray.forEach(value => {
    plugins.forEach(plugin => {
      let prefixed = plugin(property, value)
      if (prefixed) {
        Object.keys(prefixed).forEach(prop => {
          const entry = prefixed[prop]
          result[prop] = result[prop] ? mergeValues(result[prop], entry) : entry
        })
      }
    })
    if (!result[property]) {
      result[property] = value
    }
  })
  return result
}

function mergeValues(existing, toMerge) {
  let merged = existing
  let valuesToMerge = Array.isArray(toMerge) ? toMerge : [toMerge]
  valuesToMerge.forEach(value => {
    if (Array.isArray(merged) && merged.indexOf(value) === -1) {
      merged.push(value)
    } else if (merged !== value) {
      merged = [merged, value]
    }
  })
  return merged
}
<|endoftext|>


<commit_before>function BPListController($scope, $http) {
    $scope.pressures = [];

    $scope.newPressure = {
        datetaken: new Date().getTime(),
        timeofday: 'Morning',
        systolic: null,
        diastolic: null,
        pulse: null
    };

    $scope.setPressures = function(pressures) {
        $scope.pressures = pressures;
    };

    $scope.addNewPressure = function() {
        $http.post('/pressure.json', $scope.newPressure).success(function(data) {
            if(data.pressure) {
                $scope.pressures.push(data.pressure);
                $scope.newPressure.systolic = null;
                $scope.newPressure.diastolic = null;
                $scope.newPressure.pulse = null;
            } else {
                alert(JSON.stringify(data));
            }
        });
    };

    $scope.timeOptions = [
        { name: 'Morning', value: 'Morning' },
        { name: 'Evening', value: 'Evening' }
    ];

    $scope.pressure = { timeofday :
        $scope.timeOptions[0].value};

    $scope.selection = function() {
        console.log($scope.pressure);
    };

    $scope.getClass = function(pressure) {
        if(pressure.systolic < 120) {
            return 'success';
        } else if(pressure.systolic > 120 && pressure.systolic <= 139) {
                return 'warning';
        } else if(pressure.systolic >= 140 && pressure.systolic <=159) {
                return 'stage1';
        } else {
            return 'danger';
        }
    };

}
<commit_msg>corrected row color range<commit_after>function BPListController($scope, $http) {
    $scope.pressures = [];

    $scope.newPressure = {
        datetaken: new Date().getTime(),
        timeofday: 'Morning',
        systolic: null,
        diastolic: null,
        pulse: null
    };

    $scope.setPressures = function(pressures) {
        $scope.pressures = pressures;
    };

    $scope.addNewPressure = function() {
        $http.post('/pressure.json', $scope.newPressure).success(function(data) {
            if(data.pressure) {
                $scope.pressures.push(data.pressure);
                $scope.newPressure.systolic = null;
                $scope.newPressure.diastolic = null;
                $scope.newPressure.pulse = null;
            } else {
                alert(JSON.stringify(data));
            }
        });
    };

    $scope.timeOptions = [
        { name: 'Morning', value: 'Morning' },
        { name: 'Evening', value: 'Evening' }
    ];

    $scope.pressure = { timeofday :
        $scope.timeOptions[0].value};

    $scope.selection = function() {
        console.log($scope.pressure);
    };

    $scope.getClass = function(pressure) {
        if(pressure.systolic <= 120 && pressure.diastolic < 80) {
            return 'success';
        } else if(pressure.systolic >= 140 && pressure.systolic <=159 || pressure.diastolic >= 90 && pressure.diastolic <= 99) {
                return 'stage1';
        } else if(pressure.systolic > 120 && pressure.systolic <= 139 || pressure.diastolic >= 80 && pressure.diastolic <= 89) {
                return 'warning';
        } else {
            return 'danger';
        }

        console.log(pressure.systolic);
    };

}
<|endoftext|>


<commit_before>TextMorph.subclass("TextMorphRequiringExplicitAcceptance", {
  initialize: function($super, accessors) {
    $super();
    this.dontNotifyUntilTheActualModelChanges = true;
    this.connectModel({model: this, getText: "getSavedText", setText: "setSavedText"});
    this.acceptInput = true;
    this.setFill(this.backgroundColorWhenWritable || null),
    this.changed();
    this.setSavedText(this.textString); // aaa - what is this for?
    this.justAcceptedOrCancelled();
    if (accessors) { this._accessors = accessors; }
    this.refreshText();
  },

  defaultBounds: pt(5, 10).extent(pt(140, 20)),

  openForDragAndDrop: false,

  suppressGrabbing: true,

  wrap: lively.Text.WrapStyle.Shrink,

  getSavedText: function( )  {
    if (this._accessors) {
      return this._accessors.get();
    } else {
      return this.savedTextString;
    }
  },

  setSavedText: function(t)  {
    if (this._accessors) {
      this._accessors.set(t);
    } else {
      this.savedTextString = t;
      if (this.notifier) {this.notifier.notifyAllObservers();}
    }
  },

  setTextString: function($super, replacement, delayComposition, justMoreTyping) {
    var x = $super(replacement, delayComposition, justMoreTyping);
    this.updateLayoutIfNecessary();
    return x;
  },

  updateLayoutIfNecessary: function() {
    this.adjustForNewBounds(); // makes the focus halo look right   // aaa should probably be outside the conditional, or even in the Core code
    this.minimumExtentMayHaveChanged();
  },

  checkForAcceptOrCancel: function(evt) {
    if (this.hasChangedFromSavedText() && evt.getKeyCode() == Event.KEY_ESC) {
      this.cancelChanges();
      evt.stop();
      return true;
    }

    if (this.hasChangedFromSavedText() && evt.getKeyCode() == Event.KEY_RETURN && (this.returnKeyShouldAccept() || evt.isMetaDown() || evt.isAltDown() || evt.isCtrlDown())) {
      this.acceptChanges();
      evt.stop();
      return true;
    }

    return false;
  },

  onKeyDown: function($super, evt) {
    if (this.checkForAcceptOrCancel(evt)) { return true; }
    return $super(evt);
  },

  onKeyPress: function($super, evt) {
    if (this.checkForAcceptOrCancel(evt)) { return true; }
    return $super(evt);
  },

  beWritableAndSelectAll: function(evt) {
    this.beWritable();
    this.requestKeyboardFocus(evt ? evt.hand : WorldMorph.current().firstHand());
    this.doSelectAll();
  },

  wasJustShown: function(evt) {
    this.beWritableAndSelectAll(evt);
  },

  beWritable: function() {
    // nothing to do here, but children can override
  },

  hasChangedFromSavedText: function() {
    return this._hasChangedFromSavedText;
  },

  normalStyle: TextMorph.prototype.style,

  modifiedStyle: {
    borderWidth: 2,
    borderColor: Color.red
  },

  changed: function($super) {
    // Avoid infinite recursion when setting the border stuff.
    if (this._isChangingRightNow) {return;}
    this._isChangingRightNow = true;

    var currentText = this.getText();
    var savedText = this.getSavedText();
    var hasChanged = this._hasChangedFromSavedText = (currentText !== savedText);
    this.applyStyle(hasChanged ? this.modifiedStyle : this.normalStyle);

    this.adjustScale(); // aaa - this doesn't really belong here, just trying it as an experiment

    this.minimumExtentMayHaveChanged();
    delete this._isChangingRightNow;
    $super();
  },

  refreshText: function() {
    if (this.hasChangedFromSavedText()) {
      // Don't wanna lose the stuff that we've typed.
      this.changed();
    } else {
      var newText = this.getSavedText();
      if (newText !== this.getText()) {
        //this.setWrapStyle(lively.Text.WrapStyle.Shrink); /// aaa experimenting with word-wrapping

        this.setText(newText);
        this.changed();

        // aaa experiment with word-wrapping
        /*
        var extent = this.getExtent();
        if (extent.x > 600) {
          this.setExtent(extent.withX(600));
          this.setWrapStyle(lively.Text.WrapStyle.Normal);
        }
        */
      }
    }
  },

  justAcceptedOrCancelled: function() {
    this.changed();
    this.updateLayoutIfNecessary();
  },

  acceptChanges: function() {
    var newText = this.getText();
    if (newText !== this.getSavedText()) {
      this.setSavedText(newText);
    }
    this.justAcceptedOrCancelled();
  },

  cancelChanges: function() {
    this.setText(this.getSavedText());
    this.justAcceptedOrCancelled();
  },

  handlesMouseDown: function(evt) { return true; },

  returnKeyShouldAccept: function() { return false; },

  morphMenu: function(evt) {
    return this.editingCommands().createMenu(this);
  },

  editingCommands: function() {
    var cmdList = avocado.command.list.create(this);
    if (this.hasChangedFromSavedText()) {
      cmdList.addItem(["accept    [alt+enter]", this.acceptChanges]);
      cmdList.addItem(["cancel    [escape]"   , this.cancelChanges]);
    }
    return cmdList;
  }
});

TextMorphRequiringExplicitAcceptance.subclass("TwoModeTextMorph", {
  hasChangedFromSavedText: function() {
    return this.isInWritableMode;
  },

  beUnwritable: function() {
    this.acceptInput = false;
    this.setFill(this.backgroundColorWhenUnwritable || null);
    this.setNullSelectionAt(0);
    var w = this.world();
    if (w) {this.relinquishKeyboardFocus(w.firstHand());}
    this.changed();
    this.mouseHandler = this.oldMouseHandler;
    delete this.oldMouseHandler;
    this.isInWritableMode = false;
    return this;
  },

  beWritable: function() {
    this.acceptInput = true;
    this.setFill(this.backgroundColorWhenWritable || null);
    this.changed();
    this.oldMouseHandler = this.mouseHandler;
    this.enableEvents();
    this.isInWritableMode = true;
    return this;
  },

  switchEditModeOn:  function() { this.beWritable(); },
  switchEditModeOff: function() { this.beUnwritable(); },

  normalStyle: Object.extend(Object.create(TextMorph.prototype.style), {borderWidth: 0}),

  justAcceptedOrCancelled: function() {
    this.beUnwritable();
    this.updateLayoutIfNecessary();
  },

  onMouseDown: function($super, evt) {
    if (! this.isInWritableMode) {
      return this.checkForDoubleClick(evt);
    }
    return $super(evt);
  },

  canBecomeWritable: function() { return ! this.isReadOnly; },

  returnKeyShouldAccept: function() { return true; },

  onDoubleClick: function(evt) {
    if (this.canBecomeWritable()) {
      this.beWritable();
      this.onMouseDown(evt);
    }
  },

  nameOfEditCommand: 'edit',

  setNameOfEditCommand: function(n) {
    this.nameOfEditCommand = n;
    return this;
  },

  editingCommands: function($super) {
    var cmdList = $super();
    if (!this.isInWritableMode && this.canBecomeWritable()) {
      cmdList.addItem(avocado.command.create(this.nameOfEditCommand, function(evt) {this.beWritableAndSelectAll(evt);}, this));
    }
    return cmdList;
  }
});

TextMorph.prototype.style. borderColor = new Color(0.6, 0.6, 0.6);
<commit_msg>Don't do the auto-scaling TextMorph thing just yet.<commit_after>TextMorph.subclass("TextMorphRequiringExplicitAcceptance", {
  initialize: function($super, accessors) {
    $super();
    this.dontNotifyUntilTheActualModelChanges = true;
    this.connectModel({model: this, getText: "getSavedText", setText: "setSavedText"});
    this.acceptInput = true;
    this.setFill(this.backgroundColorWhenWritable || null),
    this.changed();
    this.setSavedText(this.textString); // aaa - what is this for?
    this.justAcceptedOrCancelled();
    if (accessors) { this._accessors = accessors; }
    this.refreshText();
  },

  defaultBounds: pt(5, 10).extent(pt(140, 20)),

  openForDragAndDrop: false,

  suppressGrabbing: true,

  wrap: lively.Text.WrapStyle.Shrink,

  getSavedText: function( )  {
    if (this._accessors) {
      return this._accessors.get();
    } else {
      return this.savedTextString;
    }
  },

  setSavedText: function(t)  {
    if (this._accessors) {
      this._accessors.set(t);
    } else {
      this.savedTextString = t;
      if (this.notifier) {this.notifier.notifyAllObservers();}
    }
  },

  setTextString: function($super, replacement, delayComposition, justMoreTyping) {
    var x = $super(replacement, delayComposition, justMoreTyping);
    this.updateLayoutIfNecessary();
    return x;
  },

  updateLayoutIfNecessary: function() {
    this.adjustForNewBounds(); // makes the focus halo look right   // aaa should probably be outside the conditional, or even in the Core code
    this.minimumExtentMayHaveChanged();
  },

  checkForAcceptOrCancel: function(evt) {
    if (this.hasChangedFromSavedText() && evt.getKeyCode() == Event.KEY_ESC) {
      this.cancelChanges();
      evt.stop();
      return true;
    }

    if (this.hasChangedFromSavedText() && evt.getKeyCode() == Event.KEY_RETURN && (this.returnKeyShouldAccept() || evt.isMetaDown() || evt.isAltDown() || evt.isCtrlDown())) {
      this.acceptChanges();
      evt.stop();
      return true;
    }

    return false;
  },

  onKeyDown: function($super, evt) {
    if (this.checkForAcceptOrCancel(evt)) { return true; }
    return $super(evt);
  },

  onKeyPress: function($super, evt) {
    if (this.checkForAcceptOrCancel(evt)) { return true; }
    return $super(evt);
  },

  beWritableAndSelectAll: function(evt) {
    this.beWritable();
    this.requestKeyboardFocus(evt ? evt.hand : WorldMorph.current().firstHand());
    this.doSelectAll();
  },

  wasJustShown: function(evt) {
    this.beWritableAndSelectAll(evt);
  },

  beWritable: function() {
    // nothing to do here, but children can override
  },

  hasChangedFromSavedText: function() {
    return this._hasChangedFromSavedText;
  },

  normalStyle: TextMorph.prototype.style,

  modifiedStyle: {
    borderWidth: 2,
    borderColor: Color.red
  },

  changed: function($super) {
    // Avoid infinite recursion when setting the border stuff.
    if (this._isChangingRightNow) {return;}
    this._isChangingRightNow = true;

    var currentText = this.getText();
    var savedText = this.getSavedText();
    var hasChanged = this._hasChangedFromSavedText = (currentText !== savedText);
    this.applyStyle(hasChanged ? this.modifiedStyle : this.normalStyle);

    //this.adjustScale(); // aaa - this doesn't really belong here, just trying it as an experiment

    this.minimumExtentMayHaveChanged();
    delete this._isChangingRightNow;
    $super();
  },

  refreshText: function() {
    if (this.hasChangedFromSavedText()) {
      // Don't wanna lose the stuff that we've typed.
      this.changed();
    } else {
      var newText = this.getSavedText();
      if (newText !== this.getText()) {
        //this.setWrapStyle(lively.Text.WrapStyle.Shrink); /// aaa experimenting with word-wrapping

        this.setText(newText);
        this.changed();

        // aaa experiment with word-wrapping
        /*
        var extent = this.getExtent();
        if (extent.x > 600) {
          this.setExtent(extent.withX(600));
          this.setWrapStyle(lively.Text.WrapStyle.Normal);
        }
        */
      }
    }
  },

  justAcceptedOrCancelled: function() {
    this.changed();
    this.updateLayoutIfNecessary();
  },

  acceptChanges: function() {
    var newText = this.getText();
    if (newText !== this.getSavedText()) {
      this.setSavedText(newText);
    }
    this.justAcceptedOrCancelled();
  },

  cancelChanges: function() {
    this.setText(this.getSavedText());
    this.justAcceptedOrCancelled();
  },

  handlesMouseDown: function(evt) { return true; },

  returnKeyShouldAccept: function() { return false; },

  morphMenu: function(evt) {
    return this.editingCommands().createMenu(this);
  },

  editingCommands: function() {
    var cmdList = avocado.command.list.create(this);
    if (this.hasChangedFromSavedText()) {
      cmdList.addItem(["accept    [alt+enter]", this.acceptChanges]);
      cmdList.addItem(["cancel    [escape]"   , this.cancelChanges]);
    }
    return cmdList;
  }
});

TextMorphRequiringExplicitAcceptance.subclass("TwoModeTextMorph", {
  hasChangedFromSavedText: function() {
    return this.isInWritableMode;
  },

  beUnwritable: function() {
    this.acceptInput = false;
    this.setFill(this.backgroundColorWhenUnwritable || null);
    this.setNullSelectionAt(0);
    var w = this.world();
    if (w) {this.relinquishKeyboardFocus(w.firstHand());}
    this.changed();
    this.mouseHandler = this.oldMouseHandler;
    delete this.oldMouseHandler;
    this.isInWritableMode = false;
    return this;
  },

  beWritable: function() {
    this.acceptInput = true;
    this.setFill(this.backgroundColorWhenWritable || null);
    this.changed();
    this.oldMouseHandler = this.mouseHandler;
    this.enableEvents();
    this.isInWritableMode = true;
    return this;
  },

  switchEditModeOn:  function() { this.beWritable(); },
  switchEditModeOff: function() { this.beUnwritable(); },

  normalStyle: Object.extend(Object.create(TextMorph.prototype.style), {borderWidth: 0}),

  justAcceptedOrCancelled: function() {
    this.beUnwritable();
    this.updateLayoutIfNecessary();
  },

  onMouseDown: function($super, evt) {
    if (! this.isInWritableMode) {
      return this.checkForDoubleClick(evt);
    }
    return $super(evt);
  },

  canBecomeWritable: function() { return ! this.isReadOnly; },

  returnKeyShouldAccept: function() { return true; },

  onDoubleClick: function(evt) {
    if (this.canBecomeWritable()) {
      this.beWritable();
      this.onMouseDown(evt);
    }
  },

  nameOfEditCommand: 'edit',

  setNameOfEditCommand: function(n) {
    this.nameOfEditCommand = n;
    return this;
  },

  editingCommands: function($super) {
    var cmdList = $super();
    if (!this.isInWritableMode && this.canBecomeWritable()) {
      cmdList.addItem(avocado.command.create(this.nameOfEditCommand, function(evt) {this.beWritableAndSelectAll(evt);}, this));
    }
    return cmdList;
  }
});

TextMorph.prototype.style. borderColor = new Color(0.6, 0.6, 0.6);
<|endoftext|>


<commit_before>dc.ui.DocumentDialog = dc.ui.Dialog.extend({

  ATTRIBUTES : ['title', 'source', 'description', 'related_article', 'access'],

  id        : 'edit_document_dialog',
  className : 'dialog docalog',

  callbacks : {
    '.cancel.click'     : 'close',
    '.ok.click'         : 'save',
    '.delete.click'     : 'destroy',
    '.attribute.change' : '_markChanged'
  },

  constructor : function(docs) {
    this.docs = docs;
    this.multiple = docs.length > 1;
    var title = "Edit " + this._title();
    this.base({mode : 'custom', title : title, editor : true});
    this.render();
    $(document.body).append(this.el);
  },

  render : function() {
    this.base();
    this._container = $('.custom', this.el);
    this._container.html(JST['document/document_dialog']({
      docs : this.docs, multiple : this.multiple
    }));
    var attrs = this._sharedAttributes();
    attrs['access'] = attrs['access'] || dc.access.PRIVATE;
    _.each(this.ATTRIBUTES, _.bind(function(attr) {
      $('#document_edit_' + attr).val(attrs[attr] || '');
    }, this));
    this.center();
    return this;
  },

  save : function() {
    var original = this._sharedAttributes();
    var changes = {};
    _.each(this.ATTRIBUTES, _.bind(function(attr) {
      var el = $('#document_edit_' + attr, this.el);
      var next = el.val();
      if (attr == 'access') next = parseInt(next, 10);
      if (next != original[attr] && el.hasClass('changed')) changes[attr] = next;
    }, this));
    this.close();
    if (!_.isEmpty(changes)) {
      _.each(this.docs, function(doc){ Documents.update(doc, changes); });
      dc.ui.notifier.show({mode : 'info', text : 'Updated ' + this.docs.length + ' ' + Inflector.pluralize('document', this.docs.length)});
    }
  },

  destroy : function() {
    this.close();
    if (Documents.selected().length == 0) {
        this.docs[0].set({'selected': true});
    }
    Documents.destroySelected();
  },

  _title : function() {
    if (this.multiple) return this.docs.length + ' Documents';
    return '"' + Inflector.truncate(this.docs[0].get('title'), 50) + '"';
  },

  _markChanged : function(e) {
    $(e.target).addClass('changed');
  },

  _sharedAttributes : function() {
    return _.reduce(this.ATTRIBUTES, _.bind(function(memo, attr) {
      memo[attr] = Documents.sharedAttribute(this.docs, attr);
      return memo;
    }, this), {});
  }

}, {

  open : function(doc) {
    var docs = Documents.selected();
    docs = !doc || _.include(docs, doc) ? docs : [doc];
    new dc.ui.DocumentDialog(docs);
  }

});
<commit_msg>yet another checkbusy check.<commit_after>dc.ui.DocumentDialog = dc.ui.Dialog.extend({

  ATTRIBUTES : ['title', 'source', 'description', 'related_article', 'access'],

  id        : 'edit_document_dialog',
  className : 'dialog docalog',

  callbacks : {
    '.cancel.click'     : 'close',
    '.ok.click'         : 'save',
    '.delete.click'     : 'destroy',
    '.attribute.change' : '_markChanged'
  },

  constructor : function(docs) {
    this.docs = docs;
    this.multiple = docs.length > 1;
    var title = "Edit " + this._title();
    this.base({mode : 'custom', title : title, editor : true});
    this.render();
    $(document.body).append(this.el);
  },

  render : function() {
    this.base();
    this._container = $('.custom', this.el);
    this._container.html(JST['document/document_dialog']({
      docs : this.docs, multiple : this.multiple
    }));
    var attrs = this._sharedAttributes();
    attrs['access'] = attrs['access'] || dc.access.PRIVATE;
    _.each(this.ATTRIBUTES, _.bind(function(attr) {
      $('#document_edit_' + attr).val(attrs[attr] || '');
    }, this));
    this.center();
    return this;
  },

  save : function() {
    var original = this._sharedAttributes();
    var changes = {};
    _.each(this.ATTRIBUTES, _.bind(function(attr) {
      var el = $('#document_edit_' + attr, this.el);
      var next = el.val();
      if (attr == 'access') next = parseInt(next, 10);
      if (next != original[attr] && el.hasClass('changed')) changes[attr] = next;
    }, this));
    this.close();
    if (!_.isEmpty(changes)) {
      _.each(this.docs, function(doc){ Documents.update(doc, changes); });
      dc.ui.notifier.show({mode : 'info', text : 'Updated ' + this.docs.length + ' ' + Inflector.pluralize('document', this.docs.length)});
    }
  },

  destroy : function() {
    this.close();
    if (Documents.selected().length == 0) {
        this.docs[0].set({'selected': true});
    }
    Documents.destroySelected();
  },

  _title : function() {
    if (this.multiple) return this.docs.length + ' Documents';
    return '"' + Inflector.truncate(this.docs[0].get('title'), 50) + '"';
  },

  _markChanged : function(e) {
    $(e.target).addClass('changed');
  },

  _sharedAttributes : function() {
    return _.reduce(this.ATTRIBUTES, _.bind(function(memo, attr) {
      memo[attr] = Documents.sharedAttribute(this.docs, attr);
      return memo;
    }, this), {});
  }

}, {

  open : function(doc) {
    var docs = Documents.selected();
    docs = !doc || _.include(docs, doc) ? docs : [doc];
    if (_.any(docs, function(doc){ return doc.checkBusy(); })) return;
    new dc.ui.DocumentDialog(docs);
  }

});
<|endoftext|>


<commit_before>// ==========================================================================
// Project:   The M-Project - Mobile HTML5 Application Framework
// Copyright: (c) 2010 M-Way Solutions GmbH. All rights reserved.
//            (c) 2011 panacoda GmbH. All rights reserved.
// Creator:   Dominik
// Date:      02.11.2010
// License:   Dual licensed under the MIT or GPL Version 2 licenses.
//            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
//            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
// ==========================================================================

/**
 * @class
 *
 * This defines the prototype for any button view. A button is a view element that is
 * typically used for triggering an action, e.g. switching to another page, firing a
 * request or opening a dialog.
 *
 * @extends M.View
 */
M.ButtonView = M.View.extend(
/** @scope M.ButtonView.prototype */ {

    /**
     * The type of this object.
     *
     * @type String
     */
    type: 'M.ButtonView',

    /**
     * Determines whether this button is active or not.
     *
     * Note: This property is only used if the button is part of a button group (M.ButtonGroupView).
     *
     * @type Boolean
     */
    isActive: NO,

    /**
     * Determines whether to display the button ony with an icon but no text or not.
     *
     * @type Boolean
     */
    isIconOnly: NO,

    /**
     * This property can be used to specify a certain hyperlink type for this button. It only
     * works in combination with the hyperlinkTarget property.
     *
     * @type String
     */
    hyperlinkType: null,

    /**
     * This property can be used to specify a hyperlink target for this button. It only
     * works in combination with the hyperlinkType property.
     *
     * @type String
     */
    hyperlinkTarget: null,

    /**
     * This property can be used to specify a tag, that is independent from the button's
     * value. This allows you to identify a button, without having to worry about changes
     * to its value.
     *
     * @type String
     */
    tag: null,

    /**
     * This property specifies the recommended events for this type of view.
     *
     * @type Array
     */
    recommendedEvents: ['click', 'tap'],

    /**
     * Renders a button as an input tag. Input is automatically converted by jQuery mobile.
     *
     * @private
     * @returns {String} The button view's html representation.
     */
    render: function() {
        this.computeValue();
        this.html += '<a data-role="button" id="' + this.id + '"' + this.style() + ' ';

        if(this.hyperlinkTarget && this.hyperlinkType) {
            switch (this.hyperlinkType) {
                case M.HYPERLINK_EMAIL:
                    this.html += 'rel="external" href="mailto:' + this.hyperlinkTarget + '"';
                    break;
                case M.HYPERLINK_WEBSITE:
                    this.html += 'rel="external" target="_blank" href="' + this.hyperlinkTarget + '"';
                    break;
                case M.HYPERLINK_PHONE:
                    this.html += 'rel="external" href="tel:' + this.hyperlinkTarget + '"';
                    break;
            }
        } else {
            this.html += 'href="#"';
        }

        this.html += '>' + this.value + '';

        return this.html;
    },

    /**
     * This method is responsible for registering events for view elements and its child views. It
     * basically passes the view's event-property to M.EventDispatcher to bind the appropriate
     * events.
     *
     * It extend M.View's registerEvents method with some special stuff for list views and their
     * internal events.
     */
    registerEvents: function() {
        if(!this.internalEvents) {
            this.internalEvents = {
                tap: {
                    target: this,
                    action: 'dispatchEvent'
                }
            }
        }
        this.bindToCaller(this, M.View.registerEvents)();
    },

    /**
     * Updates the value of the button with DOM access by jQuery.
     *
     * @private
     */
    renderUpdate: function() {
        this.computeValue();
        if(this.parentView && this.parentView.type === 'M.ButtonGroupView') {
            $('#' + this.id).find('.ui-btn-text').text(this.value);
        } else {
            $('#' + this.id).parent().find('.ui-btn-text').text(this.value);
        }
    },

    /**
     * Sets the button's value and calls renderUpdate() to make the value update visible.
     *
     * @param {String} value The button's new value.
     */
    setValue: function(value) {
        this.value = value;
        this.renderUpdate();
    },

    /**
     * Triggers the rendering engine, jQuery mobile, to style the button.
     *
     * @private
     */
    theme: function() {
        /* theme only if not already done */
        if(!$('#' + this.id).hasClass('ui-btn')) {
            $('#' + this.id).buttonMarkup();
        }
    },

    /**
     * Applies some style-attributes to the button.
     *
     * @private
     * @returns {String} The button's styling as html representation.
     */
    style: function() {
        var html = '';
        if(this.isInline) {
            html += ' data-inline="true"';
        }
        if(this.icon) {
            html += ' data-icon="' + this.icon + '"';
        }
        if(this.cssClass) {
            html += ' data-theme="' + this.cssClass + '"';
        }
        if(this.isIconOnly) {
            html += ' data-iconpos="notext"';
        }
        if(this.cssStyle) {
            html += 'style="' + this.cssStyle + '"';
        }
        return html;
    },

    /**
     * This method is called right before the page is loaded. If a beforeLoad-action is defined
     * for the page, it is now called.
     *
     * @param {String} id The DOM id of the event target.
     * @param {Object} event The DOM event.
     * @param {Object} nextEvent The next event (external event), if specified.
     */
    dispatchEvent: function(id, event, nextEvent) {
        if(this.isEnabled && nextEvent) {
            M.EventDispatcher.callHandler(nextEvent, event, YES);
        }
    },

    /**
     * This method can be used to disable the button. This leads to a visual 'disabled' look and
     * disabled the buttons tap/click events.
     */
    disable: function() {
        if(this.isEnabled) {
            var html = $('#' + this.id).html();
            html = '
' + html + '
';
            $('#' + this.id).html(html);
            this.isEnabled = NO;
        }
    },

    /**
     * This method can be used to enable a disabled button and make it usable again.
     */
    enable: function() {
        if(!this.isEnabled) {
            var html = $('#' + this.id + ' div').html();
            $('#' + this.id).html(html);
            this.isEnabled = YES;
        }
    }

});<commit_msg>refactored renderUpdate to properly address the button's value<commit_after>// ==========================================================================
// Project:   The M-Project - Mobile HTML5 Application Framework
// Copyright: (c) 2010 M-Way Solutions GmbH. All rights reserved.
//            (c) 2011 panacoda GmbH. All rights reserved.
// Creator:   Dominik
// Date:      02.11.2010
// License:   Dual licensed under the MIT or GPL Version 2 licenses.
//            http://github.com/mwaylabs/The-M-Project/blob/master/MIT-LICENSE
//            http://github.com/mwaylabs/The-M-Project/blob/master/GPL-LICENSE
// ==========================================================================

/**
 * @class
 *
 * This defines the prototype for any button view. A button is a view element that is
 * typically used for triggering an action, e.g. switching to another page, firing a
 * request or opening a dialog.
 *
 * @extends M.View
 */
M.ButtonView = M.View.extend(
/** @scope M.ButtonView.prototype */ {

    /**
     * The type of this object.
     *
     * @type String
     */
    type: 'M.ButtonView',

    /**
     * Determines whether this button is active or not.
     *
     * Note: This property is only used if the button is part of a button group (M.ButtonGroupView).
     *
     * @type Boolean
     */
    isActive: NO,

    /**
     * Determines whether to display the button ony with an icon but no text or not.
     *
     * @type Boolean
     */
    isIconOnly: NO,

    /**
     * This property can be used to specify a certain hyperlink type for this button. It only
     * works in combination with the hyperlinkTarget property.
     *
     * @type String
     */
    hyperlinkType: null,

    /**
     * This property can be used to specify a hyperlink target for this button. It only
     * works in combination with the hyperlinkType property.
     *
     * @type String
     */
    hyperlinkTarget: null,

    /**
     * This property can be used to specify a tag, that is independent from the button's
     * value. This allows you to identify a button, without having to worry about changes
     * to its value.
     *
     * @type String
     */
    tag: null,

    /**
     * This property specifies the recommended events for this type of view.
     *
     * @type Array
     */
    recommendedEvents: ['click', 'tap'],

    /**
     * Renders a button as an input tag. Input is automatically converted by jQuery mobile.
     *
     * @private
     * @returns {String} The button view's html representation.
     */
    render: function() {
        this.computeValue();
        this.html += '<a data-role="button" id="' + this.id + '"' + this.style() + ' ';

        if(this.hyperlinkTarget && this.hyperlinkType) {
            switch (this.hyperlinkType) {
                case M.HYPERLINK_EMAIL:
                    this.html += 'rel="external" href="mailto:' + this.hyperlinkTarget + '"';
                    break;
                case M.HYPERLINK_WEBSITE:
                    this.html += 'rel="external" target="_blank" href="' + this.hyperlinkTarget + '"';
                    break;
                case M.HYPERLINK_PHONE:
                    this.html += 'rel="external" href="tel:' + this.hyperlinkTarget + '"';
                    break;
            }
        } else {
            this.html += 'href="#"';
        }

        this.html += '>' + this.value + '';

        return this.html;
    },

    /**
     * This method is responsible for registering events for view elements and its child views. It
     * basically passes the view's event-property to M.EventDispatcher to bind the appropriate
     * events.
     *
     * It extend M.View's registerEvents method with some special stuff for list views and their
     * internal events.
     */
    registerEvents: function() {
        if(!this.internalEvents) {
            this.internalEvents = {
                tap: {
                    target: this,
                    action: 'dispatchEvent'
                }
            }
        }
        this.bindToCaller(this, M.View.registerEvents)();
    },

    /**
     * Updates the value of the button with DOM access by jQuery.
     *
     * @private
     */
    renderUpdate: function() {
        this.computeValue();
        $('#' + this.id + ' .ui-btn-text').text(this.value);
    },

    /**
     * Sets the button's value and calls renderUpdate() to make the value update visible.
     *
     * @param {String} value The button's new value.
     */
    setValue: function(value) {
        this.value = value;
        this.renderUpdate();
    },

    /**
     * Triggers the rendering engine, jQuery mobile, to style the button.
     *
     * @private
     */
    theme: function() {
        /* theme only if not already done */
        if(!$('#' + this.id).hasClass('ui-btn')) {
            $('#' + this.id).buttonMarkup();
        }
    },

    /**
     * Applies some style-attributes to the button.
     *
     * @private
     * @returns {String} The button's styling as html representation.
     */
    style: function() {
        var html = '';
        if(this.isInline) {
            html += ' data-inline="true"';
        }
        if(this.icon) {
            html += ' data-icon="' + this.icon + '"';
        }
        if(this.cssClass) {
            html += ' data-theme="' + this.cssClass + '"';
        }
        if(this.isIconOnly) {
            html += ' data-iconpos="notext"';
        }
        if(this.cssStyle) {
            html += 'style="' + this.cssStyle + '"';
        }
        return html;
    },

    /**
     * This method is called right before the page is loaded. If a beforeLoad-action is defined
     * for the page, it is now called.
     *
     * @param {String} id The DOM id of the event target.
     * @param {Object} event The DOM event.
     * @param {Object} nextEvent The next event (external event), if specified.
     */
    dispatchEvent: function(id, event, nextEvent) {
        if(this.isEnabled && nextEvent) {
            M.EventDispatcher.callHandler(nextEvent, event, YES);
        }
    },

    /**
     * This method can be used to disable the button. This leads to a visual 'disabled' look and
     * disabled the buttons tap/click events.
     */
    disable: function() {
        if(this.isEnabled) {
            var html = $('#' + this.id).html();
            html = '
' + html + '
';
            $('#' + this.id).html(html);
            this.isEnabled = NO;
        }
    },

    /**
     * This method can be used to enable a disabled button and make it usable again.
     */
    enable: function() {
        if(!this.isEnabled) {
            var html = $('#' + this.id + ' div').html();
            $('#' + this.id).html(html);
            this.isEnabled = YES;
        }
    }

});
<|endoftext|>


<commit_before>import _clone from 'lodash-es/clone';
import _forEach from 'lodash-es/forEach';
import _isEqual from 'lodash-es/isEqual';
import _unionBy from 'lodash-es/unionBy';

import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';

import { t } from '../util/locale';
import { osmChangeset } from '../osm';
import { uiChangesetEditor } from './changeset_editor';
import { uiCommitChanges } from './commit_changes';
import { uiCommitWarnings } from './commit_warnings';
import { uiRawTagEditor } from './raw_tag_editor';
import { utilDetect } from '../util/detect';
import { utilRebind } from '../util';

var changeset;
var readOnlyTags = [
    /^changesets_count$/,
    /^created_by$/,
    /^ideditor:/,
    /^imagery_used$/,
    /^host$/,
    /^locale$/
];

// treat all punctuation as hashtag delimiters - #4398
// from https://stackoverflow.com/a/25575009
var hashtagRegex = /(#[^\u2000-\u206F\u2E00-\u2E7F\s\\'!"#$%&()*+,\-.\/:;<=>?@\[\]^`{|}~]+)/g;

export function uiCommit(context) {
    var dispatch = d3_dispatch('cancel', 'save'),
        userDetails,
        _selection;

    var changesetEditor = uiChangesetEditor(context)
        .on('change', changeTags);
    var rawTagEditor = uiRawTagEditor(context)
        .on('change', changeTags);
    var commitChanges = uiCommitChanges(context);
    var commitWarnings = uiCommitWarnings(context);

    function commit(selection) {
        _selection = selection;

        var osm = context.connection();
        if (!osm) return;

        // expire stored comment and hashtags after cutoff datetime - #3947
        var commentDate = +context.storage('commentDate') || 0,
            currDate = Date.now(),
            cutoff = 2 * 86400 * 1000;   // 2 days
        if (commentDate > currDate || currDate - commentDate > cutoff) {
            context.storage('comment', null);
            context.storage('hashtags', null);
        }

        var tags;
        if (!changeset) {
            var detected = utilDetect();
            tags = {
                comment: context.storage('comment') || '',
                created_by: ('iD ' + context.version).substr(0, 255),
                imagery_used: context.history().imageryUsed().join(';').substr(0, 255),
                host: detected.host.substr(0, 255),
                locale: detected.locale.substr(0, 255)
            };

            // call findHashtags initially - this will remove stored
            // hashtags if any hashtags are found in the comment - #4304
            findHashtags(tags, true);

            var hashtags = context.storage('hashtags');
            if (hashtags) {
                tags.hashtags = hashtags;
            }

            changeset = new osmChangeset({ tags: tags });
        }

        tags = _clone(changeset.tags);

        var header = selection.selectAll('.header')
            .data([0]);

        header.enter()
            .append('div')
            .attr('class', 'header fillL')
            .append('h3')
            .text(t('commit.title'));

        var body = selection.selectAll('.body')
            .data([0]);

        body = body.enter()
            .append('div')
            .attr('class', 'body')
            .merge(body);

        // Changeset Section
        var changesetSection = body.selectAll('.changeset-editor')
            .data([0]);

        changesetSection = changesetSection.enter()
            .append('div')
            .attr('class', 'modal-section changeset-editor')
            .merge(changesetSection);

        changesetSection
            .call(changesetEditor
                .changesetID(changeset.id)
                .tags(tags)
            );

        // Warnings
        body.call(commitWarnings);

        // Upload Explanation
        var saveSection = body.selectAll('.save-section')
            .data([0]);

        saveSection = saveSection.enter()
            .append('div')
            .attr('class','modal-section save-section fillL cf')
            .merge(saveSection);

        var prose = saveSection.selectAll('.commit-info')
            .data([0]);

        prose = prose.enter()
            .append('p')
            .attr('class', 'commit-info')
            .text(t('commit.upload_explanation'))
            .merge(prose);

        osm.userDetails(function(err, user) {
            if (err) return;

            var userLink = d3_select(document.createElement('div'));

            userDetails = user;

            if (user.image_url) {
                userLink
                    .append('img')
                    .attr('src', user.image_url)
                    .attr('class', 'icon pre-text user-icon');
            }

            userLink
                .append('a')
                .attr('class','user-info')
                .text(user.display_name)
                .attr('href', osm.userURL(user.display_name))
                .attr('tabindex', -1)
                .attr('target', '_blank');

            prose
                .html(t('commit.upload_explanation_with_user', { user: userLink.html() }));
        });

        // Request Review
        var requestReview = saveSection.selectAll('.request-review')
            .data([0]);

        // Enter
        var requestReviewEnter = requestReview.enter()
            .append('div')
            .attr('class', 'request-review');

        var labelEnter = requestReviewEnter
            .append('label')
            .attr('for', 'commit-input-request-review');

        labelEnter
            .append('input')
            .attr('type', 'checkbox')
            .attr('id', 'commit-input-request-review');

        labelEnter
            .append('span')
            .text(t('commit.request_review'));

        // Update
        requestReview = requestReview
            .merge(requestReviewEnter);

        var requestReviewInput = requestReview.selectAll('input')
            .property('checked', isReviewRequested(changeset.tags))
            .on('change', toggleRequestReview);

        // Buttons
        var buttonSection = saveSection.selectAll('.buttons')
            .data([0]);

        // enter
        var buttonEnter = buttonSection.enter()
            .append('div')
            .attr('class', 'buttons fillL cf');

        buttonEnter
            .append('button')
            .attr('class', 'secondary-action col5 button cancel-button')
            .append('span')
            .attr('class', 'label')
            .text(t('commit.cancel'));

        buttonEnter
            .append('button')
            .attr('class', 'action col5 button save-button')
            .append('span')
            .attr('class', 'label')
            .text(t('commit.save'));

        // update
        buttonSection = buttonSection
            .merge(buttonEnter);

        buttonSection.selectAll('.cancel-button')
            .on('click.cancel', function() {
                dispatch.call('cancel');
            });

        buttonSection.selectAll('.save-button')
            .attr('disabled', function() {
                var n = d3_select('#preset-input-comment').node();
                return (n && n.value.length) ? null : true;
            })
            .on('click.save', function() {
                dispatch.call('save', this, changeset);
            });

        // Raw Tag Editor
        var tagSection = body.selectAll('.tag-section.raw-tag-editor')
            .data([0]);

        tagSection = tagSection.enter()
            .append('div')
            .attr('class', 'modal-section tag-section raw-tag-editor')
            .merge(tagSection);

        var expanded = !tagSection.selectAll('a.hide-toggle.expanded').empty();
        tagSection
            .call(rawTagEditor
                .expanded(expanded)
                .readOnlyTags(readOnlyTags)
                .tags(_clone(changeset.tags))
            );

        // Change summary
        body.call(commitChanges);

        function toggleRequestReview() {
            var rr = requestReviewInput.property('checked');
            updateChangeset({ review_requested: (rr ? 'yes' : undefined) });

            var expanded = !tagSection.selectAll('a.hide-toggle.expanded').empty();

            tagSection
                .call(rawTagEditor
                    .expanded(expanded)
                    .readOnlyTags(readOnlyTags)
                    .tags(_clone(changeset.tags))
                );
        }
    }

    function changeTags(changed, onInput) {
        if (changed.hasOwnProperty('comment')) {
            if (changed.comment === undefined) {
                changed.comment = '';
            }
            if (!onInput) {
                context.storage('comment', changed.comment);
                context.storage('commentDate', Date.now());
            }
        }

        updateChangeset(changed, onInput);

        if (_selection) {
            _selection.call(commit);
        }
    }

    function findHashtags(tags, commentOnly) {
        var inComment = commentTags(),
            inHashTags = hashTags();

        if (inComment !== null) {                    // when hashtags are detected in comment...
            context.storage('hashtags', null);       // always remove stored hashtags - #4304
            if (commentOnly) { inHashTags = null; }  // optionally override hashtags field
        }
        return _unionBy(inComment, inHashTags, function (s) {
            return s.toLowerCase();
        });

        // Extract hashtags from `comment`
        function commentTags() {
            return tags.comment
                .replace(/http\S*/g, '')  // drop anything that looks like a URL - #4289
                .match(hashtagRegex);
        }

        // Extract and clean hashtags from `hashtags`
        function hashTags() {
            var t = tags.hashtags || '';
            return t
                .split(/[,;\s]+/)
                .map(function (s) {
                    if (s[0] !== '#') { s = '#' + s; }    // prepend '#'
                    var matched = s.match(hashtagRegex);
                    return matched && matched[0];
                }).filter(Boolean);                       // exclude falsey
        }
    }

    function isReviewRequested(tags) {
        var rr = tags.review_requested;
        if (rr === undefined) return false;
        rr = rr.trim().toLowerCase();
        return !(rr === '' || rr === 'no');
    }

    function updateChangeset(changed, onInput) {
        var tags = _clone(changeset.tags);

        _forEach(changed, function(v, k) {
            k = k.trim().substr(0, 255);
            if (readOnlyTags.indexOf(k) !== -1) return;

            if (k !== '' && v !== undefined) {
                if (onInput) {
                    tags[k] = v;
                } else {
                    tags[k] = v.trim().substr(0, 255);
                }
            } else {
                delete tags[k];
            }
        });

        if (!onInput) {
            // when changing the comment, override hashtags with any found in comment.
            var commentOnly = changed.hasOwnProperty('comment') && (changed.comment !== '');
            var arr = findHashtags(tags, commentOnly);
            if (arr.length) {
                tags.hashtags = arr.join(';').substr(0, 255);
                context.storage('hashtags', tags.hashtags);
            } else {
                delete tags.hashtags;
                context.storage('hashtags', null);
            }
        }

        // always update userdetails, just in case user reauthenticates as someone else
        if (userDetails && userDetails.changesets_count !== undefined) {
            var changesetsCount = parseInt(userDetails.changesets_count, 10) + 1;  // #4283
            tags.changesets_count = String(changesetsCount);

            // first 100 edits - new user
            if (changesetsCount <= 100) {
                var s;
                s = context.storage('walkthrough_completed');
                if (s) {
                    tags['ideditor:walkthrough_completed'] = s;
                }

                s = context.storage('walkthrough_progress');
                if (s) {
                    tags['ideditor:walkthrough_progress'] = s;
                }

                s = context.storage('walkthrough_started');
                if (s) {
                    tags['ideditor:walkthrough_started'] = s;
                }
            }
        } else {
            delete tags.changesets_count;
        }

        if (!_isEqual(changeset.tags, tags)) {
            changeset = changeset.update({ tags: tags });
        }
    }

    commit.reset = function() {
        changeset = null;
    };

    return utilRebind(commit, dispatch, 'on');
}
<commit_msg>Allow -,_,+,& as hashtag characters<commit_after>import _clone from 'lodash-es/clone';
import _forEach from 'lodash-es/forEach';
import _isEqual from 'lodash-es/isEqual';
import _unionBy from 'lodash-es/unionBy';

import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';

import { t } from '../util/locale';
import { osmChangeset } from '../osm';
import { uiChangesetEditor } from './changeset_editor';
import { uiCommitChanges } from './commit_changes';
import { uiCommitWarnings } from './commit_warnings';
import { uiRawTagEditor } from './raw_tag_editor';
import { utilDetect } from '../util/detect';
import { utilRebind } from '../util';

var changeset;
var readOnlyTags = [
    /^changesets_count$/,
    /^created_by$/,
    /^ideditor:/,
    /^imagery_used$/,
    /^host$/,
    /^locale$/
];

// treat most punctuation (except -, _, +, &) as hashtag delimiters - #4398
// from https://stackoverflow.com/a/25575009
var hashtagRegex = /(#[^\u2000-\u206F\u2E00-\u2E7F\s\\'!"#$%()*,.\/:;<=>?@\[\]^`{|}~]+)/g;

export function uiCommit(context) {
    var dispatch = d3_dispatch('cancel', 'save'),
        userDetails,
        _selection;

    var changesetEditor = uiChangesetEditor(context)
        .on('change', changeTags);
    var rawTagEditor = uiRawTagEditor(context)
        .on('change', changeTags);
    var commitChanges = uiCommitChanges(context);
    var commitWarnings = uiCommitWarnings(context);

    function commit(selection) {
        _selection = selection;

        var osm = context.connection();
        if (!osm) return;

        // expire stored comment and hashtags after cutoff datetime - #3947
        var commentDate = +context.storage('commentDate') || 0,
            currDate = Date.now(),
            cutoff = 2 * 86400 * 1000;   // 2 days
        if (commentDate > currDate || currDate - commentDate > cutoff) {
            context.storage('comment', null);
            context.storage('hashtags', null);
        }

        var tags;
        if (!changeset) {
            var detected = utilDetect();
            tags = {
                comment: context.storage('comment') || '',
                created_by: ('iD ' + context.version).substr(0, 255),
                imagery_used: context.history().imageryUsed().join(';').substr(0, 255),
                host: detected.host.substr(0, 255),
                locale: detected.locale.substr(0, 255)
            };

            // call findHashtags initially - this will remove stored
            // hashtags if any hashtags are found in the comment - #4304
            findHashtags(tags, true);

            var hashtags = context.storage('hashtags');
            if (hashtags) {
                tags.hashtags = hashtags;
            }

            changeset = new osmChangeset({ tags: tags });
        }

        tags = _clone(changeset.tags);

        var header = selection.selectAll('.header')
            .data([0]);

        header.enter()
            .append('div')
            .attr('class', 'header fillL')
            .append('h3')
            .text(t('commit.title'));

        var body = selection.selectAll('.body')
            .data([0]);

        body = body.enter()
            .append('div')
            .attr('class', 'body')
            .merge(body);

        // Changeset Section
        var changesetSection = body.selectAll('.changeset-editor')
            .data([0]);

        changesetSection = changesetSection.enter()
            .append('div')
            .attr('class', 'modal-section changeset-editor')
            .merge(changesetSection);

        changesetSection
            .call(changesetEditor
                .changesetID(changeset.id)
                .tags(tags)
            );

        // Warnings
        body.call(commitWarnings);

        // Upload Explanation
        var saveSection = body.selectAll('.save-section')
            .data([0]);

        saveSection = saveSection.enter()
            .append('div')
            .attr('class','modal-section save-section fillL cf')
            .merge(saveSection);

        var prose = saveSection.selectAll('.commit-info')
            .data([0]);

        prose = prose.enter()
            .append('p')
            .attr('class', 'commit-info')
            .text(t('commit.upload_explanation'))
            .merge(prose);

        osm.userDetails(function(err, user) {
            if (err) return;

            var userLink = d3_select(document.createElement('div'));

            userDetails = user;

            if (user.image_url) {
                userLink
                    .append('img')
                    .attr('src', user.image_url)
                    .attr('class', 'icon pre-text user-icon');
            }

            userLink
                .append('a')
                .attr('class','user-info')
                .text(user.display_name)
                .attr('href', osm.userURL(user.display_name))
                .attr('tabindex', -1)
                .attr('target', '_blank');

            prose
                .html(t('commit.upload_explanation_with_user', { user: userLink.html() }));
        });

        // Request Review
        var requestReview = saveSection.selectAll('.request-review')
            .data([0]);

        // Enter
        var requestReviewEnter = requestReview.enter()
            .append('div')
            .attr('class', 'request-review');

        var labelEnter = requestReviewEnter
            .append('label')
            .attr('for', 'commit-input-request-review');

        labelEnter
            .append('input')
            .attr('type', 'checkbox')
            .attr('id', 'commit-input-request-review');

        labelEnter
            .append('span')
            .text(t('commit.request_review'));

        // Update
        requestReview = requestReview
            .merge(requestReviewEnter);

        var requestReviewInput = requestReview.selectAll('input')
            .property('checked', isReviewRequested(changeset.tags))
            .on('change', toggleRequestReview);

        // Buttons
        var buttonSection = saveSection.selectAll('.buttons')
            .data([0]);

        // enter
        var buttonEnter = buttonSection.enter()
            .append('div')
            .attr('class', 'buttons fillL cf');

        buttonEnter
            .append('button')
            .attr('class', 'secondary-action col5 button cancel-button')
            .append('span')
            .attr('class', 'label')
            .text(t('commit.cancel'));

        buttonEnter
            .append('button')
            .attr('class', 'action col5 button save-button')
            .append('span')
            .attr('class', 'label')
            .text(t('commit.save'));

        // update
        buttonSection = buttonSection
            .merge(buttonEnter);

        buttonSection.selectAll('.cancel-button')
            .on('click.cancel', function() {
                dispatch.call('cancel');
            });

        buttonSection.selectAll('.save-button')
            .attr('disabled', function() {
                var n = d3_select('#preset-input-comment').node();
                return (n && n.value.length) ? null : true;
            })
            .on('click.save', function() {
                dispatch.call('save', this, changeset);
            });

        // Raw Tag Editor
        var tagSection = body.selectAll('.tag-section.raw-tag-editor')
            .data([0]);

        tagSection = tagSection.enter()
            .append('div')
            .attr('class', 'modal-section tag-section raw-tag-editor')
            .merge(tagSection);

        var expanded = !tagSection.selectAll('a.hide-toggle.expanded').empty();
        tagSection
            .call(rawTagEditor
                .expanded(expanded)
                .readOnlyTags(readOnlyTags)
                .tags(_clone(changeset.tags))
            );

        // Change summary
        body.call(commitChanges);

        function toggleRequestReview() {
            var rr = requestReviewInput.property('checked');
            updateChangeset({ review_requested: (rr ? 'yes' : undefined) });

            var expanded = !tagSection.selectAll('a.hide-toggle.expanded').empty();

            tagSection
                .call(rawTagEditor
                    .expanded(expanded)
                    .readOnlyTags(readOnlyTags)
                    .tags(_clone(changeset.tags))
                );
        }
    }

    function changeTags(changed, onInput) {
        if (changed.hasOwnProperty('comment')) {
            if (changed.comment === undefined) {
                changed.comment = '';
            }
            if (!onInput) {
                context.storage('comment', changed.comment);
                context.storage('commentDate', Date.now());
            }
        }

        updateChangeset(changed, onInput);

        if (_selection) {
            _selection.call(commit);
        }
    }

    function findHashtags(tags, commentOnly) {
        var inComment = commentTags(),
            inHashTags = hashTags();

        if (inComment !== null) {                    // when hashtags are detected in comment...
            context.storage('hashtags', null);       // always remove stored hashtags - #4304
            if (commentOnly) { inHashTags = null; }  // optionally override hashtags field
        }
        return _unionBy(inComment, inHashTags, function (s) {
            return s.toLowerCase();
        });

        // Extract hashtags from `comment`
        function commentTags() {
            return tags.comment
                .replace(/http\S*/g, '')  // drop anything that looks like a URL - #4289
                .match(hashtagRegex);
        }

        // Extract and clean hashtags from `hashtags`
        function hashTags() {
            var t = tags.hashtags || '';
            return t
                .split(/[,;\s]+/)
                .map(function (s) {
                    if (s[0] !== '#') { s = '#' + s; }    // prepend '#'
                    var matched = s.match(hashtagRegex);
                    return matched && matched[0];
                }).filter(Boolean);                       // exclude falsey
        }
    }

    function isReviewRequested(tags) {
        var rr = tags.review_requested;
        if (rr === undefined) return false;
        rr = rr.trim().toLowerCase();
        return !(rr === '' || rr === 'no');
    }

    function updateChangeset(changed, onInput) {
        var tags = _clone(changeset.tags);

        _forEach(changed, function(v, k) {
            k = k.trim().substr(0, 255);
            if (readOnlyTags.indexOf(k) !== -1) return;

            if (k !== '' && v !== undefined) {
                if (onInput) {
                    tags[k] = v;
                } else {
                    tags[k] = v.trim().substr(0, 255);
                }
            } else {
                delete tags[k];
            }
        });

        if (!onInput) {
            // when changing the comment, override hashtags with any found in comment.
            var commentOnly = changed.hasOwnProperty('comment') && (changed.comment !== '');
            var arr = findHashtags(tags, commentOnly);
            if (arr.length) {
                tags.hashtags = arr.join(';').substr(0, 255);
                context.storage('hashtags', tags.hashtags);
            } else {
                delete tags.hashtags;
                context.storage('hashtags', null);
            }
        }

        // always update userdetails, just in case user reauthenticates as someone else
        if (userDetails && userDetails.changesets_count !== undefined) {
            var changesetsCount = parseInt(userDetails.changesets_count, 10) + 1;  // #4283
            tags.changesets_count = String(changesetsCount);

            // first 100 edits - new user
            if (changesetsCount <= 100) {
                var s;
                s = context.storage('walkthrough_completed');
                if (s) {
                    tags['ideditor:walkthrough_completed'] = s;
                }

                s = context.storage('walkthrough_progress');
                if (s) {
                    tags['ideditor:walkthrough_progress'] = s;
                }

                s = context.storage('walkthrough_started');
                if (s) {
                    tags['ideditor:walkthrough_started'] = s;
                }
            }
        } else {
            delete tags.changesets_count;
        }

        if (!_isEqual(changeset.tags, tags)) {
            changeset = changeset.update({ tags: tags });
        }
    }

    commit.reset = function() {
        changeset = null;
    };

    return utilRebind(commit, dispatch, 'on');
}
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var SVGO = require('svgo');

// Initialize what will be used for automatic text measurement.
var Canvas = require('canvas');
var canvasElement = new Canvas(0, 0);   // Width and height are irrelevant.
var canvasContext = canvasElement.getContext('2d');
var CanvasFont = Canvas.Font;
var color = require('chromath');
try {
  var opensans = new CanvasFont('Verdana',
      path.join(__dirname, 'Verdana.ttf'));
  canvasContext.addFont(opensans);
} catch(e) {}
canvasContext.font = '11px Verdana, "DejaVu Sans"';

// Template crafting action below.
var dot = require('dot');
var colorscheme = require(path.join(__dirname, 'colorscheme.json'));
var template = fs.readFileSync(path.join(__dirname, 'template.svg'));
var imageTemplate = dot.template(''+template);

function optimize(string, callback) {
  var svgo = new SVGO();
  svgo.optimize(string, callback);
}

function makeImage(data, cb) {
   data.colorscheme = data.colorscheme.split(",");
  if (data.colorscheme) {
    data.colorA =  color.websafe(data.colorscheme[0]).toString();
    data.colorB = color.websafe(data.colorscheme[data.colorscheme - 1]).toString();
  }
  data.widths = [
    (canvasContext.measureText(data.text[0]).width|0) + 10,
    (canvasContext.measureText(data.text[1]).width|0) + 10,
  ];
  var result = imageTemplate(data);
  // Run the SVG through SVGO.
  optimize(result, function(object) { cb(object.data); });
}

module.exports = makeImage;
<commit_msg>Whoops I messed up that code<commit_after>var fs = require('fs');
var path = require('path');
var SVGO = require('svgo');

// Initialize what will be used for automatic text measurement.
var Canvas = require('canvas');
var canvasElement = new Canvas(0, 0);   // Width and height are irrelevant.
var canvasContext = canvasElement.getContext('2d');
var CanvasFont = Canvas.Font;
var color = require('chromath');
try {
  var opensans = new CanvasFont('Verdana',
      path.join(__dirname, 'Verdana.ttf'));
  canvasContext.addFont(opensans);
} catch(e) {}
canvasContext.font = '11px Verdana, "DejaVu Sans"';

// Template crafting action below.
var dot = require('dot');
var colorscheme = require(path.join(__dirname, 'colorscheme.json'));
var template = fs.readFileSync(path.join(__dirname, 'template.svg'));
var imageTemplate = dot.template(''+template);

function optimize(string, callback) {
  var svgo = new SVGO();
  svgo.optimize(string, callback);
}

function makeImage(data, cb) {
   data.colorscheme = data.colorscheme.split(",");
  if (data.colorscheme) {
    data.colorA =  color.websafe(data.colorscheme[0]).toString();
    data.colorB =  color.websafe(data.colorscheme[data.colorscheme.length - 1]).toString();
  }
  data.widths = [
    (canvasContext.measureText(data.text[0]).width|0) + 10,
    (canvasContext.measureText(data.text[1]).width|0) + 10,
  ];
  var result = imageTemplate(data);
  // Run the SVG through SVGO.
  optimize(result, function(object) { cb(object.data); });
}

module.exports = makeImage;
<|endoftext|>


<commit_before>var Tabs = require('hypertabs')
var h = require('hyperscript')
var keyscroll = require('../keyscroll')
var open = require('open-external')

function ancestor (el) {
  if(!el) return
  if(el.tagName !== 'A') return ancestor(el.parentElement)
  return el
}

exports.needs = {
  build_scroller: 'first',
  screen_view: 'first',
  search_box: 'first',
  menu: 'first',
  external_confirm:'first'
}

exports.gives = 'screen_view'

exports.create = function (api) {
  return function (path) {
    if(path !== 'tabs') return

    function setSelected (indexes) {
      var ids = indexes.map(function (index) {
        return tabs.get(index).id
      })
      if(search)
        if(ids.length > 1)
          search.value = 'split('+ids.join(',')+')'
        else
          search.value = ids[0]
    }

    var search
    var tabs = Tabs(setSelected)

    search = api.search_box(function (path, change) {

      if(tabs.has(path)) {
        tabs.select(path)
        return true
      }
      var el = api.screen_view(path)

      if(el) {
        if(!el.title) el.title = path
        el.scroll = keyscroll(el.querySelector('.Scroller .\\.content'))
        tabs.add(el, change)
  //      localStorage.openTabs = JSON.stringify(tabs.tabs)
        return change
      }
    })

    //reposition hypertabs menu to inside a container...
    tabs.insertBefore(h('div.header.row',
        h('div.header__tabs.row', tabs.firstChild), //tabs
        h('div.header__search.row.end', [search, api.menu()])
    ), tabs.firstChild)
  //  tabs.insertBefore(search, tabs.firstChild.nextSibling)

    var saved = []
  //  try { saved = JSON.parse(localStorage.openTabs) }
  //  catch (_) { }

    if(!saved || saved.length < 3)
      saved = ['/public', '/private', '/notifications', '/data']

    saved.forEach(function (path) {
      var el = api.screen_view(path)
      if(!el) return
      el.id = el.id || path
      if (!el) return
      el.scroll = keyscroll(el.querySelector('.Scroller .\\.content'))
      if(el) tabs.add(el, false, false)
    })

    tabs.select(0)

    //handle link clicks
    window.onclick = function (ev) {
      var link = ancestor(ev.target)
      if(!link) return
      var path = link.hash.substring(1)

      ev.preventDefault()
      ev.stopPropagation()

      //let the application handle this link
      if (link.getAttribute('href') === '#') return

      //open external links.
      //this ought to be made into something more runcible
      if(link.href && open.isExternal(link.href)) return api.external_confirm(link.href)

      if(tabs.has(path))
        return tabs.select(path, !ev.ctrlKey, !!ev.shiftKey)

      var el = api.screen_view(path)
      if(el) {
        el.id = el.id || path
        el.scroll = keyscroll(el.querySelector('.Scroller .\\.content'))
        tabs.add(el, !ev.ctrlKey, !!ev.shiftKey)
  //      localStorage.openTabs = JSON.stringify(tabs.tabs)
      }

      return false
    }

    window.addEventListener('keydown', function (ev) {
      if (ev.target.nodeName === 'INPUT' || ev.target.nodeName === 'TEXTAREA')
        return
      switch(ev.keyCode) {

        // scroll through tabs
        case 72: // h
          return tabs.selectRelative(-1)
        case 76: // l
          return tabs.selectRelative(1)

        // scroll through messages
        case 74: // j
          return tabs.get(tabs.selected[0]).scroll(1)
        case 75: // k
          return tabs.get(tabs.selected[0]).scroll(-1)

        // close a tab
        case 88: // x
          if (tabs.selected) {
            var sel = tabs.selected
            var i = sel.reduce(function (a, b) { return Math.min(a, b) })
            tabs.remove(sel)
            tabs.select(Math.max(i-1, 0))
          }
          return

        // activate the search field
        case 191: // /
          if (ev.shiftKey)
            search.activate('?', ev)
          else
            search.activate('/', ev)
          return

        // navigate to a feed
        case 50: // 2
          if (ev.shiftKey)
            search.activate('@', ev)
          return

        // navigate to a channel
        case 51: // 3
          if (ev.shiftKey)
            search.activate('#', ev)
          return

        // navigate to a message
        case 53: // 5
          if (ev.shiftKey)
            search.activate('%', ev)
          return
      }
    })

    // errors tab
    var {
      container: errors,
      content: errorsContent
    } = api.build_scroller()

    // remove loader error handler
    if (window.onError) {
      window.removeEventListener('error', window.onError)
      delete window.onError
    }

    // put errors in a tab
    window.addEventListener('error', function (ev) {
      var err = ev.error || ev
      if(!tabs.has('errors'))
        tabs.add(errors, false)
      var el = h('div.message',
        h('strong', err.message),
        h('pre', err.stack))
      if (errorsContent.firstChild)
        errorsContent.insertBefore(el, errorsContent.firstChild)
      else
        errorsContent.appendChild(el)
    })

    if (process.versions.electron) {
      window.addEventListener('contextmenu', function (ev) {
        ev.preventDefault()
        var remote = require('electron').remote
        var Menu = remote.Menu
        var MenuItem = remote.MenuItem
        var menu = new Menu()
        menu.append(new MenuItem({
          label: 'Inspect Element',
          click: function () {
            remote.getCurrentWindow().inspectElement(ev.x, ev.y)
          }
        }))
        menu.popup(remote.getCurrentWindow())
      })
    }

    return tabs
  }

}
<commit_msg>Add control+scroll zooming in electron<commit_after>var Tabs = require('hypertabs')
var h = require('hyperscript')
var keyscroll = require('../keyscroll')
var open = require('open-external')

function ancestor (el) {
  if(!el) return
  if(el.tagName !== 'A') return ancestor(el.parentElement)
  return el
}

exports.needs = {
  build_scroller: 'first',
  screen_view: 'first',
  search_box: 'first',
  menu: 'first',
  external_confirm:'first'
}

exports.gives = 'screen_view'

exports.create = function (api) {
  return function (path) {
    if(path !== 'tabs') return

    function setSelected (indexes) {
      var ids = indexes.map(function (index) {
        return tabs.get(index).id
      })
      if(search)
        if(ids.length > 1)
          search.value = 'split('+ids.join(',')+')'
        else
          search.value = ids[0]
    }

    var search
    var tabs = Tabs(setSelected)

    search = api.search_box(function (path, change) {

      if(tabs.has(path)) {
        tabs.select(path)
        return true
      }
      var el = api.screen_view(path)

      if(el) {
        if(!el.title) el.title = path
        el.scroll = keyscroll(el.querySelector('.Scroller .\\.content'))
        tabs.add(el, change)
  //      localStorage.openTabs = JSON.stringify(tabs.tabs)
        return change
      }
    })

    //reposition hypertabs menu to inside a container...
    tabs.insertBefore(h('div.header.row',
        h('div.header__tabs.row', tabs.firstChild), //tabs
        h('div.header__search.row.end', [search, api.menu()])
    ), tabs.firstChild)
  //  tabs.insertBefore(search, tabs.firstChild.nextSibling)

    var saved = []
  //  try { saved = JSON.parse(localStorage.openTabs) }
  //  catch (_) { }

    if(!saved || saved.length < 3)
      saved = ['/public', '/private', '/notifications', '/data']

    saved.forEach(function (path) {
      var el = api.screen_view(path)
      if(!el) return
      el.id = el.id || path
      if (!el) return
      el.scroll = keyscroll(el.querySelector('.Scroller .\\.content'))
      if(el) tabs.add(el, false, false)
    })

    tabs.select(0)

    //handle link clicks
    window.onclick = function (ev) {
      var link = ancestor(ev.target)
      if(!link) return
      var path = link.hash.substring(1)

      ev.preventDefault()
      ev.stopPropagation()

      //let the application handle this link
      if (link.getAttribute('href') === '#') return

      //open external links.
      //this ought to be made into something more runcible
      if(link.href && open.isExternal(link.href)) return api.external_confirm(link.href)

      if(tabs.has(path))
        return tabs.select(path, !ev.ctrlKey, !!ev.shiftKey)

      var el = api.screen_view(path)
      if(el) {
        el.id = el.id || path
        el.scroll = keyscroll(el.querySelector('.Scroller .\\.content'))
        tabs.add(el, !ev.ctrlKey, !!ev.shiftKey)
  //      localStorage.openTabs = JSON.stringify(tabs.tabs)
      }

      return false
    }

    window.addEventListener('keydown', function (ev) {
      if (ev.target.nodeName === 'INPUT' || ev.target.nodeName === 'TEXTAREA')
        return
      switch(ev.keyCode) {

        // scroll through tabs
        case 72: // h
          return tabs.selectRelative(-1)
        case 76: // l
          return tabs.selectRelative(1)

        // scroll through messages
        case 74: // j
          return tabs.get(tabs.selected[0]).scroll(1)
        case 75: // k
          return tabs.get(tabs.selected[0]).scroll(-1)

        // close a tab
        case 88: // x
          if (tabs.selected) {
            var sel = tabs.selected
            var i = sel.reduce(function (a, b) { return Math.min(a, b) })
            tabs.remove(sel)
            tabs.select(Math.max(i-1, 0))
          }
          return

        // activate the search field
        case 191: // /
          if (ev.shiftKey)
            search.activate('?', ev)
          else
            search.activate('/', ev)
          return

        // navigate to a feed
        case 50: // 2
          if (ev.shiftKey)
            search.activate('@', ev)
          return

        // navigate to a channel
        case 51: // 3
          if (ev.shiftKey)
            search.activate('#', ev)
          return

        // navigate to a message
        case 53: // 5
          if (ev.shiftKey)
            search.activate('%', ev)
          return
      }
    })

    // errors tab
    var {
      container: errors,
      content: errorsContent
    } = api.build_scroller()

    // remove loader error handler
    if (window.onError) {
      window.removeEventListener('error', window.onError)
      delete window.onError
    }

    // put errors in a tab
    window.addEventListener('error', function (ev) {
      var err = ev.error || ev
      if(!tabs.has('errors'))
        tabs.add(errors, false)
      var el = h('div.message',
        h('strong', err.message),
        h('pre', err.stack))
      if (errorsContent.firstChild)
        errorsContent.insertBefore(el, errorsContent.firstChild)
      else
        errorsContent.appendChild(el)
    })

    if (process.versions.electron) {
      var {webFrame} = require('electron')

      window.addEventListener('mousewheel', function (ev) {
        var {ctrlKey, deltaY} = ev
        if(ctrlKey){
          var direction = (deltaY / Math.abs(deltaY))
          var currentZoom = webFrame.getZoomLevel()
          webFrame.setZoomLevel(currentZoom - direction)
        }
      })

      window.addEventListener('contextmenu', function (ev) {
        ev.preventDefault()
        var remote = require('electron').remote
        var Menu = remote.Menu
        var MenuItem = remote.MenuItem
        var menu = new Menu()
        menu.append(new MenuItem({
          label: 'Inspect Element',
          click: function () {
            remote.getCurrentWindow().inspectElement(ev.x, ev.y)
          }
        }))
        menu.popup(remote.getCurrentWindow())
      })
    }

    return tabs
  }

}
<|endoftext|>