social-components v0.4.16
social-components
Experimental building blocks for a generic social service.
A React implemenation is available in social-components-react.
Install
npm install social-components --saveUse
Currently this library is only used as a dependency of imageboard and webapp-frontend.
The <Post/> React component is currently being experimented on.
Model
Services
- YouTube — Get video by URL or id.
- Vimeo — Get video by URL or id.
- Instagram — Get post by URL or id.
- Twitter — Get tweet by URL or id.
API
getPostText(post: Post, options: object?): string?
import { getPostText } from 'social-components/post'Returns a textual representation of a Post
Availble options:
softLimit: number— A "soft" limit on the resulting text length. "Soft" means that the resulting text may exceed the limit.messages: object?— Localized labels ("Video", "Picture", etc). See Messages.getLinkTitle?: (linkElement: object) => string?— Formats an "untitled" link: a link for which a custom title was not set. By default, when this function is not present, links are formatted usingmessages.textContent.inline.linkTo, if it's present, or are output as the URL itself. If this function doesn't return anything then it's ignored.skipPostQuoteBlocks: boolean?— Skips all "block" (not "inline") post quotes.skipGeneratedPostQuoteBlocks: boolean?— Skip all autogenerated "block" (not "inline") post quotes. Istrueby default.skipAttachments: boolean?— Skips any attachments (embedded and non-embedded). Istrueby default.skipNonEmbeddedAttachments: boolean?— Skips non-embedded attachments. Istrueby default.skipUntitledAttachments: boolean?— Skips untitled attachments (embedded and non-embedded). Istrueby default.spaceOutParagraphs: boolean?— Defines how text for adjacent content blocks should be concatenated. By default, it concatenates it with\n\n. ItspaceOutParagraphs: falseflag is passed, it concatenates the text for adjacent content blocks with\n.trimCodeBlocksToFirstLine: boolean?— Trim code blocks to first line. Istrueby default.stopOnNewLine: boolean?— Iftruethen the function will stop on the first "new line" character of the generated text.
getInlineContentText(content: Content, options: object?): string?
import { getInlineContentText } from 'social-components/content'Returns a textual representation of an InlineContent. Use this function as an equivalent of getPostText() for inline-level content.
transformContent(content: Content, transform: function)
import { transformContent } from 'social-components/content'Recursively walks all parts of content, calling transform(part) for each such part. If transform(part) returns:
undefined, then thepartis left unchanged, and it will recurse into thepart's.content.false, then thepartis left unchanged, and it won't recurse into thepart's.content.- an array, then the array is expanded in place of the
part. - anything else, then the
partis substituted with that.
generatePostQuote(post: Post, options: object?): string?
import { generatePostQuote } from 'social-components/post'Generates a textual representation of a Post, that's intended to be used in a quote citing the post. Uses getPostText() under the hood: first starts with a strict set of options, then gradually relaxes them until some text is generated. Then compacts inter-paragraph margins into just "new line" characters, and trims the resulting text to fit into options.maxLength (adjusted by options.minFitFactor and options.maxFitFactor).
Availble options:
- All
getPostText()options are passed through. maxLength: number— A limit on the resulting text length.minFitFactor: number?— Provides some flexibility when definingmaxLengthlower boundary. SeeminFitFactoroption oftrimText().maxFitFactor: number?— Provides some flexibility when definingmaxLengthupper boundary. SeemaxFitFactoroption oftrimText().getCharactersCountPenaltyForLineBreak?: ({ textBefore: string }) => number— SeegetCharactersCountPenaltyForLineBreakoption oftrimText().trimMarkEndOfLine: string?— SeetrimMarkEndOfLineoption oftrimText().trimMarkEndOfSentence: string?— SeetrimMarkEndOfSentenceoption oftrimText().trimMarkEndOfWord: string?— SeetrimMarkEndOfWordoption oftrimText().trimMarkAbrupt: string?— SeetrimMarkAbruptoption oftrimText().
generatePreview(post: Post, options: object): Content?
import { generatePostPreview } from 'social-components/post'Generates a preview for a Post given the options.limit. If the post content is undefined then the returned preview content is too. Otherwise, the returned preview content isn't undefined. If the post content fits entirely then the preview content will be (deeply) equal to it. Otherwise, preview content will be a shortened version of content with a { type: 'read-more' } marker somewhere in the end.
Available options:
maxLength: number— Preview content (soft) limit (in "points": for text, one "point" is equal to one character, while any other non-text content has its own "points", including attachments and new line character).minFitFactor: number?— Provides some flexibility when definingmaxLengthlower boundary: sets it tominFitFactor * maxLength. The content then can usually be trimmed anywhere betweenminFitFactor * maxLengthandmaxFitFactor * maxLength. Is0.75by default.maxFitFactor: number?— Provides some flexibility when definingmaxLengthupper boundary: sets it tomaxFitFactor * maxLength. The content then can usually be trimmed anywhere betweenminFitFactor * maxLengthandmaxFitFactor * maxLengthlimit. Is1.2by default.textTrimMarkEndOfWord: string?— Appends this "trim mark" when text has to be trimmed after word end (but not after sentence end). Is "…" by default.textTrimMarkAbrupt: string?— Appends this "trim mark" when text has to be trimmed mid-word. Is "…" by default.minimizeGeneratedPostLinkBlockQuotes: boolean?— One can passtrueto indicate that auto-generated quotes are minimized by default until the user expands them manually. This would mean that auto-generated quotes shouldn't be accounted for when calculating the total length of a comment when creating a shorter "preview" for it in case it exceeds the maxum preferred length.
trimText(text: string, maxLength: number, options: object?): string
import { trimText } from 'social-components/text'Trims the text at maxLength.
Available options:
minFitFactor: number?— Provides some flexibility when definingmaxLengthlower boundary: sets it tominFitFactor * maxLength. The content then can usually be trimmed anywhere betweenminFitFactor * maxLengthandmaxFitFactor * maxLength. Is1by default, meaning "no effect".maxFitFactor: number?— Provides some flexibility when definingmaxLengthupper boundary: sets it tomaxFitFactor * maxLength. The content then can usually be trimmed anywhere betweenminFitFactor * maxLengthandmaxFitFactor * maxLengthlimit. Is1by default, meaning "no effect".getCharactersCountPenaltyForLineBreak?: ({ textBefore: string }) => number— Returns characters count equivalent for a "line break" (\n) character. The idea is to "tax" multi-line texts when trimming by characters count. By default, having\ncharacters in text is not penalized in any way and those characters aren't counted.trimPoint: string?— Preferrable trim point. Can beundefined(default),"sentence-end","sentence-or-word-end".Preferrable trim point. By default it starts with seeing if it can trim at"sentence-end", then tries to trim at"sentence-or-word-end", and then just trims at any point.trimMarkEndOfLine: string?— "Trim mark" when trimming at the end of a line. Is "" (no trim mark) by default.trimMarkEndOfSentence: string?— "Trim mark" when trimming at the end of a sentence. Is "" (no trim mark) by default.trimMarkEndOfWord: string?— "Trim mark" when trimming at the end of a word. Is " …" by default.trimMarkAbrupt: string?— "Trim mark" when trimming mid-word. Is "…" by default.
censorWords(text: string, filters: WordFilter[]): Content
import { censorWords } from 'social-components/content'Replaces words in text matching the filters with objects of shape { type: "spoiler", censored: true, content: "the-word-that-got-censored" }. The filters: WordFilter[] argument must be a list of word filters pre-compiled with the exported compileWordPatterns(censoredWords, language) function (described below).
const FILTERS = compileWordPatterns(['red', 'brown', 'orange'], 'en')
censorWords('A red fox', FILTERS) === [
'A ',
{ type: 'spoiler', censored: true, content: 'red' }
' fox'
]compileWordPatterns(patterns: string[], language: string): WordFilter[]
import { compileWordPatterns } from 'social-components/text'Compiles word patterns into filters that can be used in censorWords().
Arguments:
patterns: string[]— An array ofstringword patterns. The standard regular expression syntax applies,^meaning "word start",$meaning "word end",.meaning "any letter", etc.language: string— A lowercase two-letter language code (examples:"en","ru","de") that is used to generate a regular expression for splitting text into individual words.
Word pattern syntax:
^— Word start.$— Word end..— Any letter.[abc]— Any single one of letters: "a", "b", "c".a?— Optional letter "a"..*— Any count of any letter..+— One or more of any letter..{0,2}— Zero to two of any letter.
Word pattern examples:
^mother.*— Matches"mothercare"and"motherfather".^mother[f].*— Matches"motherfather"but not"mothercare".^mother[^f].*— Matches"mothercare"but not"motherfather".^cock$— Matches"cock"in"my cock is big"but won't match"cocktail"or"peacock".cock— Matches"cock","cocktail"and"peacock".cock$— Matches"cock"and"peacock"but not"cocktail".^cocks?— Matches"cock"and"cocks".^cock.{0,3}— Matches"cock","cocks","cocker","cockers".
loadResourceLinks(post: Post, options: object?): Promise
import { loadResourceLinks } from 'social-components/post'Loads "resource" links (such as links to YouTube and Twitter) by loading the info associated with the resources. For example, sets video .attachment on YouTube links and sets "social" .attachment on Twitter links.
Returns an object having properties:
promise: Promise— aPromisethat resolves when all resource links have finished loading. "Finished loading" here means "all resources have been attempted to be loaded" rather than "all resources have been loaded successfully", i.e. it resolves even if some (or all) resources couldn't be loaded (for example, if a YouTube link points to a video that no longer exists).stop()— Callingstop()function preventsloadResourceLinks()from making any further changes to thepostobject. Callingstop()function multiple times or after thepromisehas finished doesn't have any effect.cancel()— Callingcancel()function stopsloadResourceLinks()and reverts any changes that have been made to thepostobject. If theloadResourceLinks()function has already finished then any changes to thepostobject will be reverted anyway. Callingcancel()function multiple times doesn't have any effect.
Available options:
onContentChange: function?— Is called on this post content change (as a result of a resource link being loaded). For example, it may re-render the post.getYouTubeVideoByUrl: function?— Can be used for getting YouTube videos by URL from cache.youTubeApiKey: (string|string[])?— YouTube API key. If it's an array of keys then the first non-erroring one is used.loadPost: function?— This is a hacky point of customization to add some other custom resource loaders. Is used incaptchanto fixlynxchanpost attachment sizes and URLs.contentMaxLength: number?— If set, will re-generate.contentPreviewif the updatedcontentexceeds the limit (in "points").messages: object?— Localized labels for resource loading. This is not the same thing as Messages. This is an object having shape:videoNotFound?: string— When loading YouTube video links, if the video was not found, it sets the link'scontentto this text. Example:"Video not found".
minimizeGeneratedPostLinkBlockQuotes: boolean?— See the description of the same option ofgeneratePostPreview().
expandStandaloneAttachmentLinks(content: Content)
Expands attachment links (objects of shape { type: 'link', attachment: ... } into standalone attachments (block-level attachments: { type: 'attachment' }).
import { expandStandaloneAttachmentLinks } from "social-components/content"
const content = [
[
"See ",
{
type: "link",
attachment: {
type: "video",
video: ...
}
},
" for more info."
]
]
expandStandaloneAttachmentLinks(content)
content === [
[
"See "
],
{
type: "attachment",
attachment: {
type: "video",
video: ...
}
},
[
" for more info."
]
]visitContentParts(type: string, visit: function, content: Content): any[]
import { visitContentParts } from 'social-components/content'Calls visit(part) on each part of content being of type type. Returns a list of results returned by each visit(part). For example, the following example prints URLs of all { type: 'link' }s in a post content.
visitContentParts('link', link => console.log(link.url), post.content)trimContent(content: Content, options: object?): Content?
import { trimContent } from 'social-components/content'Trims whitespace (including newlines) in the beginning and in the end of content. content internals will be mutated. Returns the mutated content (the original content still gets mutated). Returns undefined if content became empty as a result of the trimming.
Available options:
left: boolean— Passleft: falseto prevent it from trimming on the left side.right: boolean— Passright: falseto prevent it from trimming on the right side.
trimContent([['\n', ' Text '], ['\n']]) === [['Text']]trimInlineContent(inlineContent: InlineElement[], options: object?): InlineContent?
import { trimInlineContent } from 'social-components/content'Trims whitespace (including newlines) in the beginning and in the end of content. content must be an array. content internals will be mutated. Returns the mutated content (the original content still gets mutated). Returns undefined if content became empty as a result of the trimming.
Available options:
left: boolean— Passleft: falseto prevent it from trimming on the left side.right: boolean— Passright: falseto prevent it from trimming on the right side.
trimInlineContent(['\n', { type: 'text', content: ' Text ' }, '\n'])
=== [{ type: 'text', content: 'Text' }]YouTube.getVideoByUrl(url: string, options: object): Promise<object>
import { getVideoByUrl } from 'social-components/services/youtube'Parses a YouTube video URL, queries the video info via YouTube V3 Data API and returns a Promise resolving to { type: 'video' } object.
Available options:
youTubeApiKey: string— YouTube V3 Data API key.
Some additional YouTube utilities:
// The list of possible YouTube preview picture ("thumbnail") sizes.
import { PREVIEW_PICTURE_SIZES } from 'social-components/services/youtube'
PREVIEW_PICTURE_SIZES[0] === {
name: 'maxresdefault',
width: 1280,
height: 720
}
// Returns a YouTube video object.
import { getVideo } from 'social-components/services/youtube'
getVideo(videoId, options)
// Returns a URL of a YouTube video's preview picture ("thumbnail").
import { getPictureSizeUrl } from 'social-components/services/youtube'
getPictureSizeUrl(videoId, size.name)
// Returns a YouTube video URL.
import { getVideoUrl } from 'social-components/services/youtube'
getVideoUrl(videoId, { startAt }?)
// Returns a URL for an embedded YouTube video.
// Can be used for embedding a video on a page via an `<iframe/>`.
import { getEmbeddedVideoUrl } from 'social-components/services/youtube'
getEmbeddedVideoUrl(videoId, { autoPlay, startAt }?)Vimeo.getVideoByUrl(url: string): Promise<object>
import { getVideoByUrl } from 'social-components/services/vimeo'Parses a Vimeo video URL, queries the video info via HTTP REST API and returns a Promise resolving to { type: 'video' } object.
Some additional Vimeo utilities:
// Returns a Vimeo video URL.
import { getVideoUrl } from 'social-components/services/vimeo'
getVideoUrl(videoId)
// Returns a Vimeo video object.
import { getVideo } from 'social-components/services/vimeo'
getVideo(videoId)
// Returns a URL for an embedded Vimeo video.
// Can be used for embedding a video on a page via an `<iframe/>`.
import { getEmbeddedVideoUrl } from 'social-components/services/vimeo'
getEmbeddedVideoUrl(videoId, { color, autoPlay, loop }?)Twitter.getTweetByUrl(url: string, options: object): Promise<object>
import { getTweetByUrl } from 'social-components/services/twitter'Parses a Vimeo video URL, queries the video info via HTTP REST API and returns a Promise resolving to { type: 'social' } object.
Available options:
messages: object?— Localized labels ("Video", "Picture", etc). See Messages.
Instagram.getPostByUrl(url: string): Promise<object>
import { getPostByUrl } from 'social-components/services/instagram'Parses a Vimeo video URL, queries the video info via HTTP REST API and returns a Promise resolving to { type: 'social' } object.
Miscellaneous API
unescapeText(string: string): string
import { unescapeText } from 'social-components/text'Unescapes HTML-escaped text.
unescapeText("<div/>") === "<div/>"getColorHash(string: string): string
import { getColorHash } from 'social-components/utility'Converts a string to a color.
getColorHash("Some text") === "#aabbcc"getHumanReadableLinkAddress(url: string): string
import { getHumanReadableLinkAddress } from 'social-components/utility'Returns a more human-friendly link address:
- Strips "http(s):" protocol.
- Strips the "www." part.
- Removes trailing slash.
getContentBlocks(post: Post): any[]
import { getPostThumbnailAttachment } from 'social-components/post'Returns a list of "content blocks" of the post's .content. For example, if the post's .content is just a string, then it returns an array of that string. Returns an empty array if the post has no content.
removeLeadingPostLink(post: Post, postLinkTest: function)
Removes a leading post-link, that satisfies the conditions, from the post's .content. Can be used to remove parent post quotes from replies when showing the parent post's replies tree.
The postLinkTest argument should be the condition for removing a post link: either a post id or a function that returns true when the post link should be removed.
import { removeLeadingPostLink } from 'social-components/post'
post.replies = post.replies.map((replyPost) => {
return removeLeadingPostLink(replyPost, (postLink) => postLink.meta.postId === post.id)
})getPostThumbnailAttachment(post: Post, options: object?): Attachment?
import { getPostThumbnailAttachment } from 'social-components/post'Returns an attachment that could be used as a "thumbnail" for this post. For example, could return a Picture or a Video.
Available options:
showPostThumbnailWhenThereAreMultipleAttachments— Passtrueto allow returning post thumbnail in cases when theposthas multiple thumbnail-able attachments. By default, if theposthas multiple thumbnail-able attachments, none of them will be returned.showPostThumbnailWhenThereIsNoContent— Passtrueto allow returning post thumbnail in cases when theposthas nocontent. By default, if theposthas nocontent, no post thumbnail will be returned.
getPicturesAndVideos(attachments: Attachment[]): Attachment[]
import { getPicturesAndVideos } from 'social-components/attachment'Returns Pictures and Videos.
getPictureMinSize(picture: Picture): object
import { getPictureMinSize } from 'social-components/attachment'Returns the minimum "size" of a picture, "size" being an object of shape: { width: number, height: number, url: string }.
getNonEmbeddedAttachments(post: Post): Attachment[]
import { getNonEmbeddedAttachments } from 'social-components/post'Returns a list of post attachments that aren't embedded in the post's .content.
getSortedAttachments(post: Post): Attachment[]
import { getSortedAttachments } from 'social-components/post'Sorts post attachments in the order they appear embedded in the post, plus all the rest of them that aren't embedded in the post, sorted by thumbnail height descending.
Returns undefined if the post doesn't have any attachments.
getEmbeddedAttachment(block: ContentBlock, attachments: Attachment[]?): Attachment?
import { getEmbeddedAttachment } from 'social-components/content'Returns an Attachment object for an embedded attachment block.
doesAttachmentHavePicture(attachment: Attachment): boolean?
import { doesAttachmentHavePicture } from 'social-components/attachment'Returns true if the attachment has a picture. Examples: Picture, Video.
getAttachmentThumbnailSize(attachment: Attachment): object?
import { getAttachmentThumbnailSize } from 'social-components/attachment'Returns the attachment's thumbnail size. Returns undefined if the attachment doesn't have a thumbnail.
createLinkElement(url: string, content: string?): object
import { createLinkElement } from 'social-components/content'Creates a link object from a link's URL (url) and a link's text (content). The link object can additionally have a service: string property (example: "youtube").
The url will be parsed to see if a service can be detec For example, a YouTube video URL will be parsed and the resulting link will have service set to "youtube" and content set to the video ID, and a YouTube icon could then be shown before the link's content when the link is rendered (or, if there's no icon for a service, a simple ${service}: prefix could be prepended to it).
getMimeType(url: string): string?
import { getMimeType } from 'social-components/utility'Returns a MIME type by file URL (or filesystem path, or filename). Basically, looks at the file extension. Returns undefined if the MIME type couldn't be determined.
combineQuotes(content: Content)
import { combineQuotes } from 'social-components/content'Combines { type: "quote" } objects on consequtive lines into a single { type: "quote" } object with "\n"s inside. Mutates the original content.
findContentPart(content: Content, test: function, options: object?): number[]?
import { findContentPart } from 'social-components/content'Recursively searches content for the parts for which the test(part) function returns true. Returns an "index path" (an array of recursive content part indexes, like a path in a "tree").
The test function should be a:
function(
part: (string|object),
{ getNextPart }
): boolean?Available options:
backwards: boolean— Passtrueto search from the end to the start of thecontent.
splitContent(content: Content, indexPath: number[], options: object?): Content[]
import { splitContent } from 'social-components/content'Splits content by an indexPath path into two parts: a leftPart: Content and a rightPart: Content. An indexPath of a certain part of content could be obtained via findContentPart() function.
getVideoUrl(videoId: string, provider: string, options: object?)
import { getVideoUrl } from 'social-components/service'Returns a URL of a video by the video's ID.
Supported providers:
YouTubeVimeo
Available options for YouTube:
startAt— "Start from" timestamp, in seconds.
getEmbeddedVideoUrl(videoId: string, provider: string, options: object?)
import { getEmbeddedVideoUrl } from 'social-components/service'Same as getVideoUrl with the only difference that it returns a URL for embedding a video in an <iframe/>.
renderTweet(tweetId: string, container: Element, options: object?)
import { renderTweet } from 'social-components/service'Renders a tweet in a container.
Returns a Promise that resolves to an HTML Element.
Available options:
darkMode: boolean— Passtrueto render the tweet in dark mode.locale: string— Viewer's language code (examples:"en","ru","de").
isVectorImage(image: object): boolean
import { isVectorImage } from 'social-components/image'Tells if an image is a vector one. The image argument could be a Picture or one of the Picture.sizes[].
Messages
Messages is an object with localized labels having shape:
textContent?: objectblock?: objectaudio?: string—"Audio"video?: string—"Video"picture?: string—"Picture"attachment?: string—"Attachment"inline?: objectattachment?: string—"(attachment)"link?: string—"(link)"linkTo?: string—"(link to {domain})"<!-- * An "external" URL is an "absolute" URL — a URL with a domain name. --> * Parameters: * `{domain}` — URL domain name. <!-- * `{path}` — URL path. An empty string when not present or when equal to `"/"`. --> <!-- * `linkToInternalUrl?: string` — `"{path}"` * An "internal" URL is a "relative" URL — a URL with no domain name. * Parameters: * `{path}` — URL path. `"/"` when not present. -->
videoNotFound?: string
PropTypes
import * as SocialComponentPropTypes from 'social-components/prop-types'To do
source/services/YouTube/getVideo.jsandsource/services/Vimeo/getVideo.jsboth usefetch()global function which isn't supported in Node.js. Developers using this package could optionally polyfillfetch()on server side (for example, see/fetch-polyfill).source/services/Twitter/getTweet.jsandsource/services/Instagram/getPost.jsboth usefetch-jsonpwhich doesn't work in Node.js. Some tests are skipped because of that (describe.skip()). Maybe somehow substitutefetch-jsonpfor server side.
GitHub Ban
On March 9th, 2020, GitHub, Inc. silently banned my account (erasing all my repos, issues and comments) without any notice or explanation. Because of that, all source codes had to be promptly moved to GitLab. The GitHub repo is now only used as a backup (you can star the repo there too), and the primary repo is now the GitLab one. Issues can be reported in any repo.
License
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago