<commit_before>var xhr = function(url, callback) {
    var oReq = new XMLHttpRequest();
    oReq.onload = function(e){
        callback(this.responseText, e);
    };
    oReq.open("get", url, true);
    oReq.send();
};

var colonSeparateDuration = function(num) { // in seconds
    if (typeof num == "number" && !(Number.isNaN || isNaN)(num)) {
        var minutes = Math.floor(num / 60);
        var seconds = Math.floor(num % 60);
        return "" + minutes + ":" + zpad(seconds, 2);
    } else {
        return "--:--";
    }
};

var zpad = function pad(n, width, z) { // by user Pointy on SO: stackoverflow.com/a/10073788
    z = z || "0";
    n = n + "";
    return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
};

window.AudioContext = window.AudioContext || window.webkitAudioContext;

var cbus = {};

cbus.audio = {
    DEFAULT_JUMP_AMOUNT_BACKWARD: -10,
    DEFAULT_JUMP_AMOUNT_FORWARD: 30,

    element: null,

    setElement: function(elem) {
        if (cbus.audio.element) {
            cbus.audio.pause();
            cbus.audio.element.onseeked = null;
            cbus.audio.element.onloadedmetadata = null;
        }

        cbus.audio.element = elem;
        cbus.audio.element.currentTime = 0;

        cbus.audio.element.onseeked = function() {
            cbus.audio.updatePlayerTime();
        };
        cbus.audio.element.onloadedmetadata = function() {
            cbus.audio.updatePlayerTime(true);
        };

        var episodeElem = elem.parentElement.parentElement.parentElement.parentElement;

        var episodeTitle = episodeElem.querySelector(".episode_title").textContent;
        var episodeFeedTitle = episodeElem.querySelector(".episode_feed-title").textContent;

        $(".player_time--total").text(colonSeparateDuration(cbus.audio.element.duration));
        $(".player_episode_image").css({
            backgroundImage: episodeElem.querySelector(".episode_background").style.backgroundImage
        });
        $(".player_episode_title").text(episodeTitle).attr("title", episodeTitle);
        $(".player_episode_feed-title").text(episodeFeedTitle);
    },

    updatePlayerTime: function(updateTotalLength) {
        if (cbus.audio.element && !cbus.audio.element.paused) {
            var currentTime = cbus.audio.element.currentTime;
            /* slider */
            var percentage = currentTime / cbus.audio.element.duration;
            $(".player_slider").val(Math.round(1000 * percentage));

            /* time indicator */
            $(".player_time--now").text(colonSeparateDuration(currentTime));
            if (updateTotalLength === true) {
                $(".player_time--total").text(colonSeparateDuration(cbus.audio.element.duration));
            }
        }
    },
    sliderUpdateInterval: null,

    play: function() {
        cbus.audio.element.play();
        $(".player_button--play").html("pause");
        $(".player_time--total")
    },
    pause: function() {
        cbus.audio.element.pause();
        $(".player_button--play").html("play_arrow");
    },
    stop: function() {
        cbus.audio.element.pause();
        cbus.audio.element.currentTime = 0;
    },
    jump: function(amount) {
        cbus.audio.element.currentTime += amount;
    }
};

cbus.audio.sliderUpdateInterval = setInterval(cbus.audio.updatePlayerTime, 500);

cbus.display = function(thing) {
    switch (thing) {
        case "feeds":
            $(".filters_feeds").html("");
            cbus.feeds.forEach(function(feed) {
                $(".filters_feeds").append("
\
");
            });
            break;
        case "episodes":
            $(".list--episodes").html("");

            for (var i = 0; i < Math.min(50, cbus.episodes.length); i++) {
                var episode = cbus.episodes[i];

                var episodeElem = document.createElement("cbus-episode");

                episodeElem.title = episode.title;
                episodeElem.image = episode.feed.image;
                episodeElem.feedTitle = episode.feed.title;
                episodeElem.url = episode.url;
                episodeElem.description = episode.description;

                $(".list--episodes").append(episodeElem);
            };

            break;
    }
};

cbus.update = function() {
    $(".list--episodes").html("");
    xhr("update?feeds=" + encodeURIComponent(JSON.stringify(cbus.feeds)), function(r) {
        var feedContents = JSON.parse(r);
        var episodes = [];

        console.log(feedContents);

        Object.keys(feedContents).forEach(function(feedUrl) {
            feedContents[feedUrl].items.forEach(function(episode) {
                var feed = cbus.feeds.filter(function(feed) {
                    return feed.url === feedUrl;
                })[0];

                episodes.push({
                    url: episode.url,
                    title: episode.title,
                    description: episode.description,
                    date: (new Date(episode.date).getTime() ? new Date(episode.date) : null), // check if date is valid
                    feed: feed
                });
            });
        });

        cbus.episodes = episodes.sort(function(a, b) {
            if (a.date > b.date) return -1;
            if (a.date < b.date) return 1;
            return 0;
        });
        cbus.display("episodes");
    });
};

cbus.feeds = (localStorage.getItem("cbus_feeds") ? JSON.parse(localStorage.getItem("cbus_feeds")) : []);

cbus.display("feeds");

$(".filters_control--add-feed").click(function() {
    Ply.dialog("prompt", {
        title: "Add feed",
        form: { title: "Some Random Podcast" }
    }).always(function (ui) {
        if (ui.state) {
            console.log(ui.widget);
            var feedTitle = ui.data.title;
            xhr("feedinfo?term=" + feedTitle, function(res) {
                var json = JSON.parse(res);
                console.log(json);

                var feedInfo = json[0];

                var feedTitle = feedInfo.title;
                var feedImage = feedInfo.image;
                var feedUrl = feedInfo.url;

                var feedAlreadyAdded = false;
                for (var i = 0; i < cbus.feeds.length; i++) {
                    var lfeed = cbus.feeds[i];
                    var lfeedUrl = lfeed.url;
                    if (lfeedUrl === feedUrl) {
                        feedAlreadyAdded = true;
                        break;
                    }
                }

                if (feedAlreadyAdded) {
                    Ply.dialog("alert", "You already have that feed.");
                } else {
                    cbus.feeds.push({
                        url: feedUrl,
                        title: feedTitle,
                        image: feedImage
                    });
                    localStorage.setItem("cbus_feeds", JSON.stringify(cbus.feeds));
                    Ply.dialog("alert", "Added feed.");
                }
            });
        }
    });
});

$(".list--episodes").on("click", function(e) {
    var classList = e.target.classList;
    if (classList.contains("episode_audio_button--play")) {
        cbus.audio.setElement(e.target.parentElement.querySelector(".episode_audio_player"));
        cbus.audio.play();
    }
});

$(".player").on("click", function(e) {
    var classList = e.target.classList;
    if (classList.contains("player_button")) {
        if (classList.contains("player_button--backward")) {
            cbus.audio.jump(cbus.audio.DEFAULT_JUMP_AMOUNT_BACKWARD);
        } else if (classList.contains("player_button--forward")) {
            cbus.audio.jump(cbus.audio.DEFAULT_JUMP_AMOUNT_FORWARD);
        } else if (classList.contains("player_button--play")) {
            if (!cbus.audio.element) {
                cbus.audio.setElement($(".episode_audio_player")[0]);
                cbus.audio.play();
            } else if (cbus.audio.element.paused) {
                cbus.audio.play();
            } else {
                cbus.audio.pause();
            }
        }
    }
});

$(".player_slider").on("input", function() {
    var proportion = this.value / this.max;
    cbus.audio.element.currentTime = cbus.audio.element.duration * proportion;
});

$(".filter--time").on("change", function() {
    var timeCategory = this.value;
    $(".episode").each(function(i, elem) {
        var matchableTimes = elem.dataset.time.split(",");
        if (matchableTimes.indexOf(timeCategory) !== -1) {
            elem.classList.remove("hidden");
        } else {
            elem.classList.add("hidden");
        }
    });
});

/* header actions */

$(".header_actions").on("click", function(e) {
    var classList = e.target.classList;
    if (classList.contains("header_action")) {
        if (classList.contains("header_action--show-filters")) {
            $(".content-container")[0].classList.toggle("filters-visible");
            e.target.classList.toggle("md-inactive");
        }
        if (classList.contains("header_action--refresh-episodes")) {
            cbus.update();
        }
    }
});

/* do the thing */

cbus.update();
<commit_msg>Fix errors when trying to move time slider when episode metadata not yet loaded<commit_after>var xhr = function(url, callback) {
    var oReq = new XMLHttpRequest();
    oReq.onload = function(e){
        callback(this.responseText, e);
    };
    oReq.open("get", url, true);
    oReq.send();
};

var colonSeparateDuration = function(num) { // in seconds
    if (typeof num == "number" && !(Number.isNaN || isNaN)(num)) {
        var minutes = Math.floor(num / 60);
        var seconds = Math.floor(num % 60);
        return "" + minutes + ":" + zpad(seconds, 2);
    } else {
        return "--:--";
    }
};

var zpad = function pad(n, width, z) { // by user Pointy on SO: stackoverflow.com/a/10073788
    z = z || "0";
    n = n + "";
    return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
};

window.AudioContext = window.AudioContext || window.webkitAudioContext;

var cbus = {};

cbus.audio = {
    DEFAULT_JUMP_AMOUNT_BACKWARD: -10,
    DEFAULT_JUMP_AMOUNT_FORWARD: 30,

    element: null,

    setElement: function(elem) {
        if (cbus.audio.element) {
            cbus.audio.pause();
            cbus.audio.element.onseeked = null;
            cbus.audio.element.onloadedmetadata = null;
        }

        cbus.audio.element = elem;
        cbus.audio.element.currentTime = 0;

        cbus.audio.element.onseeked = function() {
            cbus.audio.updatePlayerTime();
        };
        cbus.audio.element.onloadedmetadata = function() {
            cbus.audio.updatePlayerTime(true);
        };

        var episodeElem = elem.parentElement.parentElement.parentElement.parentElement;

        var episodeTitle = episodeElem.querySelector(".episode_title").textContent;
        var episodeFeedTitle = episodeElem.querySelector(".episode_feed-title").textContent;

        $(".player_time--total").text(colonSeparateDuration(cbus.audio.element.duration));
        $(".player_episode_image").css({
            backgroundImage: episodeElem.querySelector(".episode_background").style.backgroundImage
        });
        $(".player_episode_title").text(episodeTitle).attr("title", episodeTitle);
        $(".player_episode_feed-title").text(episodeFeedTitle);
    },

    updatePlayerTime: function(updateTotalLength) {
        if (cbus.audio.element && !cbus.audio.element.paused) {
            var currentTime = cbus.audio.element.currentTime;
            /* slider */
            var percentage = currentTime / cbus.audio.element.duration;
            $(".player_slider").val(Math.round(1000 * percentage) || 0);

            /* time indicator */
            $(".player_time--now").text(colonSeparateDuration(currentTime));
            if (updateTotalLength === true) {
                $(".player_time--total").text(colonSeparateDuration(cbus.audio.element.duration));
            }
        }
    },
    sliderUpdateInterval: null,

    play: function() {
        cbus.audio.element.play();
        $(".player_button--play").html("pause");
        $(".player_time--total")
    },
    pause: function() {
        cbus.audio.element.pause();
        $(".player_button--play").html("play_arrow");
    },
    stop: function() {
        cbus.audio.element.pause();
        cbus.audio.element.currentTime = 0;
    },
    jump: function(amount) {
        cbus.audio.element.currentTime += amount;
    }
};

cbus.audio.sliderUpdateInterval = setInterval(cbus.audio.updatePlayerTime, 500);

cbus.display = function(thing) {
    switch (thing) {
        case "feeds":
            $(".filters_feeds").html("");
            cbus.feeds.forEach(function(feed) {
                $(".filters_feeds").append("
\
");
            });
            break;
        case "episodes":
            $(".list--episodes").html("");

            for (var i = 0; i < Math.min(50, cbus.episodes.length); i++) {
                var episode = cbus.episodes[i];

                var episodeElem = document.createElement("cbus-episode");

                episodeElem.title = episode.title;
                episodeElem.image = episode.feed.image;
                episodeElem.feedTitle = episode.feed.title;
                episodeElem.url = episode.url;
                episodeElem.description = episode.description;

                $(".list--episodes").append(episodeElem);
            };

            break;
    }
};

cbus.update = function() {
    $(".list--episodes").html("");
    xhr("update?feeds=" + encodeURIComponent(JSON.stringify(cbus.feeds)), function(r) {
        var feedContents = JSON.parse(r);
        var episodes = [];

        console.log(feedContents);

        Object.keys(feedContents).forEach(function(feedUrl) {
            feedContents[feedUrl].items.forEach(function(episode) {
                var feed = cbus.feeds.filter(function(feed) {
                    return feed.url === feedUrl;
                })[0];

                episodes.push({
                    url: episode.url,
                    title: episode.title,
                    description: episode.description,
                    date: (new Date(episode.date).getTime() ? new Date(episode.date) : null), // check if date is valid
                    feed: feed
                });
            });
        });

        cbus.episodes = episodes.sort(function(a, b) {
            if (a.date > b.date) return -1;
            if (a.date < b.date) return 1;
            return 0;
        });
        cbus.display("episodes");
    });
};

cbus.feeds = (localStorage.getItem("cbus_feeds") ? JSON.parse(localStorage.getItem("cbus_feeds")) : []);

cbus.display("feeds");

$(".filters_control--add-feed").click(function() {
    Ply.dialog("prompt", {
        title: "Add feed",
        form: { title: "Some Random Podcast" }
    }).always(function (ui) {
        if (ui.state) {
            console.log(ui.widget);
            var feedTitle = ui.data.title;
            xhr("feedinfo?term=" + feedTitle, function(res) {
                var json = JSON.parse(res);
                console.log(json);

                var feedInfo = json[0];

                var feedTitle = feedInfo.title;
                var feedImage = feedInfo.image;
                var feedUrl = feedInfo.url;

                var feedAlreadyAdded = false;
                for (var i = 0; i < cbus.feeds.length; i++) {
                    var lfeed = cbus.feeds[i];
                    var lfeedUrl = lfeed.url;
                    if (lfeedUrl === feedUrl) {
                        feedAlreadyAdded = true;
                        break;
                    }
                }

                if (feedAlreadyAdded) {
                    Ply.dialog("alert", "You already have that feed.");
                } else {
                    cbus.feeds.push({
                        url: feedUrl,
                        title: feedTitle,
                        image: feedImage
                    });
                    localStorage.setItem("cbus_feeds", JSON.stringify(cbus.feeds));
                    Ply.dialog("alert", "Added feed.");
                }
            });
        }
    });
});

$(".list--episodes").on("click", function(e) {
    var classList = e.target.classList;
    if (classList.contains("episode_audio_button--play")) {
        cbus.audio.setElement(e.target.parentElement.querySelector(".episode_audio_player"));
        cbus.audio.play();
    }
});

$(".player").on("click", function(e) {
    var classList = e.target.classList;
    if (classList.contains("player_button")) {
        if (classList.contains("player_button--backward")) {
            cbus.audio.jump(cbus.audio.DEFAULT_JUMP_AMOUNT_BACKWARD);
        } else if (classList.contains("player_button--forward")) {
            cbus.audio.jump(cbus.audio.DEFAULT_JUMP_AMOUNT_FORWARD);
        } else if (classList.contains("player_button--play")) {
            if (!cbus.audio.element) {
                cbus.audio.setElement($(".episode_audio_player")[0]);
                cbus.audio.play();
            } else if (cbus.audio.element.paused) {
                cbus.audio.play();
            } else {
                cbus.audio.pause();
            }
        }
    }
});

$(".player_slider").on("input", function() {
    var proportion = this.value / this.max;
    cbus.audio.element.currentTime = cbus.audio.element.duration * proportion;
});

$(".filter--time").on("change", function() {
    var timeCategory = this.value;
    $(".episode").each(function(i, elem) {
        var matchableTimes = elem.dataset.time.split(",");
        if (matchableTimes.indexOf(timeCategory) !== -1) {
            elem.classList.remove("hidden");
        } else {
            elem.classList.add("hidden");
        }
    });
});

/* header actions */

$(".header_actions").on("click", function(e) {
    var classList = e.target.classList;
    if (classList.contains("header_action")) {
        if (classList.contains("header_action--show-filters")) {
            $(".content-container")[0].classList.toggle("filters-visible");
            e.target.classList.toggle("md-inactive");
        }
        if (classList.contains("header_action--refresh-episodes")) {
            cbus.update();
        }
    }
});

/* do the thing */

cbus.update();
<|endoftext|>


<commit_before>Package.describe({
  name: "tmeasday:publish-counts",
  summary: "Publish the count of a cursor, in real time",
  version: "0.3.7",
  git: "https://github.com/percolatestudio/publish-counts.git"
});

Package.on_use(function (api, where) {
  api.versionsFrom("METEOR@0.9.2");
  api.use('blaze', 'client', { weak:true });
  api.use('templating', 'client', {weak:true});
  api.use('mongo', 'client');
  api.add_files('publish-counts.js');
  api.export('Counts');
  api.export('publishCount', 'server');
});

Package.on_test(function (api) {
  api.use([
    'tmeasday:publish-counts',
    'tinytest',
    'facts']);

  api.add_files('publish-counts_tests.js');
});
<commit_msg>package.js & version bump<commit_after>Package.describe({
  name: "tmeasday:publish-counts",
  summary: "Publish the count of a cursor, in real time",
  version: "0.3.8",
  git: "https://github.com/percolatestudio/publish-counts.git"
});

Package.on_use(function (api, where) {
  api.versionsFrom("METEOR@0.9.2");
  api.use(['blaze', 'templating'], 'client', { weak:true });
  api.use('mongo', 'client');
  api.add_files('publish-counts.js');
  api.export('Counts');
  api.export('publishCount', 'server');
});

Package.on_test(function (api) {
  api.use([
    'tmeasday:publish-counts',
    'tinytest',
    'facts']);

  api.add_files('publish-counts_tests.js');
});
<|endoftext|>


<commit_before>import { h } from 'preact' // eslint-disable-line no-unused-vars
import { Provider, connect } from 'preact-redux' // introduces 2.9kb on gzipped bundle, todo: barf, fix
import { getCurrentUrl } from './../store/selectors/meta'
import Header from './Header'
import Articles from './Articles'
import Article from './Article'
import Splash from './Splash'

const Page = ({ id, currentUrl }) => { // todo: make routing more robust
  if (currentUrl.indexOf('/articles/') > -1) {
    return

  } else if (currentUrl === '/articles') {
    return <Articles />
  } else {
    return <Splash />
  }
}

const App = ({ store, currentUrl }) => (
  <Provider store={store}>

      <Page currentUrl={currentUrl} />

  </Provider>
)

export default connect(
  (state) => ({
    currentUrl: getCurrentUrl(state)
  })
)(App)
<commit_msg>update<commit_after>import { h } from 'preact' // eslint-disable-line no-unused-vars
import { Provider, connect } from 'preact-redux' // introduces 2.9kb on gzipped bundle, todo: barf, fix
import { getCurrentUrl } from './../store/selectors/meta'
import Header from './Header'
import Articles from './Articles'
import Article from './Article'
import Splash from './Splash'

const Page = connect(
  (state) => ({
    currentUrl: getCurrentUrl(state)
  })
)(({ currentUrl }) => { // todo: make routing more robust
  if (currentUrl.indexOf('/articles/') > -1) {
    return

  } else if (currentUrl === '/articles') {
    return <Articles />
  } else {
    return <Splash />
  }
})

export default ({ store, currentUrl }) => (
  <Provider store={store}>

      <Page />

  </Provider>
)
<|endoftext|>


<commit_before>var fs = Npm.require('fs');
var path = Npm.require('path');

Package.describe({
  "summary": "Render you app even before the DDP connection comes live. - magic?",
  "version": "2.3.2",
  "git": "https://github.com/meteorhacks/fast-render",
  "name": "meteorhacks:fast-render"
});

Npm.depends({
  "connect": "2.13.0"
});

Package.onUse(function(api) {
  configure(api);
  api.export('FastRender', ['client', 'server']);
  api.export('__init_fast_render', ['client']);
});

Package.onTest(function(api) {
  configure(api);
  api.use('tinytest', ['client', 'server']);
  api.use('http', 'server');

  api.addFiles([
    'tests/utils.js'
  ], ['client', 'server']);

  api.addFiles([
    'tests/client/fast_render.js',
    'tests/client/ddp_update.js'
  ], 'client');

  api.addFiles([
    'tests/server/context.js',
    'tests/server/integration.js'
  ], 'server');
});

function configure (api) {
  api.versionsFrom('METEOR@0.9.3');
  api.use('meteorhacks:inject-data@1.2.3', ['client', 'server']);
  api.use('iron:router@0.9.0 || 1.0.0', ['client', 'server'], {weak: true});
  api.use('chuangbo:cookie@1.1.0', 'client');
  api.use('meteorhacks:picker@1.0.1', 'server');

  api.use(['minimongo', 'livedata', 'ejson', 'underscore', 'webapp', 'routepolicy', 'accounts-base'], ['server']);
  api.use(['minimongo', 'underscore', 'deps', 'ejson', 'accounts-base'], ['client']);

  api.addFiles([
    'lib/utils.js'
  ], ['client', 'server']);

  api.addFiles([
    'lib/server/namespace.js',
    'lib/server/utils.js',
    'lib/server/routes.js',
    'lib/server/publish_context.js',
    'lib/server/context.js',
    'lib/server/iron_router_support.js',
  ], 'server');

  api.addFiles([
    'lib/client/fast_render.js',
    'lib/client/debugger.js',
    'lib/client/ddp_update.js',
    'lib/client/auth.js',
    'lib/client/boot.js'
  ], 'client');

}
<commit_msg>Bump version<commit_after>var fs = Npm.require('fs');
var path = Npm.require('path');

Package.describe({
  "summary": "Render you app even before the DDP connection comes live. - magic?",
  "version": "2.4.0",
  "git": "https://github.com/meteorhacks/fast-render",
  "name": "meteorhacks:fast-render"
});

Npm.depends({
  "connect": "2.13.0"
});

Package.onUse(function(api) {
  configure(api);
  api.export('FastRender', ['client', 'server']);
  api.export('__init_fast_render', ['client']);
});

Package.onTest(function(api) {
  configure(api);
  api.use('tinytest', ['client', 'server']);
  api.use('http', 'server');

  api.addFiles([
    'tests/utils.js'
  ], ['client', 'server']);

  api.addFiles([
    'tests/client/fast_render.js',
    'tests/client/ddp_update.js'
  ], 'client');

  api.addFiles([
    'tests/server/context.js',
    'tests/server/integration.js'
  ], 'server');
});

function configure (api) {
  api.versionsFrom('METEOR@0.9.3');
  api.use('meteorhacks:inject-data@1.2.3', ['client', 'server']);
  api.use('iron:router@0.9.0 || 1.0.0', ['client', 'server'], {weak: true});
  api.use('chuangbo:cookie@1.1.0', 'client');
  api.use('meteorhacks:picker@1.0.1', 'server');

  api.use(['minimongo', 'livedata', 'ejson', 'underscore', 'webapp', 'routepolicy', 'accounts-base'], ['server']);
  api.use(['minimongo', 'underscore', 'deps', 'ejson', 'accounts-base'], ['client']);

  api.addFiles([
    'lib/utils.js'
  ], ['client', 'server']);

  api.addFiles([
    'lib/server/namespace.js',
    'lib/server/utils.js',
    'lib/server/routes.js',
    'lib/server/publish_context.js',
    'lib/server/context.js',
    'lib/server/iron_router_support.js',
  ], 'server');

  api.addFiles([
    'lib/client/fast_render.js',
    'lib/client/debugger.js',
    'lib/client/ddp_update.js',
    'lib/client/auth.js',
    'lib/client/boot.js'
  ], 'client');

}
<|endoftext|>


<commit_before>var PRss = Class.create(PFeedItem, {
    version: '1.0',
    language: '',
    imgUrl: '',
    initialize: function($super, rssFeed) {
        $super(rssFeed.channel[0]);
        this.items = [];
        this._parse(rssFeed);
    },
    _parse: function(rssFeed) {
        this.findVersion(rssFeed);
        this.findLanguage(rssFeed.channel);
        this.findImage(rssFeed.channel);

        // Different location for items
        var feedItems = (this.version == '1.0') ? rssFeed.item : rssFeed.channel[0].item;
        // Check to make sure feedItems is an array
        feedItems = (!Object.isArray(feedItems)) ? $A(feedItems) : feedItems;

        feedItems.each( function(value, index) {
            var item = new PRssItem(value);
            this.items.push(item);
        }, this);
    },
    findVersion: function(feed) {
        this.setPropertyFromFeed(feed, 'version', '@version');
        if(this.version === '') {
            this.version = '1.0';
        }
    },
    findLanguage: function(feed) {
        this.setPropertyFromFeed(feed, 'language', 'language');
    },
    findDescription: function(feed) {
        // Over-ride the default findDescription inherited from PFeedItem
        this.setPropertyFromFeed(feed, 'description', 'description');
    },
    findUpdated: function(feed) {
        // Over-ride the default findUpdated inherited from PFeedItem
        this.setPropertyFromFeed(feed, 'updated', 'lastBuildDate');
    },
    findImage: function(feed) {
        // Find an image tag if one exists
        // feed should be the channel tag...which is going to be an array
        // so select the first item in the array
        if(feed[0].hasOwnProperty('image')) {
            this.setPropertyFromFeed(feed[0].image, 'imgUrl', 'url');
        }
    }
});

<commit_msg>Finds album art successfully.<commit_after>var PRss = Class.create(PFeedItem, {
    version: '1.0',
    language: '',
    imgUrl: '',
    initialize: function($super, rssFeed) {
        $super(rssFeed.channel[0]);
        this.items = [];
        this._parse(rssFeed);
    },
    _parse: function(rssFeed) {
        this.findVersion(rssFeed);
        this.findLanguage(rssFeed.channel);
        this.findImage(rssFeed.channel);

        // Different location for items
        var feedItems = (this.version == '1.0') ? rssFeed.item : rssFeed.channel[0].item;
        // Check to make sure feedItems is an array
        feedItems = (!Object.isArray(feedItems)) ? $A(feedItems) : feedItems;

        feedItems.each( function(value, index) {
            var item = new PRssItem(value);
            this.items.push(item);
        }, this);
    },
    findVersion: function(feed) {
        this.setPropertyFromFeed(feed, 'version', '@version');
        if(this.version === '') {
            this.version = '1.0';
        }
    },
    findLanguage: function(feed) {
        this.setPropertyFromFeed(feed, 'language', 'language');
    },
    findDescription: function(feed) {
        // Over-ride the default findDescription inherited from PFeedItem
        this.setPropertyFromFeed(feed, 'description', 'description');
    },
    findUpdated: function(feed) {
        // Over-ride the default findUpdated inherited from PFeedItem
        this.setPropertyFromFeed(feed, 'updated', 'lastBuildDate');
    },
    findImage: function(feed) {
        // Find an image tag if one exists
        // feed should be the channel tag...which is going to be an array
        // so select the first item in the array
        if(feed[0].hasOwnProperty('image')) {
        	feed[0].image.detect(function(imageNode) {
	        	this.setPropertyFromFeed(imageNode, 'imgUrl', 'url');
	        	return (this.imgUrl !== undefined && !this.imgUrl.blank());
	        }, this);
        }
    }
});

<|endoftext|>


<commit_before>/**
 * Tag-closer extension for CodeMirror.
 *
 * This extension adds an "autoCloseTags" option that can be set to
 * either true to get the default behavior, or an object to further
 * configure its behavior.
 *
 * These are supported options:
 *
 * `whenClosing` (default true)
 *   Whether to autoclose when the '/' of a closing tag is typed.
 * `whenOpening` (default true)
 *   Whether to autoclose the tag when the final '>' of an opening
 *   tag is typed.
 * `dontCloseTags` (default is empty tags for HTML, none for XML)
 *   An array of tag names that should not be autoclosed.
 * `indentTags` (default is block tags for HTML, none for XML)
 *   An array of tag names that should, when opened, cause a
 *   blank line to be added inside the tag, and the blank line and
 *   closing line to be indented.
 *
 * See demos/closetag.html for a usage example.
 */

(function() {
  CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) {
    if (old != CodeMirror.Init && old)
      cm.removeKeyMap("autoCloseTags");
    if (!val) return;
    var map = {name: "autoCloseTags"};
    if (typeof val != "object" || val.whenClosing)
      map["'/'"] = function(cm) { return autoCloseSlash(cm); };
    if (typeof val != "object" || val.whenOpening)
      map["'>'"] = function(cm) { return autoCloseGT(cm); };
    cm.addKeyMap(map);
  });

  var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param",
                       "source", "track", "wbr"];
  var htmlIndent = ["applet", "blockquote", "body", "button", "div", "dl", "fieldset", "form", "frameset", "h1", "h2", "h3", "h4",
                    "h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"];

  function autoCloseGT(cm) {
    var pos = cm.getCursor(), tok = cm.getTokenAt(pos);
    var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
    if (inner.mode.name != "xml" || !state.tagName || cm.getOption("disableInput")) return CodeMirror.Pass;

    var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html";
    var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
    var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);

    var tagName = state.tagName;
    if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
    var lowerTagName = tagName.toLowerCase();
    // Don't process the '>' at the end of an end-tag or self-closing tag
    if (!tagName ||
        tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) ||
        tok.type == "tag" && state.type == "closeTag" ||
        tok.string.indexOf("/") == (tok.string.length - 1) || // match something like <someTagName />
        dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 ||
        CodeMirror.scanForClosingTag && CodeMirror.scanForClosingTag(cm, pos, tagName,
                                                                     Math.min(cm.lastLine() + 1, pos.line + 50)))
      return CodeMirror.Pass;

    var doIndent = indentTags && indexOf(indentTags, lowerTagName) > -1;
    var curPos = doIndent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1);
    cm.replaceSelection(">" + (doIndent ? "\n\n" : "") + "</" + tagName + ">",
                        {head: curPos, anchor: curPos});
    if (doIndent) {
      cm.indentLine(pos.line + 1, null, true);
      cm.indentLine(pos.line + 2, null);
    }
  }

  function autoCloseSlash(cm) {
    var pos = cm.getCursor(), tok = cm.getTokenAt(pos);
    var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
    if (tok.type == "string" || tok.string.charAt(0) != "<" ||
        tok.start != pos.ch - 1 || inner.mode.name != "xml" ||
        cm.getOption("disableInput"))
      return CodeMirror.Pass;

    var tagName = state.context && state.context.tagName;
    if (tagName) cm.replaceSelection("/" + tagName + ">", "end");
    else return CodeMirror.Pass;
  }

  function indexOf(collection, elt) {
    if (collection.indexOf) return collection.indexOf(elt);
    for (var i = 0, e = collection.length; i < e; ++i)
      if (collection[i] == elt) return i;
    return -1;
  }
})();
<commit_msg>[closetag addon] Adjust to multi-selections<commit_after>/**
 * Tag-closer extension for CodeMirror.
 *
 * This extension adds an "autoCloseTags" option that can be set to
 * either true to get the default behavior, or an object to further
 * configure its behavior.
 *
 * These are supported options:
 *
 * `whenClosing` (default true)
 *   Whether to autoclose when the '/' of a closing tag is typed.
 * `whenOpening` (default true)
 *   Whether to autoclose the tag when the final '>' of an opening
 *   tag is typed.
 * `dontCloseTags` (default is empty tags for HTML, none for XML)
 *   An array of tag names that should not be autoclosed.
 * `indentTags` (default is block tags for HTML, none for XML)
 *   An array of tag names that should, when opened, cause a
 *   blank line to be added inside the tag, and the blank line and
 *   closing line to be indented.
 *
 * See demos/closetag.html for a usage example.
 */

(function() {
  CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) {
    if (old != CodeMirror.Init && old)
      cm.removeKeyMap("autoCloseTags");
    if (!val) return;
    var map = {name: "autoCloseTags"};
    if (typeof val != "object" || val.whenClosing)
      map["'/'"] = function(cm) { return autoCloseSlash(cm); };
    if (typeof val != "object" || val.whenOpening)
      map["'>'"] = function(cm) { return autoCloseGT(cm); };
    cm.addKeyMap(map);
  });

  var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param",
                       "source", "track", "wbr"];
  var htmlIndent = ["applet", "blockquote", "body", "button", "div", "dl", "fieldset", "form", "frameset", "h1", "h2", "h3", "h4",
                    "h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"];

  function autoCloseGT(cm) {
    if (cm.getOption("disableInput")) return CodeMirror.Pass;
    var ranges = cm.listSelections(), replacements = [];
    for (var i = 0; i < ranges.length; i++) {
      if (!ranges[i].empty()) return CodeMirror.Pass;
      var pos = ranges[i].head, tok = cm.getTokenAt(pos);
      var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
      if (inner.mode.name != "xml" || !state.tagName) return CodeMirror.Pass;
      var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html";
      var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
      var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);

      var tagName = state.tagName;
      if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
      var lowerTagName = tagName.toLowerCase();
      // Don't process the '>' at the end of an end-tag or self-closing tag
      if (!tagName ||
          tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) ||
          tok.type == "tag" && state.type == "closeTag" ||
          tok.string.indexOf("/") == (tok.string.length - 1) || // match something like <someTagName />
          dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 ||
          CodeMirror.scanForClosingTag && CodeMirror.scanForClosingTag(cm, pos, tagName,
                                                                       Math.min(cm.lastLine() + 1, pos.line + 50)))
        return CodeMirror.Pass;

      var indent = indentTags && indexOf(indentTags, lowerTagName) > -1;
      replacements[i] = {indent: indent,
                         text: ">" + (indent ? "\n\n" : "") + "</" + tagName + ">",
                         newPos: indent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1)};
    }

    for (var i = ranges.length - 1; i >= 0; i--) {
      var info = replacements[i];
      cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, "+insert");
      var sel = cm.listSelections().slice(0);
      sel[i] = {head: info.newPos, anchor: info.newPos};
      cm.setSelections(sel);
      if (info.indent) {
        cm.indentLine(info.newPos.line, null, true);
        cm.indentLine(info.newPos.line + 1, null, true);
      }
    }
  }

  function autoCloseSlash(cm) {
    if (cm.getOption("disableInput")) return CodeMirror.Pass;
    var ranges = cm.listSelections(), replacements = [];
    for (var i = 0; i < ranges.length; i++) {
      if (!ranges[i].empty()) return CodeMirror.Pass;
      var pos = ranges[i].head, tok = cm.getTokenAt(pos);
      var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
      if (tok.type == "string" || tok.string.charAt(0) != "<" ||
          tok.start != pos.ch - 1 || inner.mode.name != "xml" ||
          !state.context || !state.context.tagName)
        return CodeMirror.Pass;
      replacements[i] = "/" + state.context.tagName + ">";
    }
    cm.replaceSelections(replacements, null, "+input");
  }

  function indexOf(collection, elt) {
    if (collection.indexOf) return collection.indexOf(elt);
    for (var i = 0, e = collection.length; i < e; ++i)
      if (collection[i] == elt) return i;
    return -1;
  }
})();
<|endoftext|>


<commit_before>import gql from 'graphql-tag';
import Link from 'next/link';
import { useEffect, useRef, useCallback, useState } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { Box, Divider, Snackbar } from '@material-ui/core';
import { ngettext, msgid, t } from 'ttag';

import { useRouter } from 'next/router';
import { useQuery, useLazyQuery } from '@apollo/react-hooks';
import Head from 'next/head';

import withData from 'lib/apollo';
import useCurrentUser from 'lib/useCurrentUser';
import { nl2br, linkify, ellipsis } from 'lib/text';
import { usePushToDataLayer } from 'lib/gtm';

import { format, formatDistanceToNow } from 'lib/dateWithLocale';
import isValid from 'date-fns/isValid';

import AppLayout from 'components/AppLayout';
import Ribbon from 'components/Ribbon';
import Hyperlinks from 'components/Hyperlinks';
import CurrentReplies from 'components/CurrentReplies';
import ReplyRequestReason from 'components/ReplyRequestReason';
import CreateReplyRequestForm from 'components/CreateReplyRequestForm';
import NewReplySection from 'components/NewReplySection';
import ArticleInfo from 'components/ArticleInfo';
import ArticleCategories from 'components/ArticleCategories';
import TrendPlot from 'components/TrendPlot';
import cx from 'clsx';

const useStyles = makeStyles(theme => ({
  root: {
    display: 'flex',
    padding: '24px 0',
    flexDirection: 'column',
    [theme.breakpoints.up('md')]: {
      flexDirection: 'row',
      alignItems: 'flex-start',
    },
  },
  title: {
    padding: '2px 12px',
    fontSize: 12,
    lineHeight: '20px',
    fontWeight: 700,
    [theme.breakpoints.up('md')]: {
      fontSize: 14,
      padding: '10px 28px',
    },
  },
  firstReported: {
    fontSize: 12,
    lineHeight: '20px',
    color: theme.palette.secondary[200],
    [theme.breakpoints.up('md')]: {
      fontSize: 14,
    },
  },
  card: {
    background: theme.palette.common.white,
    borderRadius: 8,
  },
  main: {
    flex: 1,
    marginRight: 0,
    minWidth: 0,
    [theme.breakpoints.up('md')]: {
      flex: 3,
      marginRight: 12,
    },
  },
  aside: {
    flex: 1,
    background: 'transparent',
    minWidth: 0,
    [theme.breakpoints.up('md')]: {
      padding: '21px 19px',
      background: theme.palette.common.white,
    },
    '& h4': {
      [theme.breakpoints.up('md')]: {
        marginBottom: 0,
        paddingBottom: 16,
        borderBottom: `1px solid ${theme.palette.secondary[500]}`,
      },
    },
  },
  newReplyContainer: {
    position: 'fixed',
    zIndex: theme.zIndex.modal,
    height: '100%',
    width: '100%',
    top: 0,
    left: 0,
    background: theme.palette.common.white,
    [theme.breakpoints.up('md')]: {
      zIndex: 10,
      position: 'relative',
      padding: '28px 16px',
      marginTop: 24,
      borderRadius: 8,
    },
  },
  similarMessageContainer: {
    backgroundColor: theme.palette.common.white,
    minWidth: '100%',
    padding: '17px 19px',
    marginRight: theme.spacing(2),
    borderRadius: 8,
    textDecoration: 'none',
    color: 'inherit',
    [theme.breakpoints.up('md')]: {
      padding: '16px 0 0 0 ',
      margin: 0,
      width: 'auto',
      borderBottom: `1px solid ${theme.palette.secondary[100]}`,
      '&:last-child': {
        borderBottom: 'none',
      },
    },
  },
  text: {
    display: 'box',
    overflow: 'hidden',
    boxOrient: 'vertical',
    textOverflow: 'ellipsis',
    lineClamp: 5,
  },
}));

const LOAD_ARTICLE = gql`
  query LoadArticlePage($id: String!) {
    GetArticle(id: $id) {
      id
      text
      requestedForReply
      replyRequestCount
      replyCount
      createdAt
      references {
        type
      }
      hyperlinks {
        ...HyperlinkData
      }
      replyRequests {
        ...ReplyRequestInfo
      }
      articleReplies {
        ...CurrentRepliesData
      }
      ...RelatedArticleData
      similarArticles: relatedArticles {
        edges {
          node {
            id
            text
            ...ArticleInfo
          }
        }
      }
      articleCategories {
        ...ArticleCategoryData
        ...AddCategoryDialogData
      }
      stats {
        date
        webVisit
        lineVisit
      }
    }
  }
  ${Hyperlinks.fragments.HyperlinkData}
  ${ReplyRequestReason.fragments.ReplyRequestInfo}
  ${CurrentReplies.fragments.CurrentRepliesData}
  ${NewReplySection.fragments.RelatedArticleData}
  ${ArticleCategories.fragments.ArticleCategoryData}
  ${ArticleCategories.fragments.AddCategoryDialogData}
  ${ArticleInfo.fragments.articleInfo}
`;

const LOAD_ARTICLE_FOR_USER = gql`
  query LoadArticlePageForUser($id: String!) {
    GetArticle(id: $id) {
      id # Required, https://github.com/apollographql/apollo-client/issues/2510
      replyRequests {
        ...ReplyRequestInfoForUser
      }
      articleReplies {
        ...ArticleReplyForUser
      }
      articleCategories {
        ...ArticleCategoryDataForUser
        ...AddCategoryDialogData
      }
    }
  }
  ${ReplyRequestReason.fragments.ReplyRequestInfoForUser}
  ${CurrentReplies.fragments.ArticleReplyForUser}
  ${ArticleCategories.fragments.ArticleCategoryDataForUser}
  ${ArticleCategories.fragments.AddCategoryDialogData}
`;

function ArticlePage() {
  const { query } = useRouter();
  const [showForm, setShowForm] = useState(false);
  const [flashMessage, setFlashMessage] = useState(0);
  const articleVars = { id: query.id };

  const { data, loading } = useQuery(LOAD_ARTICLE, {
    variables: articleVars,
  });
  const [
    loadArticleForUser,
    { refetch: refetchArticleForUser, called: articleForUserCalled },
  ] = useLazyQuery(LOAD_ARTICLE_FOR_USER, {
    variables: articleVars,
    fetchPolicy: 'network-only',
  });
  const currentUser = useCurrentUser();

  const replySectionRef = useRef(null);
  const newReplyRef = useRef(null);

  const classes = useStyles();

  // Load user field when currentUser changes
  useEffect(() => {
    if (!articleForUserCalled) {
      loadArticleForUser();
    } else {
      refetchArticleForUser();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentUser]);

  const handleNewReplySubmit = useCallback(() => {
    if (!replySectionRef.current) return;
    replySectionRef.current.scrollIntoView({ behavior: 'smooth' });
    setFlashMessage(t`Your reply has been submitted.`);
  }, []);

  const handleError = useCallback(error => {
    console.error(error);
    setFlashMessage(error.toString());
  }, []);

  const handleFormClose = () => setShowForm(false);

  const article = data?.GetArticle;

  usePushToDataLayer(!!article, { event: 'dataLoaded' });

  if (loading) {
    return (
      <AppLayout>

          <title>{t`Loading`}</title>

        Loading...
      </AppLayout>
    );
  }

  if (!article) {
    return (
      <AppLayout>

          <title>{t`Not found`}</title>

        {t`Message does not exist`}
      </AppLayout>
    );
  }

  const { replyRequestCount, text, hyperlinks } = article;
  const similarArticles = article?.similarArticles?.edges || [];

  const createdAt = article.createdAt
    ? new Date(article.createdAt)
    : new Date();

  const timeAgoStr = formatDistanceToNow(createdAt);

  return (
    <AppLayout>

        <title>
          {ellipsis(article.text, { wordCount: 100 })} | {t`Cofacts`}
        </title>

          <Box
            className={classes.card}
            position="relative"
            pb={{ xs: '13px', md: '21px' }}
          >
            <Box
              display="flex"
              justifyContent="space-between"
              alignItems="center"
              pt="12px"
              pr={{ xs: '12px', md: '19px' }}
            >
              <Ribbon className={classes.title}>
                {ngettext(
                  msgid`${replyRequestCount} person report this message`,
                  `${replyRequestCount} people report this message`,
                  replyRequestCount
                )}
              </Ribbon>

              {isValid(createdAt) && (

                  {t`First reported ${timeAgoStr} ago`}

              )}
            </Box>
            <Box px={{ xs: '12px', md: '19px' }}>
              <Box py={3} overflow="hidden">
                {nl2br(
                  linkify(text, {
                    props: {
                      target: '_blank',
                    },
                  })
                )}
                <Hyperlinks hyperlinks={hyperlinks} />
              </Box>
              <ArticleCategories
                articleId={article.id}
                articleCategories={article.articleCategories.filter(
                  ({ status }) => status === 'NORMAL'
                )}
              />
              <TrendPlot data={article.stats} />
              <Divider />

                {article.replyRequests.map(replyRequest => (
                  <ReplyRequestReason
                    key={replyRequest.id}
                    articleId={article.id}
                    replyRequest={replyRequest}
                  />
                ))}
                <CreateReplyRequestForm
                  requestedForReply={article.requestedForReply}
                  articleId={article.id}
                  onNewReplyButtonClick={() => {
                    setShowForm(true);
                    // use setTimeout to make sure the form has shown
                    setTimeout(
                      () =>
                        newReplyRef.current.scrollIntoView({
                          behavior: 'smooth',
                        }),
                      0
                    );
                  }}
                />

            </Box>
          </Box>

          {showForm && (

              <NewReplySection
                article={article}
                existingReplyIds={(article?.articleReplies || []).map(
                  ({ replyId }) => replyId
                )}
                relatedArticles={article?.relatedArticles}
                onSubmissionComplete={handleNewReplySubmit}
                onError={handleError}
                onClose={handleFormClose}
              />

          )}

          <Box
            className={classes.card}
            position="relative"
            px={{ xs: '12px', md: '19px' }}
            py={{ xs: '13px', md: '21px' }}
            mt={3}
            id="current-replies"
            ref={replySectionRef}
          >

{t`${article.articleReplies.length} replies to the message`}

            <Divider classes={{ root: classes.divider }} />
            <CurrentReplies articleReplies={article.articleReplies} />
          </Box>

{t`Similar messages`}

          {similarArticles.length ? (
            <Box
              display="flex"
              flexDirection={{ xs: 'row', md: 'column' }}
              overflow="auto"
            >
              {similarArticles.map(({ node }) => (
                <Link key={node.id} href={`/article/${node.id}`}>

{node.text}

                    <Box pt={1.5} pb={2}>
                      <ArticleInfo article={node} />
                    </Box>

                </Link>
              ))}
            </Box>
          ) : (
            <Box
              textAlign="center"
              pt={4}
              pb={3}
            >{t`No similar messages found`}</Box>
          )}

      <Snackbar
        open={!!flashMessage}
        onClose={() => setFlashMessage('')}
        message={flashMessage}
      />
    </AppLayout>
  );
}

export default withData(ArticlePage);
<commit_msg>[article] add LINE fab<commit_after>import gql from 'graphql-tag';
import Link from 'next/link';
import { useEffect, useRef, useCallback, useState } from 'react';
import { makeStyles } from '@material-ui/core/styles';
import { Box, Divider, Snackbar } from '@material-ui/core';
import { ngettext, msgid, t } from 'ttag';

import { useRouter } from 'next/router';
import { useQuery, useLazyQuery } from '@apollo/react-hooks';
import Head from 'next/head';

import withData from 'lib/apollo';
import useCurrentUser from 'lib/useCurrentUser';
import { nl2br, linkify, ellipsis } from 'lib/text';
import { usePushToDataLayer } from 'lib/gtm';

import { format, formatDistanceToNow } from 'lib/dateWithLocale';
import isValid from 'date-fns/isValid';
import { LINE_URL } from 'constants/urls';

import AddIcon from '@material-ui/icons/AddCircleOutline';
import Fab from '@material-ui/core/Fab';
import AppLayout from 'components/AppLayout';
import Ribbon from 'components/Ribbon';
import Hyperlinks from 'components/Hyperlinks';
import CurrentReplies from 'components/CurrentReplies';
import ReplyRequestReason from 'components/ReplyRequestReason';
import CreateReplyRequestForm from 'components/CreateReplyRequestForm';
import NewReplySection from 'components/NewReplySection';
import ArticleInfo from 'components/ArticleInfo';
import ArticleCategories from 'components/ArticleCategories';
import TrendPlot from 'components/TrendPlot';
import cx from 'clsx';

const useStyles = makeStyles(theme => ({
  root: {
    display: 'flex',
    padding: '24px 0',
    flexDirection: 'column',
    [theme.breakpoints.up('md')]: {
      flexDirection: 'row',
      alignItems: 'flex-start',
    },
  },
  title: {
    padding: '2px 12px',
    fontSize: 12,
    lineHeight: '20px',
    fontWeight: 700,
    [theme.breakpoints.up('md')]: {
      fontSize: 14,
      padding: '10px 28px',
    },
  },
  firstReported: {
    fontSize: 12,
    lineHeight: '20px',
    color: theme.palette.secondary[200],
    [theme.breakpoints.up('md')]: {
      fontSize: 14,
    },
  },
  card: {
    background: theme.palette.common.white,
    borderRadius: 8,
  },
  main: {
    flex: 1,
    marginRight: 0,
    minWidth: 0,
    [theme.breakpoints.up('md')]: {
      flex: 3,
      marginRight: 12,
    },
  },
  aside: {
    flex: 1,
    background: 'transparent',
    minWidth: 0,
    [theme.breakpoints.up('md')]: {
      padding: '21px 19px',
      background: theme.palette.common.white,
    },
    '& h4': {
      [theme.breakpoints.up('md')]: {
        marginBottom: 0,
        paddingBottom: 16,
        borderBottom: `1px solid ${theme.palette.secondary[500]}`,
      },
    },
  },
  newReplyContainer: {
    position: 'fixed',
    zIndex: theme.zIndex.modal,
    height: '100%',
    width: '100%',
    top: 0,
    left: 0,
    background: theme.palette.common.white,
    [theme.breakpoints.up('md')]: {
      zIndex: 10,
      position: 'relative',
      padding: '28px 16px',
      marginTop: 24,
      borderRadius: 8,
    },
  },
  similarMessageContainer: {
    backgroundColor: theme.palette.common.white,
    minWidth: '100%',
    padding: '17px 19px',
    marginRight: theme.spacing(2),
    borderRadius: 8,
    textDecoration: 'none',
    color: 'inherit',
    [theme.breakpoints.up('md')]: {
      padding: '16px 0 0 0 ',
      margin: 0,
      width: 'auto',
      borderBottom: `1px solid ${theme.palette.secondary[100]}`,
      '&:last-child': {
        borderBottom: 'none',
      },
    },
  },
  text: {
    display: 'box',
    overflow: 'hidden',
    boxOrient: 'vertical',
    textOverflow: 'ellipsis',
    lineClamp: 5,
  },
  lineFab: {
    position: 'fixed',
    zIndex: theme.zIndex.speedDial,
    right: 20,
    bottom: 20,
    background: theme.palette.common.green1,
    color: '#fff',
    height: 52,
    borderRadius: 32,

    '&:hover': {
      background: theme.palette.common.green2,
    },
  },
  lineFabText: {
    lineHeight: '20px',
    margin: '0 12px',
  },
}));

const LOAD_ARTICLE = gql`
  query LoadArticlePage($id: String!) {
    GetArticle(id: $id) {
      id
      text
      requestedForReply
      replyRequestCount
      replyCount
      createdAt
      references {
        type
      }
      hyperlinks {
        ...HyperlinkData
      }
      replyRequests {
        ...ReplyRequestInfo
      }
      articleReplies {
        ...CurrentRepliesData
      }
      ...RelatedArticleData
      similarArticles: relatedArticles {
        edges {
          node {
            id
            text
            ...ArticleInfo
          }
        }
      }
      articleCategories {
        ...ArticleCategoryData
        ...AddCategoryDialogData
      }
      stats {
        date
        webVisit
        lineVisit
      }
    }
  }
  ${Hyperlinks.fragments.HyperlinkData}
  ${ReplyRequestReason.fragments.ReplyRequestInfo}
  ${CurrentReplies.fragments.CurrentRepliesData}
  ${NewReplySection.fragments.RelatedArticleData}
  ${ArticleCategories.fragments.ArticleCategoryData}
  ${ArticleCategories.fragments.AddCategoryDialogData}
  ${ArticleInfo.fragments.articleInfo}
`;

const LOAD_ARTICLE_FOR_USER = gql`
  query LoadArticlePageForUser($id: String!) {
    GetArticle(id: $id) {
      id # Required, https://github.com/apollographql/apollo-client/issues/2510
      replyRequests {
        ...ReplyRequestInfoForUser
      }
      articleReplies {
        ...ArticleReplyForUser
      }
      articleCategories {
        ...ArticleCategoryDataForUser
        ...AddCategoryDialogData
      }
    }
  }
  ${ReplyRequestReason.fragments.ReplyRequestInfoForUser}
  ${CurrentReplies.fragments.ArticleReplyForUser}
  ${ArticleCategories.fragments.ArticleCategoryDataForUser}
  ${ArticleCategories.fragments.AddCategoryDialogData}
`;

function ArticlePage() {
  const { query } = useRouter();
  const [showForm, setShowForm] = useState(false);
  const [flashMessage, setFlashMessage] = useState(0);
  const articleVars = { id: query.id };

  const { data, loading } = useQuery(LOAD_ARTICLE, {
    variables: articleVars,
  });
  const [
    loadArticleForUser,
    { refetch: refetchArticleForUser, called: articleForUserCalled },
  ] = useLazyQuery(LOAD_ARTICLE_FOR_USER, {
    variables: articleVars,
    fetchPolicy: 'network-only',
  });
  const currentUser = useCurrentUser();

  const replySectionRef = useRef(null);
  const newReplyRef = useRef(null);

  const classes = useStyles();

  // Load user field when currentUser changes
  useEffect(() => {
    if (!articleForUserCalled) {
      loadArticleForUser();
    } else {
      refetchArticleForUser();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [currentUser]);

  const handleNewReplySubmit = useCallback(() => {
    if (!replySectionRef.current) return;
    replySectionRef.current.scrollIntoView({ behavior: 'smooth' });
    setFlashMessage(t`Your reply has been submitted.`);
  }, []);

  const handleError = useCallback(error => {
    console.error(error);
    setFlashMessage(error.toString());
  }, []);

  const handleFormClose = () => setShowForm(false);

  const article = data?.GetArticle;

  usePushToDataLayer(!!article, { event: 'dataLoaded' });

  if (loading) {
    return (
      <AppLayout>

          <title>{t`Loading`}</title>

        Loading...
      </AppLayout>
    );
  }

  if (!article) {
    return (
      <AppLayout>

          <title>{t`Not found`}</title>

        {t`Message does not exist`}
      </AppLayout>
    );
  }

  const { replyRequestCount, text, hyperlinks } = article;
  const similarArticles = article?.similarArticles?.edges || [];

  const createdAt = article.createdAt
    ? new Date(article.createdAt)
    : new Date();

  const timeAgoStr = formatDistanceToNow(createdAt);

  return (
    <AppLayout>

        <title>
          {ellipsis(article.text, { wordCount: 100 })} | {t`Cofacts`}
        </title>

          <Box
            className={classes.card}
            position="relative"
            pb={{ xs: '13px', md: '21px' }}
          >
            <Box
              display="flex"
              justifyContent="space-between"
              alignItems="center"
              pt="12px"
              pr={{ xs: '12px', md: '19px' }}
            >
              <Ribbon className={classes.title}>
                {ngettext(
                  msgid`${replyRequestCount} person report this message`,
                  `${replyRequestCount} people report this message`,
                  replyRequestCount
                )}
              </Ribbon>

              {isValid(createdAt) && (

                  {t`First reported ${timeAgoStr} ago`}

              )}
            </Box>
            <Box px={{ xs: '12px', md: '19px' }}>
              <Box py={3} overflow="hidden">
                {nl2br(
                  linkify(text, {
                    props: {
                      target: '_blank',
                    },
                  })
                )}
                <Hyperlinks hyperlinks={hyperlinks} />
              </Box>
              <ArticleCategories
                articleId={article.id}
                articleCategories={article.articleCategories.filter(
                  ({ status }) => status === 'NORMAL'
                )}
              />
              <TrendPlot data={article.stats} />
              <Divider />

                {article.replyRequests.map(replyRequest => (
                  <ReplyRequestReason
                    key={replyRequest.id}
                    articleId={article.id}
                    replyRequest={replyRequest}
                  />
                ))}
                <CreateReplyRequestForm
                  requestedForReply={article.requestedForReply}
                  articleId={article.id}
                  onNewReplyButtonClick={() => {
                    setShowForm(true);
                    // use setTimeout to make sure the form has shown
                    setTimeout(
                      () =>
                        newReplyRef.current.scrollIntoView({
                          behavior: 'smooth',
                        }),
                      0
                    );
                  }}
                />

            </Box>
          </Box>

          {showForm && (

              <NewReplySection
                article={article}
                existingReplyIds={(article?.articleReplies || []).map(
                  ({ replyId }) => replyId
                )}
                relatedArticles={article?.relatedArticles}
                onSubmissionComplete={handleNewReplySubmit}
                onError={handleError}
                onClose={handleFormClose}
              />

          )}

          <Box
            className={classes.card}
            position="relative"
            px={{ xs: '12px', md: '19px' }}
            py={{ xs: '13px', md: '21px' }}
            mt={3}
            id="current-replies"
            ref={replySectionRef}
          >

{t`${article.articleReplies.length} replies to the message`}

            <Divider classes={{ root: classes.divider }} />
            <CurrentReplies articleReplies={article.articleReplies} />
          </Box>

{t`Similar messages`}

          {similarArticles.length ? (
            <Box
              display="flex"
              flexDirection={{ xs: 'row', md: 'column' }}
              overflow="auto"
            >
              {similarArticles.map(({ node }) => (
                <Link key={node.id} href={`/article/${node.id}`}>

{node.text}

                    <Box pt={1.5} pb={2}>
                      <ArticleInfo article={node} />
                    </Box>

                </Link>
              ))}
            </Box>
          ) : (
            <Box
              textAlign="center"
              pt={4}
              pb={3}
            >{t`No similar messages found`}</Box>
          )}

      <Snackbar
        open={!!flashMessage}
        onClose={() => setFlashMessage('')}
        message={flashMessage}
      />
      {!currentUser && (
        <Fab
          size="large"
          variant="extended"
          aria-label="Add friend"
          data-ga="Add LINE friend FAB"
          className={classes.lineFab}
          href={LINE_URL}
          target="_blank"
        >
          <AddIcon />

            LINE 機器人

            查謠言詐騙

        </Fab>
      )}
    </AppLayout>
  );
}

export default withData(ArticlePage);
<|endoftext|>


<commit_before>(function ($, $$) {
	'use strict';

	var ENTER_KEY = 13;

	// hack of taking out html elements from DOM so that agility's view can use it
	// we need 'outerhtml' also, as agilityjs will append DOM, so removing it
	var drawHtml = function (selector) {
		return $(selector).remove().wrap('<div>').parent().html();
	};

	// simple Two layer composition:
	// individual 'todoitem' and 'app' which holds multiple todoitems
	$(function () {
		// todo item
		var todoitem = $$({
			model: {
				title: '',
				completed: false
			},
			view: {
				format: drawHtml('#todo-list li'),
				style: '.hidden { display: none }'
			},
			controller: {
				'change:completed': function () {
					this.view.$().toggleClass('completed', this.model.get('completed'));
					app.updateStatus();
				},
				'dblclick &': function () {
					this.view.$().addClass('editing');
					this.view.$('.edit').focus();
				},
				'click .destroy': function () {
					this.destroy();
				},
				'create': function () {
					this.view.$().toggleClass('completed', this.model.get('completed'));
				},
				'change': function () {
					this.save();
				},
				'destroy': function () {
					this.erase();
				},
				'blur input': function () {
					this.updateTitle();
				},
				'keyup input': function () {
					if (event.which === ENTER_KEY) {
						this.updateTitle();
					}
				}
			},
			updateTitle: function () {
				var title = this.model.get('title').trim();

				this.view.$().removeClass('editing');

				if (title) {
					this.model.set({
						title: title
					});
				} else {
					this.destroy();
				}
			}
		}).persist($$.adapter.localStorage, {
			collection: 'todos-agilityjs'
		});

		// the main application which holds todo items
		var app = $$({
			model: {
				todoCount: '0',
				pluralizer: '',
				completedCount: '0',
				newtitle: '',
				mainStyle: '',
				clearBtnStyle: ''
			},
			view: {
				format: drawHtml('#todoapp'),
				style: '.hidden { display: none }'
			},
			controller: {
				'remove': function () {
					this.updateStatus();
				},
				'append': function () {
					this.updateStatus();
				},
				'keyup #new-todo': function (e) {
					var title = $('#new-todo').val().trim();
					if (e.which === ENTER_KEY && title) {
						var item = $$(todoitem, {
							title: title
						}).save();
						this.append(item, '#todo-list');
						e.target.value = '';  // clear input field
					}
				},
				'click #toggle-all': function () {
					var ischecked = this.view.$('#toggle-all').prop('checked');
					this.each(function (id, item) {
						item.model.set({
							completed: ischecked
						});
					});
				},
				'click #clear-completed': function () {
					this.each(function (id, item) {
						if (item.model.get('completed')) {
							item.destroy();
						}
					});
				}
			},
			// utility functions
			updateStatus: function () {
				// update counts
				var count = this.size();
				var completedCount = 0;

				this.each(function (id, item) {
					if (item.model.get('completed')) {
						completedCount++;
					}
				});

				this.model.set({
					todoCount: count - completedCount + '',
					pluralizer: count - completedCount === 1 ? '' : 's',
					completedCount: completedCount + '',
					mainStyle: count === 0 ? 'hidden' : '',
					clearBtnStyle: completedCount === 0 ? 'hidden' : ''
				});

				// update toggle-all checked status
				$('#toggle-all').prop('checked', completedCount === count);
				// update the view according to the current filter.
				this.applyFilter();
			},
			// filter handler
			filters: {
				'#/': function () {
					return true;
				},
				'#/active': function (item) {
					return !item.model.get('completed');
				},
				'#/completed': function (item) {
					return item.model.get('completed');
				}
			},
			applyFilter: function (hash) {
				var isVisible = this.filters[hash || location.hash];
				if (isVisible) {
					this.each(function (id, item) {
						item.view.$().toggleClass('hidden', !isVisible(item));
					});
				}
			}
		}).persist();

		$$.document.prepend(app);

		// load from localStorage
		app.gather(todoitem, 'append', '#todo-list').updateStatus();

		// manual routing (not supported by agilityjs)
		$(window).on('hashchange', function () {
			var hash = location.hash;
			app.applyFilter(hash);
			$('#filters a').each(function () {
				$(this).toggleClass('selected', hash === $(this).attr('href'));
			});
		});

		if (location.hash) {
			$(window).trigger('hashchange');
		}
	});
})(window.jQuery, window.agility);
<commit_msg>Agility - cancel editing on escape keypress<commit_after>(function ($, $$) {
	'use strict';

	var ENTER_KEY = 13;
	var ESCAPE_KEY = 27;

	// hack of taking out html elements from DOM so that agility's view can use it
	// we need 'outerhtml' also, as agilityjs will append DOM, so removing it
	var drawHtml = function (selector) {
		return $(selector).remove().wrap('<div>').parent().html();
	};

	// simple Two layer composition:
	// individual 'todoitem' and 'app' which holds multiple todoitems
	$(function () {
		// todo item
		var todoitem = $$({
			model: {
				title: '',
				completed: false
			},
			view: {
				format: drawHtml('#todo-list li'),
				style: '.hidden { display: none }'
			},
			controller: {
				'change:completed': function () {
					this.view.$().toggleClass('completed', this.model.get('completed'));
					app.updateStatus();
				},
				'dblclick &': function () {
					this.view.$().addClass('editing');
					this.view.$('.edit').focus();
				},
				'click .destroy': function () {
					this.destroy();
				},
				'create': function () {
					this.view.$().toggleClass('completed', this.model.get('completed'));
				},
				'change': function () {
					this.save();
				},
				'destroy': function () {
					this.erase();
				},
				'blur input': function () {
					this.updateTitle();
				},
				'keyup input': function () {
					if (event.which === ENTER_KEY) {
						this.updateTitle();
					} else if (event.which === ESCAPE_KEY) {
						this.revertTitleEdit();
					}

				}
			},
			updateTitle: function () {
				var title = this.model.get('title').trim();

				this.view.$().removeClass('editing');

				if (title) {
					this.model.set({
						title: title
					});
				} else {
					this.destroy();
				}
			},
			revertTitleEdit: function() {
				this.view.$().removeClass('editing');
				// restore title
				this.view.$(".edit").val(this.model.get('title'));
			}
		}).persist($$.adapter.localStorage, {
			collection: 'todos-agilityjs'
		});

		// the main application which holds todo items
		var app = $$({
			model: {
				todoCount: '0',
				pluralizer: '',
				completedCount: '0',
				newtitle: '',
				mainStyle: '',
				clearBtnStyle: ''
			},
			view: {
				format: drawHtml('#todoapp'),
				style: '.hidden { display: none }'
			},
			controller: {
				'remove': function () {
					this.updateStatus();
				},
				'append': function () {
					this.updateStatus();
				},
				'keyup #new-todo': function (e) {
					var title = $('#new-todo').val().trim();
					if (e.which === ENTER_KEY && title) {
						var item = $$(todoitem, {
							title: title
						}).save();
						this.append(item, '#todo-list');
						e.target.value = '';  // clear input field
					}
				},
				'click #toggle-all': function () {
					var ischecked = this.view.$('#toggle-all').prop('checked');
					this.each(function (id, item) {
						item.model.set({
							completed: ischecked
						});
					});
				},
				'click #clear-completed': function () {
					this.each(function (id, item) {
						if (item.model.get('completed')) {
							item.destroy();
						}
					});
				}
			},
			// utility functions
			updateStatus: function () {
				// update counts
				var count = this.size();
				var completedCount = 0;

				this.each(function (id, item) {
					if (item.model.get('completed')) {
						completedCount++;
					}
				});

				this.model.set({
					todoCount: count - completedCount + '',
					pluralizer: count - completedCount === 1 ? '' : 's',
					completedCount: completedCount + '',
					mainStyle: count === 0 ? 'hidden' : '',
					clearBtnStyle: completedCount === 0 ? 'hidden' : ''
				});

				// update toggle-all checked status
				$('#toggle-all').prop('checked', completedCount === count);
				// update the view according to the current filter.
				this.applyFilter();
			},
			// filter handler
			filters: {
				'#/': function () {
					return true;
				},
				'#/active': function (item) {
					return !item.model.get('completed');
				},
				'#/completed': function (item) {
					return item.model.get('completed');
				}
			},
			applyFilter: function (hash) {
				var isVisible = this.filters[hash || location.hash];
				if (isVisible) {
					this.each(function (id, item) {
						item.view.$().toggleClass('hidden', !isVisible(item));
					});
				}
			}
		}).persist();

		$$.document.prepend(app);

		// load from localStorage
		app.gather(todoitem, 'append', '#todo-list').updateStatus();

		// manual routing (not supported by agilityjs)
		$(window).on('hashchange', function () {
			var hash = location.hash;
			app.applyFilter(hash);
			$('#filters a').each(function () {
				$(this).toggleClass('selected', hash === $(this).attr('href'));
			});
		});

		if (location.hash) {
			$(window).trigger('hashchange');
		}
	});
})(window.jQuery, window.agility);
<|endoftext|>


<commit_before>////
//// The point here is to given some examples of how
//// selenium-webdriver works with promises, etc. Also a proving
//// ground for non-Mocha test types. Read all the way through as
//// there are negative examples as well.
////
//// See README.org in this directory.
////
//// Usage:
////  : AMIGO=http://amigo.geneontology.org node test_examples.js
////

// Setup the driver we'll use.
var firefox = require('selenium-webdriver/firefox');
var driver = new firefox.Driver();

// SEtup some useful aliases, etc.
var By = require('selenium-webdriver').By;
var until = require('selenium-webdriver').until;
var assert = require('chai').assert;

// Get which AmiGO we want from the env, or default.
var target = 'http://amigo.geneontology.org/';
if( process.env['AMIGO'] ){
    target = process.env['AMIGO'];
}

// // NOTE:
// // This seemingly simple test cannot run because there may not be a
// // title yet when this code is reached (browser still loading,
// // etc.). We need to wait, but since we're asynchronous we
// // cannot. From the next example we'll start using implicit promises from
// // the selenium-webdriver framework.
// driver.get(target);
// assert.equal(driver.getTitle(), 'AmiGO 2: Welcome');

// Another try, this time using the implicit promises. The anonymous
// function after the "then" is called when the getTitle variable is
// resolved.
driver.get(target);
driver.getTitle().then(function(title){
    assert.equal(title, 'AmiGO 2: Welcome');
});

// Another way to write the previous function (except this time with a
// slightly different target) explicitly using the promise chaining
// would be as below. However, I think it's safe to say that it's a
// bit nasty looking and we'd rather avoid it if we can.
driver.get(target + '/grebe').then(
    function(){
	driver.getTitle().then(
	    function(title){
		assert.equal(title, 'AmiGO 2: Grebe');
	    });
    });

// Building on this, here's an example of form submission and a
// simple check that we get to the medial search page.
driver.get(target);
driver.findElement(By.id('gsf-query')).sendKeys('neurogenesis');
driver.findElement(By.id('query-form')).submit();
driver.getTitle().then(
    function(title){
	assert.equal(title, 'AmiGO 2: Search Directory');
    });

// Now an example with Google to demonstrate something. While rather
// redundant on its surface, this shows an unexpected JS behaviour
// well. Because Google no longer does a direct query on search,
// rather a JS update on input, the initial title is /not/ "webdriver
// - Google Search", but rather just "Google" (which would be the
// initial title without the wait).
driver.get('http://www.google.com');
driver.findElement(By.name('q')).sendKeys('webdriver');
driver.findElement(By.name('btnG')).click();
driver.wait(until.titleIs('webdriver - Google Search'), 1000);
driver.getTitle().then(
    function(title){
	assert.equal(title, 'webdriver - Google Search');
    });

// // NOTE:
// // Okay, given the above, for our final trick we're going to try and
// // check that something appears in an autocomplete dropdown as we
// // type. This next example, oddly, does not work. Why? Because (from
// // what I can tell) the ui-autocomplete class appears in the DOM
// // before the rest of the autocomplete dropdown text does. This also
// // means that the test /will/ run when done manually, if run line by
// // separate line in the REPL--the text gets fully written into the
// // "ui-autocomplete" element because you're going slower. Okay, let's
// // try again.
// driver.get(target);
// driver.findElement(By.id('gsf-query')).sendKeys('neurogenesis');
// driver.wait(until.elementLocated(By.className('ui-autocomplete')), 1000);
// driver.findElement(By.className('ui-autocomplete')).getText().then(
//     function(text){
// 	console.log('text: ', text);
// 	assert.notEqual(-1, text.search('GO:0022008'));
//     });

// Okay, another try, this time realizing that we really have to wait
// for our target to appear before we can check on it, and not wait
// for a proxy ("ui-autocomplete" in the previous example).
driver.get(target);
driver.findElement(By.id('gsf-query')).sendKeys('neurogenesis');
driver.wait(until.elementTextContains(
    driver.findElement(By.className('ui-autocomplete')), 'GO:0022008'), 1000);
driver.findElement(By.className('ui-autocomplete')).getText().then(
    function(text){
	//console.log(text);
	assert.notEqual(-1, text.search('neurogenesis \\(GO:0022008\\)'),
			'found neurogenesis properly in the dropdown');
    });

// And now we're done, so close it out nicely.
driver.quit();
<commit_msg>added and confirmed phantomjs; #176<commit_after>////
//// The point here is to given some examples of how
//// selenium-webdriver works with promises, etc. Also a proving
//// ground for non-Mocha test types. Read all the way through as
//// there are negative examples as well.
////
//// See README.org in this directory.
////
//// Usage:
////  : AMIGO=http://amigo.geneontology.org node test_examples.js
////

// Setup the driver we'll use. Default to Firefox, but allow others
// with an environmental variable.
var driver = null;
if( process.env['BROWSER'] && process.env['BROWSER'] === 'chrome' ){
    // BUG/TODO: Still working out chrome/chromium
    // webdriver. Currently bad libs on Ubuntu, so not going to
    // progress much there.
    // var webdriver = require('selenium-webdriver');
    // driver = new webdriver.Builder().
    // 	withCapabilities(webdriver.Capabilities.chrome()).
    // 	build();
    var webdriver = require('selenium-webdriver/chrome');
    driver = new webdriver.Driver();
}else if( process.env['BROWSER'] && process.env['BROWSER'] === 'phantomjs' ){
    var webdriver = require('selenium-webdriver/phantomjs');
    driver = new webdriver.Driver();
}else{
    // Default to Firefox.
    var webdriver = require('selenium-webdriver/firefox');
    driver = new webdriver.Driver();
}

// Get which AmiGO we want from the env, or default.
var target = 'http://amigo.geneontology.org/';
if( process.env['AMIGO'] ){
    target = process.env['AMIGO'];
}

// Setup some useful aliases, etc.
var By = require('selenium-webdriver').By;
var until = require('selenium-webdriver').until;
var assert = require('chai').assert;

// // NOTE:
// // This seemingly simple test cannot run because there may not be a
// // title yet when this code is reached (browser still loading,
// // etc.). We need to wait, but since we're asynchronous we
// // cannot. From the next example we'll start using implicit promises from
// // the selenium-webdriver framework.
// driver.get(target);
// assert.equal(driver.getTitle(), 'AmiGO 2: Welcome');

// Another try, this time using the implicit promises. The anonymous
// function after the "then" is called when the getTitle variable is
// resolved.
driver.get(target);
driver.getTitle().then(function(title){
    assert.equal(title, 'AmiGO 2: Welcome');
});

// Another way to write the previous function (except this time with a
// slightly different target) explicitly using the promise chaining
// would be as below. However, I think it's safe to say that it's a
// bit nasty looking and we'd rather avoid it if we can.
driver.get(target + '/grebe').then(
    function(){
	driver.getTitle().then(
	    function(title){
		assert.equal(title, 'AmiGO 2: Grebe');
	    });
    });

// Building on this, here's an example of form submission and a
// simple check that we get to the medial search page.
driver.get(target);
driver.findElement(By.id('gsf-query')).sendKeys('neurogenesis');
driver.findElement(By.id('query-form')).submit();
driver.getTitle().then(
    function(title){
	assert.equal(title, 'AmiGO 2: Search Directory');
    });

// Now an example with Google to demonstrate something. While rather
// redundant on its surface, this shows an unexpected JS behaviour
// well. Because Google no longer does a direct query on search,
// rather a JS update on input, the initial title is /not/ "webdriver
// - Google Search", but rather just "Google" (which would be the
// initial title without the wait).
driver.get('http://www.google.com');
driver.findElement(By.name('q')).sendKeys('webdriver');
driver.findElement(By.name('btnG')).click();
driver.wait(until.titleIs('webdriver - Google Search'), 3000);
driver.getTitle().then(
    function(title){
	assert.equal(title, 'webdriver - Google Search');
    });

// // NOTE:
// // Okay, given the above, for our final trick we're going to try and
// // check that something appears in an autocomplete dropdown as we
// // type. This next example, oddly, does not work. Why? Because (from
// // what I can tell) the ui-autocomplete class appears in the DOM
// // before the rest of the autocomplete dropdown text does. This also
// // means that the test /will/ run when done manually, if run line by
// // separate line in the REPL--the text gets fully written into the
// // "ui-autocomplete" element because you're going slower. Okay, let's
// // try again.
// driver.get(target);
// driver.findElement(By.id('gsf-query')).sendKeys('neurogenesis');
// driver.wait(until.elementLocated(By.className('ui-autocomplete')), 3000);
// driver.findElement(By.className('ui-autocomplete')).getText().then(
//     function(text){
// 	console.log('text: ', text);
// 	assert.notEqual(-1, text.search('GO:0022008'));
//     });

// Okay, another try, this time realizing that we really have to wait
// for our target to appear before we can check on it, and not wait
// for a proxy ("ui-autocomplete" in the previous example).
driver.get(target);
// // Firefox a little slow sometimes here, so wait for it.
// driver.wait(until.titleIs('AmiGO 2: Welcome'), 3000);
driver.findElement(By.id('gsf-query')).sendKeys('neurogenesis');
driver.wait(until.elementTextContains(
    driver.findElement(By.className('ui-autocomplete')), 'GO:0022008'), 3000);
driver.findElement(By.className('ui-autocomplete')).getText().then(
    function(text){
	//console.log(text);
	assert.notEqual(-1, text.search('neurogenesis \\(GO:0022008\\)'),
			'found neurogenes is properly in the dropdown');
    });

// And now we're done, so close it out nicely.
driver.quit();
<|endoftext|>


<commit_before>import { connect } from 'react-redux';
import NavBar from '../components/layout/NavBar';
import TopBar from '../components/layout/TopBar';
import Footer from '../components/layout/Footer';
import dictIcons from '../img/icons/index';
import { logoutAPI } from '../actions';
import { components } from '../params';
import { isPageFullScreen, isFooterHidden } from '../utils';
import { portalVersion } from '../versions';

export const setActive = link => ({
  type: 'ACTIVE_CHANGED',
  data: link,
});

export const initActive = link => ({
  type: 'ACTIVE_INIT',
  data: link,
});

export const ReduxNavBar = (() => {
  const mapStateToProps = state => ({
    navTitle: components.navigation.title,
    navItems: components.navigation.items,
    dictIcons,
    activeTab: state.bar.active,
    userAccess: state.userAccess.access,
    isFullWidth: isPageFullScreen(state.bar.active),
  });

  // Bar chart does not dispatch anything
  const mapDispatchToProps = dispatch => ({
    onActiveTab: link => dispatch(setActive(link)),
    onInitActive: () => dispatch(initActive()),
  });

  return connect(mapStateToProps, mapDispatchToProps)(NavBar);
})();

export const ReduxTopBar = (() => {
  const mapStateToProps = state => ({
    navTitle: components.navigation.title,
    topItems: components.topBar.items,
    useProfileDropdown: components.topBar.useProfileDropdown,
    activeTab: state.bar.active,
    user: state.user,
    userAuthMapping: state.userAuthMapping,
    isFullWidth: isPageFullScreen(state.bar.active),
  });

  // Bar chart does not dispatch anything
  const mapDispatchToProps = dispatch => ({
    onActiveTab: link => dispatch(setActive(link)),
    onLogoutClick: () => dispatch(logoutAPI()),
  });

  return connect(mapStateToProps, mapDispatchToProps)(TopBar);
})();

export const ReduxFooter = (() => {
  const mapStateToProps = state => ({
    portalVersion,
    links: components.footer ? components.footer.links : [],
    dictionaryVersion: state.versionInfo.dictionaryVersion,
    apiVersion: state.versionInfo.apiVersion,
    hidden: isFooterHidden(state.bar.active),
  });

  return connect(mapStateToProps)(Footer);
})();
<commit_msg>fix/highlightActiveNavItem: change the way activeTab is set<commit_after>import { connect } from 'react-redux';
import NavBar from '../components/layout/NavBar';
import TopBar from '../components/layout/TopBar';
import Footer from '../components/layout/Footer';
import dictIcons from '../img/icons/index';
import { logoutAPI } from '../actions';
import { components } from '../params';
import { isPageFullScreen, isFooterHidden } from '../utils';
import { portalVersion } from '../versions';

export const setActive = link => ({
  type: 'ACTIVE_CHANGED',
  data: link,
});

export const initActive = link => ({
  type: 'ACTIVE_INIT',
  data: link,
});

export const ReduxNavBar = (() => {
  const mapStateToProps = state => ({
    navTitle: components.navigation.title,
    navItems: components.navigation.items,
    dictIcons,
    activeTab: location.pathname,
    userAccess: state.userAccess.access,
    isFullWidth: isPageFullScreen(state.bar.active),
  });

  // Bar chart does not dispatch anything
  const mapDispatchToProps = dispatch => ({
    onActiveTab: link => dispatch(setActive(link)),
    onInitActive: () => dispatch(initActive()),
  });

  return connect(mapStateToProps, mapDispatchToProps)(NavBar);
})();

export const ReduxTopBar = (() => {
  const mapStateToProps = state => ({
    navTitle: components.navigation.title,
    topItems: components.topBar.items,
    useProfileDropdown: components.topBar.useProfileDropdown,
    activeTab: state.bar.active,
    user: state.user,
    userAuthMapping: state.userAuthMapping,
    isFullWidth: isPageFullScreen(state.bar.active),
  });

  // Bar chart does not dispatch anything
  const mapDispatchToProps = dispatch => ({
    onActiveTab: link => dispatch(setActive(link)),
    onLogoutClick: () => dispatch(logoutAPI()),
  });

  return connect(mapStateToProps, mapDispatchToProps)(TopBar);
})();

export const ReduxFooter = (() => {
  const mapStateToProps = state => ({
    portalVersion,
    links: components.footer ? components.footer.links : [],
    dictionaryVersion: state.versionInfo.dictionaryVersion,
    apiVersion: state.versionInfo.apiVersion,
    hidden: isFooterHidden(state.bar.active),
  });

  return connect(mapStateToProps)(Footer);
})();
<|endoftext|>


<commit_before>import * as ActionTypes from "../constants/ActionTypes"

const initialState = {
  0: {
    id: 0,
    code: "M",
    x: 50,
    y: 50,
    isActive: true,
    isRelative: false,
    parameters: {},
  },
}

const point = (state, action) => {
  switch (action.type) {
  case ActionTypes.ADD_POINT:
    return {
      id: action.pointId,
      code: action.code,
      x: action.x,
      y: action.y,
      isActive: false,
      isRelative: action.code === action.code.toLowerCase(),
      parameters: action.parameters,
    }

  case ActionTypes.SET_POINT_CODE:
    return {
      ...state,
      code: action.code,
      isRelative: action.code === action.code.toLowerCase(),
      parameters: action.parameters,
    }

  // set position of only one point
  case ActionTypes.SET_POINT_X:
    return {
      ...state,
      x: action.x,
    }

  case ActionTypes.SET_POINT_Y:
    return {
      ...state,
      y: action.y,
    }

  // set position of multiple points
  case ActionTypes.SET_POINTS_X:
    return {
      ...state,
      x: state.x + action.dx,
    }

  case ActionTypes.SET_POINTS_Y:
    return {
      ...state,
      y: state.y + action.dy,
    }

  case ActionTypes.DEACTIVATE_POINTS:
    return {
      ...state,
      isActive: false,
    }

  case ActionTypes.SET_ACTIVE_POINT:
    return {
      ...state,
      isActive: action.isActive,
    }

  case ActionTypes.SET_POINT_PARAMETERS:
    return {
      ...state,
      parameters: action.parameters,
    }

  default:
    return state
  }
}

export default (state = initialState, action) => {
  switch (action.type) {
  case ActionTypes.ADD_POINT:
    return {
      ...state,
      [action.pointId]: point(undefined, action),
    }

  case ActionTypes.DEACTIVATE_POINTS:
    return Object.keys(state).reduce((acc, key) => ({
      ...acc,
      [key]: point(state[key], action),
    }), {})

  case ActionTypes.SET_POINTS_X:
  case ActionTypes.SET_POINTS_Y:
    return Object.keys(state).reduce((acc, key) => ({
      ...acc,
      [key]: action.pointIds.includes(state[key].id) ?
        point(state[key], action) : state[key],
    }), {})

  case ActionTypes.SET_POINT_X:
  case ActionTypes.SET_POINT_Y:
  case ActionTypes.SET_POINT_CODE:
  case ActionTypes.SET_ACTIVE_POINT:
  case ActionTypes.SET_POINT_PARAMETERS:
    return {
      ...state,
      [action.pointId]: point(state[action.pointId], action),
    }

  default:
    return state
  }
}
<commit_msg>Move anchors with points on dragging<commit_after>import * as ActionTypes from "../constants/ActionTypes"

const initialState = {
  0: {
    id: 0,
    code: "M",
    x: 50,
    y: 50,
    isActive: true,
    isRelative: false,
    parameters: {},
  },
}

const point = (state, action) => {
  switch (action.type) {
  case ActionTypes.ADD_POINT:
    return {
      id: action.pointId,
      code: action.code,
      x: action.x,
      y: action.y,
      isActive: false,
      isRelative: action.code === action.code.toLowerCase(),
      parameters: action.parameters,
    }

  case ActionTypes.SET_POINT_CODE:
    return {
      ...state,
      code: action.code,
      isRelative: action.code === action.code.toLowerCase(),
      parameters: action.parameters,
    }

  // set position of only one point
  case ActionTypes.SET_POINT_X:
    return {
      ...state,
      x: action.x,
    }

  case ActionTypes.SET_POINT_Y:
    return {
      ...state,
      y: action.y,
    }

  // set position of multiple points
  case ActionTypes.SET_POINTS_X:
    return {
      ...state,
      x: state.x + action.dx,
      parameters: {
        ...state.parameters,
        ...(state.parameters.x1 ? { x1: state.parameters.x1 + action.dx } : {}),
        ...(state.parameters.x2 ? { x2: state.parameters.x2 + action.dx } : {}),
      },
    }

  case ActionTypes.SET_POINTS_Y:
    return {
      ...state,
      y: state.y + action.dy,
      parameters: {
        ...state.parameters,
        ...(state.parameters.y1 ? { y1: state.parameters.y1 + action.dy } : {}),
        ...(state.parameters.y2 ? { y2: state.parameters.y2 + action.dy } : {}),
      },
    }

  case ActionTypes.DEACTIVATE_POINTS:
    return {
      ...state,
      isActive: false,
    }

  case ActionTypes.SET_ACTIVE_POINT:
    return {
      ...state,
      isActive: action.isActive,
    }

  case ActionTypes.SET_POINT_PARAMETERS:
    return {
      ...state,
      parameters: action.parameters,
    }

  default:
    return state
  }
}

export default (state = initialState, action) => {
  switch (action.type) {
  case ActionTypes.ADD_POINT:
    return {
      ...state,
      [action.pointId]: point(undefined, action),
    }

  case ActionTypes.DEACTIVATE_POINTS:
    return Object.keys(state).reduce((acc, key) => ({
      ...acc,
      [key]: point(state[key], action),
    }), {})

  case ActionTypes.SET_POINTS_X:
  case ActionTypes.SET_POINTS_Y:
    return Object.keys(state).reduce((acc, key) => ({
      ...acc,
      [key]: action.pointIds.includes(state[key].id) ?
        point(state[key], action) : state[key],
    }), {})

  case ActionTypes.SET_POINT_X:
  case ActionTypes.SET_POINT_Y:
  case ActionTypes.SET_POINT_CODE:
  case ActionTypes.SET_ACTIVE_POINT:
  case ActionTypes.SET_POINT_PARAMETERS:
    return {
      ...state,
      [action.pointId]: point(state[action.pointId], action),
    }

  default:
    return state
  }
}
<|endoftext|>