<commit_before>/*
use this as a guide:
    http://arduino.cc/en/Hacking/BuildProcess
 */

var fs = require('fs');
var wrench = require('wrench');
var sh = require('execSync');
var wrench = require('wrench');
var LIBRARIES = require('./libraries');

String.prototype.endsWith = function(suffix) {
    return this.indexOf(suffix, this.length - suffix.length) !== -1;
};
String.prototype.startsWith = function(suffix) {
    return this.indexOf(suffix) == 0;
};

function checkfile(path) {
    if(!fs.existsSync(path)) throw new Error("file not found " + path);
}

function detectLibs(code) {
    var libs = [];
    var lines = code.split('\n');
    lines.forEach(function(line){
        var re = /\s*#include\s*[<"](\w+)\.h[>"]/i;
        var res = line.match(re);
        if(res) libs.push(res[1]);
    });
    return libs;
}

var FUNCTION_DEFINITION_REGEX =  /(void)\s+(\w+)\((.*)\)/;

function generateDecs(code) {
    var decs = [];
    code.split('\n').forEach(function(line) {
        var def = line.match(FUNCTION_DEFINITION_REGEX);
        if(def) {
            var dec = def[1]+' '+def[2]+'('+def[3]+');\n';
            decs.push(dec);
        }
    });
    return decs;
}

function generateCPPFile(cfile,sketchPath) {
    //write the standard header
    fs.writeFileSync(cfile,'#include "Arduino.h"\n');

    var funcdecs = [];
    var codes = [];

    //loop through all sketch files
    fs.readdirSync(sketchPath).forEach(function(file){
        if(file.toLowerCase().endsWith('.ino')) {
            var code = fs.readFileSync(sketchPath+'/'+file).toString();
            generateDecs(code).forEach(function(dec){
                funcdecs.push(dec);
            });
            codes.push(code);
        }
    })

    //insert the generated definitions
    funcdecs.forEach(function(dec){
        fs.appendFileSync(cfile,dec);
    });

    //insert the code chunks
    codes.forEach(function(def){
        fs.appendFileSync(cfile,def);
    })

    //extra newline just in case
    fs.appendFileSync(cfile,"\n");

}

function calculateLibs(list, paths, libs, debug, cb, plat) {
    console.log('the list is',list);
    LIBRARIES.install(list,function() {
        console.log('done installing libraries');

        //install libs if needed, and add to the include paths
        list.forEach(function(libname){
            if(libname == 'Arduino') return; //already included, skip it
            debug('looking at lib',libname);
            var lib = LIBRARIES.getById(libname.toLowerCase());
            if(!lib) {
                debug("ERROR. couldn't find library",libname);
                return;
            }
            if(!lib.isInstalled()) {
                debug("not installed yet. we must install it");
                throw new Error("library should alredy be installed! " + libname);
            }
            debug("include path = ",lib.getIncludePaths(plat));
            lib.getIncludePaths(plat).forEach(function(path) { paths.push(path); });
            libs.push(lib);
            if(lib.dependencies) {
                console.log("deps = ",lib.dependencies);
                lib.dependencies.map(function(libname) {
                    return LIBRARIES.getById(libname);
                }).map(function(lib){
                    console.log("looking at lib",lib);
                    debug("include path = ",lib.getIncludePaths(plat));
                    lib.getIncludePaths(plat).forEach(function(path) { paths.push(path); });
                    libs.push(lib);
                })
            }
        });

        cb();
    });
    return;

}

function listdir(path) {
    return fs.readdirSync(path)
    .filter(function(file) {
        if(file.startsWith('.')) return false;
        return true;
    })
    .map(function(file) {
        return path+'/'+file;
    });
}

function exec(cmd, cb) {
    //console.log(cmd.join(' '));
    var result = sh.exec(cmd.join(' '));
    if(result.code != 0) {
        //debug("there was a problem running",cmd,result);
        var err = new Error("there was a problem running " + cmd.join(" "));
        err.cmd = cmd;
        err.output = result.stdout;
        console.log(err.output);
        throw err;
    }
    if(cb) cb();
}

function linkFile(options, file, outdir) {
    var cmd = [
        options.platform.getCompilerBinaryPath()+'/avr-ar',
        'rcs',
        outdir+'/core.a',
        file,
    ];
    exec(cmd);
}

function linkElfFile(options, libofiles, outdir) {

    //link everything into the .elf file
    var elfcmd = [
        options.platform.getCompilerBinaryPath()+'/avr-gcc', //gcc
        '-Os', //??
        '-Wl,--gc-sections', //not using relax yet
        '-mmcu='+options.device.build.mcu, //the mcu, ex: atmega168
        '-o', //??
        outdir+'/'+options.name+'.cpp.elf',
        outdir+'/'+options.name+'.cpp.o',
    ];

    elfcmd = elfcmd.concat(libofiles);
    elfcmd = elfcmd.concat([
        outdir+'/core.a',
        '-L'+__dirname+'/'+outdir,
        '-lm',
    ]);

    exec(elfcmd);
}

function extractEEPROMData(options, outdir) {
    var eepcmd = [
        options.platform.getCompilerBinaryPath()+'/avr-objcopy',
        '-O',
        'ihex',
        '-j',
        '.eeprom',
        '--set-section-flags=.eeprom=alloc,load',
        '--no-change-warnings',
        '--change-section-lma',
        '.eeprom=0',
        outdir+'/'+options.name+'.cpp.elf',
        outdir+'/'+options.name+'.eep',
    ];

    exec(eepcmd, function() { console.log("eed");});
}

function buildHexFile(options, outdir) {
    var hexcmd = [
        options.platform.getCompilerBinaryPath()+'/avr-objcopy',
        '-O',
        'ihex',
        '-R',
        '.eeprom',
        outdir+'/'+options.name+'.cpp.elf',
        outdir+'/'+options.name+'.hex',
    ];
    exec(hexcmd, function() { console.log("hexed"); });
}

function processList(list, cb, publish) {
    if(list.length <= 0) {
        cb();
        return;
    }
    var item = list.shift();
    try {
        item(function() {
            console.log("--------------------");
            processList(list,cb, publish);
        });
    } catch(err) {
        console.log("there was an error");
        console.log(err.toString());
        console.log("publish = ", publish);
        publish({
            type:'error',
            message:err.toString(),
            path:err.path,
            errno: err.errno,
            code: err.code,
        });
    }
}

exports.compile = function(sketchPath, outdir,options, publish, sketchDir, finalcb) {

    function debug(message) {
        var args = Array.prototype.slice.call(arguments);
        //console.log(args.join(' '));
        publish({type:"compile", message:args.join(" ")});
    }

    debug("compiling ",sketchPath,"to dir",outdir);
    debug("root sketch dir = ",sketchDir);

    var tmp = "build/tmp";
    //wrench.rmdirSyncRecursive(outpath, true);
    wrench.mkdirSyncRecursive(tmp);
    wrench.mkdirSyncRecursive(outdir);

    debug("assembling the sketch in the directory",tmp);
    checkfile(tmp);

    var tasks = [];

    var cfile = tmp+'/'+options.name+'.cpp';
    var cfiles = [];
    var includepaths = [];
    var libextra = [];
    var plat = options.platform;

    //generate the CPP file and copy all files to the output directory
    tasks.push(function(cb) {
        debug("generating",cfile);
        generateCPPFile(cfile,sketchPath);

        cfiles.push(cfile);
        //compile sketch files
        function copyToDir(file, indir, outdir) {
            console.log("copying ",file);
            var text = fs.readFileSync(indir+'/'+file);
            fs.writeFileSync(outdir+'/'+file,text);
        }
        fs.readdirSync(sketchDir).forEach(function(file) {
            if(file.toLowerCase().endsWith('.h')) copyToDir(file,sketchDir,tmp);
            if(file.toLowerCase().endsWith('.cpp')) copyToDir(file,sketchDir,tmp);
            cfiles.push(tmp+'/'+file);
        });
        cb();
    })

    // scan for the included libs
    // make sure they are all installed
    // collect their include paths
    tasks.push(function(cb) {

        var includedLibs = detectLibs(fs.readFileSync(cfile).toString());
        debug('scanned for included libs',includedLibs);

        //assemble library paths
        var librarypaths = [];
        //global libs
        debug("standard arduino libs = ",plat.getStandardLibraryPath());
        fs.readdirSync(plat.getStandardLibraryPath()).forEach(function(lib) {
            librarypaths.push(plat.getStandardLibraryPath()+'/'+lib);
        });

        //TODO userlibs

        //standard global includes for the arduino core itself
        includepaths.push(plat.getCorePath(options.device));
        includepaths.push(plat.getVariantPath(options.device));
        includepaths.push(sketchDir);

        console.log("include path =",includepaths);
        console.log("includedlibs = ", includedLibs);
        calculateLibs(includedLibs,includepaths,libextra, debug, cb, plat);
    });

    //actually compile code
    tasks.push(function(cb) {
        console.log("moving on now");
        //debug("included libs = ", includedLibs);
        debug("include paths = ", includepaths);
        debug("using 3rd party libraries",libextra.map(function(lib) { return lib.id }).join(', '));
        compileFiles(options,outdir,includepaths,cfiles,debug);
        cb();
    });

    //compile the 3rd party libs
    tasks.push(function(cb) {
        debug("compiling 3rd party libs");
        libextra.forEach(function(lib) {
            debug('compiling library: ',lib.id);
            var paths = lib.getIncludePaths(plat);
            var cfiles = [];
            paths.forEach(function(path) {
                wrench.readdirSyncRecursive(path)
                    .filter(function(filename) {
                        if(filename.startsWith('examples/')) return false;
                        if(filename.toLowerCase().endsWith('.c')) return true;
                        if(filename.toLowerCase().endsWith('.cpp')) return true;
                        return false;
                    })
                    .forEach(function(filename) {
                        cfiles.push(path+'/'+filename);
                    })
                ;
            });
            compileFiles(options, outdir, includepaths, cfiles, debug);
        });
        cb();
    });

    //compile core
    tasks.push(function(cb) {
        debug("compiling core files");
        var cfiles = listdir(plat.getCorePath(options.device));
        compileFiles(options,outdir,includepaths,cfiles,debug);
        cb();
    });

    //compile core avr-libc
    tasks.push(function(cb) {
        var cfiles = listdir(plat.getCorePath(options.device)+'/avr-libc');
        compileFiles(options,outdir,includepaths,cfiles,debug);
        cb();
    });

    //link everything into core.a
    tasks.push(function(cb) {
        listdir(outdir)
            .filter(function(file){
                if(file.endsWith('.d')) return false;
                return true;
            })
            .forEach(function(file) {
                debug("linking",file);
                linkFile(options,file,outdir);
            });

        debug("building elf file");

        var libofiles = [];
        libextra.forEach(function(lib) {
            var paths = lib.getIncludePaths(plat);
            paths.forEach(function(path) {
                listdir(path).filter(function(file) {
                    if(file.endsWith('.cpp')) return true;
                    return false;
                }).map(function(filename) {
                    libofiles.push('build/out/'+filename.substring(filename.lastIndexOf('/')+1) + '.o');
                });
            });
        });

        linkElfFile(options,libofiles,outdir);
        cb();
    });

    // 5. extract EEPROM data (from EEMEM directive) to .eep file.
    tasks.push(function(cb) {
        debug("extracting EEPROM data");
        extractEEPROMData(options,outdir);
        cb();
    });

    // 6. build the .hex file
    tasks.push(function(cb) {
        debug("building .HEX file");
        buildHexFile(options,outdir);
        cb();
    });
    processList(tasks,finalcb, publish);
    return;

}

function compileFiles(options, outdir, includepaths, cfiles,debug) {
    cfiles.forEach(function(file) {
        var fname = file.substring(file.lastIndexOf('/')+1);
        if(fname.startsWith('.')) return;
        if(file.toLowerCase().endsWith('.txt')) return;
        if(file.toLowerCase().endsWith('.md')) return;
        if(file.toLowerCase().endsWith('.h')) return;
        if(file.toLowerCase().endsWith('examples')) return;
        if(file.toLowerCase().endsWith('/avr-libc')) return;
        console.log("looking at file",file);
        if(file.toLowerCase().endsWith('.c')) {
            compileC(options,outdir, includepaths, file,debug);
            return;
        }
        if(file.toLowerCase().endsWith('.cpp')) {
            compileCPP(options,outdir, includepaths, file,debug);
            return;
        }
        debug("still need to compile",file);
        //throw new Error("couldn't compile file: "+file);
    })
}

function compileCPP(options, outdir, includepaths, cfile,debug) {
    debug("compiling ",cfile);//,"to",outdir,"with options",options);
    //console.log("include files = ",includepaths);
    //console.log("options = ",options);

    var cmd = [
        options.platform.getCompilerBinaryPath(options.device)+"/avr-g++",
        "-c", //compile, don't link
        '-g', //include debug info and line numbers
        '-Os', //optimize for size
        '-Wall', //turn on verbose warnings
        '-fno-exceptions',// ??
        '-ffunction-sections',// put each function in it's own section
        '-fdata-sections', //??
        '-mmcu='+options.device.build.mcu,
        '-DF_CPU='+options.device.build.f_cpu,
        '-MMD',//output dependency info
        '-DARDUINO=105', //??
        '-DUSB_VID='+options.device.build.vid, //??
        '-DUSB_PID='+options.device.build.pid, //??
    ];

    includepaths.forEach(function(path){
        cmd.push("-I"+path);
    })

    cmd.push(cfile); //add the actual c++ file
    cmd.push('-o'); //output object file
    var filename = cfile.substring(cfile.lastIndexOf('/')+1);
    cmd.push(outdir+'/'+filename+'.o');
    debug(cmd.join(' '));

    exec(cmd);
}

function compileC(options, outdir, includepaths, cfile, debug) {
    console.log(cfile);
    debug("compiling ",cfile);//,"to",outdir,"with options",options);
    var cmd = [
    options.platform.getCompilerBinaryPath(options.device)+"/avr-gcc", //gcc
        "-c", //compile, don't link
        '-g', //include debug info and line numbers
        '-Os', //optimize for size
        '-Wall', //turn on verbose warnings
        '-ffunction-sections',// put each function in it's own section
        '-fdata-sections', //??
        '-mmcu='+options.device.build.mcu,
        '-DF_CPU='+options.device.build.f_cpu,
        '-MMD',//output dependency info
        '-DARDUINO=105', //??
        '-DUSB_VID='+options.device.vid, //??
        '-DUSB_PID='+options.device.pid, //??
    ];
    includepaths.forEach(function(path){
        cmd.push("-I"+path);
    })
    cmd.push(cfile); //add the actual c file
    cmd.push('-o');
    var filename = cfile.substring(cfile.lastIndexOf('/')+1);
    cmd.push(outdir+'/'+filename+'.o');

    exec(cmd);
}
<commit_msg>nuke build dir before building<commit_after>/*
use this as a guide:
    http://arduino.cc/en/Hacking/BuildProcess
 */

var fs = require('fs');
var wrench = require('wrench');
var sh = require('execSync');
var wrench = require('wrench');
var LIBRARIES = require('./libraries');

String.prototype.endsWith = function(suffix) {
    return this.indexOf(suffix, this.length - suffix.length) !== -1;
};
String.prototype.startsWith = function(suffix) {
    return this.indexOf(suffix) == 0;
};

function checkfile(path) {
    if(!fs.existsSync(path)) throw new Error("file not found " + path);
}

function detectLibs(code) {
    var libs = [];
    var lines = code.split('\n');
    lines.forEach(function(line){
        var re = /\s*#include\s*[<"](\w+)\.h[>"]/i;
        var res = line.match(re);
        if(res) libs.push(res[1]);
    });
    return libs;
}

var FUNCTION_DEFINITION_REGEX =  /(void)\s+(\w+)\((.*)\)/;

function generateDecs(code) {
    var decs = [];
    code.split('\n').forEach(function(line) {
        var def = line.match(FUNCTION_DEFINITION_REGEX);
        if(def) {
            var dec = def[1]+' '+def[2]+'('+def[3]+');\n';
            decs.push(dec);
        }
    });
    return decs;
}

function generateCPPFile(cfile,sketchPath) {
    //write the standard header
    fs.writeFileSync(cfile,'#include "Arduino.h"\n');

    var funcdecs = [];
    var codes = [];

    //loop through all sketch files
    fs.readdirSync(sketchPath).forEach(function(file){
        if(file.toLowerCase().endsWith('.ino')) {
            var code = fs.readFileSync(sketchPath+'/'+file).toString();
            generateDecs(code).forEach(function(dec){
                funcdecs.push(dec);
            });
            codes.push(code);
        }
    })

    //insert the generated definitions
    funcdecs.forEach(function(dec){
        fs.appendFileSync(cfile,dec);
    });

    //insert the code chunks
    codes.forEach(function(def){
        fs.appendFileSync(cfile,def);
    })

    //extra newline just in case
    fs.appendFileSync(cfile,"\n");

}

function calculateLibs(list, paths, libs, debug, cb, plat) {
    console.log('the list is',list);
    LIBRARIES.install(list,function() {
        console.log('done installing libraries');

        //install libs if needed, and add to the include paths
        list.forEach(function(libname){
            if(libname == 'Arduino') return; //already included, skip it
            debug('looking at lib',libname);
            var lib = LIBRARIES.getById(libname.toLowerCase());
            if(!lib) {
                debug("ERROR. couldn't find library",libname);
                return;
            }
            if(!lib.isInstalled()) {
                debug("not installed yet. we must install it");
                throw new Error("library should alredy be installed! " + libname);
            }
            debug("include path = ",lib.getIncludePaths(plat));
            lib.getIncludePaths(plat).forEach(function(path) { paths.push(path); });
            libs.push(lib);
            if(lib.dependencies) {
                console.log("deps = ",lib.dependencies);
                lib.dependencies.map(function(libname) {
                    return LIBRARIES.getById(libname);
                }).map(function(lib){
                    console.log("looking at lib",lib);
                    debug("include path = ",lib.getIncludePaths(plat));
                    lib.getIncludePaths(plat).forEach(function(path) { paths.push(path); });
                    libs.push(lib);
                })
            }
        });

        cb();
    });
    return;

}

function listdir(path) {
    return fs.readdirSync(path)
    .filter(function(file) {
        if(file.startsWith('.')) return false;
        return true;
    })
    .map(function(file) {
        return path+'/'+file;
    });
}

function exec(cmd, cb) {
    //console.log(cmd.join(' '));
    var result = sh.exec(cmd.join(' '));
    if(result.code != 0) {
        //debug("there was a problem running",cmd,result);
        var err = new Error("there was a problem running " + cmd.join(" "));
        err.cmd = cmd;
        err.output = result.stdout;
        console.log(err.output);
        throw err;
    }
    if(cb) cb();
}

function linkFile(options, file, outdir) {
    var cmd = [
        options.platform.getCompilerBinaryPath()+'/avr-ar',
        'rcs',
        outdir+'/core.a',
        file,
    ];
    exec(cmd);
}

function linkElfFile(options, libofiles, outdir) {

    //link everything into the .elf file
    var elfcmd = [
        options.platform.getCompilerBinaryPath()+'/avr-gcc', //gcc
        '-Os', //??
        '-Wl,--gc-sections', //not using relax yet
        '-mmcu='+options.device.build.mcu, //the mcu, ex: atmega168
        '-o', //??
        outdir+'/'+options.name+'.cpp.elf',
        outdir+'/'+options.name+'.cpp.o',
    ];

    elfcmd = elfcmd.concat(libofiles);
    elfcmd = elfcmd.concat([
        outdir+'/core.a',
        '-L'+__dirname+'/'+outdir,
        '-lm',
    ]);

    exec(elfcmd);
}

function extractEEPROMData(options, outdir) {
    var eepcmd = [
        options.platform.getCompilerBinaryPath()+'/avr-objcopy',
        '-O',
        'ihex',
        '-j',
        '.eeprom',
        '--set-section-flags=.eeprom=alloc,load',
        '--no-change-warnings',
        '--change-section-lma',
        '.eeprom=0',
        outdir+'/'+options.name+'.cpp.elf',
        outdir+'/'+options.name+'.eep',
    ];

    exec(eepcmd, function() { console.log("eed");});
}

function buildHexFile(options, outdir) {
    var hexcmd = [
        options.platform.getCompilerBinaryPath()+'/avr-objcopy',
        '-O',
        'ihex',
        '-R',
        '.eeprom',
        outdir+'/'+options.name+'.cpp.elf',
        outdir+'/'+options.name+'.hex',
    ];
    exec(hexcmd, function() { console.log("hexed"); });
}

function processList(list, cb, publish) {
    if(list.length <= 0) {
        cb();
        return;
    }
    var item = list.shift();
    try {
        item(function() {
            console.log("--------------------");
            processList(list,cb, publish);
        });
    } catch(err) {
        console.log("there was an error");
        console.log(err.toString());
        console.log("publish = ", publish);
        publish({
            type:'error',
            message:err.toString(),
            path:err.path,
            errno: err.errno,
            code: err.code,
        });
    }
}

exports.compile = function(sketchPath, outdir,options, publish, sketchDir, finalcb) {

    function debug(message) {
        var args = Array.prototype.slice.call(arguments);
        //console.log(args.join(' '));
        publish({type:"compile", message:args.join(" ")});
    }

    debug("compiling ",sketchPath,"to dir",outdir);
    debug("root sketch dir = ",sketchDir);

    var tmp = "build/tmp";
    wrench.rmdirSyncRecursive(outdir, true);
    wrench.mkdirSyncRecursive(tmp);
    wrench.mkdirSyncRecursive(outdir);

    debug("assembling the sketch in the directory",tmp);
    checkfile(tmp);

    var tasks = [];

    var cfile = tmp+'/'+options.name+'.cpp';
    var cfiles = [];
    var includepaths = [];
    var libextra = [];
    var plat = options.platform;

    //generate the CPP file and copy all files to the output directory
    tasks.push(function(cb) {
        debug("generating",cfile);
        generateCPPFile(cfile,sketchPath);

        cfiles.push(cfile);
        //compile sketch files
        function copyToDir(file, indir, outdir) {
            console.log("copying ",file);
            var text = fs.readFileSync(indir+'/'+file);
            fs.writeFileSync(outdir+'/'+file,text);
        }
        fs.readdirSync(sketchDir).forEach(function(file) {
            if(file.toLowerCase().endsWith('.h')) copyToDir(file,sketchDir,tmp);
            if(file.toLowerCase().endsWith('.cpp')) copyToDir(file,sketchDir,tmp);
            cfiles.push(tmp+'/'+file);
        });
        cb();
    })

    // scan for the included libs
    // make sure they are all installed
    // collect their include paths
    tasks.push(function(cb) {

        var includedLibs = detectLibs(fs.readFileSync(cfile).toString());
        debug('scanned for included libs',includedLibs);

        //assemble library paths
        var librarypaths = [];
        //global libs
        debug("standard arduino libs = ",plat.getStandardLibraryPath());
        fs.readdirSync(plat.getStandardLibraryPath()).forEach(function(lib) {
            librarypaths.push(plat.getStandardLibraryPath()+'/'+lib);
        });

        //TODO userlibs

        //standard global includes for the arduino core itself
        includepaths.push(plat.getCorePath(options.device));
        includepaths.push(plat.getVariantPath(options.device));
        includepaths.push(sketchDir);

        console.log("include path =",includepaths);
        console.log("includedlibs = ", includedLibs);
        calculateLibs(includedLibs,includepaths,libextra, debug, cb, plat);
    });

    //actually compile code
    tasks.push(function(cb) {
        console.log("moving on now");
        //debug("included libs = ", includedLibs);
        debug("include paths = ", includepaths);
        debug("using 3rd party libraries",libextra.map(function(lib) { return lib.id }).join(', '));
        compileFiles(options,outdir,includepaths,cfiles,debug);
        cb();
    });

    //compile the 3rd party libs
    tasks.push(function(cb) {
        debug("compiling 3rd party libs");
        libextra.forEach(function(lib) {
            debug('compiling library: ',lib.id);
            var paths = lib.getIncludePaths(plat);
            var cfiles = [];
            paths.forEach(function(path) {
                wrench.readdirSyncRecursive(path)
                    .filter(function(filename) {
                        if(filename.startsWith('examples/')) return false;
                        if(filename.toLowerCase().endsWith('.c')) return true;
                        if(filename.toLowerCase().endsWith('.cpp')) return true;
                        return false;
                    })
                    .forEach(function(filename) {
                        cfiles.push(path+'/'+filename);
                    })
                ;
            });
            compileFiles(options, outdir, includepaths, cfiles, debug);
        });
        cb();
    });

    //compile core
    tasks.push(function(cb) {
        debug("compiling core files");
        var cfiles = listdir(plat.getCorePath(options.device));
        compileFiles(options,outdir,includepaths,cfiles,debug);
        cb();
    });

    //compile core avr-libc
    tasks.push(function(cb) {
        var cfiles = listdir(plat.getCorePath(options.device)+'/avr-libc');
        compileFiles(options,outdir,includepaths,cfiles,debug);
        cb();
    });

    //link everything into core.a
    tasks.push(function(cb) {
        listdir(outdir)
            .filter(function(file){
                if(file.endsWith('.d')) return false;
                return true;
            })
            .forEach(function(file) {
                debug("linking",file);
                linkFile(options,file,outdir);
            });

        debug("building elf file");

        var libofiles = [];
        libextra.forEach(function(lib) {
            var paths = lib.getIncludePaths(plat);
            paths.forEach(function(path) {
                listdir(path).filter(function(file) {
                    if(file.endsWith('.cpp')) return true;
                    return false;
                }).map(function(filename) {
                    libofiles.push('build/out/'+filename.substring(filename.lastIndexOf('/')+1) + '.o');
                });
            });
        });

        linkElfFile(options,libofiles,outdir);
        cb();
    });

    // 5. extract EEPROM data (from EEMEM directive) to .eep file.
    tasks.push(function(cb) {
        debug("extracting EEPROM data");
        extractEEPROMData(options,outdir);
        cb();
    });

    // 6. build the .hex file
    tasks.push(function(cb) {
        debug("building .HEX file");
        buildHexFile(options,outdir);
        cb();
    });
    processList(tasks,finalcb, publish);
    return;

}

function compileFiles(options, outdir, includepaths, cfiles,debug) {
    cfiles.forEach(function(file) {
        var fname = file.substring(file.lastIndexOf('/')+1);
        if(fname.startsWith('.')) return;
        if(file.toLowerCase().endsWith('.txt')) return;
        if(file.toLowerCase().endsWith('.md')) return;
        if(file.toLowerCase().endsWith('.h')) return;
        if(file.toLowerCase().endsWith('examples')) return;
        if(file.toLowerCase().endsWith('/avr-libc')) return;
        console.log("looking at file",file);
        if(file.toLowerCase().endsWith('.c')) {
            compileC(options,outdir, includepaths, file,debug);
            return;
        }
        if(file.toLowerCase().endsWith('.cpp')) {
            compileCPP(options,outdir, includepaths, file,debug);
            return;
        }
        debug("still need to compile",file);
        //throw new Error("couldn't compile file: "+file);
    })
}

function compileCPP(options, outdir, includepaths, cfile,debug) {
    debug("compiling ",cfile);//,"to",outdir,"with options",options);
    //console.log("include files = ",includepaths);
    //console.log("options = ",options);

    var cmd = [
        options.platform.getCompilerBinaryPath(options.device)+"/avr-g++",
        "-c", //compile, don't link
        '-g', //include debug info and line numbers
        '-Os', //optimize for size
        '-Wall', //turn on verbose warnings
        '-fno-exceptions',// ??
        '-ffunction-sections',// put each function in it's own section
        '-fdata-sections', //??
        '-mmcu='+options.device.build.mcu,
        '-DF_CPU='+options.device.build.f_cpu,
        '-MMD',//output dependency info
        '-DARDUINO=105', //??
        '-DUSB_VID='+options.device.build.vid, //??
        '-DUSB_PID='+options.device.build.pid, //??
    ];

    includepaths.forEach(function(path){
        cmd.push("-I"+path);
    })

    cmd.push(cfile); //add the actual c++ file
    cmd.push('-o'); //output object file
    var filename = cfile.substring(cfile.lastIndexOf('/')+1);
    cmd.push(outdir+'/'+filename+'.o');
    debug(cmd.join(' '));

    exec(cmd);
}

function compileC(options, outdir, includepaths, cfile, debug) {
    console.log(cfile);
    debug("compiling ",cfile);//,"to",outdir,"with options",options);
    var cmd = [
    options.platform.getCompilerBinaryPath(options.device)+"/avr-gcc", //gcc
        "-c", //compile, don't link
        '-g', //include debug info and line numbers
        '-Os', //optimize for size
        '-Wall', //turn on verbose warnings
        '-ffunction-sections',// put each function in it's own section
        '-fdata-sections', //??
        '-mmcu='+options.device.build.mcu,
        '-DF_CPU='+options.device.build.f_cpu,
        '-MMD',//output dependency info
        '-DARDUINO=105', //??
        '-DUSB_VID='+options.device.vid, //??
        '-DUSB_PID='+options.device.pid, //??
    ];
    includepaths.forEach(function(path){
        cmd.push("-I"+path);
    })
    cmd.push(cfile); //add the actual c file
    cmd.push('-o');
    var filename = cfile.substring(cfile.lastIndexOf('/')+1);
    cmd.push(outdir+'/'+filename+'.o');

    exec(cmd);
}
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'

import pic01 from '../images/pic01.jpg'
import pic02 from '../images/pic02.jpg'
import pic03 from '../images/pic03.jpg'

class Main extends React.Component {
  render() {

    let close =
 {this.props.onCloseArticle()}}>

    return (

        <article id="intro" className={`${this.props.article === 'intro' ? 'active' : ''} ${this.props.articleTimeout ? 'timeout' : ''}`} style={{display:'none'}}>

Intro

Experienced Software Engineer with a demonstrated history of working in the tech, health and arts industries. Skilled in Wireframing, Business Process Design, Javascript, Web Design, and Management

          {close}

        <article id="work" className={`${this.props.article === 'work' ? 'active' : ''} ${this.props.articleTimeout ? 'timeout' : ''}`} style={{display:'none'}}>

Work

Adipiscing magna sed dolor elit. Praesent eleifend dignissim arcu, at eleifend sapien imperdiet ac. Aliquam erat volutpat. Praesent urna nisi, fringila lorem et vehicula lacinia quam. Integer sollicitudin mauris nec lorem luctus ultrices.

Nullam et orci eu lorem consequat tincidunt vivamus et sagittis libero. Mauris aliquet magna magna sed nunc rhoncus pharetra. Pellentesque condimentum sem. In efficitur ligula tate urna. Maecenas laoreet massa vel lacinia pellentesque lorem ipsum dolor. Nullam et orci eu lorem consequat tincidunt. Vivamus et sagittis libero. Mauris aliquet magna magna sed nunc rhoncus amet feugiat tempus.

          {close}

        <article id="about" className={`${this.props.article === 'about' ? 'active' : ''} ${this.props.articleTimeout ? 'timeout' : ''}`} style={{display:'none'}}>

About

Lorem ipsum dolor sit amet, consectetur et adipiscing elit. Praesent eleifend dignissim arcu, at eleifend sapien imperdiet ac. Aliquam erat volutpat. Praesent urna nisi, fringila lorem et vehicula lacinia quam. Integer sollicitudin mauris nec lorem luctus ultrices. Aliquam libero et malesuada fames ac ante ipsum primis in faucibus. Cras viverra ligula sit amet ex mollis mattis lorem ipsum dolor sit amet.

          {close}

        <article id="contact" className={`${this.props.article === 'contact' ? 'active' : ''} ${this.props.articleTimeout ? 'timeout' : ''}`} style={{display:'none'}}>

Contact

          <form method="post" action="#">

              Name
              <input type="text" name="name" id="name" />

              Email
              <input type="text" name="email" id="email" />

              Message
              <textarea name="message" id="message" rows="4"></textarea>

<input type="submit" value="Send Message" className="special" />

<input type="reset" value="Reset" />

          </form>

LinkedIn

Instagram

GitHub

          {close}

    )
  }
}

Main.propTypes = {
  route: PropTypes.object,
  article: PropTypes.string,
  articleTimeout: PropTypes.bool,
  onCloseArticle: PropTypes.func,
  timeout: PropTypes.bool,
  setWrapperRef: PropTypes.func.isRequired,
}

export default Main
<commit_msg>Netlify config<commit_after>import React from 'react'
import PropTypes from 'prop-types'

import pic01 from '../images/pic01.jpg'
import pic02 from '../images/pic02.jpg'
import pic03 from '../images/pic03.jpg'

class Main extends React.Component {
  render() {

    let close =
 {this.props.onCloseArticle()}}>

    return (

        <article id="intro" className={`${this.props.article === 'intro' ? 'active' : ''} ${this.props.articleTimeout ? 'timeout' : ''}`} style={{display:'none'}}>

Intro

Experienced Software Engineer with a demonstrated history of working in the tech, health and arts industries. Skilled in Wireframing, Business Process Design, Javascript, Web Design, and Management

          {close}

        <article id="work" className={`${this.props.article === 'work' ? 'active' : ''} ${this.props.articleTimeout ? 'timeout' : ''}`} style={{display:'none'}}>

Work

Adipiscing magna sed dolor elit. Praesent eleifend dignissim arcu, at eleifend sapien imperdiet ac. Aliquam erat volutpat. Praesent urna nisi, fringila lorem et vehicula lacinia quam. Integer sollicitudin mauris nec lorem luctus ultrices.

Nullam et orci eu lorem consequat tincidunt vivamus et sagittis libero. Mauris aliquet magna magna sed nunc rhoncus pharetra. Pellentesque condimentum sem. In efficitur ligula tate urna. Maecenas laoreet massa vel lacinia pellentesque lorem ipsum dolor. Nullam et orci eu lorem consequat tincidunt. Vivamus et sagittis libero. Mauris aliquet magna magna sed nunc rhoncus amet feugiat tempus.

          {close}

        <article id="about" className={`${this.props.article === 'about' ? 'active' : ''} ${this.props.articleTimeout ? 'timeout' : ''}`} style={{display:'none'}}>

About

Lorem ipsum dolor sit amet, consectetur et adipiscing elit. Praesent eleifend dignissim arcu, at eleifend sapien imperdiet ac. Aliquam erat volutpat. Praesent urna nisi, fringila lorem et vehicula lacinia quam. Integer sollicitudin mauris nec lorem luctus ultrices. Aliquam libero et malesuada fames ac ante ipsum primis in faucibus. Cras viverra ligula sit amet ex mollis mattis lorem ipsum dolor sit amet.

          {close}

        <article id="contact" className={`${this.props.article === 'contact' ? 'active' : ''} ${this.props.articleTimeout ? 'timeout' : ''}`} style={{display:'none'}}>

Contact

          <form method="post" action="#" netlify>

              Name
              <input type="text" name="name" id="name" />

              Email
              <input type="text" name="email" id="email" />

              Message
              <textarea name="message" id="message" rows="4"></textarea>

<input type="submit" value="Send Message" className="special" />

<input type="reset" value="Reset" />

          </form>

LinkedIn

Instagram

GitHub

          {close}

    )
  }
}

Main.propTypes = {
  route: PropTypes.object,
  article: PropTypes.string,
  articleTimeout: PropTypes.bool,
  onCloseArticle: PropTypes.func,
  timeout: PropTypes.bool,
  setWrapperRef: PropTypes.func.isRequired,
}

export default Main
<|endoftext|>


<commit_before>var drafts = (function () {

var exports = {};

var draft_model = (function () {
    var exports = {};

    // the key that the drafts are stored under.
    var KEY = "drafts";
    var ls = localstorage();
    ls.version = 1;

    function getTimestamp() {
        return new Date().getTime();
    }

    function get() {
        return ls.get(KEY) || {};
    }
    exports.get = get;

    exports.getDraft = function (id) {
        return get()[id] || false;
    };

    function save(drafts) {
        ls.set(KEY, drafts);
    }

    exports.addDraft = function (draft) {
        var drafts = get();

        // use the base16 of the current time + a random string to reduce
        // collisions to essentially zero.
        var id = getTimestamp().toString(16) + "-" + Math.random().toString(16).split(/\./).pop();

        draft.updatedAt = getTimestamp();
        drafts[id] = draft;
        save(drafts);

        return id;
    };

    exports.editDraft = function (id, draft) {
        var drafts = get();

        if (drafts[id]) {
            draft.updatedAt = getTimestamp();
            drafts[id] = draft;
            save(drafts);
        }
    };

    exports.deleteDraft = function (id) {
        var drafts = get();

        delete drafts[id];
        save(drafts);
    };

    return exports;
}());

exports.draft_model = draft_model;

exports.snapshot_message = function () {
    if (!compose_state.composing() || (compose_state.message_content() === "")) {
        // If you aren't in the middle of composing the body of a
        // message, don't try to snapshot.
        return;
    }

    // Save what we can.
    var message = {
        type: compose_state.get_message_type(),
        content: compose_state.message_content(),
    };
    if (message.type === "private") {
        var recipient = compose_state.recipient();
        message.reply_to = recipient;
        message.private_message_recipient = recipient;
    } else {
        message.stream = compose_state.stream_name();
        message.subject = compose_state.subject();
    }
    return message;
};

exports.update_draft = function () {
    var draft = drafts.snapshot_message();
    var draft_id = $("#new_message_content").data("draft-id");

    if (draft_id !== undefined) {
        if (draft !== undefined) {
            draft_model.editDraft(draft_id, draft);
        } else {
            draft_model.deleteDraft(draft_id);
        }
    } else {
        if (draft !== undefined) {
            var new_draft_id = draft_model.addDraft(draft);
            $("#new_message_content").data("draft-id", new_draft_id);
        }
    }
};

exports.delete_draft_after_send = function () {
    var draft_id = $("#new_message_content").data("draft-id");
    if (draft_id) {
        draft_model.deleteDraft(draft_id);
    }
    $("#new_message_content").removeData("draft-id");
};

exports.restore_draft = function (draft_id) {
    var draft = draft_model.getDraft(draft_id);
    if (!draft) {
        return;
    }

    var draft_copy = _.extend({}, draft);
    if ((draft_copy.type === "stream" &&
         draft_copy.stream.length > 0 &&
             draft_copy.subject.length > 0) ||
                 (draft_copy.type === "private" &&
                  draft_copy.reply_to.length > 0)) {
        draft_copy = _.extend({replying_to_message: draft_copy},
                              draft_copy);
    }

    if (draft.type === "stream") {
        if (draft.stream !== "") {
            narrow.activate([{operator: "stream", operand: draft.stream},
                             {operator: "topic", operand: draft.subject}],
                             {select_first_unread: true, trigger: "restore draft"});
        }
    } else {
        if (draft.private_message_recipient !== "") {
            narrow.activate([{operator: "pm-with", operand: draft.private_message_recipient}],
                             {select_first_unread: true, trigger: "restore draft"});
        }
    }

    overlays.close_overlay("drafts");
    compose_fade.clear_compose();
    if (draft.type === "stream" && draft.stream === "") {
        draft_copy.subject = "";
    }
    compose_actions.start(draft_copy.type, draft_copy);
    compose_ui.autosize_textarea();
    $("#new_message_content").data("draft-id", draft_id);
};

exports.setup_page = function (callback) {
    function setup_event_handlers() {
        $(".restore-draft").on("click", function (e) {
            e.stopPropagation();

            var draft_row = $(this).closest(".draft-row");
            var draft_id = draft_row.data("draft-id");
            exports.restore_draft(draft_id);
        });

        $(".draft_controls .delete-draft").on("click", function () {
            var draft_row = $(this).closest(".draft-row");
            var draft_id = draft_row.data("draft-id");

            exports.draft_model.deleteDraft(draft_id);
            draft_row.remove();

            if ($("#drafts_table .draft-row").length === 0) {
                $('#drafts_table .no-drafts').show();
            }
        });
    }

    function format_drafts(data) {
        var drafts = _.mapObject(data, function (draft, id) {
            var formatted;
            if (draft.type === "stream") {
                // In case there is no stream for the draft, we need a
                // single space char for proper rendering of the stream label
                var space_string = new Handlebars.SafeString(" ");
                var stream = (draft.stream.length > 0 ? draft.stream : space_string);
                var draft_topic = draft.subject.length === 0 ?
                        compose.empty_topic_placeholder() : draft.subject;

                formatted = {
                    draft_id: id,
                    is_stream: true,
                    stream: stream,
                    stream_color: stream_data.get_color(draft.stream),
                    topic: draft_topic,
                    raw_content: draft.content,

                };

                markdown.apply_markdown(formatted);
            } else {
                var emails = util.extract_pm_recipients(draft.private_message_recipient);
                var recipients = _.map(emails, function (email) {
                    email = email.trim();
                    var person = people.get_by_email(email);
                    if (person !== undefined) {
                        return person.full_name;
                    }
                    return email;
                }).join(', ');

                formatted = {
                    draft_id: id,
                    is_stream: false,
                    recipients: recipients,
                    raw_content: draft.content,
                };
                markdown.apply_markdown(formatted);
            }
            return formatted;
        });
        return drafts;
    }

    function _populate_and_fill() {
        $('#drafts_table').empty();
        var drafts = format_drafts(draft_model.get());
        var rendered = templates.render('draft_table_body', { drafts: drafts });
        $('#drafts_table').append(rendered);
        if ($("#drafts_table .draft-row").length > 0) {
            $('#drafts_table .no-drafts').hide();
        }

        if (callback) {
            callback();
        }

        setup_event_handlers();
    }

    function populate_and_fill() {
        i18n.ensure_i18n(function () {
            _populate_and_fill();
        });
    }
    populate_and_fill();
};

function drafts_initialize_focus(event_name) {
    // If a draft is not focused in draft modal, then focus the last draft
    // if up_arrow is clicked or the first draft if down_arrow is clicked.
    if (event_name !== "up_arrow" && event_name !== "down_arrow" || $(".draft-info-box:focus")[0]) {
        return;
    }

    var draft_arrow = draft_model.get();
    var draft_id_arrow = Object.getOwnPropertyNames(draft_arrow);
    if (draft_id_arrow.length === 0) { // empty drafts modal
        return;
    }

    var draft_element;
    if (event_name === "up_arrow") {
        draft_element = document.querySelectorAll('[data-draft-id="' + draft_id_arrow[draft_id_arrow.length-1] + '"]');
    } else if (event_name === "down_arrow") {
        draft_element = document.querySelectorAll('[data-draft-id="' + draft_id_arrow[0] + '"]');
    }
    var focus_element = draft_element[0].children[0];
    focus_element.focus();
}

function drafts_scroll(next_focus_draft_row) {
    if (next_focus_draft_row[0] === undefined) {
        return;
    }
    if (next_focus_draft_row[0].children[0] === undefined) {
        return;
    }
    next_focus_draft_row[0].children[0].focus();

    // If focused draft is first draft, scroll to the top.
    if ($(".draft-info-box:first")[0].parentElement === next_focus_draft_row[0]) {
        $(".drafts-list")[0].scrollTop = 0;
    }

    // If focused draft is the last draft, scroll to the bottom.
    if ($(".draft-info-box:last")[0].parentElement === next_focus_draft_row[0]) {
        $(".drafts-list")[0].scrollTop = $('.drafts-list')[0].scrollHeight - $('.drafts-list').height();
    }

    // If focused draft is cut off from the top, scroll up halfway in draft modal.
    if (next_focus_draft_row.position().top < 55) {
        // 55 is the minimum distance from the top that will require extra scrolling.
        $(".drafts-list")[0].scrollTop -= $(".drafts-list")[0].clientHeight / 2;
    }

    // If focused draft is cut off from the bottom, scroll down halfway in draft modal.
    var dist_from_top = next_focus_draft_row.position().top;
    var total_dist = dist_from_top + next_focus_draft_row[0].clientHeight;
    var dist_from_bottom = $(".drafts-container")[0].clientHeight - total_dist;
    if (dist_from_bottom < -4) {
        //-4 is the min dist from the bottom that will require extra scrolling.
        $(".drafts-list")[0].scrollTop += $(".drafts-list")[0].clientHeight / 2;
    }
}

exports.drafts_handle_events = function (e, event_key) {
    var draft_arrow = draft_model.get();
    var draft_id_arrow = Object.getOwnPropertyNames(draft_arrow);
    drafts_initialize_focus(event_key);

    // This detects up arrow key presses when the draft overlay
    // is open and scrolls through the drafts.
    if (event_key === "up_arrow") {
        var focus_draft_up_row = $(".draft-info-box:focus")[0].parentElement;
        var prev_focus_draft_row = $(focus_draft_up_row).prev();
        drafts_scroll(prev_focus_draft_row);
    }

    // This detects down arrow key presses when the draft overlay
    // is open and scrolls through the drafts.
    if (event_key === "down_arrow") {
        var focus_draft_down_row = $(".draft-info-box:focus")[0].parentElement;
        var next_focus_draft_row = $(focus_draft_down_row).next();
        drafts_scroll(next_focus_draft_row);
    }

    var elt = document.activeElement;
    var focused_draft = $(elt.parentElement)[0].getAttribute("data-draft-id");
    // Allows user to delete drafts with backspace
    if (event_key === "backspace") {
        if (elt.parentElement.hasAttribute("data-draft-id")) {
            var focus_draft_back_row = $(elt)[0].parentElement;
            var backnext_focus_draft_row = $(focus_draft_back_row).next();
            var backprev_focus_draft_row = $(focus_draft_back_row).prev();
            var delete_id;
            if (backnext_focus_draft_row[0] !== undefined) {
                delete_id = backnext_focus_draft_row[0].getAttribute("data-draft-id");
            } else if (backprev_focus_draft_row[0] !== undefined) {
                delete_id = backprev_focus_draft_row[0].getAttribute("data-draft-id");
            }
            drafts.draft_model.deleteDraft(focused_draft);
            document.activeElement.parentElement.remove();
            var new_focus_element = document.querySelectorAll('[data-draft-id="' + delete_id + '"]');
            if (new_focus_element[0] !== undefined) {
                new_focus_element[0].children[0].focus();
            }
            if ($("#drafts_table .draft-row").length === 0) {
                $('#drafts_table .no-drafts').show();
            }
        }
    }

    // This handles when pressing enter while looking at drafts.
    // It restores draft that is focused.
    if (event_key === "enter") {
        if (document.activeElement.parentElement.hasAttribute("data-draft-id")) {
            exports.restore_draft(focused_draft);
        } else {
            var first_draft = draft_id_arrow[draft_id_arrow.length-1];
            exports.restore_draft(first_draft);
        }
    }
};

exports.toggle = function () {
    if (overlays.drafts_open()) {
        overlays.close_overlay("drafts");
    } else {
        exports.launch();
    }
};

exports.launch = function () {
    exports.setup_page(function () {
        overlays.open_overlay({
            name: 'drafts',
            overlay: $('#draft_overlay'),
            on_close: function () {
                hashchange.exit_overlay();
            },
        });

        var draft_list = drafts.draft_model.get();
        var draft_id_list = Object.getOwnPropertyNames(draft_list);
        if (draft_id_list.length > 0) {
            var last_draft = draft_id_list[draft_id_list.length-1];
            var last_draft_element = document.querySelectorAll('[data-draft-id="' + last_draft + '"]');
            var focus_element = last_draft_element[0].children[0];
            focus_element.focus();
            $(".drafts-list")[0].scrollTop = $('.drafts-list')[0].scrollHeight - $('.drafts-list').height();
        }
    });
};

exports.initialize = function () {
    window.addEventListener("beforeunload", function () {
        exports.update_draft();
    });

    $("#new_message_content").focusout(exports.update_draft);
};

return exports;

}());
if (typeof module !== 'undefined') {
    module.exports = drafts;
}
<commit_msg>drafts: Automatically delete misbehaving drafts while formatting.<commit_after>var drafts = (function () {

var exports = {};

var draft_model = (function () {
    var exports = {};

    // the key that the drafts are stored under.
    var KEY = "drafts";
    var ls = localstorage();
    ls.version = 1;

    function getTimestamp() {
        return new Date().getTime();
    }

    function get() {
        return ls.get(KEY) || {};
    }
    exports.get = get;

    exports.getDraft = function (id) {
        return get()[id] || false;
    };

    function save(drafts) {
        ls.set(KEY, drafts);
    }

    exports.addDraft = function (draft) {
        var drafts = get();

        // use the base16 of the current time + a random string to reduce
        // collisions to essentially zero.
        var id = getTimestamp().toString(16) + "-" + Math.random().toString(16).split(/\./).pop();

        draft.updatedAt = getTimestamp();
        drafts[id] = draft;
        save(drafts);

        return id;
    };

    exports.editDraft = function (id, draft) {
        var drafts = get();

        if (drafts[id]) {
            draft.updatedAt = getTimestamp();
            drafts[id] = draft;
            save(drafts);
        }
    };

    exports.deleteDraft = function (id) {
        var drafts = get();

        delete drafts[id];
        save(drafts);
    };

    return exports;
}());

exports.draft_model = draft_model;

exports.snapshot_message = function () {
    if (!compose_state.composing() || (compose_state.message_content() === "")) {
        // If you aren't in the middle of composing the body of a
        // message, don't try to snapshot.
        return;
    }

    // Save what we can.
    var message = {
        type: compose_state.get_message_type(),
        content: compose_state.message_content(),
    };
    if (message.type === "private") {
        var recipient = compose_state.recipient();
        message.reply_to = recipient;
        message.private_message_recipient = recipient;
    } else {
        message.stream = compose_state.stream_name();
        message.subject = compose_state.subject();
    }
    return message;
};

exports.update_draft = function () {
    var draft = drafts.snapshot_message();
    var draft_id = $("#new_message_content").data("draft-id");

    if (draft_id !== undefined) {
        if (draft !== undefined) {
            draft_model.editDraft(draft_id, draft);
        } else {
            draft_model.deleteDraft(draft_id);
        }
    } else {
        if (draft !== undefined) {
            var new_draft_id = draft_model.addDraft(draft);
            $("#new_message_content").data("draft-id", new_draft_id);
        }
    }
};

exports.delete_draft_after_send = function () {
    var draft_id = $("#new_message_content").data("draft-id");
    if (draft_id) {
        draft_model.deleteDraft(draft_id);
    }
    $("#new_message_content").removeData("draft-id");
};

exports.restore_draft = function (draft_id) {
    var draft = draft_model.getDraft(draft_id);
    if (!draft) {
        return;
    }

    var draft_copy = _.extend({}, draft);
    if ((draft_copy.type === "stream" &&
         draft_copy.stream.length > 0 &&
             draft_copy.subject.length > 0) ||
                 (draft_copy.type === "private" &&
                  draft_copy.reply_to.length > 0)) {
        draft_copy = _.extend({replying_to_message: draft_copy},
                              draft_copy);
    }

    if (draft.type === "stream") {
        if (draft.stream !== "") {
            narrow.activate([{operator: "stream", operand: draft.stream},
                             {operator: "topic", operand: draft.subject}],
                             {select_first_unread: true, trigger: "restore draft"});
        }
    } else {
        if (draft.private_message_recipient !== "") {
            narrow.activate([{operator: "pm-with", operand: draft.private_message_recipient}],
                             {select_first_unread: true, trigger: "restore draft"});
        }
    }

    overlays.close_overlay("drafts");
    compose_fade.clear_compose();
    if (draft.type === "stream" && draft.stream === "") {
        draft_copy.subject = "";
    }
    compose_actions.start(draft_copy.type, draft_copy);
    compose_ui.autosize_textarea();
    $("#new_message_content").data("draft-id", draft_id);
};

exports.setup_page = function (callback) {
    function setup_event_handlers() {
        $(".restore-draft").on("click", function (e) {
            e.stopPropagation();

            var draft_row = $(this).closest(".draft-row");
            var draft_id = draft_row.data("draft-id");
            exports.restore_draft(draft_id);
        });

        $(".draft_controls .delete-draft").on("click", function () {
            var draft_row = $(this).closest(".draft-row");
            var draft_id = draft_row.data("draft-id");

            exports.draft_model.deleteDraft(draft_id);
            draft_row.remove();

            if ($("#drafts_table .draft-row").length === 0) {
                $('#drafts_table .no-drafts').show();
            }
        });
    }

    function format_drafts(data) {
        var drafts = {};
        _.each(data, function (draft, id) {
            var formatted;
            if (draft.type === "stream") {
                // In case there is no stream for the draft, we need a
                // single space char for proper rendering of the stream label
                var space_string = new Handlebars.SafeString(" ");
                var stream = (draft.stream.length > 0 ? draft.stream : space_string);
                var draft_topic = draft.subject.length === 0 ?
                        compose.empty_topic_placeholder() : draft.subject;

                formatted = {
                    draft_id: id,
                    is_stream: true,
                    stream: stream,
                    stream_color: stream_data.get_color(draft.stream),
                    topic: draft_topic,
                    raw_content: draft.content,

                };
            } else {
                var emails = util.extract_pm_recipients(draft.private_message_recipient);
                var recipients = _.map(emails, function (email) {
                    email = email.trim();
                    var person = people.get_by_email(email);
                    if (person !== undefined) {
                        return person.full_name;
                    }
                    return email;
                }).join(', ');

                formatted = {
                    draft_id: id,
                    is_stream: false,
                    recipients: recipients,
                    raw_content: draft.content,
                };
            }

            try {
                markdown.apply_markdown(formatted);
            } catch (error) {
                // In the unlikely event that there is syntax in the
                // draft content which our markdown processor is
                // unable to process, we delete the draft, so that the
                // drafts overlay can be opened without any errors.
                // We also report the exception to the server so that
                // the bug can be fixed.
                draft_model.deleteDraft(id);
                blueslip.error("Error in rendering draft.", {
                    draft_content: draft.content,
                }, error.stack);
                return;
            }

            drafts[id] = formatted;
        });
        return drafts;
    }

    function _populate_and_fill() {
        $('#drafts_table').empty();
        var drafts = format_drafts(draft_model.get());
        var rendered = templates.render('draft_table_body', { drafts: drafts });
        $('#drafts_table').append(rendered);
        if ($("#drafts_table .draft-row").length > 0) {
            $('#drafts_table .no-drafts').hide();
        }

        if (callback) {
            callback();
        }

        setup_event_handlers();
    }

    function populate_and_fill() {
        i18n.ensure_i18n(function () {
            _populate_and_fill();
        });
    }
    populate_and_fill();
};

function drafts_initialize_focus(event_name) {
    // If a draft is not focused in draft modal, then focus the last draft
    // if up_arrow is clicked or the first draft if down_arrow is clicked.
    if (event_name !== "up_arrow" && event_name !== "down_arrow" || $(".draft-info-box:focus")[0]) {
        return;
    }

    var draft_arrow = draft_model.get();
    var draft_id_arrow = Object.getOwnPropertyNames(draft_arrow);
    if (draft_id_arrow.length === 0) { // empty drafts modal
        return;
    }

    var draft_element;
    if (event_name === "up_arrow") {
        draft_element = document.querySelectorAll('[data-draft-id="' + draft_id_arrow[draft_id_arrow.length-1] + '"]');
    } else if (event_name === "down_arrow") {
        draft_element = document.querySelectorAll('[data-draft-id="' + draft_id_arrow[0] + '"]');
    }
    var focus_element = draft_element[0].children[0];
    focus_element.focus();
}

function drafts_scroll(next_focus_draft_row) {
    if (next_focus_draft_row[0] === undefined) {
        return;
    }
    if (next_focus_draft_row[0].children[0] === undefined) {
        return;
    }
    next_focus_draft_row[0].children[0].focus();

    // If focused draft is first draft, scroll to the top.
    if ($(".draft-info-box:first")[0].parentElement === next_focus_draft_row[0]) {
        $(".drafts-list")[0].scrollTop = 0;
    }

    // If focused draft is the last draft, scroll to the bottom.
    if ($(".draft-info-box:last")[0].parentElement === next_focus_draft_row[0]) {
        $(".drafts-list")[0].scrollTop = $('.drafts-list')[0].scrollHeight - $('.drafts-list').height();
    }

    // If focused draft is cut off from the top, scroll up halfway in draft modal.
    if (next_focus_draft_row.position().top < 55) {
        // 55 is the minimum distance from the top that will require extra scrolling.
        $(".drafts-list")[0].scrollTop -= $(".drafts-list")[0].clientHeight / 2;
    }

    // If focused draft is cut off from the bottom, scroll down halfway in draft modal.
    var dist_from_top = next_focus_draft_row.position().top;
    var total_dist = dist_from_top + next_focus_draft_row[0].clientHeight;
    var dist_from_bottom = $(".drafts-container")[0].clientHeight - total_dist;
    if (dist_from_bottom < -4) {
        //-4 is the min dist from the bottom that will require extra scrolling.
        $(".drafts-list")[0].scrollTop += $(".drafts-list")[0].clientHeight / 2;
    }
}

exports.drafts_handle_events = function (e, event_key) {
    var draft_arrow = draft_model.get();
    var draft_id_arrow = Object.getOwnPropertyNames(draft_arrow);
    drafts_initialize_focus(event_key);

    // This detects up arrow key presses when the draft overlay
    // is open and scrolls through the drafts.
    if (event_key === "up_arrow") {
        var focus_draft_up_row = $(".draft-info-box:focus")[0].parentElement;
        var prev_focus_draft_row = $(focus_draft_up_row).prev();
        drafts_scroll(prev_focus_draft_row);
    }

    // This detects down arrow key presses when the draft overlay
    // is open and scrolls through the drafts.
    if (event_key === "down_arrow") {
        var focus_draft_down_row = $(".draft-info-box:focus")[0].parentElement;
        var next_focus_draft_row = $(focus_draft_down_row).next();
        drafts_scroll(next_focus_draft_row);
    }

    var elt = document.activeElement;
    var focused_draft = $(elt.parentElement)[0].getAttribute("data-draft-id");
    // Allows user to delete drafts with backspace
    if (event_key === "backspace") {
        if (elt.parentElement.hasAttribute("data-draft-id")) {
            var focus_draft_back_row = $(elt)[0].parentElement;
            var backnext_focus_draft_row = $(focus_draft_back_row).next();
            var backprev_focus_draft_row = $(focus_draft_back_row).prev();
            var delete_id;
            if (backnext_focus_draft_row[0] !== undefined) {
                delete_id = backnext_focus_draft_row[0].getAttribute("data-draft-id");
            } else if (backprev_focus_draft_row[0] !== undefined) {
                delete_id = backprev_focus_draft_row[0].getAttribute("data-draft-id");
            }
            drafts.draft_model.deleteDraft(focused_draft);
            document.activeElement.parentElement.remove();
            var new_focus_element = document.querySelectorAll('[data-draft-id="' + delete_id + '"]');
            if (new_focus_element[0] !== undefined) {
                new_focus_element[0].children[0].focus();
            }
            if ($("#drafts_table .draft-row").length === 0) {
                $('#drafts_table .no-drafts').show();
            }
        }
    }

    // This handles when pressing enter while looking at drafts.
    // It restores draft that is focused.
    if (event_key === "enter") {
        if (document.activeElement.parentElement.hasAttribute("data-draft-id")) {
            exports.restore_draft(focused_draft);
        } else {
            var first_draft = draft_id_arrow[draft_id_arrow.length-1];
            exports.restore_draft(first_draft);
        }
    }
};

exports.toggle = function () {
    if (overlays.drafts_open()) {
        overlays.close_overlay("drafts");
    } else {
        exports.launch();
    }
};

exports.launch = function () {
    exports.setup_page(function () {
        overlays.open_overlay({
            name: 'drafts',
            overlay: $('#draft_overlay'),
            on_close: function () {
                hashchange.exit_overlay();
            },
        });

        var draft_list = drafts.draft_model.get();
        var draft_id_list = Object.getOwnPropertyNames(draft_list);
        if (draft_id_list.length > 0) {
            var last_draft = draft_id_list[draft_id_list.length-1];
            var last_draft_element = document.querySelectorAll('[data-draft-id="' + last_draft + '"]');
            var focus_element = last_draft_element[0].children[0];
            focus_element.focus();
            $(".drafts-list")[0].scrollTop = $('.drafts-list')[0].scrollHeight - $('.drafts-list').height();
        }
    });
};

exports.initialize = function () {
    window.addEventListener("beforeunload", function () {
        exports.update_draft();
    });

    $("#new_message_content").focusout(exports.update_draft);
};

return exports;

}());
if (typeof module !== 'undefined') {
    module.exports = drafts;
}
<|endoftext|>


<commit_before>var assert = require('assert')
var crypto = require('crypto')
var os = require('os')
var path = require('path')
var fs = require(process.cwd())

/* global afterEach, beforeEach, describe, it */

var SIZE = 16 * 64 * 1024 + 7

describe('+ copySync()', function () {
  var TEST_DIR

  beforeEach(function (done) {
    TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync')
    fs.emptyDir(TEST_DIR, done)
  })

  afterEach(function (done) {
    fs.remove(TEST_DIR, done)
  })

  describe('> when the source is a file', function () {
    it('should copy the file synchronously', function () {
      var fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_src')
      var fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')

      fs.writeFileSync(fileSrc, crypto.randomBytes(SIZE))

      var srcMd5 = crypto.createHash('md5').update(fs.readFileSync(fileSrc)).digest('hex')
      var destMd5 = ''

      fs.copySync(fileSrc, fileDest)

      destMd5 = crypto.createHash('md5').update(fs.readFileSync(fileDest)).digest('hex')
      assert.strictEqual(srcMd5, destMd5)
    })

    it('should follow symlinks', function () {
      var fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_src')
      var fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')
      var linkSrc = path.join(TEST_DIR, 'TEST_fs-extra_copy_link')

      fs.writeFileSync(fileSrc, crypto.randomBytes(SIZE))

      var srcMd5 = crypto.createHash('md5').update(fs.readFileSync(fileSrc)).digest('hex')
      var destMd5 = ''

      fs.symlinkSync(fileSrc, linkSrc)
      fs.copySync(linkSrc, fileDest)
      destMd5 = crypto.createHash('md5').update(fs.readFileSync(fileDest)).digest('hex')
      assert.strictEqual(srcMd5, destMd5)
    })

    it('should maintain file mode', function () {
      var fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_src')
      var fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')
      fs.writeFileSync(fileSrc, crypto.randomBytes(SIZE))

      fs.chmodSync(fileSrc, parseInt('750', 8))
      fs.copySync(fileSrc, fileDest)

      var statSrc = fs.statSync(fileSrc)
      var statDest = fs.statSync(fileDest)
      assert.strictEqual(statSrc.mode, statDest.mode)
    })

    it('should only copy files allowed by filter fn', function () {
      var srcFile1 = path.join(TEST_DIR, '1.html')
      var srcFile2 = path.join(TEST_DIR, '2.css')
      var srcFile3 = path.join(TEST_DIR, '3.jade')

      fs.writeFileSync(srcFile1, '')
      fs.writeFileSync(srcFile2, '')
      fs.writeFileSync(srcFile3, '')

      var destFile1 = path.join(TEST_DIR, 'dest1.html')
      var destFile2 = path.join(TEST_DIR, 'dest2.css')
      var destFile3 = path.join(TEST_DIR, 'dest3.jade')

      var filter = function (s) { return s.split('.').pop() !== 'css' }

      fs.copySync(srcFile1, destFile1, filter)
      fs.copySync(srcFile2, destFile2, filter)
      fs.copySync(srcFile3, destFile3, filter)

      assert(fs.existsSync(destFile1))
      assert(!fs.existsSync(destFile2))
      assert(fs.existsSync(destFile3))
    })

    describe('> when the destination dir does not exist', function () {
      it('should create the destination directory and copy the file', function () {
        var src = path.join(TEST_DIR, 'file.txt')
        var dest = path.join(TEST_DIR, 'this/path/does/not/exist/copied.txt')
        var data = 'did it copy?\n'

        fs.writeFileSync(src, data, 'utf8')
        fs.copySync(src, dest)

        var data2 = fs.readFileSync(dest, 'utf8')

        assert.strictEqual(data, data2)
      })
    })

    describe('> when clobber option is passed', function () {
      var src, dest
      var srcData = 'some src data'

      beforeEach(function () {
        src = path.join(TEST_DIR, 'src-file')
        dest = path.join(TEST_DIR, 'des-file')

        // source file must always exist in these cases
        fs.writeFileSync(src, srcData)
      })

      describe('> when destination file does NOT exist', function () {
        describe('> when clobber is true', function () {
          it('should copy the file and not throw an error', function () {
            fs.copySync(src, dest, {clobber: true})
            var destData = fs.readFileSync(dest, 'utf8')
            assert.strictEqual(srcData, destData)
          })
        })

        describe('> when clobber is false', function () {
          it('should copy the file and not throw an error', function () {
            fs.copySync(src, dest, {clobber: false})
            var destData = fs.readFileSync(dest, 'utf8')
            assert.strictEqual(srcData, destData)
          })
        })
      })

      describe('when destination file does exist', function () {
        var destData
        beforeEach(function () {
          destData = 'some dest data'
          fs.writeFileSync(dest, destData)
        })

        describe('> when clobber is true', function () {
          it('should copy the file and not throw an error', function () {
            fs.copySync(src, dest, {clobber: true})
            destData = fs.readFileSync(dest, 'utf8')
            assert.strictEqual(srcData, destData)
          })
        })

        describe('> when clobber is false', function () {
          it('should copy the file and THROW an error', function () {
            assert.throws(function () {
              fs.copySync(src, dest, {clobber: false})
            })

            // copy never happened
            var destDataNew = fs.readFileSync(dest, 'utf8')
            assert.strictEqual(destData, destDataNew)
          })
        })

        describe('> when clobber is true and dest is readonly', function () {
          it('should copy the file and not throw an error', function () {
            try {
              fs.chmodSync(dest, parseInt('444', 8))
              fs.copySync(src, dest, {clobber: true})
              destData = fs.readFileSync(dest, 'utf8')
              assert.strictEqual(srcData, destData)
            } finally {
              // destination file is readonly so just remove it so we don't affect other tests
              fs.unlinkSync(dest)
            }
          })
        })
      })
    })
  })
})
<commit_msg>Adding copy sync test for src file without write perms<commit_after>var assert = require('assert')
var crypto = require('crypto')
var os = require('os')
var path = require('path')
var fs = require(process.cwd())

/* global afterEach, beforeEach, describe, it */

var SIZE = 16 * 64 * 1024 + 7

describe('+ copySync()', function () {
  var TEST_DIR

  beforeEach(function (done) {
    TEST_DIR = path.join(os.tmpdir(), 'fs-extra', 'copy-sync')
    fs.emptyDir(TEST_DIR, done)
  })

  afterEach(function (done) {
    fs.remove(TEST_DIR, done)
  })

  describe('> when the source is a file', function () {
    it('should copy the file synchronously', function () {
      var fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_src')
      var fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')

      fs.writeFileSync(fileSrc, crypto.randomBytes(SIZE))

      var srcMd5 = crypto.createHash('md5').update(fs.readFileSync(fileSrc)).digest('hex')
      var destMd5 = ''

      fs.copySync(fileSrc, fileDest)

      destMd5 = crypto.createHash('md5').update(fs.readFileSync(fileDest)).digest('hex')
      assert.strictEqual(srcMd5, destMd5)
    })

    it('should follow symlinks', function () {
      var fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_src')
      var fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')
      var linkSrc = path.join(TEST_DIR, 'TEST_fs-extra_copy_link')

      fs.writeFileSync(fileSrc, crypto.randomBytes(SIZE))

      var srcMd5 = crypto.createHash('md5').update(fs.readFileSync(fileSrc)).digest('hex')
      var destMd5 = ''

      fs.symlinkSync(fileSrc, linkSrc)
      fs.copySync(linkSrc, fileDest)
      destMd5 = crypto.createHash('md5').update(fs.readFileSync(fileDest)).digest('hex')
      assert.strictEqual(srcMd5, destMd5)
    })

    it('should maintain file mode', function () {
      var fileSrc = path.join(TEST_DIR, 'TEST_fs-extra_src')
      var fileDest = path.join(TEST_DIR, 'TEST_fs-extra_copy')
      fs.writeFileSync(fileSrc, crypto.randomBytes(SIZE))

      fs.chmodSync(fileSrc, parseInt('750', 8))
      fs.copySync(fileSrc, fileDest)

      var statSrc = fs.statSync(fileSrc)
      var statDest = fs.statSync(fileDest)
      assert.strictEqual(statSrc.mode, statDest.mode)
    })

    it('should only copy files allowed by filter fn', function () {
      var srcFile1 = path.join(TEST_DIR, '1.html')
      var srcFile2 = path.join(TEST_DIR, '2.css')
      var srcFile3 = path.join(TEST_DIR, '3.jade')

      fs.writeFileSync(srcFile1, '')
      fs.writeFileSync(srcFile2, '')
      fs.writeFileSync(srcFile3, '')

      var destFile1 = path.join(TEST_DIR, 'dest1.html')
      var destFile2 = path.join(TEST_DIR, 'dest2.css')
      var destFile3 = path.join(TEST_DIR, 'dest3.jade')

      var filter = function (s) { return s.split('.').pop() !== 'css' }

      fs.copySync(srcFile1, destFile1, filter)
      fs.copySync(srcFile2, destFile2, filter)
      fs.copySync(srcFile3, destFile3, filter)

      assert(fs.existsSync(destFile1))
      assert(!fs.existsSync(destFile2))
      assert(fs.existsSync(destFile3))
    })

    describe('> when the destination dir does not exist', function () {
      it('should create the destination directory and copy the file', function () {
        var src = path.join(TEST_DIR, 'file.txt')
        var dest = path.join(TEST_DIR, 'this/path/does/not/exist/copied.txt')
        var data = 'did it copy?\n'

        fs.writeFileSync(src, data, 'utf8')
        fs.copySync(src, dest)

        var data2 = fs.readFileSync(dest, 'utf8')

        assert.strictEqual(data, data2)
      })
    })

    describe('> when the source file does not have write permissions', function () {
      it('should be able to copy contents of file', function () {
        var fileSrc = path.join(TEST_DIR, 'file.txt')
        var fileDest = path.join(TEST_DIR, 'file-copy.txt')
        var data = 'did it copy?'

        fs.writeFileSync(fileSrc, data, 'utf8')
        fs.chmodSync(fileSrc, '0444')

        fs.copySync(fileSrc, fileDest)

        var data2 = fs.readFileSync(fileDest, 'utf8')

        assert.strictEqual(data, data2)
      })
    })

    describe('> when clobber option is passed', function () {
      var src, dest
      var srcData = 'some src data'

      beforeEach(function () {
        src = path.join(TEST_DIR, 'src-file')
        dest = path.join(TEST_DIR, 'des-file')

        // source file must always exist in these cases
        fs.writeFileSync(src, srcData)
      })

      describe('> when destination file does NOT exist', function () {
        describe('> when clobber is true', function () {
          it('should copy the file and not throw an error', function () {
            fs.copySync(src, dest, {clobber: true})
            var destData = fs.readFileSync(dest, 'utf8')
            assert.strictEqual(srcData, destData)
          })
        })

        describe('> when clobber is false', function () {
          it('should copy the file and not throw an error', function () {
            fs.copySync(src, dest, {clobber: false})
            var destData = fs.readFileSync(dest, 'utf8')
            assert.strictEqual(srcData, destData)
          })
        })
      })

      describe('when destination file does exist', function () {
        var destData
        beforeEach(function () {
          destData = 'some dest data'
          fs.writeFileSync(dest, destData)
        })

        describe('> when clobber is true', function () {
          it('should copy the file and not throw an error', function () {
            fs.copySync(src, dest, {clobber: true})
            destData = fs.readFileSync(dest, 'utf8')
            assert.strictEqual(srcData, destData)
          })
        })

        describe('> when clobber is false', function () {
          it('should copy the file and THROW an error', function () {
            assert.throws(function () {
              fs.copySync(src, dest, {clobber: false})
            })

            // copy never happened
            var destDataNew = fs.readFileSync(dest, 'utf8')
            assert.strictEqual(destData, destDataNew)
          })
        })

        describe('> when clobber is true and dest is readonly', function () {
          it('should copy the file and not throw an error', function () {
            try {
              fs.chmodSync(dest, parseInt('444', 8))
              fs.copySync(src, dest, {clobber: true})
              destData = fs.readFileSync(dest, 'utf8')
              assert.strictEqual(srcData, destData)
            } finally {
              // destination file is readonly so just remove it so we don't affect other tests
              fs.unlinkSync(dest)
            }
          })
        })
      })
    })
  })
})
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import { LinksTabBar } from './lib/links-tabbar';
import { LinkPreview } from './lib/link-detail-preview';
import { SidebarSwitcher } from '/components/lib/icons/icon-sidebar-switch.js';
import { api } from '../api';
import { Route, Link } from 'react-router-dom';
import { Comments } from './lib/comments';
import { Spinner } from './lib/icons/icon-spinner';
import { LoadingScreen } from './loading';
import { makeRoutePath, getContactDetails } from '../lib/util';
import CommentItem from './lib/comment-item';

export class LinkDetail extends Component {
  constructor(props) {
    super(props);
    this.state = {
      comment: "",
      data: props.data,
      commentFocus: false,
      pending: new Set(),
      disabled: false
    };

    this.setComment = this.setComment.bind(this);
  }

  updateData(submission) {
    this.setState({
      data: submission
    });
  }

  componentDidMount() {
    // if we have no preloaded data, and we aren't expecting it, get it
    if (!this.state.data.title) {
      api.getSubmission(
        this.props.resourcePath, this.props.url, this.updateData.bind(this)
      );
    }
  }

  componentDidUpdate(prevProps) {
    if (this.props.url !== prevProps.url) {
      this.updateData(this.props.data);
    }
    if (prevProps.comments && prevProps.comments["0"] &&
      this.props.comments && this.props.comments["0"]) {
        let prevFirstComment = prevProps.comments["0"][0];
        let thisFirstComment = this.props.comments["0"][0];
        if ((prevFirstComment && prevFirstComment.udon) &&
          (thisFirstComment && thisFirstComment.udon)) {
          if (this.state.pending.has(thisFirstComment.udon)) {
            let pending = this.state.pending;
            pending.delete(thisFirstComment.udon);
            this.setState({
              pending: pending
            });
          }
        }
    }
  }

  onClickPost() {
    let url = this.props.url || "";

    let pending = this.state.pending;
    pending.add(this.state.comment);
    this.setState({ pending: pending, disabled: true  });

    api.postComment(
      this.props.resourcePath,
      url,
      this.state.comment
    ).then(() => {
      this.setState({ comment: "", disabled: false });
    });

  }

  setComment(event) {
    this.setState({ comment: event.target.value });
  }

  render() {
    let props = this.props;

    const data = this.state.data || props.data;

    if (!data.ship) {
      return <LoadingScreen/>;
    }

    let ship = data.ship || "zod";
    let title = data.title || "";
    let url = data.url || "";

    const commentCount = props.comments
      ? props.comments.totalItems
      : data.commentCount || 0;

    let comments = commentCount + " comment" + (commentCount === 1 ? "" : "s");

    const { nickname } = getContactDetails(props.contacts[ship]);

    let activeClasses = this.state.comment
      ? "black white-d pointer"
      : "gray2 b--gray2";

    let focus = (this.state.commentFocus)
      ? "b--black b--white-d"
      : "b--gray4 b--gray2-d";

    let our = getContactDetails(props.contacts[window.ship]);

    let pendingArray = Array.from(this.state.pending).map((com, i) => {
      return(
        <CommentItem
          key={i}
          color={our.color}
          nickname={our.nickname}
          ship={window.ship}
          pending={true}
          content={com}
          member={our.member}
          time={new Date().getTime()}
        />
      )
    })

    return (

          <SidebarSwitcher
            sidebarShown={props.sidebarShown}
            popout={props.popout}
          />
          <Link
            className="dib f9 fw4 pt2 gray2 lh-solid"
            to={makeRoutePath(props.resourcePath, props.popout, props.page)}>
            {`<- ${props.resource.metadata.title}`}
          </Link>
          <LinksTabBar {...props} popout={props.popout} resourcePath={props.resourcePath} />

            <LinkPreview
              title={title}
              url={url}
              comments={comments}
              nickname={nickname}
              ship={ship}
              resourcePath={props.resourcePath}
              page={props.page}
              linkIndex={props.linkIndex}
              time={this.state.data.time}
            />

                <textarea
                  className="w-100 bg-gray0-d white-d f8 pa2 pr8"
                  style={{
                    resize: "none",
                    height: 75
                  }}
                  placeholder="Leave a comment on this link"
                  onChange={this.setComment}
                  onFocus={() => this.setState({commentFocus: true})}
                  onBlur={() => this.setState({commentFocus: false})}
                  value={this.state.comment}
                />
                <button
                  className={
                    "f8 bg-gray0-d ml2 absolute " + activeClasses
                  }
                  disabled={!this.state.comment || this.state.disabled}
                  onClick={this.onClickPost.bind(this)}
                  style={{
                    bottom: 12,
                    right: 8
                  }}>
                  Post
                </button>

              <Spinner awaiting={this.state.disabled} classes="absolute pt5 right-0" text="Posting comment..." />
              {pendingArray}

            <Comments
              resourcePath={props.resourcePath}
              key={props.resourcePath + props.commentPage}
              comments={props.comments}
              commentPage={props.commentPage}
              contacts={props.contacts}
              popout={props.popout}
              url={props.url}
              linkPage={props.page}
              linkIndex={props.linkIndex}
            />

    );
  }
}

export default LinkDetail;
<commit_msg>link: fix meta key propagation for comment posting<commit_after>import React, { Component } from 'react'
import { LinksTabBar } from './lib/links-tabbar';
import { LinkPreview } from './lib/link-detail-preview';
import { SidebarSwitcher } from '/components/lib/icons/icon-sidebar-switch.js';
import { api } from '../api';
import { Route, Link } from 'react-router-dom';
import { Comments } from './lib/comments';
import { Spinner } from './lib/icons/icon-spinner';
import { LoadingScreen } from './loading';
import { makeRoutePath, getContactDetails } from '../lib/util';
import CommentItem from './lib/comment-item';

export class LinkDetail extends Component {
  constructor(props) {
    super(props);
    this.state = {
      comment: "",
      data: props.data,
      commentFocus: false,
      pending: new Set(),
      disabled: false
    };

    this.setComment = this.setComment.bind(this);
  }

  updateData(submission) {
    this.setState({
      data: submission
    });
  }

  componentDidMount() {
    // if we have no preloaded data, and we aren't expecting it, get it
    if (!this.state.data.title) {
      api.getSubmission(
        this.props.resourcePath, this.props.url, this.updateData.bind(this)
      );
    }
  }

  componentDidUpdate(prevProps) {
    if (this.props.url !== prevProps.url) {
      this.updateData(this.props.data);
    }
    if (prevProps.comments && prevProps.comments["0"] &&
      this.props.comments && this.props.comments["0"]) {
        let prevFirstComment = prevProps.comments["0"][0];
        let thisFirstComment = this.props.comments["0"][0];
        if ((prevFirstComment && prevFirstComment.udon) &&
          (thisFirstComment && thisFirstComment.udon)) {
          if (this.state.pending.has(thisFirstComment.udon)) {
            let pending = this.state.pending;
            pending.delete(thisFirstComment.udon);
            this.setState({
              pending: pending
            });
          }
        }
    }
  }

  onClickPost() {
    let url = this.props.url || "";

    let pending = this.state.pending;
    pending.add(this.state.comment);
    this.setState({ pending: pending, disabled: true  });

    api.postComment(
      this.props.resourcePath,
      url,
      this.state.comment
    ).then(() => {
      this.setState({ comment: "", disabled: false });
    });

  }

  setComment(event) {
    this.setState({ comment: event.target.value });
  }

  render() {
    let props = this.props;

    const data = this.state.data || props.data;

    if (!data.ship) {
      return <LoadingScreen/>;
    }

    let ship = data.ship || "zod";
    let title = data.title || "";
    let url = data.url || "";

    const commentCount = props.comments
      ? props.comments.totalItems
      : data.commentCount || 0;

    let comments = commentCount + " comment" + (commentCount === 1 ? "" : "s");

    const { nickname } = getContactDetails(props.contacts[ship]);

    let activeClasses = this.state.comment
      ? "black white-d pointer"
      : "gray2 b--gray2";

    let focus = (this.state.commentFocus)
      ? "b--black b--white-d"
      : "b--gray4 b--gray2-d";

    let our = getContactDetails(props.contacts[window.ship]);

    let pendingArray = Array.from(this.state.pending).map((com, i) => {
      return(
        <CommentItem
          key={i}
          color={our.color}
          nickname={our.nickname}
          ship={window.ship}
          pending={true}
          content={com}
          member={our.member}
          time={new Date().getTime()}
        />
      )
    })

    return (

          <SidebarSwitcher
            sidebarShown={props.sidebarShown}
            popout={props.popout}
          />
          <Link
            className="dib f9 fw4 pt2 gray2 lh-solid"
            to={makeRoutePath(props.resourcePath, props.popout, props.page)}>
            {`<- ${props.resource.metadata.title}`}
          </Link>
          <LinksTabBar {...props} popout={props.popout} resourcePath={props.resourcePath} />

            <LinkPreview
              title={title}
              url={url}
              comments={comments}
              nickname={nickname}
              ship={ship}
              resourcePath={props.resourcePath}
              page={props.page}
              linkIndex={props.linkIndex}
              time={this.state.data.time}
            />

                <textarea
                  className="w-100 bg-gray0-d white-d f8 pa2 pr8"
                  style={{
                    resize: "none",
                    height: 75
                  }}
                  placeholder="Leave a comment on this link"
                  onChange={this.setComment}
                  onKeyDown={e => {
                    if (
                      (e.getModifierState("Control") || e.metaKey) &&
                      e.key === "Enter"
                    ) {
                      this.onClickPost();
                    }
                  }}
                  onFocus={() => this.setState({ commentFocus: true })}
                  onBlur={() => this.setState({ commentFocus: false })}
                  value={this.state.comment}
                />
                <button
                  className={
                    "f8 bg-gray0-d ml2 absolute " + activeClasses
                  }
                  disabled={!this.state.comment || this.state.disabled}
                  onClick={this.onClickPost.bind(this)}
                  style={{
                    bottom: 12,
                    right: 8
                  }}>
                  Post
                </button>

              <Spinner awaiting={this.state.disabled} classes="absolute pt5 right-0" text="Posting comment..." />
              {pendingArray}

            <Comments
              resourcePath={props.resourcePath}
              key={props.resourcePath + props.commentPage}
              comments={props.comments}
              commentPage={props.commentPage}
              contacts={props.contacts}
              popout={props.popout}
              url={props.url}
              linkPage={props.page}
              linkIndex={props.linkIndex}
            />

    );
  }
}

export default LinkDetail;
<|endoftext|>


<commit_before>'use strict';

var express = require('express'),
    router = express.Router(),
    _ = require('lodash'),
    logger = require('../logger'),
    transformerProxy = require('transformer-proxy'),
    mock = require('../service/mock'),
    proxy = require('../proxy'),
    utils = require('../utils'),
    Mock = require('mockjs');

router.use((req, res, next) => {
    logger.info('%s %s %s', req.method, req.url, req.path);
    next();
});

/**
 * handle all api not match system apis
 * @method handleApi
 * @param  {[type]}   req  [description]
 * @param  {[type]}   res  [description]
 * @param  {Function} next [description]
 * @return {[type]}        [description]
 */
router.use((req, res, next) => {
    let url = decodeURI(req.url);
    let path = decodeURI(req.path);
    let beginPath = (url.match(/\/\w+/) || [""])[0];
    var author = req.headers.author;
    if (author) {
        beginPath = author;
        delete req.headers.author;
    }

    var project = mock.getProjects(beginPath);
    if (project) {
        var normalApis = mock.getNormalApis(project._id) || {};
        var regApis = mock.getRegApis(project._id);
        var api = normalApis[path];
        if (api && api.type == req.method) { // match normal
            if (api.dataHandler == "over") {
                let data = JSON.parse(api.result);
                return res.json(data);
            } else {
                req.proxy = project.proxy;
                req._extendData = api.result;
                next();
            }
        } else if (regApis) { // match reg
            regApis.forEach((api) => {
                if (new RegExp(api.regexp).test(url) && api.type == req.method) {
                    if (api.dataHandler == "over") {
                        let data = JSON.parse(api.result);
                        return res.json(data);
                    } else {
                        req.proxy = project.proxy;
                        req._extendData = api.result;
                        next();
                    }
                }
            });

            //TODO
            if (url.indexOf(':') != -1) {
                return res.status(200).json({
                    result: "error happens! have you replace your parameter? "
                });
            }
        } else {
            //TODO
            next();
        }
    } else {
        //TODO
        next();
    }
});

// override data
router.use(transformerProxy((data, req, res) => {
    if (req._extendData) {
        var ret = JSON.parse(data);
        var extend = JSON.parse(req._extendData);
        ret = _.merge(ret, Mock.mock(extend));

        console.log(ret);

        return JSON.stringify(ret);
    }

    return data;
}));

router.use((req, res, next) => {
    if (req.proxy) {
        proxy.web(req, res, {
            target: req.proxy,
            toProxy: true,
            changeOrigin: true
        });
    } else {
        next();
    }
});

var deps = [];
var ret = [{
    router: router,
    route: '/'
}];
deps.forEach((dep) => {
    ret.push(require(dep));
});

module.exports = ret;
<commit_msg>add mock data<commit_after>'use strict';

var express = require('express'),
    router = express.Router(),
    _ = require('lodash'),
    logger = require('../logger'),
    transformerProxy = require('transformer-proxy'),
    mock = require('../service/mock'),
    proxy = require('../proxy'),
    utils = require('../utils'),
    Mock = require('mockjs');

router.use((req, res, next) => {
    logger.info('%s %s %s', req.method, req.url, req.path);
    next();
});

/**
 * handle all api not match system apis
 * @method handleApi
 * @param  {[type]}   req  [description]
 * @param  {[type]}   res  [description]
 * @param  {Function} next [description]
 * @return {[type]}        [description]
 */
router.use((req, res, next) => {
    let url = decodeURI(req.url);
    let path = decodeURI(req.path);
    let beginPath = (url.match(/\/\w+/) || [""])[0];
    var author = req.headers.author;
    if (author) {
        beginPath = author;
        delete req.headers.author;
    }

    var project = mock.getProjects(beginPath);
    if (project) {
        var normalApis = mock.getNormalApis(project._id) || {};
        var regApis = mock.getRegApis(project._id);
        var api = normalApis[path];
        if (api && api.type == req.method) { // match normal
            if (api.dataHandler == "over") {
                let data = JSON.parse(api.result);
                return res.json(Mock.mock(data));
            } else {
                req.proxy = project.proxy;
                req._extendData = api.result;
                next();
            }
        } else if (regApis) { // match reg
            regApis.forEach((api) => {
                if (new RegExp(api.regexp).test(url) && api.type == req.method) {
                    if (api.dataHandler == "over") {
                        let data = JSON.parse(api.result);
                        return res.json(Mock.mock(data));
                    } else {
                        req.proxy = project.proxy;
                        req._extendData = api.result;
                        next();
                    }
                }
            });

            //TODO
            if (url.indexOf(':') != -1) {
                return res.status(200).json({
                    result: "error happens! have you replace your parameter? "
                });
            }
        } else {
            //TODO
            next();
        }
    } else {
        //TODO
        next();
    }
});

// override data
router.use(transformerProxy((data, req, res) => {
    if (req._extendData) {
        var ret = JSON.parse(data);
        var extend = JSON.parse(req._extendData);
        ret = _.merge(ret, Mock.mock(extend));

        console.log(ret);

        return JSON.stringify(ret);
    }

    return data;
}));

router.use((req, res, next) => {
    if (req.proxy) {
        proxy.web(req, res, {
            target: req.proxy,
            toProxy: true,
            changeOrigin: true
        });
    } else {
        next();
    }
});

var deps = [];
var ret = [{
    router: router,
    route: '/'
}];
deps.forEach((dep) => {
    ret.push(require(dep));
});

module.exports = ret;
<|endoftext|>