123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- <script src="{{ url_for('static', filename='tweets-ui.js') }}"></script>
- <script>
- {% if notes_app_url %}
- var notesAppUrl = {{ notes_app_url | tojson }}
- {% endif %}
- if (!window['dataset']) {
- {% if visjs_enabled %}
- window.dataset = new vis.DataSet();
- {% else %}
- window.dataset = {
- items: [],
- update: function (items) {
- dataset.items = dataset.items.concat(items);
- },
- get: function () {
- return items;
- }
- }
- {% endif %}
- }
- </script>
- <script>
- function feed_item_to_activity (fi) {
-
- var group = 'tweet';
- var y = 1;
- if (fi.retweeted_tweet_id) {
- group = 'retweet';
- y = 2;
- } else if (fi.replied_tweet_id) {
- group = 'reply';
- y = 3;
- }
-
- return {
- //'id': fi.id,
- 'x': new Date(fi.created_at),
- 'y': y,
- 'group': group,
- 'feed_item': fi
- }
- }
-
- function feed_item_to_likes (fi) {
-
- if ( !fi['public_metrics'] || !fi.public_metrics['like_count'] ) {
- return;
- }
-
- var group = 'likes';
- var y = fi.public_metrics.like_count;
-
-
- return {
- //'id': fi.id,
- 'x': new Date(fi.created_at),
- 'y': y,
- 'group': group,
- 'feed_item': fi
- }
- }
-
- function feed_item_to_replies (fi) {
-
- if ( !fi['public_metrics'] || !fi.public_metrics['reply_count'] ) {
- return;
- }
-
- var group = 'replies';
- var y = fi.public_metrics.reply_count;
-
-
- return {
- //'id': fi.id,
- 'x': new Date(fi.created_at),
- 'y': y,
- 'group': group,
- 'feed_item': fi
- }
- }
- </script>
- {% if twitter_live_enabled and visjs_enabled and not skip_plot %}
- <div class="w-100" style="position: sticky; top: 20px; background-color: silver; padding: 20px 0; margin: 10px 0;">
- <div id="visualization"></div>
-
- </div>
- {% endif %}
- <ul id="tweets" class="tweets z-0">
- {% for tweet in tweets %}
- <li class="tweet d-flex flex-column mb-2 {% if tweet.is_marked %}marked{% endif %}">
- <script>
-
-
- var feedItem = {{ tweet | tojson }};
- var plotItems = [];
-
- var likesPoint = feed_item_to_likes(feedItem);
- if (likesPoint) { plotItems.push(likesPoint); }
-
- var repliesPoint = feed_item_to_replies(feedItem);
- if (repliesPoint) { plotItems.push(repliesPoint); }
-
- if (!plotItems.length) {
- plotItems.push(feed_item_to_activity(feedItem))
- }
-
-
-
- dataset.update(plotItems);
- </script>
- {% if tweet.retweeted_by %}
- <div class="d-flex flex-row">
- <p class="text-end pe-3" style="width: 60px; max-width: 60px; min-width: 60px">
- <i class="bi-repeat"></i>
- </p>
- <p class="flex-grow-1"><a class="moon-gray" href="{{ tweet.retweeted_by_url }}">{{ tweet.retweeted_by }} Retweeted</a></p>
- </div>
- {% endif %}
- <div class="d-flex flex-row">
- {% include "partial/timeline-tweet-bs.html" %}
-
-
- </div>
- <div class="d-flex flex-row">
- <div class="tweet-actions-box d-flex flex-row justify-content-lg-between border-bottom g-2 p-2 flex-grow-1 flex-wrap" style="margin-left: 60px"">
-
-
- {% if tweet.actions.view_replies %}
- <a class="btn btn-sm btn-secondary m-1" href="{{ url_for(tweet.actions.view_replies.route, **tweet.actions.view_replies.route_params) }}">
-
- <i class="bi-chat"></i>
- replies
- </a>
- {% endif %}
-
-
-
- {% if show_thread_controls and tweet.conversation_id %}
- {% with tweet=tweets[0] %}
- {% if tweet.actions.view_thread %}
- <a class="btn btn-sm btn-secondary m-1" href="{{ url_for(tweet.actions.view_thread.route, **tweet.actions.view_thread.route_params) }}">author thread</a>
- {% endif %}
- {% if tweet.actions.view_conversation %}
- <a class="btn btn-sm btn-secondary m-1" href="{{ url_for(tweet.actions.view_conversation.route, **tweet.actions.view_conversation.route_params) }}">full convo</a>
- {% endif %}
- {% endwith %}
- {% endif %}
-
- {% if tweet.actions.view_activity %}
- <a class="btn btn-sm btn-secondary m-1" href="{{ url_for(tweet.actions.view_activity.route, **tweet.actions.view_activity.route_params) }}">
-
- <i class="bi-graph-up"></i>
- activity
- </a>
- {% endif %}
-
- {% if tweet.actions.retweet %}
- <a class="btn btn-sm btn-secondary m-1" hx-post="{{ url_for(tweet.actions.retweet.route, **tweet.actions.retweet.route_params) }}">
-
- <i class="bi-repeat"></i>
- retweet
-
- </a>
- {% endif %}
-
- {% if tweet.actions.bookmark %}
- <a class="btn btn-sm btn-secondary m-1" hx-post="{{ url_for(tweet.actions.bookmark.route, **tweet.actions.bookmark.route_params) }}">
-
- <i class="bi-bookmark"></i>
- bookmark
-
- </a>
- {% if tweet.actions.delete_bookmark %}
- <a class="btn btn-sm btn-secondary m-1" hx-delete="{{ url_for(tweet.actions.delete_bookmark.route, **tweet.actions.delete_bookmark.route_params) }}">-</a>
- {% endif %}
- {% endif %}
-
- <a class="btn btn-sm btn-secondary m-1" class="tweet-action copy-formatted" href="javascript:copyTweetToClipboard('{{ tweet.id }}')">copy formatted</a>
- {% if notes_app_url %}
- <a class="btn btn-sm btn-secondary m-1" class="tweet-action swipe-to-note" href="javascript:swipeTweetToNotesApp('{{ tweet.id }}')">swipe to note</a>
- {% endif %}
- </div>
- </div>
-
- </li>
- {% endfor %}
- {% if query.next_data_url %}
- <li style="height: 50px; vertical-align: middle"
- hx-get="{{ query.next_data_url }}"
- hx-trigger="revealed"
- hx-swap="outerHTML"
- hx-select="ul#tweets > li"
- >
- <center style="height: 100%">
- <span class="js-only">
- Loading more tweets...
- </span>
- </center>
- </li>
-
- {% elif query.next_page_url %}
- <li style="height: 50px; vertical-align: middle"
- >
- <center style="height: 100%">
- <a href="{{ query.next_page_url }}">
- Go to Next Page
- </a>
-
-
- </center>
-
- </li>
-
- {% endif %}
- <li style="display: none">
- <script>
- // https://stackoverflow.com/questions/22663353/algorithm-to-remove-extreme-outliers-in-array
- // we should remove outliers on the X axis. That will mainly be RTs with old dates.
- // we might also be able to get the date of RT as opposed to OG tweet date.
-
- // https://towardsdatascience.com/ways-to-detect-and-remove-the-outliers-404d16608dba
- var profileDataEl = document.querySelector('#profile-data');
-
- if (window['dataset'] && profileDataEl) {
- profileDataEl.innerHTML = dataset.get().filter(i => 'public_metrics' in i).map(i => i.public_metrics.like_count).join(', ');
- }
- {% if visjs_enabled %}
- if (window.profileActivity) {
- window.profileActivity.fit()
- }
- {% endif %}
- </script>
- </li>
- </ul>
- {% if twitter_live_enabled and visjs_enabled and not skip_plot %}
-
- <script>
- function onClick (e) {
- // we need to scan the dataset between min/max x/y
- //
- // TODO we want to scale these based on the zoom level / pixel values
- //
- // FIXME sometimes we get several points:
- // We could also go for the closest point within the bound.
- // Perhaps cycle through upon multiple clicks.
- // For now we can just zoom in closer.
- //
- // range: graph2d.components[3].options.dataAxis.left.range.max
- // fixing this is lower priority since it is currently static.
- graph2d = window.profileActivity;
-
- var timeWindow = graph2d.getWindow();
- var windowInSeconds = (timeWindow.end - timeWindow.start) / 1000;
-
- var pixelWidth = graph2d.dom.centerContainer.offsetWidth;
-
- var secondsPerPixel = windowInSeconds / pixelWidth;
-
- console.log('secondsPerPixel = ' + secondsPerPixel);
-
-
- //var MAX_TIME_DIFF = 1000 * 60 * 60;
-
- var MAX_TIME_DIFF = 10 * secondsPerPixel * 1000;
- var MAX_VALUE_DIFF = 10;
-
- console.log(`click. value=${e.value[0]}, time=${e.time}`);
- console.log(e);
-
- var nearbyItems = dataset.get({filter: function (item) {
- var timeDiff = new Date(item.x).getTime() - e.time.getTime();
- var valueDiff = item.y - e.value[0];
-
- return Math.abs(timeDiff) < MAX_TIME_DIFF
- && Math.abs(valueDiff) < MAX_VALUE_DIFF;
-
- }});
-
- //console.log([e.time, e.value[0]]);
- console.log('nearby points:');
- console.log(nearbyItems);
- }
- var container = document.getElementById('visualization');
- var options = {
- sort: false,
- sampling:false,
- style:'points',
- dataAxis: {
- width: '88px',
- //visible: false,
- left: {
- range: {
- min: 0, max: 10
- }
- }
- },
- drawPoints: {
- enabled: true,
- size: 6,
- style: 'circle' // square, circle
- },
- defaultGroup: 'Scatterplot',
- graphHeight: '50px',
- width: '100%'
- };
- var groups = [{id: 'feed_item'}];
- window.profileActivity = new vis.Graph2d(container, window.dataset, groups, options);
- window.profileActivity.on('click', onClick);
- </script>
-
- {% endif %}
|