First commit

This commit is contained in:
jeremy@jermolene.com 2022-11-12 17:06:54 +00:00
parent 344110e289
commit f922149b32
18 changed files with 722 additions and 0 deletions

View File

@ -233,6 +233,15 @@ node $TW5_BUILD_TIDDLYWIKI \
--build index \
|| exit 1
# /editions/twitter-archivist/index.html Twitter Archivist edition
node $TW5_BUILD_TIDDLYWIKI \
./editions/twitter-archivist \
--verbose \
--load $TW5_BUILD_OUTPUT/build.tid \
--output $TW5_BUILD_OUTPUT/editions/twitter-archivist/ \
--build index \
|| exit 1
######################################################
#
# Plugin demos

View File

@ -0,0 +1,3 @@
title: $:/DefaultTiddlers
HelloThere

View File

@ -0,0 +1,3 @@
title: HelloThere
{{$:/plugins/tiddlywiki/twitter-archivist/readme}}

View File

@ -0,0 +1,3 @@
title: $:/SiteTitle
Get Your Tweets Into ~TiddlyWiki

View File

@ -0,0 +1,3 @@
title: $:/SiteTitle
Twitter Archivist

View File

@ -0,0 +1,16 @@
{
"description": "Twitter Archivist Edition",
"plugins": [
"tiddlywiki/twitter-archivist"
],
"languages": [
],
"themes": [
"tiddlywiki/vanilla",
"tiddlywiki/snowwhite"
],
"build": {
"index": [
"--rendertiddler","$:/core/save/all","index.html","text/plain"]
}
}

View File

@ -0,0 +1,264 @@
/*\
title: $:/plugins/tiddlywiki/twitter-archivist/archivist.js
type: application/javascript
module-type: utils
Utility class for manipulating Twitter archives
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
function TwitterArchivist(options) {
options = options || {};
this.source = options.source;
}
TwitterArchivist.prototype.loadArchive = async function(options) {
options = options || {};
const wiki = options.wiki;
await this.source.init();
// Process the manifest and profile
const manifestData = await this.loadTwitterJsData("data/manifest.js","window.__THAR_CONFIG = ",""),
profileData = await this.loadTwitterJsData("data/profile.js","window.YTD.profile.part0 = ",""),
accountData = await this.loadTwitterJsData("data/account.js","window.YTD.account.part0 = ",""),
username = manifestData.userInfo.userName,
user_id = manifestData.userInfo.accountId;
wiki.addTiddler({
title: "Twitter Archive for @" + username,
tags: "$:/tags/TwitterArchive",
user_id: user_id,
username: username,
displayname: manifestData.userInfo.displayName,
generation_date: $tw.utils.stringifyDate(new Date(manifestData.archiveInfo.generationDate)),
account_created_date: $tw.utils.stringifyDate(new Date(accountData[0].account.createdAt)),
bio: profileData[0].profile.description.bio,
website: profileData[0].profile.description.website,
location: profileData[0].profile.description.location
});
// Process the media
await this.source.processFiles("data/tweets_media","base64",function(mediaItem) {
var ext = mediaItem.filename.split(".").slice(-1)[0];
if("jpg png".split(" ").indexOf(ext) !== -1) {
var extensionInfo = $tw.utils.getFileExtensionInfo("." + ext),
type = extensionInfo ? extensionInfo.type : null;
wiki.addTiddler({
title: "Tweet Media - " + mediaItem.filename,
tags: "$:/tags/TweetMedia",
status_id: mediaItem.filename.split("-")[0],
text: mediaItem.contents,
type: type
});
}
});
// Process the favourites
const likeData = await this.loadTwitterJsData("data/like.js","window.YTD.like.part0 = ","");
$tw.utils.each(likeData,function(like) {
// Create the tweet tiddler
var tiddler = {
title: "Tweet - " + like.like.tweetId,
text: "\\rules only html entity extlink\n" + (like.like.fullText || "").replace("\n","<br>"),
status_id: like.like.tweetId,
liked_by: user_id,
tags: "$:/tags/Tweet"
};
wiki.addTiddler(tiddler);
});
// Process the tweets
const tweetData = await this.loadTwitterJsData("data/tweets.js","window.YTD.tweets.part0 = ","");
$tw.utils.each(tweetData,function(tweet) {
// Compile the tags for the tweet
var tags = ["$:/tags/Tweet"];
// Create tiddlers for each mentioned tweeter, and hyperlink the mention in the test
var rawText = tweet.tweet.full_text,
posText = 0,
text = "";
var mentions = [];
$tw.utils.each(tweet.tweet.entities.user_mentions,function(mention) {
var title = "Tweeter - " + mention.id_str;
tags.push(title);
wiki.addTiddler({
title: title,
screenname: "@" + mention.screen_name,
tags: "$:/tags/Tweeter",
user_id: mention.id_str,
name: mention.name
});
text = text +
$tw.utils.htmlEncode(rawText.substring(posText,mention.indices[0])) +
"<$link to=\"" + title + "\">" +
$tw.utils.htmlEncode(rawText.substring(mention.indices[0],mention.indices[1])) +
"</$link>";
posText = mention.indices[1];
mentions.push(mention.id_str);
});
if(posText < rawText.length) {
text = text + $tw.utils.htmlEncode(rawText.substring(posText));
}
text = text.replace("\n","<br>");
// Create the tweet tiddler
var tiddler = {
title: "Tweet - " + tweet.tweet.id_str,
text: "\\rules only html entity extlink\n" + text,
status_id: tweet.tweet.id_str,
user_id: user_id,
favorite_count: tweet.tweet.favorite_count,
retweet_count: tweet.tweet.retweet_count,
tags: tags,
created: $tw.utils.stringifyDate(new Date(tweet.tweet.created_at)),
modified: $tw.utils.stringifyDate(new Date(tweet.tweet.created_at))
};
if(tweet.tweet.in_reply_to_status_id_str) {
tiddler.in_reply_to_status_id = tweet.tweet.in_reply_to_status_id_str;
}
if(mentions.length > 0) {
tiddler.mention_user_ids = $tw.utils.stringifyList(mentions);
}
wiki.addTiddler(tiddler);
});
};
TwitterArchivist.prototype.loadTwitterJsData = async function(filePath,prefix,suffix) {
var tweetFileData = await this.source.loadTwitterJsData(filePath);
if(prefix) {
if(tweetFileData.slice(0,prefix.length) !== prefix) {
throw "Reading Twitter JS file " + filePath + " missing prefix '" + prefix + "'";
}
tweetFileData = tweetFileData.slice(prefix.length);
}
if(suffix) {
if(tweetFileData.slice(-suffix.length) !== suffix) {
throw "Reading Twitter JS file " + filePath + " missing suffix '" + suffix + "'";
}
tweetFileData = tweetFileData.slice(0,tweetFileData.length - suffix.length);
}
return JSON.parse(tweetFileData);
};
function TwitterArchivistSourceNodeJs(options) {
options = options || {};
this.archivePath = options.archivePath;
}
TwitterArchivistSourceNodeJs.prototype.init = async function() {
};
TwitterArchivistSourceNodeJs.prototype.processFiles = async function(dirPath,encoding,callback) {
var fs = require("fs"),
path = require("path"),
dirPath = path.resolve(this.archivePath,dirPath),
filenames = fs.readdirSync(dirPath);
$tw.utils.each(filenames,function(filename) {
callback({
filename: filename,
contents: fs.readFileSync(path.resolve(dirPath,filename),encoding)
});
});
};
TwitterArchivistSourceNodeJs.prototype.loadTwitterJsData = async function(filePath) {
var fs = require("fs"),
path = require("path");
return fs.readFileSync(path.resolve(this.archivePath,filePath),"utf8");
};
function TwitterArchivistSourceBrowser(options) {
options = options || {};
}
TwitterArchivistSourceBrowser.prototype.init = async function() {
// Open directory
this.rootDirHandle = await window.showDirectoryPicker();
};
TwitterArchivistSourceBrowser.prototype.processFiles = async function(dirPath,encoding,callback) {
const dirHandle = await this.walkDirectory(dirPath.split("/"));
for await (const [filename, fileHandle] of dirHandle.entries()) {
const contents = await fileHandle.getFile();
callback({
filename: filename,
contents: arrayBufferToBase64(await contents.arrayBuffer())
});
}
};
TwitterArchivistSourceBrowser.prototype.loadTwitterJsData = async function(filePath) {
const filePathParts = filePath.split("/");
const dirHandle = await this.walkDirectory(filePathParts.slice(0,-1));
const fileHandle = await dirHandle.getFileHandle(filePathParts.slice(-1)[0]);
const contents = await fileHandle.getFile();
return await contents.text();
};
TwitterArchivistSourceBrowser.prototype.walkDirectory = async function(arrayDirectoryEntries) {
var entries = arrayDirectoryEntries.slice(0),
dirHandle = this.rootDirHandle;
while(entries.length > 0) {
dirHandle = await dirHandle.getDirectoryHandle(entries[0]);
entries.shift();
}
return dirHandle;
};
// Thanks to MatheusFelipeMarinho
// https://github.com/MatheusFelipeMarinho/venom/blob/43ead0bfffa57a536a5cff67dd909e55da9f0915/src/lib/wapi/helper/array-buffer-to-base64.js#L55
function arrayBufferToBase64(arrayBuffer) {
var base64 = '';
var encodings =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var bytes = new Uint8Array(arrayBuffer);
var byteLength = bytes.byteLength;
var byteRemainder = byteLength % 3;
var mainLength = byteLength - byteRemainder;
var a, b, c, d;
var chunk;
// Main loop deals with bytes in chunks of 3
for (var i = 0; i < mainLength; i = i + 3) {
// Combine the three bytes into a single integer
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
// Use bitmasks to extract 6-bit segments from the triplet
a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
d = chunk & 63; // 63 = 2^6 - 1
// Convert the raw binary segments to the appropriate ASCII encoding
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
}
// Deal with the remaining bytes and padding
if (byteRemainder == 1) {
chunk = bytes[mainLength];
a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
// Set the 4 least significant bits to zero
b = (chunk & 3) << 4; // 3 = 2^2 - 1
base64 += encodings[a] + encodings[b] + '==';
} else if (byteRemainder == 2) {
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
// Set the 2 least significant bits to zero
c = (chunk & 15) << 2; // 15 = 2^4 - 1
base64 += encodings[a] + encodings[b] + encodings[c] + '=';
}
return base64;
}
exports.TwitterArchivist = TwitterArchivist;
exports.TwitterArchivistSourceNodeJs = TwitterArchivistSourceNodeJs;
exports.TwitterArchivistSourceBrowser = TwitterArchivistSourceBrowser;
})();

View File

@ -0,0 +1,2 @@
title: $:/config/TiddlerInfo/Mode
text: sticky

View File

@ -0,0 +1,53 @@
/*\
title: $:/plugins/tiddlywiki/twitter-archivist/loadtwitterarchive.js
type: application/javascript
module-type: command
Read tiddlers from an unzipped Twitter archive
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
var widget = require("$:/core/modules/widgets/widget.js");
exports.info = {
name: "loadtwitterarchive",
synchronous: false
};
var Command = function(params,commander,callback) {
this.params = params;
this.commander = commander;
this.callback = callback;
};
Command.prototype.execute = function() {
var self = this;
if(this.params.length < 1) {
return "Missing path to Twitter archive";
}
var archivePath = this.params[0];
// Load tweets
var archiveSource = new $tw.utils.TwitterArchivistSourceNodeJs({
archivePath: archivePath
}),
archivist = new $tw.utils.TwitterArchivist({
source: archiveSource
});
archivist.loadArchive({
wiki: this.commander.wiki
}).then(function() {
self.callback(null);
}).catch(function(err) {
self.callback(err);
});
return null;
};
exports.Command = Command;
})();

View File

@ -0,0 +1,204 @@
title: $:/plugins/tiddlywiki/twitter-archivist/macros
tags: $:/tags/Macro
\define skinny-tabs(tabNames,tabCaptions,defaultTab,state)
<$let
currTab={{{ [<__state__>get[text]else<__defaultTab__>] }}}
>
<div class="tc-tab-set">
<div class="tc-tab-buttons">
<$list filter="[enlist<__tabNames__>]" variable="tab" counter="tabCounter">
<$let
caption={{{ [enlist<__tabCaptions__>nth<tabCounter>] }}}
>
<$list filter="[<tab>match<currTab>]" variable="ignore">
<$button aria-checked="true" class="tc-tab-selected" role="switch">
<$action-setfield $tiddler=<<__state__>> $value=<<tab>>/>
<$text text=<<caption>>/>
</$button>
</$list>
<$list filter="[<tab>!match<currTab>]" variable="ignore">
<$button role="switch">
<$action-setfield $tiddler=<<__state__>> $value=<<tab>>/>
<$text text=<<caption>>/>
</$button>
</$list>
</$let>
</$list>
</div>
<div class="tc-tab-divider"></div>
<div class="tc-tab-content">
<$list filter="[enlist<__tabNames__>]" variable="tab" counter="tabCounter">
<$list filter="[<tab>match<currTab>]" variable="ignore">
<div class="tc-reveal">
<$macrocall $name=<<currTab>>/>
</div>
</$list>
<$list filter="[<tab>!match<currTab>]" variable="ignore">
<div class="tc-reveal" hidden="true"></div>
</$list>
</$list>
</div>
</div>
</$let>
\end
\define list-archives()
\whitespace trim
<ul>
<$list filter="[tag[$:/tags/TwitterArchive]sort[displayname]]">
<li>
<$link><$text text=<<currentTiddler>>/></$link>
</li>
</$list>
</ul>
\end
\define show-archive()
<$let
user_id={{!!user_id}}
>
<div class="tc-twitter-archive">
<table>
<tbody>
<<show-archive-attribute "Username" "username" prefix:"@">>
<<show-archive-attribute "Display Name" "displayname">>
<<show-archive-attribute "Bio" "bio">>
<<show-archive-attribute "Location" "location">>
<<show-archive-attribute "Website" "website">>
<<show-archive-calculated-attribute "Number of Tweets" "[tag[$:/tags/Tweet]field:user_id<user_id>count[]]">>
<<show-archive-calculated-attribute "Number of Favorites Received" "[tag[$:/tags/Tweet]field:user_id<user_id>] :reduce[<currentTiddler>get[favorite_count]else[0]add<accumulator>]">>
<<show-archive-calculated-attribute "Number of Retweets Received" "[tag[$:/tags/Tweet]field:user_id<user_id>] :reduce[<currentTiddler>get[retweet_count]else[0]add<accumulator>]">>
<<show-archive-calculated-attribute "Number of Tweeters Mentioned" "[tag[$:/tags/Tweeter]count[]]">>
<<show-archive-attribute "User ID" "user_id">>
<<show-archive-attribute "Account Creation Date" "account_created_date" format:"date" template:"DDth mmm YYYY 0hh:0mm:0ss">>
<<show-archive-attribute "Archive Generation Date" "generation_date" format:"date" template:"DDth mmm YYYY 0hh:0mm:0ss">>
</tbody>
</table>
<$macrocall $name="skinny-tabs" tabNames="show-archive-tweets show-favorited-tweets" tabCaptions="Tweets Favourites" defaultTab="show-archive-tweets" state=<<qualify "$:/state/skinny-tabs/archive">>/>
</div>
</$let>
\end
\define show-archive-tweets()
<$let user_id={{!!user_id}}>
<$list filter="[tag[$:/tags/Tweet]field:user_id<user_id>!sort[created]limit[50]]">
<<show-tweet>>
</$list>
</$let>
\end
\define show-favorited-tweets()
<$let user_id={{!!user_id}}>
<$list filter="[tag[$:/tags/Tweet]field:liked_by<user_id>limit[50]]">
<<show-tweet>>
</$list>
</$let>
\end
\define show-archive-attribute(caption,field,prefix,format:"text",template)
<tr>
<th>
<$text text=<<__caption__>>/>
</th>
<td>
<$text text={{{ [<__prefix__>] }}}/>
<$view field=<<__field__>> format=<<__format__>> template=<<__template__>>/>
</td>
</tr>
\end
\define show-archive-calculated-attribute(caption,filter)
<tr>
<th>
<$text text=<<__caption__>>/>
</th>
<td>
<$text text={{{ [subfilter<__filter__>] }}}/>
</td>
</tr>
\end
\define show-tweet()
<div class="tc-twitter-tweet">
<div class="tc-twitter-tweet-header">
<$let user_id={{{ [<__archive__>get[user_id]] }}}>
<$list filter="[{!user_id}match<user_id>]" variable="ignore">
<span class="tc-twitter-tweet-header-displayname">
<$text text={{{ [<__archive__>get[displayname]] }}}/>
</span>
<span class="tc-twitter-tweet-header-username">
@<$text text={{{ [<__archive__>get[username]] }}}/>
</span>
</$list>
</$let>
<$link to=<<currentTiddler>>>
<span class="tc-twitter-tweet-header-date">
<$view field="created" format="date" template="DDth mmm YYYY 0hh:0mm:0ss"/>
</span>
</$link>
</div>
<$list filter="[<__title__>get[in_reply_to_status_id]addprefix[Tweet - ]is[tiddler]]" variable="replyTo">
<div class="tc-twitter-tweet-reply-to">
Reply to <$link to=<<replyTo>>><$text text=<<replyTo>>/></$link>
</div>
</$list>
<div class="tc-twitter-tweet-body">
<$transclude field="text"/>
</div>
<div class="tc-twitter-tweet-media">
<$let status_id={{!!status_id}}>
<$list filter="[tag[$:/tags/TweetMedia]field:status_id<status_id>]" variable="mediaItem">
<$transclude tiddler=<<mediaItem>>/>
</$list>
</$let>
</div>
<div class="tc-twitter-tweet-footer">
<$list filter="[<currentTiddler>has[retweet_count]]" variable="ignore">
<span class="tc-twitter-tweet-footer-retweets">
Retweets: <$view field="retweet_count" format="text"/>
</span>
</$list>
<$list filter="[<currentTiddler>has[favorite_count]]" variable="ignore">
<span class="tc-twitter-tweet-footer-likes">
Likes: <$view field="favorite_count" format="text"/>
</span>
</$list>
<span class="tc-twitter-tweet-footer-twitter-link">
<a href={{{ [{!!status_id}addprefix[https://twitter.com/i/web/status/]] }}} rel="noopener noreferrer" target="_blank">View on Twitter</a>
</span>
</div>
</div>
\end
\define show-tweet-thread(archive)
<div class="tc-twitter-tweet-thread">
<$list filter="[<currentTiddler>has[in_reply_to_status_id]]" variable="ignore">
<div class="tc-twitter-tweet-reply">
<$tiddler tiddler={{{ [<currentTiddler>get[in_reply_to_status_id]addprefix[Tweet - ]] }}}>
<$macrocall $name="show-tweet"/>
</$tiddler>
</div>
</$list>
<$macrocall $name="show-tweet"/>
</div>
\end
\define show-tweeter()
<table>
<tbody>
<tr><th>Username</th><td><$text text={{!!screenname}}/></td></tr>
<tr><th>Display Name</th><td><$text text={{!!name}}/></td></tr>
<tr><th>User ID</th><td><$text text={{!!user_id}}/></td></tr>
</tbody>
</table>
<a href={{{ [{!!user_id}addprefix[https://twitter.com/intent/user?user_id=]] }}} rel="noopener noreferrer" target="_blank">View on Twitter</a>
<$macrocall $name="skinny-tabs" tabNames="show-tweeter-mentions" tabCaptions="Mentions" defaultTab="show-tweeter-mentions" state=<<qualify "$:/state/skinny-tabs/tweeter-mentions">>/>
\end
\define show-tweeter-mentions()
<$list filter="[tag[$:/tags/Tweet]tag<currentTiddler>]">
<$macrocall $name="show-tweet" archive=<<currentTiddler>> title=<<currentTiddler>>/>
</$list>
\end

View File

@ -0,0 +1,6 @@
{
"title": "$:/plugins/tiddlywiki/twitter-archivist",
"name": "Twitter Archivist",
"description": "Twitter archiving tools",
"list": "readme"
}

View File

@ -0,0 +1,55 @@
title: $:/plugins/tiddlywiki/twitter-archivist/readme
! Introduction
The Twitter Archivist imports the tweets and associated media from a [[Twitter Archive|https://help.twitter.com/en/managing-your-account/how-to-download-your-twitter-archive]] as individual tiddlers.
The Twitter Archivist plugin is available from the official plugin library for installation in your own wikis.
! Limitations
This initial version of the Twitter Archivist has several shortcomings that may be addressed in the future:
* Does not handle editable tweets
* Does not handle direct messages
! Limitations of Twitter Archives
The Twitter Archive format itself has many shortcomings which affect this tool:
* Retweets come through as old-school RTs, which means that they are often truncated
* Likes only have minimal information, lacking date, author and mentions
* External links go to the t.co shortener
* Twitter archives can be delivered in multiple parts, but this tool has only been tested with single archives. It is hoped that cumulatively importing each of the archives in turn should work
A future version of this tool may use the Twitter API to get around these restrictions.
! Getting Started
First, request your Tweet archive from Twitter. Once it is available, download and unzip it.
The Twitter Archivist can operate in the browser or under Node.js.
!! In the Browser
To import a Twitter archive in the browser, click the button below and navigate to the root of the archive:
<$button>
<$action-sendmessage $message="tm-load-twitter-archive"/>
Open Twitter archive
</$button>
!! Under Node.js
To import a Twitter archive under Node.js, use the `--loadtwitterarchive` command:
```
tiddlywiki editions/twitter-archivist/ --loadtwitterarchive '/path/to/archive' --build index
```
! Imported Archives
Any imported archives will show here:
<<list-archives>>

View File

@ -0,0 +1,38 @@
/*\
title: $:/plugins/tiddlywiki/twitter-archivist/startup.js
type: application/javascript
module-type: startup
Twitter initialisation
\*/
(function(){
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
// Export name and synchronous status
exports.name = "twitter-archivist";
exports.after = ["startup"];
exports.synchronous = true;
exports.startup = function() {
$tw.rootWidget.addEventListener("tm-load-twitter-archive",function(event) {
// Load tweets
var archiveSource = new $tw.utils.TwitterArchivistSourceBrowser({
}),
archivist = new $tw.utils.TwitterArchivist({
source: archiveSource
});
archivist.loadArchive({
wiki: $tw.wiki
}).then(function() {
alert("Archived tweets imported");
}).catch(function(err) {
alert("Error importing archived tweets: " + err);
});
});
};
})();

View File

@ -0,0 +1,47 @@
title: $:/plugins/tiddlywiki/twitter-archivist/styles
tags: [[$:/tags/Stylesheet]]
code-body: yes
\rules only filteredtranscludeinline transcludeinline macrodef macrocallinline macrocallblock
.tc-twitter-tweet {
border: 1px solid <<colour muted-foreground>>;
border-radius: 8px;
margin: 1em 0;
padding: 1em;
}
.tc-twitter-tweet-reply {
font-size: 0.7em;
}
.tc-twitter-tweet-reply .tc-twitter-tweet {
margin: 0.5em 0 0.5em 1em;
padding: 0.5em;
}
.tc-twitter-tweet-header-displayname {
font-weight: bold;
}
.tc-twitter-tweet-header-username,
.tc-twitter-tweet-header-date {
color: #536471;
}
.tc-twitter-tweet-reply-to {
font-size: 0.7em;
}
.tc-twitter-tweet-body {
margin: 0.25em 0;
line-height: 1.3;
}
.tc-twitter-tweet-reply .tc-twitter-tweet-body {
margin: 0.5em 0;
}
.tc-twitter-tweet-footer {
font-size: 0.8em;
}

View File

@ -0,0 +1,3 @@
title: $:/plugins/tiddlywiki/twitter-archivist/template/archive
<<show-archive>>

View File

@ -0,0 +1,3 @@
title: $:/plugins/tiddlywiki/twitter-archivist/template/tweet
<<show-tweet-thread>>

View File

@ -0,0 +1,3 @@
title: $:/plugins/tiddlywiki/twitter-archivist/template/tweeter
<<show-tweeter>>

View File

@ -0,0 +1,7 @@
title: $:/plugins/tiddlywiki/twitter-archivist/view-template-body-cascade
tags: $:/tags/ViewTemplateBodyFilter
list-before:
[tag[$:/tags/Tweet]then[$:/plugins/tiddlywiki/twitter-archivist/template/tweet]]
[tag[$:/tags/TwitterArchive]then[$:/plugins/tiddlywiki/twitter-archivist/template/archive]]
[tag[$:/tags/Tweeter]then[$:/plugins/tiddlywiki/twitter-archivist/template/tweeter]]