diff --git a/README.md b/README.md index 7000439..ea2260f 100644 --- a/README.md +++ b/README.md @@ -54,14 +54,23 @@ To download a tweet thread, paste the link to the **LAST** tweet in the thread, ![The modal to download a new tweet.](https://raw.githubusercontent.com/kbravh/obsidian-tweet-to-markdown/main/images/tweet_url_modal.png) -Once the tweet is downloaded, you'll be presented a window to set the name of the file that will be created. You can use the variables `[[handle]]`, `[[name]]`, `[[text]]`, and `[[id]]` when naming your file, which will be automatically replaced according to the following chart. The file extension `.md` will be added automatically. +Once the tweet is downloaded, you'll be presented a window to set the name of the file that will be created. You can use the variables `[[handle]]`, `[[name]]`, `[[text]]`, `[[date]]`, and `[[id]]` when naming your file, which will be automatically replaced according to the following chart. The file extension `.md` will be added automatically. | Variable | Replacement | |:---:|---| -|[[handle]]|The user's handle (the part that follows the @ symbol)| -|[[name]]|The user's name| -|[[id]]|The unique ID assigned to the tweet| -|[[text]]|The entire text of the tweet| +|`[[handle]]`|The user's handle (the part that follows the @ symbol)| +|`[[name]]`|The user's name| +|`[[id]]`|The unique ID assigned to the tweet| +|`[[text]]`|The entire text of the tweet (truncated to fit OS filename length restrictions)| +|`[[date]]`|The date that the tweet was created| + +If the `[[date]]` variable is provided, it will by default use the locale and time format defined in your [settings](https://github.com/kbravh/obsidian-tweet-to-markdown#date-format) (it's towards the bottom of this readme). + +- If you would like to use a different format, include it after the date with a semicolon: `[[date:LL]]`. +- If you would like a different locale, include it after the format with another semicolon: `[[date:LL:es]]`. +- If you want the same format as your settings but a different locale, just leave the format section blank: `[[date::es]]`. + +Please check the `moment` documentation for a list of all available [locales](https://github.com/moment/moment/tree/develop/src/locale) and [formats](https://momentjs.com/docs/#/displaying/format/). ![The modal to name a downloaded tweet.](https://raw.githubusercontent.com/kbravh/obsidian-tweet-to-markdown/main/images/tweet_complete_modal.png) @@ -77,14 +86,23 @@ On the Tweet to Markdown settings page in Obsidian, you can customize the way th ### Custom File Name -Tweets are, by default, saved with the filename `[[handle]] - [[id]].md`. You can instead enter your own format in the **Filename** field using the variables `[[name]]`, `[[handle]]`, `[[text]]`, and `[[id]]` in your filename, which will be automatically replaced according to the following chart. The file extension `.md` will be added automatically. +Tweets are, by default, saved with the filename `[[handle]] - [[id]].md`. You can instead enter your own format in the **Filename** field using the variables `[[name]]`, `[[handle]]`, `[[text]]`, ``[[date]]`, and `[[id]]` in your filename, which will be automatically replaced according to the following chart. The file extension `.md` will be added automatically. | Variable | Replacement | |:---:|---| -|[[handle]]|The user's handle (the part that follows the @ symbol)| -|[[name]]|The user's name| -|[[id]]|The unique ID assigned to the tweet| -|[[text]]|The entire text of the tweet (truncated to fit OS filename length restrictions)| +|`[[handle]]`|The user's handle (the part that follows the @ symbol)| +|`[[name]]`|The user's name| +|`[[id]]`|The unique ID assigned to the tweet| +|`[[text]]`|The entire text of the tweet (truncated to fit OS filename length restrictions)| +|`[[date]]`|The date that the tweet was created| + +If the `[[date]]` variable is provided, it will by default use the locale and time format defined in your [settings](https://github.com/kbravh/obsidian-tweet-to-markdown#date-format) (it's towards the bottom of this readme). + +- If you would like to use a different format, include it after the date with a semicolon: `[[date:LL]]`. +- If you would like a different locale, include it after the format with another semicolon: `[[date:LL:es]]`. +- If you want the same format as your settings but a different locale, just leave the format section blank: `[[date::es]]`. + +Please check the `moment` documentation for a list of all available [locales](https://github.com/moment/moment/tree/develop/src/locale) and [formats](https://momentjs.com/docs/#/displaying/format/). ### Custom File Path diff --git a/__fixtures__/tweets/cashtag_tweet.ts b/__fixtures__/tweets/cashtag_tweet.ts new file mode 100644 index 0000000..3bb66de --- /dev/null +++ b/__fixtures__/tweets/cashtag_tweet.ts @@ -0,0 +1,65 @@ +import {Tweet} from 'src/types/tweet' + +export const cashtagTweet: Tweet = { + data: { + public_metrics: { + retweet_count: 0, + reply_count: 0, + like_count: 1, + quote_count: 0, + }, + created_at: '2020-09-02T16:15:47.000Z', + id: '1301192107143561219', + entities: { + urls: [ + { + start: 102, + end: 125, + url: 'https://t.co/qnyDphmJm2', + expanded_url: + 'https://twitter.com/BTheriot2014/status/1301180406226513921', + display_url: 'twitter.com/BTheriot2014/s…', + }, + ], + hashtags: [ + { + start: 22, + end: 31, + tag: 'cashtags', + }, + { + start: 88, + end: 95, + tag: 'coffee', + }, + ], + cashtags: [ + { + start: 52, + end: 57, + tag: 'SBUX', + }, + ], + }, + conversation_id: '1301192107143561219', + text: 'Today I learned about #cashtags - and found out my $SBUX is in current tweet! Must be #coffee time! https://t.co/qnyDphmJm2', + referenced_tweets: [ + { + type: 'quoted', + id: '1301180406226513921', + }, + ], + author_id: '1058876047465209856', + }, + includes: { + users: [ + { + id: '1058876047465209856', + username: 'Ceascape_ca', + name: 'ceascape.business.solutions', + profile_image_url: + 'https://pbs.twimg.com/profile_images/1058877044015038464/u68hN9LW_normal.jpg', + }, + ], + }, +} diff --git a/__fixtures__/tweets/image_tweet.ts b/__fixtures__/tweets/image_tweet.ts new file mode 100644 index 0000000..2444d51 --- /dev/null +++ b/__fixtures__/tweets/image_tweet.ts @@ -0,0 +1,81 @@ +import {Tweet} from 'src/types/tweet' + +export const ImageTweet: Tweet = { + data: { + created_at: '2020-08-10T15:30:23.000Z', + conversation_id: '1292845757297557505', + id: '1292845757297557505', + attachments: { + media_keys: ['3_1292845624120025090', '3_1292845644567269376'], + }, + entities: { + annotations: [ + { + start: 104, + end: 115, + probability: 0.9801, + type: 'Person', + normalized_text: 'Mary Douglas', + }, + ], + urls: [ + { + start: 260, + end: 283, + url: 'https://t.co/O2P7WRO1XL', + expanded_url: 'http://maggieappleton.com/dirt', + display_url: 'maggieappleton.com/dirt', + }, + { + start: 284, + end: 307, + url: 'https://t.co/PSk7lHiv7z', + expanded_url: + 'https://twitter.com/Mappletons/status/1292845757297557505/photo/1', + display_url: 'pic.twitter.com/PSk7lHiv7z', + media_key: '3_1292845624120025090', + }, + { + start: 284, + end: 307, + url: 'https://t.co/PSk7lHiv7z', + expanded_url: + 'https://twitter.com/Mappletons/status/1292845757297557505/photo/1', + display_url: 'pic.twitter.com/PSk7lHiv7z', + media_key: '3_1292845644567269376', + }, + ], + }, + author_id: '1343443016', + text: '"Dirt is matter out of place" - the loveliest definition of dirt you could hope for from anthropologist Mary Douglas in her classic 1966 book Purity and Danger\n\nHair on my head? Clean. Hair on the table? Dirty!\n\nIllustrating & expanding on her main ideas: https://t.co/O2P7WRO1XL https://t.co/PSk7lHiv7z', + public_metrics: { + retweet_count: 29, + reply_count: 11, + like_count: 191, + quote_count: 2, + }, + }, + includes: { + media: [ + { + media_key: '3_1292845624120025090', + type: 'photo', + url: 'https://pbs.twimg.com/media/EfEcPs8XoAIXwvH.jpg', + }, + { + media_key: '3_1292845644567269376', + type: 'photo', + url: 'https://pbs.twimg.com/media/EfEcQ5HX0AA2EvY.jpg', + }, + ], + users: [ + { + username: 'Mappletons', + id: '1343443016', + name: 'Maggie Appleton 🧭', + profile_image_url: + 'https://pbs.twimg.com/profile_images/1079304561892966406/1AHsGSnz_normal.jpg', + }, + ], + }, +} diff --git a/__fixtures__/tweets/index.ts b/__fixtures__/tweets/index.ts new file mode 100644 index 0000000..f069965 --- /dev/null +++ b/__fixtures__/tweets/index.ts @@ -0,0 +1,5 @@ +export * from './cashtag_tweet' +export * from './image_tweet' +export * from './mentions_tweet' +export * from './poll_tweet' +export * from './tweet_thread' diff --git a/__fixtures__/tweets/mentions_tweet.ts b/__fixtures__/tweets/mentions_tweet.ts new file mode 100644 index 0000000..b011e78 --- /dev/null +++ b/__fixtures__/tweets/mentions_tweet.ts @@ -0,0 +1,77 @@ +import {Tweet} from 'src/types/tweet' + +export const MentionsTweet: Tweet = { + data: { + text: "I've just created a Node.js CLI tool to save tweets as Markdown, great for @NotionHQ, @RoamResearch, @obsdmd, and other Markdown based note-taking systems! https://t.co/9qzNhz5cmN", + public_metrics: { + retweet_count: 0, + reply_count: 0, + like_count: 0, + quote_count: 0, + }, + created_at: '2020-09-09T17:55:42.000Z', + entities: { + urls: [ + { + start: 156, + end: 179, + url: 'https://t.co/9qzNhz5cmN', + expanded_url: 'https://github.com/kbravh/tweet-to-markdown', + display_url: 'github.com/kbravh/tweet-t…', + images: [ + { + url: 'https://pbs.twimg.com/news_img/1501819606129950722/opZrrpCT?format=jpg&name=orig', + width: 1280, + height: 640, + }, + { + url: 'https://pbs.twimg.com/news_img/1501819606129950722/opZrrpCT?format=jpg&name=150x150', + width: 150, + height: 150, + }, + ], + status: 200, + title: + 'GitHub - kbravh/tweet-to-markdown: A command line tool to convert Tweets to Markdown.', + description: + 'A command line tool to convert Tweets to Markdown. - GitHub - kbravh/tweet-to-markdown: A command line tool to convert Tweets to Markdown.', + unwound_url: 'https://github.com/kbravh/tweet-to-markdown', + }, + ], + mentions: [ + { + start: 75, + end: 84, + username: 'NotionHQ', + id: '708915428454576128', + }, + { + start: 86, + end: 99, + username: 'RoamResearch', + id: '1190410678273626113', + }, + { + start: 101, + end: 108, + username: 'obsdmd', + id: '1239876481951596545', + }, + ], + }, + id: '1303753964291338240', + author_id: '1143604512999034881', + conversation_id: '1303753964291338240', + }, + includes: { + users: [ + { + username: 'kbravh', + name: 'Karey Higuera 🦈', + id: '1143604512999034881', + profile_image_url: + 'https://pbs.twimg.com/profile_images/1163169960505610240/R8BoDqiT_normal.jpg', + }, + ], + }, +} diff --git a/__fixtures__/tweets/poll_tweet.ts b/__fixtures__/tweets/poll_tweet.ts new file mode 100644 index 0000000..843d307 --- /dev/null +++ b/__fixtures__/tweets/poll_tweet.ts @@ -0,0 +1,48 @@ +import {Tweet} from 'src/types/tweet' + +export const PollTweet: Tweet = { + data: { + conversation_id: '1029121914260860929', + id: '1029121914260860929', + created_at: '2018-08-13T21:45:59.000Z', + text: 'Which is Better?', + attachments: { + poll_ids: ['1029121913858269191'], + }, + public_metrics: { + retweet_count: 7, + reply_count: 11, + like_count: 47, + quote_count: 2, + }, + author_id: '4071934995', + }, + includes: { + polls: [ + { + id: '1029121913858269191', + options: [ + { + position: 1, + label: 'Spring', + votes: 1373, + }, + { + position: 2, + label: 'Fall', + votes: 3054, + }, + ], + }, + ], + users: [ + { + id: '4071934995', + name: 'polls', + username: 'polls', + profile_image_url: + 'https://pbs.twimg.com/profile_images/660160253913382913/qgvYqknJ_normal.jpg', + }, + ], + }, +} diff --git a/__fixtures__/tweets/tweet_thread.ts b/__fixtures__/tweets/tweet_thread.ts new file mode 100644 index 0000000..73d8fa0 --- /dev/null +++ b/__fixtures__/tweets/tweet_thread.ts @@ -0,0 +1,186 @@ +import {Tweet} from 'src/types/tweet' + +export const TweetThread: Tweet[] = [ + { + data: { + author_id: '221658618', + conversation_id: '1277645969975377923', + entities: { + annotations: [ + { + start: 150, + end: 154, + probability: 0.5227, + type: 'Product', + normalized_text: 'Apple', + }, + ], + urls: [ + { + start: 172, + end: 195, + url: 'https://t.co/YjOLsIGVRD', + expanded_url: + 'https://twitter.com/geoffreylitt/status/1277645969975377923/photo/1', + display_url: 'pic.twitter.com/YjOLsIGVRD', + media_key: '3_1277628647332089863', + }, + { + start: 172, + end: 195, + url: 'https://t.co/YjOLsIGVRD', + expanded_url: + 'https://twitter.com/geoffreylitt/status/1277645969975377923/photo/1', + display_url: 'pic.twitter.com/YjOLsIGVRD', + media_key: '3_1277628708845768704', + }, + ], + }, + public_metrics: { + retweet_count: 13, + reply_count: 5, + like_count: 119, + quote_count: 1, + }, + id: '1277645969975377923', + text: 'A theory about why tools like Airtable and Notion are so compelling: they provide a much-needed synthesis between the design philosophies of UNIX and Apple.\n\nShort thread: https://t.co/YjOLsIGVRD', + attachments: { + media_keys: ['3_1277628647332089863', '3_1277628708845768704'], + }, + created_at: '2020-06-29T16:51:51.000Z', + }, + includes: { + media: [ + { + media_key: '3_1277628647332089863', + type: 'photo', + url: 'https://pbs.twimg.com/media/EbsMfE8XkAc9TiK.png', + }, + { + media_key: '3_1277628708845768704', + type: 'photo', + url: 'https://pbs.twimg.com/media/EbsMiqGX0AAwi9R.jpg', + }, + ], + users: [ + { + name: 'Geoffrey Litt', + profile_image_url: + 'https://pbs.twimg.com/profile_images/722626068293763072/4erM-SPN_normal.jpg', + id: '221658618', + username: 'geoffreylitt', + }, + ], + }, + }, + { + data: { + public_metrics: { + retweet_count: 2, + reply_count: 1, + like_count: 29, + quote_count: 0, + }, + text: 'UNIX is still the best working example of "tools not apps": small sharp tools that the user can flexibly compose to meet their needs.\n\nOnce you\'ve written a few bash pipelines, it\'s hard to be satisfied with disconnected, siloed "apps"', + conversation_id: '1277645969975377923', + created_at: '2020-06-29T16:51:51.000Z', + entities: { + annotations: [ + { + start: 0, + end: 3, + probability: 0.4213, + type: 'Product', + normalized_text: 'UNIX', + }, + ], + }, + id: '1277645971401433090', + author_id: '221658618', + referenced_tweets: [ + { + type: 'replied_to', + id: '1277645969975377923', + }, + ], + }, + includes: { + users: [ + { + id: '221658618', + profile_image_url: + 'https://pbs.twimg.com/profile_images/722626068293763072/4erM-SPN_normal.jpg', + name: 'Geoffrey Litt', + username: 'geoffreylitt', + }, + ], + }, + }, + { + data: { + attachments: { + media_keys: ['3_1277630070321025025'], + }, + id: '1277645972529647616', + text: 'The problem is, while the roots are solid, the terminal as UI is extremely hostile to users, esp beginners. No discoverability, cryptic flags, lots of cruft and chaos.\n\nhttps://t.co/JOVVRw3iWU https://t.co/TjOL7PXU2y', + conversation_id: '1277645969975377923', + referenced_tweets: [ + { + type: 'quoted', + id: '1187357294415302657', + }, + { + type: 'replied_to', + id: '1277645971401433090', + }, + ], + entities: { + urls: [ + { + start: 169, + end: 192, + url: 'https://t.co/JOVVRw3iWU', + expanded_url: + 'https://twitter.com/geoffreylitt/status/1187357294415302657', + display_url: 'twitter.com/geoffreylitt/s…', + }, + { + start: 193, + end: 216, + url: 'https://t.co/TjOL7PXU2y', + expanded_url: + 'https://twitter.com/geoffreylitt/status/1277645972529647616/photo/1', + display_url: 'pic.twitter.com/TjOL7PXU2y', + media_key: '3_1277630070321025025', + }, + ], + }, + created_at: '2020-06-29T16:51:52.000Z', + author_id: '221658618', + public_metrics: { + retweet_count: 0, + reply_count: 1, + like_count: 19, + quote_count: 0, + }, + }, + includes: { + media: [ + { + media_key: '3_1277630070321025025', + type: 'photo', + url: 'https://pbs.twimg.com/media/EbsNx5_XkAEncJW.png', + }, + ], + users: [ + { + username: 'geoffreylitt', + id: '221658618', + profile_image_url: + 'https://pbs.twimg.com/profile_images/722626068293763072/4erM-SPN_normal.jpg', + name: 'Geoffrey Litt', + }, + ], + }, + }, +] diff --git a/__mocks__/obsidian.ts b/__mocks__/obsidian.ts index 2ec703b..28f54ac 100644 --- a/__mocks__/obsidian.ts +++ b/__mocks__/obsidian.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line node/no-unpublished-import export class App {} export class Editor {} export class MarkdownView {} diff --git a/manifest.json b/manifest.json index af42240..9cd73b8 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-tweet-to-markdown", "name": "Tweet to Markdown", - "version": "2.6.0", + "version": "2.7.0", "minAppVersion": "0.12.17", "description": "Save tweets as Markdown files, along with their images, polls, etc.", "author": "kbravh", diff --git a/package.json b/package.json index 6cebb8c..ff6da62 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-tweet-to-markdown", - "version": "2.6.0", + "version": "2.7.0", "description": "Save tweets as beautiful markdown files in Obsidian (https://obsidian.md)", "main": "main.js", "engines": { @@ -24,6 +24,7 @@ "@types/uuid": "^8.3.3", "gts": "^3.1.0", "jest": "^27.5.1", + "moment": "^2.29.3", "obsidian": "^0.12.17", "rollup": "^2.32.1", "ts-jest": "^27.1.4", diff --git a/src/TweetCompleteModal.ts b/src/TweetCompleteModal.ts index 1fb05ab..9fcb1ca 100644 --- a/src/TweetCompleteModal.ts +++ b/src/TweetCompleteModal.ts @@ -15,7 +15,10 @@ export class TweetCompleteModal extends Modal { titleEl.setText('Name tweet file') let filename = sanitizeFilename( - createFilename(this.plugin.currentTweet, this.plugin.settings.filename), + createFilename(this.plugin.currentTweet, this.plugin.settings.filename, { + locale: this.plugin.settings.dateLocale, + format: this.plugin.settings.dateFormat, + }), 'decode' ) @@ -29,7 +32,10 @@ export class TweetCompleteModal extends Modal { input .onChange(value => { filename = sanitizeFilename( - createFilename(this.plugin.currentTweet, value), + createFilename(this.plugin.currentTweet, value, { + locale: this.plugin.settings.dateLocale, + format: this.plugin.settings.dateFormat, + }), 'decode' ) }) diff --git a/src/types/plugin.ts b/src/types/plugin.ts index 81a7d2a..7c73f71 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -5,3 +5,8 @@ export enum TweetCompleteActions { } export type TweetCompleteAction = keyof typeof TweetCompleteActions + +export type TimestampFormat = { + locale: string + format: string +} diff --git a/src/types/tweet.ts b/src/types/tweet.ts index ee80e56..6a6e816 100644 --- a/src/types/tweet.ts +++ b/src/types/tweet.ts @@ -19,7 +19,7 @@ export interface Data { public_metrics: Metrics entities?: Entities conversation_id?: string - attachments?: Attachment[] + attachments?: Attachment referenced_tweets?: ReferencedTweet[] } @@ -35,6 +35,12 @@ export interface Error { detail: string } +export interface Includes { + polls?: Poll[] + users: User[] + media?: Media[] +} + export interface Media { media_key: string type: 'photo' | 'gif' | 'video' @@ -43,9 +49,10 @@ export interface Media { } export interface Mention { - start: string - end: string + start: number + end: number username: string + id?: string } export interface Metrics { @@ -55,6 +62,12 @@ export interface Metrics { quote_count: number } +export interface OpenGraphImage { + url: string + width: number + height: number +} + export interface Poll { id: string options: PollOption[] @@ -77,12 +90,6 @@ export interface Tag { tag: string } -export interface Includes { - polls?: Poll[] - users: User[] - media?: Media[] -} - export interface Tweet { includes: Includes data: Data @@ -98,6 +105,12 @@ export interface TweetURL { url: string expanded_url: string display_url: string + media_key?: string + images?: OpenGraphImage[] + status?: number + title?: string + description?: string + unwound_url?: string } export interface User { diff --git a/src/util.ts b/src/util.ts index b5bc91f..bb86139 100644 --- a/src/util.ts +++ b/src/util.ts @@ -13,6 +13,7 @@ import {createDownloadManager, DownloadManager} from './downloadManager' import type {Media, Poll, Tweet, User} from './types/tweet' import {decode} from 'html-entities' import {moment} from 'obsidian' +import {TimestampFormat} from './types/plugin' import TTM from 'main' import {TTMSettings} from './settings' import {unicodeSubstring} from './unicodeSubstring' @@ -199,20 +200,47 @@ export const truncateBytewise = (string: string, length: number): string => { /** * Creates a filename based on the tweet and the user defined options. - * @param {Tweet} tweet - The entire tweet object from the Twitter v2 API - * @param {filename} string - The filename provided by the user - * @returns {string} - The filename based on tweet and options + * @param tweet - The entire tweet object from the Twitter v2 API + * @param filename - The filename provided by the user + * @returns - The filename based on tweet and options */ -export const createFilename = (tweet: Tweet, filename = ''): string => { +export const createFilename = ( + tweet: Tweet, + filename = '', + timestampFormat: TimestampFormat +): string => { filename = filename ? filename : '[[handle]] - [[id]]' filename = filename.replace(/\.md$/, '') // remove md extension if provided filename = filename.replace('[[name]]', tweet.includes.users[0].name) filename = filename.replace('[[handle]]', tweet.includes.users[0].username) filename = filename.replace('[[id]]', tweet.data.id) filename = filename.replace('[[text]]', tweet.data.text) + // date + const dateRegex = /\[\[(date[:\w-]*)\]\]/ + if (dateRegex.test(filename)) { + const dateCommand = filename.match(dateRegex) + if (dateCommand) { + const [, format, locale] = dateCommand[1].split(':') + filename = filename.replace( + dateRegex, + formatTimestamp(tweet.data.created_at, { + format: format || timestampFormat.format, + locale: locale || timestampFormat.locale, + }) + ) + } + } return sanitizeFilename(filename) + '.md' } +export const formatTimestamp = ( + timestamp: string, + timestampFormat: TimestampFormat +): string => + moment(timestamp) + .locale(timestampFormat.locale) + .format(timestampFormat.format) + /** * Creates media links to embed media into the markdown file * @param {Media[]} media - The tweet media object provided by the Twitter v2 API @@ -320,9 +348,10 @@ export const buildMarkdown = async ( }) } - const date = moment(tweet.data.created_at) - .locale(plugin.settings.dateLocale) - .format(plugin.settings.dateFormat) + const date = formatTimestamp(tweet.data.created_at, { + locale: plugin.settings.dateLocale, + format: plugin.settings.dateFormat, + }) const displayDate = (plugin: TTM, date: string): string => plugin.settings.includeDate ? ` - ${date}` : '' @@ -622,7 +651,10 @@ export const pasteTweet = async ( if (plugin.settings.embedMethod === 'text') { text = text.replace(placeholder, markdown) } else { - let filename = createFilename(tweet, plugin.settings.filename) + let filename = createFilename(tweet, plugin.settings.filename, { + locale: plugin.settings.dateLocale, + format: plugin.settings.dateFormat, + }) filename = sanitizeFilename(filename, 'decode') const location = sanitizeFilename( plugin.settings.noteLocation, diff --git a/tests/util.test.ts b/tests/util.test.ts index d6eef05..891089d 100644 --- a/tests/util.test.ts +++ b/tests/util.test.ts @@ -1,4 +1,14 @@ -import {getTweetID, sanitizeFilename} from '../src/util' +import {createFilename, getTweetID, sanitizeFilename} from '../src/util' +import {ImageTweet} from '../__fixtures__/tweets' + +jest.mock('obsidian', () => { + // eslint-disable-next-line node/no-unpublished-import + const moment = require('moment') + return { + __esModule: true, + moment, + } +}) describe('Tweet ID', () => { it('Extracts tweet Id from regular URL', async () => { @@ -28,3 +38,96 @@ describe('Sanitize filename', () => { expect(sanitizeFilename('file/name.md', 'encode')).toBe('filename.md') }) }) + +describe('Create filename', () => { + it('Defaults to "handle - id" if no pattern provided', () => { + expect( + createFilename(ImageTweet, '', { + locale: 'en', + format: 'YYYY-MM-DD', + }) + ).toBe('Mappletons - 1292845757297557505.md') + }) + it('Sanitizes unsafe filename characters', () => { + expect( + createFilename(ImageTweet, '?<>hello:*|"', { + locale: 'en', + format: 'YYYY-MM-DD', + }) + ).toBe('hello.md') + }) + it('Replaces handle, id, and name', () => { + expect( + createFilename(ImageTweet, '[[handle]] - [[id]] - [[name]]', { + locale: 'en', + format: 'YYYY-MM-DD', + }) + ).toBe('Mappletons - 1292845757297557505 - Maggie Appleton 🧭.md') + }) + it('Does not double extension if .md is present', () => { + expect( + createFilename(ImageTweet, '[[handle]] - [[id]] - [[name]].md', { + locale: 'en', + format: 'YYYY-MM-DD', + }) + ).toBe('Mappletons - 1292845757297557505 - Maggie Appleton 🧭.md') + }) + it('Replaces text and truncates', () => { + expect( + createFilename(ImageTweet, '[[text]]', { + locale: 'en', + format: 'YYYY-MM-DD', + }) + ).toBe( + 'Dirt is matter out of place - the loveliest definition of dirt you could hope for from anthropologist Mary Douglas in her classic 1966 book Purity and DangerHair on my head Clean. Hair on the table Dirty!Illustrating & expanding on her main ideas h.md' + ) + }) + it('Replaces date with format from arguments', () => { + expect( + createFilename(ImageTweet, '[[handle]] - [[date]]', { + locale: 'en', + format: 'YYYY-MM-DD', + }) + ).toBe('Mappletons - 2020-08-10.md') + }) + it('Replaces date with format from arguments with different locale', () => { + expect( + createFilename(ImageTweet, '[[handle]] - [[date]]', { + locale: 'es', + format: 'LL', + }) + ).toBe('Mappletons - 10 de agosto de 2020.md') + }) + it('Replaces date with inline format', () => { + expect( + createFilename(ImageTweet, '[[handle]] - [[date:LL]]', { + locale: 'en', + format: 'YYYY-DD-MM', + }) + ).toBe('Mappletons - August 10, 2020.md') + }) + it('Replaces date with inline format with different locale', () => { + expect( + createFilename(ImageTweet, '[[handle]] - [[date:LL:es]]', { + locale: 'en', + format: 'YYYY-DD-MM', + }) + ).toBe('Mappletons - 10 de agosto de 2020.md') + }) + it('Replaces date with inline locale and default format', () => { + expect( + createFilename(ImageTweet, '[[handle]] - [[date::es]]', { + locale: 'en', + format: 'LL', + }) + ).toBe('Mappletons - 10 de agosto de 2020.md') + }) + it('Partial date does not trigger date logic', () => { + expect( + createFilename(ImageTweet, '[[date - [[handle]]', { + locale: 'en', + format: 'YYYY-MM-DD', + }) + ).toBe('[[date - Mappletons.md') + }) +}) diff --git a/versions.json b/versions.json index d21d628..7bd34f1 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "2.6.0": "0.12.17" + "2.7.0": "0.12.17" } diff --git a/yarn.lock b/yarn.lock index 68d6dfa..2ad0102 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3079,6 +3079,11 @@ moment@2.29.1: resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== +moment@^2.29.3: + version "2.29.3" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3" + integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"