├── .nvmrc
├── .markdownlint.yml
├── .github
├── FUNDING.yml
├── workflows
│ ├── build.yml
│ ├── github-pages-deploy.yml
│ └── netlify-deploy.yml
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── PULL_REQUEST_TEMPLATE.md
├── docs
├── .vuepress
│ ├── public
│ │ ├── favicon.ico
│ │ ├── icons
│ │ │ ├── android-chrome-192x192.png
│ │ │ ├── apple-touch-icon-152x152.png
│ │ │ └── msapplication-icon-144x144.png
│ │ └── manifest.json
│ ├── styles
│ │ ├── palette.styl
│ │ └── index.styl
│ └── config.js
├── guide
│ ├── deploying.md
│ ├── getting-started.md
│ ├── README.md
│ ├── available-methods.md
│ ├── how-it-works.md
│ ├── custom-pages.md
│ └── custom-posts.md
└── README.md
├── src
├── styles
│ ├── vars.css
│ ├── types
│ │ ├── quote.css
│ │ ├── link.css
│ │ ├── chat.css
│ │ └── photo.css
│ ├── components
│ │ ├── tags.css
│ │ └── card.css
│ ├── base.css
│ └── reset.css
└── scripts
│ ├── styles.js
│ ├── templates
│ ├── posts
│ │ ├── text.js
│ │ ├── link.js
│ │ ├── audio.js
│ │ ├── quote.js
│ │ ├── video.js
│ │ ├── chat.js
│ │ └── photo.js
│ └── pages
│ │ ├── tagged.js
│ │ ├── post.js
│ │ └── home.js
│ ├── utils.js
│ └── tumblr-builder.js
├── .gitignore
├── .vscode
├── extensions.json
└── settings.json
├── .npmignore
├── .editorconfig
├── examples
├── basic-usage
│ ├── basic-usage.js
│ └── index.html
├── custom-pages
│ ├── index.html
│ └── custom-pages.js
├── custom-posts
│ ├── index.html
│ └── custom-posts.js
├── dist
│ ├── basic-usage.js
│ ├── custom-pages.js
│ └── custom-posts.js
└── webpack.config.js
├── README.md
├── postcss.config.js
├── LICENSE
├── netlify
├── index.html
├── netlify.js
├── netlify.css
└── webpack.config.js
├── CHANGELOG.md
├── webpack.config.js
└── package.json
/.nvmrc:
--------------------------------------------------------------------------------
1 | 12.14.0
2 |
--------------------------------------------------------------------------------
/.markdownlint.yml:
--------------------------------------------------------------------------------
1 | MD013: false # Line length
2 | MD033: false # Inline HTML
3 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [yoriiis]
4 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoriiis/tumblr-builder/HEAD/docs/.vuepress/public/favicon.ico
--------------------------------------------------------------------------------
/src/styles/vars.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --gray-10: rgba(0, 0, 0, 0.1);
3 | --gray-65: rgba(0, 0, 0, 0.65);
4 | --white: #fff;
5 | }
6 |
--------------------------------------------------------------------------------
/src/styles/types/quote.css:
--------------------------------------------------------------------------------
1 | @import "@commonVars";
2 |
3 | .card {
4 | .card-blockquote {
5 | margin-bottom: 20px;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | # Local Netlify folder
4 | .netlify
5 |
6 | docs/site
7 | **/*.map
8 | netlify/dist/
9 | netlify/assets/
10 | dist
11 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/icons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoriiis/tumblr-builder/HEAD/docs/.vuepress/public/icons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/icons/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoriiis/tumblr-builder/HEAD/docs/.vuepress/public/icons/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/icons/msapplication-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yoriiis/tumblr-builder/HEAD/docs/.vuepress/public/icons/msapplication-icon-144x144.png
--------------------------------------------------------------------------------
/docs/.vuepress/styles/palette.styl:
--------------------------------------------------------------------------------
1 | // colors
2 | $accentColor = #001935
3 | $textColor = #2c3e50
4 | $borderColor = #eaecef
5 | $codeBgColor = #282c34
6 | $arrowBgColor = #ccc
7 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "esbenp.prettier-vscode",
5 | "shd101wyy.markdown-preview-enhanced",
6 | "Tobermory.es6-string-html"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .github/
2 | .netlify/
3 | .vscode
4 | docs/
5 | examples/
6 | netlify/
7 | src/
8 | .editorconfig
9 | .gitignore
10 | .markdownlint.yml
11 | .nvmrc
12 | postcss.config.js
13 | webpack.config.js
14 |
--------------------------------------------------------------------------------
/src/styles/types/link.css:
--------------------------------------------------------------------------------
1 | @import "@commonVars";
2 |
3 | .card-link {
4 | .card-linkItem {
5 | display: block;
6 | padding: 16px 12px;
7 | border: 1px solid var(--gray-10);
8 | margin-bottom: 20px;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | charset = utf-8
8 | end_of_line = lf
9 | insert_final_newline = true
10 | indent_style = tab
11 | indent_size = 4
12 | trim_trailing_whitespace = true
--------------------------------------------------------------------------------
/src/styles/types/chat.css:
--------------------------------------------------------------------------------
1 | @import "@commonVars";
2 |
3 | .card-chat {
4 | .card-conversations {
5 | margin-bottom: 20px;
6 |
7 | li {
8 | margin-bottom: 10px;
9 |
10 | &:last-child {
11 | margin-bottom: 0;
12 | }
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/scripts/styles.js:
--------------------------------------------------------------------------------
1 | import '../styles/reset.css'
2 | import '../styles/base.css'
3 |
4 | import '../styles/components/tags.css'
5 | import '../styles/components/card.css'
6 |
7 | import '../styles/types/photo.css'
8 | import '../styles/types/quote.css'
9 | import '../styles/types/chat.css'
10 | import '../styles/types/link.css'
11 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "TumblrBuilder",
3 | "short_name": "TumblrBuilder",
4 | "icons": [
5 | {
6 | "src": "icons/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "index.html",
12 | "display": "standalone",
13 | "background_color": "#fff",
14 | "theme_color": "#001935"
15 | }
16 |
--------------------------------------------------------------------------------
/docs/.vuepress/styles/index.styl:
--------------------------------------------------------------------------------
1 | .custom-block.tip {
2 | border-color: $accentColor;
3 | }
4 |
5 | .badge.tip {
6 | background-color: $accentColor !important;
7 | }
8 |
9 | /*
10 | * Quick fix in order to wait the next release
11 | * https://github.com/vuejs/vuepress/issues/2334
12 | */
13 | .sidebar .dropdown-wrapper .dropdown-title {
14 | pointer-events: auto;
15 | }
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | build:
13 | uses: yoriiis/actions/.github/workflows/test-and-build.yml@main
14 | with:
15 | node-version: 12.14
16 | command-build: |
17 | npm run build
18 | npm run build:examples
19 |
--------------------------------------------------------------------------------
/.github/workflows/github-pages-deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to GitHub Pages
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - main
8 |
9 | jobs:
10 | github:
11 | permissions:
12 | contents: read
13 | pages: write
14 | id-token: write
15 | uses: yoriiis/actions/.github/workflows/github-pages-deploy.yml@main
16 | with:
17 | node-version: 12.14
18 | build-dir: docs/site
19 | command-build: npm run docs:build
20 |
--------------------------------------------------------------------------------
/examples/basic-usage/basic-usage.js:
--------------------------------------------------------------------------------
1 | const tumblr = new window.TumblrBuilder({
2 | element: document.querySelector('#tumblr-app'),
3 | host: 'tmblr-builder.tumblr.com',
4 | apiKey: 'X3Hk9uvqCx5OJJVKm9AZX8uh6wf1OhLPtKK5vCJcmQUyngWabO',
5 | limitData: 250,
6 | cache: true,
7 | cacheMethod: 'sessionStorage',
8 | nearBottom: 350,
9 | elementsPerPage: 2
10 | })
11 |
12 | // Initialize the Tumblr from the instance
13 | tumblr.init().then(response => {
14 | console.log(response)
15 | })
16 |
--------------------------------------------------------------------------------
/src/styles/components/tags.css:
--------------------------------------------------------------------------------
1 | @import "@commonVars";
2 |
3 | .tags {
4 | margin-bottom: 20px;
5 |
6 | .card-body {
7 | padding-bottom: 10px;
8 | }
9 |
10 | li {
11 | margin-bottom: 10px;
12 | margin-right: 10px;
13 | display: inline-block;
14 | vertical-align: top;
15 |
16 | &:last-child {
17 | margin-right: 0;
18 | }
19 | }
20 |
21 | a {
22 | color: var(--gray-65);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/basic-usage/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Basic usage - TumblrBuilder
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/custom-pages/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Custom pages - TumblrBuilder
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/custom-posts/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Custom posts - TumblrBuilder
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.github/workflows/netlify-deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to Netlify
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | netlify:
10 | uses: yoriiis/actions/.github/workflows/netlify-deploy.yml@main
11 | with:
12 | node-version: 12.14
13 | build-dir: netlify
14 | command-build: |
15 | npm run build
16 | npm run build:netlify
17 | cp -r dist/ netlify/assets/
18 | secrets:
19 | NETLIFY_APP_ID: ${{ secrets.NETLIFY_APP_ID }}
20 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
21 |
--------------------------------------------------------------------------------
/docs/guide/deploying.md:
--------------------------------------------------------------------------------
1 | # Deploying
2 |
3 | Deploy the app on Tumblr or to an external site.
4 |
5 | ## Tumblr
6 |
7 | Tumblr blog have a customize page to import custom HTML.
8 |
9 | - Copy the HTML content
10 | - Open Tumblr editor `https://www.tumblr.com/customize/`
11 | - Paste the HTML content
12 | - Host the asset files (scripts and styles)
13 | - Navigate to the Tumblr
14 |
15 | ## External site
16 |
17 | All requests use the Tumblr API, so the app can works outside the Tumblr ecosystem and be host on any external site with the usual processes.
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TumblrBuilder
2 |
3 | 
4 |
5 | ## Installation
6 |
7 | First, install the library from [npm](https://www.npmjs.com/package/tumblr-builder).
8 |
9 | ```bash
10 | npm install tumblr-builder --save-dev
11 | ```
12 |
13 | ## Documentation
14 |
15 | Check out the docs at [yoriiis.github.io/tumblr-builder](https://yoriiis.github.io/tumblr-builder).
16 |
17 | ## License
18 |
19 | TumblrBuilder and his documentation are under the [MIT License](http://opensource.org/licenses/MIT).
20 |
21 | Created with ♥ by [@yoriiis](http://github.com/yoriiis).
22 |
--------------------------------------------------------------------------------
/src/styles/base.css:
--------------------------------------------------------------------------------
1 | @import "@commonVars";
2 |
3 | body {
4 | font-family: "Helvetica Neue", Arial, sans-serif;
5 | }
6 |
7 | .container {
8 | max-width: 500px;
9 | margin: 0 auto;
10 | padding: 10px 10px 0;
11 | }
12 |
13 | .nav {
14 | margin: 20px 0;
15 | }
16 |
17 | .subnav {
18 | margin-bottom: 20px;
19 | }
20 |
21 | .relatedPosts {
22 | &-title {
23 | margin-bottom: 24px;
24 | padding-top: 20px;
25 | }
26 | }
27 |
28 | .posts {
29 | margin-bottom: 20px;
30 | }
31 |
32 | a {
33 | text-decoration: none;
34 | color: #000;
35 |
36 | &:hover {
37 | text-decoration: underline;
38 | }
39 | }
40 |
41 | strong {
42 | font-weight: 500;
43 | }
44 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "🚀 Feature request"
3 | about: Suggest an idea for this project
4 | labels: enhancement
5 | ---
6 |
7 | ## 🚀 Feature Proposal
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 |
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 |
15 | A clear and concise description of what you want to happen.
16 |
17 | **Describe alternatives you've considered**
18 |
19 | A clear and concise description of any alternative solutions or features you've considered.
20 |
21 | **Additional context**
22 |
23 | Add any other context or screenshots about the feature request here.
24 |
--------------------------------------------------------------------------------
/src/styles/types/photo.css:
--------------------------------------------------------------------------------
1 | @import "@commonVars";
2 |
3 | .card-photo {
4 | .card-photos {
5 | margin-bottom: 20px;
6 | }
7 |
8 | &.photoset {
9 | .card-photos {
10 | display: grid;
11 | grid-template-columns: repeat(2, 1fr);
12 | grid-row-gap: 20px;
13 | grid-column-gap: 20px;
14 | align-items: center;
15 |
16 | li {
17 | display: none;
18 | }
19 |
20 | li:nth-child(3n + 1) {
21 | grid-column: 1/3;
22 | }
23 |
24 | li:nth-child(-n + 3) {
25 | display: block;
26 | }
27 | }
28 | }
29 |
30 | .card-picture {
31 | display: block;
32 | width: 100%;
33 | height: auto;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | plugins: [
5 | require('postcss-import')({
6 | resolve (id) {
7 | let aliasFound = false
8 | const listAlias = [
9 | {
10 | alias: '@commonVars',
11 | path: './src/styles/vars.css'
12 | }
13 | ]
14 |
15 | listAlias.forEach(item => {
16 | if (id.match(new RegExp(item.alias, 'g'))) {
17 | aliasFound = path.resolve(__dirname, item.path, id.slice(16))
18 | }
19 | })
20 |
21 | return aliasFound || id
22 | }
23 | }),
24 | require('postcss-preset-env')({
25 | stage: 2,
26 | features: {
27 | 'custom-properties': {
28 | warnings: true,
29 | preserve: false
30 | }
31 | }
32 | }),
33 | require('postcss-nested')(),
34 | require('postcss-custom-media')()
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | heroText: TumblrBuilder
4 | tagline: Javascript library to build custom Tumblr from API
5 | actionText: Get Started →
6 | actionLink: /guide/
7 | features:
8 | - title: Default and custom theme
9 | details: All pages and article types have minimalist theme with the main fields and can individually be overrides
10 | - title: Extra features
11 | details: Infinite scroll on articles list, display all hashtags of articles, get related posts and increase load performance with browser storage
12 | - title: Out of the box
13 | details: TumblrBuilder can works inside a Tumblr blog and outside in your personal website, thanks to the API
14 | footer: MIT Licensed | Copyright © 2013-2020 Yoriiis
15 | ---
16 |
17 | # Easy install
18 |
19 | ```bash
20 | npm install tumblr-builder --save-dev
21 | ```
22 |
--------------------------------------------------------------------------------
/docs/guide/getting-started.md:
--------------------------------------------------------------------------------
1 | # Getting started
2 |
3 | ## Installation
4 |
5 | First, install the library from [npm](https://www.npmjs.com/package/tumblr-builder).
6 |
7 | ::: tip Package manager
8 | In the following example we will use `npm` but `yarn` also work :heart:
9 | :::
10 |
11 | ```bash
12 | npm install tumblr-builder --save-dev
13 | ```
14 |
15 | ## Get an API Key
16 |
17 | TumblrBuilder use [Tumblr API v2](https://www.tumblr.com/docs/en/api/v2), to use it you must have an API key. The API key is a unique identifier that is used to authenticate requests associated with your project.
18 |
19 | First, [register your application](https://www.tumblr.com/oauth/register) and get the API key.
20 |
21 | You must include the API key with the `apiKey` parameter in the [Javascript instanciation](how-it-works.html#instanciation).
22 |
--------------------------------------------------------------------------------
/src/scripts/templates/posts/text.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Template for text Tumblr post type
3 | *
4 | * @param {Object} datas Datas for the post
5 | *
6 | * @returns {String} HTML string for the template
7 | */
8 |
9 | export default function TemplateText (datas) {
10 | /* prettier-ignore */
11 | return `
12 |
13 |
14 |
${datas.title}
15 | ${datas.body}
16 |
23 |
24 |
25 | `
26 | }
27 |
--------------------------------------------------------------------------------
/src/scripts/templates/posts/link.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Template for link Tumblr post type
3 | *
4 | * @param {Object} datas Datas for the post
5 | *
6 | * @returns {String} HTML string for the template
7 | */
8 |
9 | export default function TemplateLink (datas) {
10 | /* prettier-ignore */
11 | return `
12 |
25 | `
26 | }
27 |
--------------------------------------------------------------------------------
/src/scripts/templates/pages/tagged.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Template for Tumblr tagged page
3 | *
4 | * @param {Object}
5 | * @param {Object} templates List of all template pages functions
6 | * @param {Object} tag The current hashtag from the route
7 | * @param {Object} posts List of all articles to display
8 | *
9 | * @returns {String} HTML string for the template
10 | */
11 |
12 | export default function TemplateTagged ({ templates, tag, posts }) {
13 | /* prettier-ignore */
14 | return `
15 |
16 |
21 |
22 |
23 | Tagged: ${tag}
24 |
25 | ${posts.map(post => templates[post.type](post)).join('')}
26 |
27 | `
28 | }
29 |
--------------------------------------------------------------------------------
/src/scripts/templates/posts/audio.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Template for audio Tumblr post type
3 | *
4 | * @param {Object} datas Datas for the post
5 | *
6 | * @returns {String} HTML string for the template
7 | */
8 |
9 | export default function TemplateAudio (datas) {
10 | /* prettier-ignore */
11 | return `
12 |
13 |
14 | ${datas.player}
15 |
16 |
26 |
27 | `
28 | }
29 |
--------------------------------------------------------------------------------
/src/scripts/templates/posts/quote.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Template for quote Tumblr post type
3 | *
4 | * @param {Object} datas Datas for the post
5 | *
6 | * @returns {String} HTML string for the template
7 | */
8 |
9 | export default function TemplateQuote (datas) {
10 | /* prettier-ignore */
11 | return `
12 |
13 |
14 |
15 | ${datas.text}
16 |
17 |
18 |
25 |
26 |
27 | `
28 | }
29 |
--------------------------------------------------------------------------------
/src/scripts/templates/posts/video.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Template for video Tumblr post type
3 | *
4 | * @param {Object} datas Datas for the post
5 | *
6 | * @returns {String} HTML string for the template
7 | */
8 |
9 | export default function TemplateVideo (datas) {
10 | /* prettier-ignore */
11 | return `
12 |
13 |
14 | ${datas.player[2].embed_code}
15 |
16 |
26 |
27 | `
28 | }
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "🐛 Bug report"
3 | about: Create a report to help us improve
4 | labels: bug
5 | ---
6 |
7 | ## 🐛 Bug Report
8 |
9 | Briefly describe the issue.
10 | Include a [reduced test case](https://css-tricks.com/reduced-test-cases/) if possible (CodePen or another).
11 |
12 | ## Steps to reproduce
13 |
14 | Explain in detail the exact steps necessary to reproduce the issue. 1. 2. 3.
15 |
16 | ## Results
17 |
18 | ### Expected
19 |
20 | Please describe what you expected to see.
21 |
22 | ### Actual
23 |
24 | Please describe what actually happened.
25 |
26 | ### Error output
27 |
28 | If there are any errors at all, please include them here.
29 |
30 | ## Additional information
31 |
32 | Please include any additional information necessary here. Including the following:
33 |
34 | ### versions
35 |
36 | #### TumblrBuilder
37 |
38 | What version of `TumblrBuilder` does this occur with?
39 |
40 | #### Browsers
41 |
42 | What browser are affected?
43 |
44 | #### OS
45 |
46 | What platforms (operating systems and devices) are affected?
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Yoriiis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/scripts/templates/pages/post.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Template for Tumblr post page
3 | *
4 | * @param {Object}
5 | * @param {Object} templates List of all template pages functions
6 | * @param {Object} posts List of all articles to display
7 | * @param {Object} relatedPosts List of related posts for the associated article
8 | *
9 | * @returns {String} HTML string for the template
10 | */
11 |
12 | export default function TemplatePost ({ templates, posts, relatedPosts }) {
13 | /* prettier-ignore */
14 | return `
15 |
16 |
21 |
22 |
23 |
24 | ${posts.map(post => templates[post.type](post)).join('')}
25 |
26 | ${relatedPosts.length ? `
27 |
28 |
Related posts
29 | ${relatedPosts.map(post => templates[post.type](post)).join('')}
30 |
31 | ` : ''}
32 | `
33 | }
34 |
--------------------------------------------------------------------------------
/src/scripts/utils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Get current scroll position
3 | *
4 | * @returns {Integer} Scroll position
5 | */
6 | export function getScrollTop () {
7 | return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
8 | }
9 |
10 | /**
11 | * Get a unique random number between min/max
12 | *
13 | * @param {*} numPicks Number of items to returns
14 | * @param {*} min Minimal value to start the random
15 | * @param {*} max Maximal value to end the random
16 | *
17 | * @returns {Array} Array of random numbers
18 | */
19 | export function getRandoms (numPicks, min, max) {
20 | var len = max - min + 1
21 | var nums = new Array(len)
22 | var selections = []
23 | var i = 0
24 | var j = 0
25 |
26 | if (min === 0) {
27 | if (numPicks > max + 1) numPicks = max
28 | } else {
29 | if (numPicks > max - min + 1) numPicks = max
30 | }
31 |
32 | // Initialize the array
33 | for (i = 0; i < len; i++) {
34 | nums[i] = i + min
35 | }
36 |
37 | // Randomly pick one from the array
38 | for (j = 0; j < numPicks; j++) {
39 | var index = Math.floor(Math.random() * nums.length)
40 | selections.push(nums[index])
41 | nums.splice(index, 1)
42 | }
43 |
44 | return selections
45 | }
46 |
--------------------------------------------------------------------------------
/src/scripts/templates/posts/chat.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Template for chat Tumblr post type
3 | *
4 | * @param {Object} datas Datas for the post
5 | *
6 | * @returns {String} HTML string for the template
7 | */
8 |
9 | export default function TemplateChat (datas) {
10 | /* prettier-ignore */
11 | return `
12 |
13 |
14 |
${datas.title}
15 |
16 | ${datas.dialogue.map(line => `
17 |
18 | ${line.label}
19 | ${line.phrase}
20 |
21 | `).join('')}
22 |
23 |
30 |
31 |
32 | `
33 | }
34 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "[javascript]": {
3 | "editor.defaultFormatter": "esbenp.prettier-vscode",
4 | "editor.formatOnSave": true
5 | },
6 | "[postcss]": {
7 | "editor.defaultFormatter": "esbenp.prettier-vscode",
8 | "editor.formatOnSave": true
9 | },
10 | "[html]": {
11 | "editor.defaultFormatter": "esbenp.prettier-vscode",
12 | "editor.formatOnSave": true
13 | },
14 | "[markdown]": {
15 | "editor.defaultFormatter": "esbenp.prettier-vscode",
16 | "editor.formatOnSave": true
17 | },
18 | "[json]": {
19 | "editor.defaultFormatter": "esbenp.prettier-vscode",
20 | "editor.formatOnSave": true
21 | },
22 | "editor.formatOnSave": false,
23 | "editor.tabSize": 4,
24 | "files.associations": {
25 | "*.css": "postcss",
26 | "*.html": "html",
27 | "*.ts": "typescript",
28 | "*.json": "json"
29 | },
30 | "files.trimTrailingWhitespace": true,
31 | "javascript.format.enable": false,
32 | "javascript.validate.enable": false,
33 | "search.exclude": {
34 | "**/node_modules": true
35 | },
36 | "editor.codeActionsOnSave": {
37 | "source.fixAll.eslint": true
38 | },
39 | "editor.rulers": [100]
40 | }
--------------------------------------------------------------------------------
/examples/dist/basic-usage.js:
--------------------------------------------------------------------------------
1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s="./examples/basic-usage/basic-usage.js")}({"./examples/basic-usage/basic-usage.js":function(e,t){new window.TumblrBuilder({element:document.querySelector("#tumblr-app"),host:"tmblr-builder.tumblr.com",apiKey:"X3Hk9uvqCx5OJJVKm9AZX8uh6wf1OhLPtKK5vCJcmQUyngWabO",limitData:250,cache:!0,cacheMethod:"sessionStorage",nearBottom:350,elementsPerPage:2}).init().then(e=>{console.log(e)})}});
--------------------------------------------------------------------------------
/src/scripts/templates/pages/home.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Template for Tumblr home page
3 | *
4 | * @param {Object}
5 | * @param {Object} templates List of all template pages functions
6 | * @param {Object} tags List of all hashtags from all available article
7 | * @param {Object} posts List of all articles to display
8 | *
9 | * @returns {String} HTML string for the template
10 | */
11 |
12 | export default function TemplateHome ({ templates, tags, posts }) {
13 | /* prettier-ignore */
14 | return `
15 |
16 |
21 |
22 |
23 |
33 |
34 | ${posts.map(post => templates[post.type](post)).join('')}
35 |
36 | `
37 | }
38 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
13 |
14 | ## What type of PR is this? (check all applicable)
15 |
16 | - [ ] Refactor
17 | - [ ] Feature
18 | - [ ] Bug Fix
19 | - [ ] Optimization
20 | - [ ] Documentation Update
21 |
22 | ## Description
23 |
24 | Please describe the change as necessary.
25 | If it's a feature or enhancement please be as detailed as possible.
26 | If it's a bug fix, please link the issue that it fixes or describe the bug in as much detail.
27 |
28 | ## Related Tickets & Documents
29 |
30 | ## Mobile & Desktop Screenshots/Recordings (if there are UI changes)
31 |
32 | ## Added tests?
33 |
34 | - [ ] Yes
35 | - [ ] No, because they aren't needed
36 | - [ ] No, because I need help
37 |
38 | ## Added to documentation?
39 |
40 | - [ ] VuePress docs
41 | - [ ] Readme
42 | - [ ] No documentation needed
43 |
44 | ## [optional] Are there any post deployment tasks we need to perform?
45 |
--------------------------------------------------------------------------------
/src/scripts/templates/posts/photo.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Template for photo/photoset Tumblr post type
3 | *
4 | * @param {Object} datas Datas for the post
5 | *
6 | * @returns {String} HTML string for the template
7 | */
8 |
9 | export default function TemplatePhoto (datas) {
10 | const isPhotoset = datas.photos.length > 1
11 | /* prettier-ignore */
12 | return `
13 |
14 |
15 |
16 | ${datas.photos.map(photo => `
17 |
18 |
19 |
20 | `).join('')}
21 |
22 | ${datas.summary
23 | ? `
${datas.summary} `
24 | : ''}
25 |
32 |
33 |
34 | `
35 | }
36 |
--------------------------------------------------------------------------------
/src/styles/components/card.css:
--------------------------------------------------------------------------------
1 | @import "@commonVars";
2 |
3 | .card {
4 | margin-bottom: 25px;
5 | position: relative;
6 | display: flex;
7 | flex-direction: column;
8 | word-wrap: break-word;
9 | border-radius: 4px;
10 | border: 1px solid var(--gray-10);
11 |
12 | &:last-child {
13 | margin-bottom: 0;
14 | }
15 |
16 | &-body {
17 | padding: 20px;
18 | }
19 |
20 | &-title {
21 | font-size: 20px;
22 | line-height: 24px;
23 | font-weight: 500;
24 | margin-bottom: 20px;
25 | display: block;
26 | }
27 |
28 | p {
29 | font-size: 16px;
30 | line-height: 24px;
31 | margin-bottom: 20px;
32 | }
33 |
34 | &-iframe {
35 | position: relative;
36 | height: 0;
37 | padding-bottom: 56.25%;
38 |
39 | iframe {
40 | width: 100%;
41 | height: 100%;
42 | position: absolute;
43 | top: 0;
44 | left: 0;
45 | }
46 | }
47 |
48 | &-tags {
49 | margin-top: 10px;
50 |
51 | li {
52 | margin-top: 10px;
53 | margin-right: 10px;
54 | display: inline-block;
55 | vertical-align: top;
56 |
57 | &:last-child {
58 | margin-right: 0;
59 | }
60 | }
61 |
62 | a {
63 | color: var(--gray-65);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/netlify/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | TumblrBuilder
5 |
6 |
7 |
8 |
14 |
15 |
16 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/netlify/netlify.js:
--------------------------------------------------------------------------------
1 | import TumblrBuilder from '../dist/tumblr-builder'
2 | import '../dist/tumblr-builder.css'
3 |
4 | import './netlify.css'
5 |
6 | const host = window.sessionStorage.getItem('tumblr') || 'tmblr-builder.tumblr.com'
7 |
8 | const tumblr = new TumblrBuilder({
9 | element: document.querySelector('#tumblr-app'),
10 | host,
11 | apiKey: 'ZbYkYWjthx8Uf7eUY6795MYOKUYRb9xoAXlneH7AmAtyv0LQ2x',
12 | limitData: 250,
13 | cache: true,
14 | cacheMethod: 'sessionStorage',
15 | nearBottom: 350,
16 | elementsPerPage: 2
17 | })
18 |
19 | // Initialize the Tumblr from the instance
20 | tumblr.init().then(response => {
21 | console.log(response)
22 |
23 | // No result, redirect to a fresh home with default host
24 | if (response === false) {
25 | window.sessionStorage.removeItem('tumblr')
26 | }
27 | })
28 |
29 | const formInputText = document.querySelector('#form-text')
30 | window.sessionStorage.setItem('tumblr', host)
31 | formInputText.value = host.split('.tumblr.com')[0]
32 |
33 | // Update the host from the form
34 | document.querySelector('.form').addEventListener('submit', e => {
35 | e.preventDefault()
36 |
37 | const inputValue = formInputText.value || false
38 | if (inputValue) {
39 | window.sessionStorage.setItem('tumblr', `${inputValue}.tumblr.com`)
40 | window.sessionStorage.removeItem('TumblrBuilderJsonData')
41 | window.location.href = ''
42 | }
43 | })
44 |
45 | // Update the dark mode from the button
46 | const html = document.querySelector('html')
47 | html.querySelector('[data-button-darkmode]').addEventListener('click', e => {
48 | e.preventDefault()
49 |
50 | const darkMode = html.classList.contains('darkMode')
51 | if (darkMode) {
52 | html.classList.remove('darkMode')
53 | } else {
54 | html.classList.add('darkMode')
55 | }
56 |
57 | window.localStorage.setItem('tumblrDarkMode', !darkMode)
58 | })
59 |
--------------------------------------------------------------------------------
/docs/guide/README.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | Build a custom Tumblr theme with Javascript!
4 |
5 | ## Online playground
6 |
7 | If you're interested in playing around with TumblrBuilder, you can use the online code playground on [CodePen](https://codepen.io/yoriiis/pen/abvZWdv).
8 |
9 | ## First examples
10 |
11 | If you prefer to use your own text editor, you can also download the [repository](https://github.com/yoriiis/tumblr-builder) from Github.
12 |
13 | The project includes several examples in the `./examples/` directory. All examples described below are fully functional with all pages (home, tagged, post) and all article types (audio, chat, link, photo, quote, text, video).
14 |
15 | ### Basic usage
16 |
17 | The example includes all default templates for pages and articles.
18 |
19 | See the [Basic usage](https://github.com/yoriiis/tumblr-builder/tree/master/examples/basic-usage) example on Github.
20 |
21 | ### Custom pages
22 |
23 | The example includes custom templates for pages and default templates for articles.
24 |
25 | See the [Custom pages](https://github.com/yoriiis/tumblr-builder/tree/master/examples/custom-pages) example on Github.
26 |
27 | ### Custom posts
28 |
29 | The example includes custom templates for articles and default templates for pages.
30 |
31 | See the [Custom posts](https://github.com/yoriiis/tumblr-builder/tree/master/examples/custom-posts) example on Github.
32 |
33 | ## Tumblr contents
34 |
35 | All pages described below have their own default templates and can be individually customized with [custom pages](custom-pages.html). Same for all these article types with [custom posts](custom-posts.html).
36 |
37 | ### Tumblr pages
38 |
39 | A Tumblr blog is composed by 3 pages.
40 |
41 | - Home page
42 | - Tagged page
43 | - Post page
44 |
45 | ### Tumblr articles
46 |
47 | A Tumblr blog accepts 7 different article types.
48 |
49 | - Audio
50 | - Chat
51 | - Link
52 | - Photo
53 | - Quote
54 | - Text
55 | - Video
56 |
--------------------------------------------------------------------------------
/netlify/netlify.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --gray: #80868b;
3 | --gray-10: rgba(0, 0, 0, 0.1);
4 |
5 | /* Darkmode */
6 | --dark: #202124;
7 | --gray-darkMode: #5f6368;
8 | --transition-darkMode: 250ms ease-out;
9 | --yellow: #ffe300;
10 | }
11 |
12 | /* Form to update on Tumblr host */
13 | .tmblr-iframe-themed .form {
14 | margin-top: 50px;
15 | }
16 |
17 | .form {
18 | text-align: right;
19 | position: relative;
20 | display: flex;
21 | height: 38px;
22 | justify-content: space-between;
23 |
24 | input {
25 | width: 100%;
26 | height: 100%;
27 | padding: 6px 12px;
28 | border-radius: 4px;
29 | font-size: 16px;
30 | background-color: inherit;
31 | border: 1px solid var(--gray-10);
32 | }
33 |
34 | .form-text {
35 | margin-bottom: 10px;
36 | margin-right: 10px;
37 | color: var(--gray);
38 | }
39 |
40 | .form-submit {
41 | cursor: pointer;
42 | color: var(--gray);
43 | width: auto;
44 | }
45 | }
46 |
47 | /* Dark mode */
48 | html {
49 | transition: background var(--transition-darkMode);
50 | }
51 |
52 | .card {
53 | transition: border var(--transition-darkMode);
54 | }
55 |
56 | .button-darkMode {
57 | width: 40px;
58 | height: 40px;
59 | border-radius: 50%;
60 | cursor: pointer;
61 | margin-right: 10px;
62 |
63 | &:focus {
64 | outline: none;
65 | }
66 |
67 | svg {
68 | fill: #000;
69 | transition: fill var(--transition-darkMode);
70 | }
71 | }
72 |
73 | .darkMode {
74 | background-color: var(--dark);
75 | color: var(--gray);
76 |
77 | .button-darkMode svg {
78 | fill: var(--yellow);
79 | }
80 |
81 | a,
82 | .card-tags a,
83 | .tags a {
84 | color: var(--gray);
85 | }
86 |
87 | .card,
88 | .card-linkItem,
89 | .form input {
90 | border: 1px solid var(--gray-darkMode);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 2.0.0
4 |
5 | ### New features
6 |
7 | - Add documentation built with VuePress and host on Github pages
8 | - Add Webpack for resources bundling
9 | - Add Github Actions for CI
10 | - Add linter tools: MarkdownLint, Stylelint, ESLint and Prettier
11 | - Add examples: basic usage, custom pages, custom posts
12 | - Add Netlify demo
13 | - Add default template for pages and posts
14 |
15 | ### Updates
16 |
17 | - Rework in ES6
18 |
19 | ### Breaking changes
20 |
21 | - The library name is replaced by `TumblrBuilder`
22 |
23 | ## 1.8.0
24 |
25 | ### Updates
26 |
27 | - Support hash in url
28 | - Add debug mode to show log error and warning in development
29 | - Support API V1 & V2
30 | - Instanciation of the class simplified
31 | - Add method to enable/disable scrollInfini
32 | - Delete webStorage if user disable cache (avoid close tab)
33 | - Add support webStorage session and local
34 | - Add a option to use API or just use infiniteScroll
35 | - Detec IE8 by feature and disable infiniteScroll because there is a limit with Tumblr (API V1 : ajax and getJSON KO. API V2 getJSON OK). Use pagination for this browser.
36 |
37 | ### Fixes
38 |
39 | - Fix bug in loop to get data
40 | - Fix bug IE11 and popin authentification
41 | - Fix bug `getRelatedPost` and simplified call
42 |
43 | ### Removes
44 |
45 | - Remove method `getFirstPushHome` (duplicate by `getPostOfPage()`)
46 |
47 | ## 1.7.0
48 |
49 | ### Updates
50 |
51 | - Change URL of ajax request in tagged page
52 |
53 | ### Fixes
54 |
55 | - Fix infinite scroll on tagged page
56 |
57 | ## 1.6.0
58 |
59 | ### Updates
60 |
61 | - Encode url in `getTagPage()`, fix (#2)
62 | - `getRelatedPosts()` use `getTagsPost()` and sessionStorage. There is a limit with related posts, only for X first post (X = limitPostInJSON). Log "Unknown idPost"
63 | - Remove console.log
64 |
65 | ### Fixes
66 |
67 | - Fix infinite scroll disable on tag page
68 | - Fix number loop and `countMissingPost` in `getAllJSON()`
69 |
70 | ## 1.5.0
71 |
72 | ### Updates
73 |
74 | - First release of `Tumblr`
75 | - Update README
76 |
--------------------------------------------------------------------------------
/src/styles/reset.css:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html,
7 | body,
8 | div,
9 | span,
10 | applet,
11 | object,
12 | iframe,
13 | h1,
14 | h2,
15 | h3,
16 | h4,
17 | h5,
18 | h6,
19 | p,
20 | blockquote,
21 | pre,
22 | a,
23 | abbr,
24 | acronym,
25 | address,
26 | big,
27 | cite,
28 | code,
29 | del,
30 | dfn,
31 | em,
32 | img,
33 | ins,
34 | kbd,
35 | q,
36 | s,
37 | samp,
38 | small,
39 | strike,
40 | strong,
41 | sub,
42 | sup,
43 | tt,
44 | var,
45 | b,
46 | u,
47 | i,
48 | center,
49 | dl,
50 | dt,
51 | dd,
52 | ol,
53 | ul,
54 | li,
55 | fieldset,
56 | form,
57 | label,
58 | legend,
59 | table,
60 | caption,
61 | tbody,
62 | tfoot,
63 | thead,
64 | tr,
65 | th,
66 | td,
67 | article,
68 | aside,
69 | canvas,
70 | details,
71 | embed,
72 | figure,
73 | figcaption,
74 | footer,
75 | header,
76 | hgroup,
77 | menu,
78 | nav,
79 | output,
80 | ruby,
81 | section,
82 | summary,
83 | time,
84 | mark,
85 | audio,
86 | video {
87 | margin: 0;
88 | padding: 0;
89 | border: 0;
90 | font: inherit;
91 | vertical-align: baseline;
92 | }
93 |
94 | /* HTML5 display-role reset for older browsers */
95 | article,
96 | aside,
97 | details,
98 | figcaption,
99 | figure,
100 | footer,
101 | header,
102 | hgroup,
103 | menu,
104 | nav,
105 | section {
106 | display: block;
107 | }
108 |
109 | body {
110 | line-height: 1;
111 | }
112 |
113 | ol,
114 | ul {
115 | list-style: none;
116 | }
117 |
118 | blockquote,
119 | q {
120 | quotes: none;
121 | }
122 |
123 | blockquote::before,
124 | blockquote::after,
125 | q::before,
126 | q::after {
127 | content: "";
128 | content: none;
129 | }
130 |
131 | table {
132 | border-collapse: collapse;
133 | border-spacing: 0;
134 | }
135 |
136 | /* Custom */
137 | * {
138 | box-sizing: border-box;
139 | }
140 |
141 | html {
142 | -ms-text-size-adjust: 100%;
143 | -webkit-text-size-adjust: 100%;
144 | }
145 |
146 | button {
147 | background-color: transparent;
148 | border: none;
149 | }
150 |
--------------------------------------------------------------------------------
/examples/custom-pages/custom-pages.js:
--------------------------------------------------------------------------------
1 | const tumblr = new window.TumblrBuilder({
2 | element: document.querySelector('#tumblr-app'),
3 | host: 'tmblr-builder.tumblr.com',
4 | apiKey: 'X3Hk9uvqCx5OJJVKm9AZX8uh6wf1OhLPtKK5vCJcmQUyngWabO',
5 | limitData: 250,
6 | cache: true,
7 | cacheMethod: 'sessionStorage',
8 | nearBottom: 350,
9 | elementsPerPage: 2,
10 | templatesPages: {
11 | home: ({ templates, tags, posts }) => {
12 | /* prettier-ignore */
13 | return `
14 | Custom home page
15 |
16 |
21 |
22 |
23 |
33 |
34 | ${posts.map(post => templates[post.type](post)).join('')}
35 |
36 | `
37 | },
38 | tagged: ({ templates, tag, posts }) => {
39 | /* prettier-ignore */
40 | return `
41 | Custom tagged page
42 |
43 |
48 |
49 |
50 | Tagged: ${tag}
51 |
52 | ${posts.map(post => templates[post.type](post)).join('')}
53 |
54 | `
55 | },
56 | post: ({ templates, posts, relatedPosts }) => {
57 | /* prettier-ignore */
58 | return `
59 | Custom post pages
60 |
61 |
66 |
67 |
68 |
69 | ${posts.map(post => templates[post.type](post)).join('')}
70 |
71 | ${relatedPosts.length ? `
72 |
73 |
My related posts
74 | ${relatedPosts.map(post => templates[post.type](post)).join('')}
75 |
76 | ` : ''}
77 | `
78 | }
79 | }
80 | })
81 |
82 | // Initialize the Tumblr from the instance
83 | tumblr.init().then(response => {
84 | console.log(response)
85 | })
86 |
--------------------------------------------------------------------------------
/netlify/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
4 | const ProgressBarPlugin = require('progress-bar-webpack-plugin')
5 | const TerserPlugin = require('terser-webpack-plugin')
6 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
7 |
8 | module.exports = (env, argv) => {
9 | const isProduction = argv.mode === 'production'
10 |
11 | return {
12 | watch: !isProduction,
13 | entry: {
14 | netlify: `${path.resolve(__dirname, './netlify.js')}`
15 | },
16 | watchOptions: {
17 | ignored: /node_modules/
18 | },
19 | devtool: !isProduction ? 'source-map' : 'none',
20 | output: {
21 | path: path.resolve(__dirname, './dist'),
22 | filename: '[name].js',
23 | sourceMapFilename: '[file].map'
24 | },
25 | module: {
26 | rules: [
27 | {
28 | test: /\.js$/,
29 | include: [path.resolve(__dirname, './')],
30 | use: [
31 | {
32 | loader: 'babel-loader'
33 | }
34 | ]
35 | },
36 | {
37 | test: /\.css$/,
38 | include: [path.resolve(__dirname, './'), path.resolve(__dirname, '../dist')],
39 | use: [
40 | MiniCssExtractPlugin.loader,
41 | {
42 | loader: 'css-loader'
43 | },
44 | {
45 | loader: 'postcss-loader',
46 | options: {
47 | config: {
48 | path: path.resolve(__dirname, './')
49 | }
50 | }
51 | }
52 | ]
53 | }
54 | ]
55 | },
56 | plugins: [
57 | new ProgressBarPlugin(),
58 | new MiniCssExtractPlugin({
59 | filename: '[name].css',
60 | chunkFilename: '[name].css'
61 | }),
62 | new webpack.optimize.ModuleConcatenationPlugin()
63 | ],
64 | stats: {
65 | assets: true,
66 | colors: true,
67 | hash: false,
68 | timings: true,
69 | chunks: false,
70 | chunkModules: false,
71 | modules: false,
72 | children: false,
73 | entrypoints: false,
74 | excludeAssets: /.map$/,
75 | assetsSort: '!size'
76 | },
77 | optimization: {
78 | minimizer: [
79 | new TerserPlugin({
80 | extractComments: false,
81 | cache: true,
82 | parallel: true,
83 | sourceMap: false,
84 | terserOptions: {
85 | extractComments: 'all',
86 | compress: {
87 | drop_console: false
88 | },
89 | mangle: true
90 | }
91 | }),
92 | new OptimizeCSSAssetsPlugin({})
93 | ],
94 | namedModules: true,
95 | removeAvailableModules: true,
96 | removeEmptyChunks: true,
97 | mergeDuplicateChunks: true,
98 | occurrenceOrder: true,
99 | providedExports: false,
100 | splitChunks: false
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const ProgressBarPlugin = require('progress-bar-webpack-plugin')
4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
5 |
6 | const TerserPlugin = require('terser-webpack-plugin')
7 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
8 |
9 | module.exports = (env, argv) => {
10 | const isProduction = argv.mode === 'production'
11 |
12 | return {
13 | watch: !isProduction,
14 | entry: {
15 | 'tumblr-builder': './src/scripts/tumblr-builder.js'
16 | },
17 | watchOptions: {
18 | ignored: /node_modules/
19 | },
20 | devtool: !isProduction ? 'source-map' : 'none',
21 | output: {
22 | path: path.resolve(__dirname, './dist'),
23 | publicPath: '/dist/',
24 | filename: '[name].js',
25 | sourceMapFilename: '[file].map',
26 | libraryTarget: 'umd',
27 | libraryExport: 'default',
28 | library: 'TumblrBuilder'
29 | },
30 | module: {
31 | rules: [
32 | {
33 | test: /\.js$/,
34 | include: path.resolve(__dirname, './src'),
35 | use: [
36 | {
37 | loader: 'babel-loader'
38 | }
39 | ]
40 | },
41 | {
42 | test: /\.css$/,
43 | include: [path.resolve(__dirname, './src')],
44 | use: [
45 | MiniCssExtractPlugin.loader,
46 | { loader: 'css-loader' },
47 | {
48 | loader: 'postcss-loader',
49 | options: {
50 | config: {
51 | path: path.resolve(__dirname, './')
52 | }
53 | }
54 | }
55 | ]
56 | }
57 | ]
58 | },
59 | plugins: [
60 | new ProgressBarPlugin(),
61 | new webpack.optimize.ModuleConcatenationPlugin(),
62 | new MiniCssExtractPlugin({
63 | filename: '[name].css',
64 | chunkFilename: '[name].css'
65 | })
66 | ],
67 | stats: {
68 | assets: true,
69 | colors: true,
70 | hash: false,
71 | timings: true,
72 | chunks: false,
73 | chunkModules: false,
74 | modules: false,
75 | children: false,
76 | entrypoints: false,
77 | excludeAssets: /.map$/,
78 | assetsSort: '!size'
79 | },
80 | optimization: {
81 | minimizer: [
82 | new TerserPlugin({
83 | extractComments: false,
84 | cache: true,
85 | parallel: true,
86 | sourceMap: false,
87 | terserOptions: {
88 | extractComments: 'all',
89 | compress: {
90 | drop_console: false
91 | },
92 | mangle: true
93 | }
94 | }),
95 | new OptimizeCSSAssetsPlugin({})
96 | ],
97 | namedModules: true,
98 | removeAvailableModules: true,
99 | removeEmptyChunks: true,
100 | mergeDuplicateChunks: true,
101 | occurrenceOrder: true,
102 | providedExports: false,
103 | splitChunks: false
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/examples/dist/custom-pages.js:
--------------------------------------------------------------------------------
1 | !function(t){var e={};function n(a){if(e[a])return e[a].exports;var s=e[a]={i:a,l:!1,exports:{}};return t[a].call(s.exports,s,s.exports,n),s.l=!0,s.exports}n.m=t,n.c=e,n.d=function(t,e,a){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:a})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var a=Object.create(null);if(n.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var s in t)n.d(a,s,function(e){return t[e]}.bind(null,s));return a},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s="./examples/custom-pages/custom-pages.js")}({"./examples/custom-pages/custom-pages.js":function(t,e){new window.TumblrBuilder({element:document.querySelector("#tumblr-app"),host:"tmblr-builder.tumblr.com",apiKey:"X3Hk9uvqCx5OJJVKm9AZX8uh6wf1OhLPtKK5vCJcmQUyngWabO",limitData:250,cache:!0,cacheMethod:"sessionStorage",nearBottom:350,elementsPerPage:2,templatesPages:{home:({templates:t,tags:e,posts:n})=>`\n\t\t\t\tCustom home page\n\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tHome \n\t\t\t\t\t\t\t \n\t\t\t\t\t\t \n\t\t\t\t\t\n\t\t\t\t \n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t${n.map(e=>t[e.type](e)).join("")}\n\t\t\t\t
\n\t\t\t`,tagged:({templates:t,tag:e,posts:n})=>`\n\t\t\t\tCustom tagged page\n\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tHome \n\t\t\t\t\t\t\t \n\t\t\t\t\t\t \n\t\t\t\t\t\n\t\t\t\t \n\t\t\t\tTagged: ${e} \n\t\t\t\t\n\t\t\t\t\t${n.map(e=>t[e.type](e)).join("")}\n\t\t\t\t
\n\t\t\t`,post:({templates:t,posts:e,relatedPosts:n})=>`\n\t\t\t\tCustom post pages\n\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tHome \n\t\t\t\t\t\t\t \n\t\t\t\t\t\t \n\t\t\t\t\t\n\t\t\t\t \n\t\t\t\t\n\t\t\t\t\t${e.map(e=>t[e.type](e)).join("")}\n\t\t\t\t
\n\t\t\t\t${n.length?`\n\t\t\t\t\t\n\t\t\t\t\t\t
My related posts \n\t\t\t\t\t\t${n.map(e=>t[e.type](e)).join("")}\n\t\t\t\t\t\n\t\t\t\t`:""}\n \t\t`}}).init().then(t=>{console.log(t)})}});
--------------------------------------------------------------------------------
/examples/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const webpack = require('webpack')
3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin')
4 | const ProgressBarPlugin = require('progress-bar-webpack-plugin')
5 | const TerserPlugin = require('terser-webpack-plugin')
6 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
7 |
8 | module.exports = (env, argv) => {
9 | const isProduction = argv.mode === 'production'
10 |
11 | return {
12 | watch: !isProduction,
13 | entry: {
14 | 'basic-usage': `${path.resolve(__dirname, './basic-usage/basic-usage.js')}`,
15 | 'custom-pages': `${path.resolve(__dirname, './custom-pages/custom-pages.js')}`,
16 | 'custom-posts': `${path.resolve(__dirname, './custom-posts/custom-posts.js')}`
17 | },
18 | watchOptions: {
19 | ignored: /node_modules/
20 | },
21 | devtool: !isProduction ? 'source-map' : 'none',
22 | output: {
23 | path: path.resolve(__dirname, './dist'),
24 | filename: '[name].js',
25 | sourceMapFilename: '[file].map'
26 | },
27 | module: {
28 | rules: [
29 | {
30 | test: /\.js$/,
31 | include: [path.resolve(__dirname, './')],
32 | use: [
33 | {
34 | loader: 'babel-loader'
35 | }
36 | ]
37 | },
38 | {
39 | test: /\.css$/,
40 | include: [path.resolve(__dirname, './'), path.resolve(__dirname, '../dist')],
41 | use: [
42 | MiniCssExtractPlugin.loader,
43 | {
44 | loader: 'css-loader'
45 | },
46 | {
47 | loader: 'postcss-loader',
48 | options: {
49 | config: {
50 | path: path.resolve(__dirname, './')
51 | }
52 | }
53 | }
54 | ]
55 | }
56 | ]
57 | },
58 | plugins: [
59 | new ProgressBarPlugin(),
60 | new MiniCssExtractPlugin({
61 | filename: '[name].css',
62 | chunkFilename: '[name].css'
63 | }),
64 | new webpack.optimize.ModuleConcatenationPlugin()
65 | ],
66 | stats: {
67 | assets: true,
68 | colors: true,
69 | hash: false,
70 | timings: true,
71 | chunks: false,
72 | chunkModules: false,
73 | modules: false,
74 | children: false,
75 | entrypoints: false,
76 | excludeAssets: /.map$/,
77 | assetsSort: '!size'
78 | },
79 | optimization: {
80 | minimizer: [
81 | new TerserPlugin({
82 | extractComments: false,
83 | cache: true,
84 | parallel: true,
85 | sourceMap: false,
86 | terserOptions: {
87 | extractComments: 'all',
88 | compress: {
89 | drop_console: false
90 | },
91 | mangle: true
92 | }
93 | }),
94 | new OptimizeCSSAssetsPlugin({})
95 | ],
96 | namedModules: true,
97 | removeAvailableModules: true,
98 | removeEmptyChunks: true,
99 | mergeDuplicateChunks: true,
100 | occurrenceOrder: true,
101 | providedExports: false,
102 | splitChunks: false
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/docs/.vuepress/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | base: '/tumblr-builder/',
3 | dest: 'docs/site',
4 | markdown: {
5 | lineNumbers: true
6 | },
7 | title: 'TumblrBuilder',
8 | description: 'Javascript library to build custom Tumblr from API',
9 | head: [
10 | ['link', { rel: 'manifest', href: '/manifest.json' }],
11 | ['link', { rel: 'shortcut icon', type: 'image/png', href: '/favicon.ico' }],
12 | ['meta', { name: 'theme-color', content: '#001935' }],
13 | ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
14 | ['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'black' }],
15 | ['link', { rel: 'apple-touch-icon', href: 'icons/apple-touch-icon-152x152.png' }],
16 | [
17 | 'meta',
18 | { name: 'msapplication-TileImage', content: 'icons/msapplication-icon-144x144.png' }
19 | ],
20 |
21 | ['meta', { name: 'msapplication-TileColor', content: '#001935' }]
22 | ],
23 | plugins: [
24 | [
25 | '@vuepress/pwa',
26 | {
27 | serviceWorker: true,
28 | updatePopup: true
29 | }
30 | ],
31 | '@vuepress/back-to-top',
32 | '@vuepress/nprogress'
33 | ],
34 | themeConfig: {
35 | repo: 'yoriiis/tumblr-builder',
36 | repoLabel: 'Github',
37 | docsDir: 'docs',
38 | docsBranch: 'master',
39 | editLinks: true,
40 | editLinkText: 'Edit this page',
41 | smoothScroll: true,
42 | nextLinks: true,
43 | prevLinks: true,
44 | lastUpdated: 'Last Updated',
45 | search: true,
46 | searchMaxSuggestions: 10,
47 | searchPlaceholder: 'Search...',
48 | algolia: {
49 | apiKey: '02b27ef69549260976707846ce013194',
50 | indexName: 'tumblr-builder'
51 | },
52 | activeHeaderLinks: false,
53 | displayAllHeaders: false,
54 | nav: [
55 | { text: 'Guide', link: '/guide/' },
56 | {
57 | text: 'Demo',
58 | items: [
59 | {
60 | text: 'Tumblr',
61 | link: 'http://tmblr-builder.tumblr.com'
62 | },
63 | { text: 'Out of the box', link: 'https://tumblr-builder.netlify.app' }
64 | ]
65 | },
66 | {
67 | text: 'Learn more',
68 | items: [
69 | {
70 | text: 'Tumblr',
71 | items: [
72 | {
73 | text: 'API',
74 | link: 'https://www.tumblr.com/docs/en/api/v2'
75 | },
76 | {
77 | text: 'Custom theme',
78 | link: 'https://www.tumblr.com/docs/en/custom_themes'
79 | }
80 | ]
81 | },
82 | {
83 | text: 'Miscellaneous',
84 | items: [
85 | {
86 | text: 'Changelog',
87 | link:
88 | 'https://github.com/yoriiis/tumblr-builder/blob/master/CHANGELOG.md'
89 | }
90 | ]
91 | }
92 | ]
93 | }
94 | ],
95 | sidebar: {
96 | '/guide/': [
97 | {
98 | title: 'Guide',
99 | collapsable: false,
100 | sidebarDepth: 2,
101 | children: [
102 | '',
103 | ['getting-started', 'Getting started'],
104 | ['how-it-works', 'How it works'],
105 | ['available-methods', 'Available methods'],
106 | ['deploying', 'Deploying']
107 | ]
108 | },
109 | {
110 | title: 'Advanced',
111 | collapsable: false,
112 | sidebarDepth: 2,
113 | children: [
114 | ['custom-pages', 'Custom pages'],
115 | ['custom-posts', 'Custom posts']
116 | ]
117 | }
118 | ]
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/docs/guide/available-methods.md:
--------------------------------------------------------------------------------
1 | # Available methods
2 |
3 | List of functions exposed by the instance.
4 |
5 | ::: tip API research
6 | Functions `getAllTags` and `getRelatedPosts` executes research in the collection of articles get from the API according to the [limitData](how-it-works.html#limitdata) option. The limit can be adjust to fit your need.
7 | :::
8 |
9 | ## init
10 |
11 | - Return: `Promise`
12 |
13 | The function build the app and returns a promise. **API dependents functions must be called when `init` function is ready.**
14 |
15 | ```javascript
16 | tumblr.init().then(response => {
17 | // App is render and ready
18 | });
19 | ```
20 |
21 | Since `init()` returns a promise, you can achieve the same as the above using the new [ES2017 async/await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) syntax:
22 |
23 | ```javascript
24 | const response = await tumblr.init();
25 | ```
26 |
27 | The function exposes a `response` object with datas from the API including following fields.
28 |
29 | ### response.totalPosts
30 |
31 | - Type: `Integer`
32 | - Default: `0`
33 |
34 | The total of articles available on the Tumblr blog, according to the [host](how-it-works.html#host) option.
35 |
36 | ### response.posts
37 |
38 | - Type: `Array`
39 | - Default: `[]`
40 |
41 | List of articles datas returns by the app.
42 |
43 | ### response.tags
44 |
45 | - Type: `Array`
46 | - Default: `[]`
47 |
48 | List of all hashtags for all articles requested by the app.
49 |
50 | ## getAllTags
51 |
52 | - Return: `Array`
53 |
54 | The function get the list of all hashtags from all available articles.
55 |
56 | ```javascript
57 | tumblr.getAllTags();
58 | ```
59 |
60 | ## getRelatedPosts
61 |
62 | - Return: `Array`
63 |
64 | The function get related posts according to a specific post.
65 |
66 | ```javascript
67 | tumblr.getRelatedPosts({
68 | postId,
69 | tags,
70 | limit,
71 | ignoreTags
72 | });
73 | ```
74 |
75 | The function has an object parameter with following fields.
76 |
77 | ### postId
78 |
79 | - Type: `String`
80 | - Default: `''`
81 |
82 | Tells the function the id of the associated article.
83 |
84 | ### tags
85 |
86 | - Type: `Array`
87 | - Default: `[]`
88 |
89 | Tells the function the list of hashtags for the associated article.
90 |
91 | ### limit
92 |
93 | - Type: `Integer`
94 | - Default: `3`
95 |
96 | Tells the function the limit of results to return.
97 |
98 | ### ignoreTags
99 |
100 | - Type: `Array`
101 | - Default: `[]`
102 |
103 | Tells the function the list of hashtags to ignore for the research.
104 |
105 | ## getRoute
106 |
107 | - Return: `String`
108 |
109 | The function get the current hash from the route.
110 |
111 | ```javascript
112 | tumblr.getRoute();
113 | ```
114 |
115 | ## getPageType
116 |
117 | - Return: `String||null`
118 | - Possible value: `'home'` `'tagged'` `'post'` `null`
119 |
120 | The function get the page type from the route.
121 |
122 | ```javascript
123 | tumblr.getPageType();
124 | ```
125 |
126 | ## getPostIdFromRoute
127 |
128 | - Return: `String`
129 |
130 | The function get the post id from the route (`/post/` route only).
131 |
132 | ```javascript
133 | tumblr.getPostIdFromRoute();
134 | ```
135 |
136 | ## getHashTagFromRoute
137 |
138 | - Return: `String`
139 |
140 | The function get the hashtag from the route (`/tagged/` route only).
141 |
142 | ```javascript
143 | tumblr.getHashTagFromRoute();
144 | ```
145 |
--------------------------------------------------------------------------------
/docs/guide/how-it-works.md:
--------------------------------------------------------------------------------
1 | # How it works
2 |
3 | Now dependencies and API key are done, let's write our first custom Tumblr blog.
4 |
5 | ## HTML structure
6 |
7 | Create the container for the app with a selector easily accessible.
8 |
9 | ```html
10 |
11 |
12 |
13 | ```
14 |
15 | ## Initialize
16 |
17 | Then, we will initialize the constructor. **Highlight lines are mandatory parameters.**
18 |
19 | ```javascript{2,3,4}
20 | const tumblr = new TumblrBuilder({
21 | element: null,
22 | host: "",
23 | apiKey: "",
24 | limitData: 250,
25 | cache: false,
26 | cacheMethod: "sessionStorage",
27 | nearBottom: 350,
28 | elementsPerPage: 20,
29 | templatesPages: {},
30 | templatesPosts: {}
31 | });
32 | ```
33 |
34 | Next, create the Tumblr app.
35 |
36 | ```javascript
37 | tumblr.init().then(response => {
38 | // App is render and ready
39 | });
40 | ```
41 |
42 | ## Options
43 |
44 | ### element
45 |
46 | - Type: `HTMLElement`
47 | - Default: `null`
48 |
49 | The HTML element where the app will render.
50 |
51 | ### host
52 |
53 | - Type: `String`
54 | - Default: `''`
55 |
56 | The Tumblr blog hostname, for example `.tumblr.com`. The API will fetch datas from this blog.
57 |
58 | ### apiKey
59 |
60 | - Type: `String`
61 | - Default: `''`
62 |
63 | The Tumblr API key for API usage.
64 |
65 | ### limitData
66 |
67 | - Type: `Integer`
68 | - Default: `250`
69 |
70 | Number of articles to get from the API. The articles list is used to extract a list of all hashtags and find related articles relations by hashtags.
71 |
72 | ::: tip Use the browser storage
73 | To decrease the number of API requests and improve load performance, use the `cache` option to store all datas in the browser storage.
74 | :::
75 |
76 | ### cache
77 |
78 | - Type: `Boolean`
79 | - Default: `false`
80 |
81 | Tell the library whether to store API datas in the browser storage. This option improve performance, because app retrieves datas from the browser storage and no API requests are executed on the home page.
82 |
83 | Infinite scroll will used in priority the datas from the browser storage for firsts scrolling requests if available.
84 |
85 | ::: warning Cache priotity
86 | If `cache` option is enabled the app will used datas from the browser storage in priority. To see the lastest updates to the app while contributing to Tumblr, you will probably need to delete datas from browser storage or use incognito navigation.
87 | :::
88 |
89 | ### cacheMethod
90 |
91 | - Type: `String`
92 | - Default: `'sessionStorage'`
93 | - Possible value: `'sessionStorage'` `'localStorage'`
94 |
95 | Tell the method to use for the browser storage.
96 |
97 | ### nearBottom
98 |
99 | - Type: `Integer`
100 | - Default: `350`
101 |
102 | Tell from how many pixels from the bottom of the page the infinite scroll will trigger new requests.
103 |
104 | ### elementsPerPage
105 |
106 | - Type: `Integer`
107 | - Default: `20`
108 |
109 | Tell how many articles are display by page, for the first page request and next page requests with infinite scroll.
110 |
111 | ### templatesPages
112 |
113 | - Type: `Object`
114 | - Default: `{}`
115 | - Possible value: `{home, tagged, post}`
116 |
117 | Tell whether to override default templates for pages (home, tagged, post).
118 |
119 | Example of implementation:
120 |
121 | ```javascript
122 | new TumblrBuilder({
123 | templatesPages: {
124 | home: ({ templates, tags, posts }) => {},
125 | tagged: ({ templates, tag, posts }) => {},
126 | post: ({ templates, posts, relatedPosts }) => {}
127 | }
128 | });
129 | ```
130 |
131 | More information about [custom pages](custom-pages.html).
132 |
133 | ### templatesPosts
134 |
135 | - Type: `Object`
136 | - Default: `{}`
137 | - Possible value: `{audio, chat, link, photo, quote, text, video}`
138 |
139 | Tell whether to override default templates for article (audio, chat, link, photo, quote, text, video).
140 |
141 | Example of implementation:
142 |
143 | ```javascript
144 | new TumblrBuilder({
145 | templatesPosts: {
146 | audio: datas => {},
147 | chat: datas => {},
148 | link: datas => {},
149 | photo: datas => {},
150 | quote: datas => {},
151 | text: datas => {},
152 | video: datas => {}
153 | }
154 | });
155 | ```
156 |
157 | More information about [custom posts](custom-posts.html).
158 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tumblr-builder",
3 | "version": "2.0.0",
4 | "description": "Javascript library to build custom Tumblr from API",
5 | "keywords": [
6 | "tumblr",
7 | "builder",
8 | "tumblr api",
9 | "tumblr custom theme",
10 | "tumblr custom pages",
11 | "tumblr custom posts",
12 | "get related posts",
13 | "infinite scroll"
14 | ],
15 | "homepage": "https://github.com/yoriiis/tumblr-builder",
16 | "bugs": "https://github.com/yoriiis/tumblr-builder/issues",
17 | "repository": "https://github.com/yoriiis/tumblr-builder.git",
18 | "license": "MIT",
19 | "author": "Yoriiis",
20 | "main": "dist/tumblr.js",
21 | "scripts": {
22 | "build": "rm -rf ./dist && webpack --mode=production",
23 | "build:all": "npm run build && npm run build:examples && npm run build:netlify",
24 | "build:examples": "rm -rf ./examples/dist && webpack --config=./examples/webpack.config.js --mode=production",
25 | "build:netlify": "rm -rf ./netlify/dist && webpack --config=./netlify/webpack.config.js --mode=production",
26 | "docs:build": "vuepress build docs",
27 | "docs:dev": "vuepress dev docs --host localhost",
28 | "start": "webpack --mode=development",
29 | "start:examples": "webpack --config=./examples/webpack.config.js --mode=development",
30 | "start:netlify": "webpack --config=./netlify/webpack.config.js --mode=development",
31 | "test": "npm run test:eslint && npm run test:stylelint npm run test:markdown",
32 | "test:eslint": "eslint . --ignore-pattern dist --ignore-pattern docs",
33 | "test:markdown": "markdownlint '**/*.md' --ignore node_modules --ignore CHANGELOG.md",
34 | "test:stylelint": "stylelint './src/**/*.css' './examples/**/*.css' --ignore-pattern examples/dist"
35 | },
36 | "babel": {
37 | "plugins": [
38 | "babel-plugin-dynamic-import-node",
39 | "@babel/plugin-proposal-class-properties"
40 | ],
41 | "presets": [
42 | [
43 | "@babel/preset-env",
44 | {
45 | "targets": {
46 | "node": "12.14.0"
47 | }
48 | }
49 | ]
50 | ]
51 | },
52 | "browserslist": [
53 | "last 5 version"
54 | ],
55 | "prettier": {
56 | "printWidth": 100,
57 | "overrides": [
58 | {
59 | "files": "*.md",
60 | "options": {
61 | "proseWrap": "preserve",
62 | "tabWidth": 2,
63 | "useTabs": false
64 | }
65 | }
66 | ]
67 | },
68 | "eslintConfig": {
69 | "env": {
70 | "browser": true,
71 | "es6": true,
72 | "node": true
73 | },
74 | "parser": "babel-eslint",
75 | "parserOptions": {
76 | "ecmaFeatures": {
77 | "impliedStrict": true,
78 | "experimentalObjectRestSpread": true
79 | },
80 | "ecmaVersion": 6,
81 | "sourceType": "module"
82 | },
83 | "extends": "standard",
84 | "rules": {
85 | "indent": [
86 | "error",
87 | "tab",
88 | {
89 | "ignoredNodes": [
90 | "TemplateLiteral > *"
91 | ]
92 | }
93 | ],
94 | "no-console": 0,
95 | "no-tabs": 0
96 | },
97 | "globals": {
98 | "document": false,
99 | "window": false
100 | }
101 | },
102 | "stylelint": {
103 | "extends": "stylelint-config-standard",
104 | "rules": {
105 | "indentation": 4,
106 | "unit-whitelist": [
107 | "%",
108 | "px",
109 | "fr",
110 | "ms"
111 | ]
112 | }
113 | },
114 | "dependencies": {},
115 | "devDependencies": {
116 | "@babel/core": "7.9.0",
117 | "@babel/plugin-proposal-class-properties": "7.8.3",
118 | "@babel/preset-env": "7.9.0",
119 | "@vuepress/plugin-back-to-top": "1.4.1",
120 | "@vuepress/plugin-nprogress": "1.4.1",
121 | "@vuepress/plugin-pwa": "1.4.1",
122 | "babel-eslint": "10.0.3",
123 | "babel-loader": "8.0.6",
124 | "babel-plugin-dynamic-import-node": "2.3.0",
125 | "css-loader": "3.4.2",
126 | "eslint": "6.8.0",
127 | "eslint-config-standard": "14.1.0",
128 | "eslint-plugin-import": "2.20.0",
129 | "eslint-plugin-node": "11.0.0",
130 | "eslint-plugin-promise": "4.2.1",
131 | "eslint-plugin-standard": "4.0.1",
132 | "expose-loader": "0.7.5",
133 | "file-loader": "5.0.2",
134 | "markdownlint-cli": "0.21.0",
135 | "mini-css-extract-plugin": "0.4.1",
136 | "netlify-cli": "^2.47.0",
137 | "npm-force-resolutions": "0.0.3",
138 | "optimize-css-assets-webpack-plugin": "5.0.3",
139 | "postcss-custom-media": "7.0.8",
140 | "postcss-import": "12.0.1",
141 | "postcss-loader": "3.0.0",
142 | "postcss-nested": "4.2.1",
143 | "postcss-preset-env": "6.7.0",
144 | "prettier-eslint": "9.0.1",
145 | "prettier-stylelint": "0.4.2",
146 | "progress-bar-webpack-plugin": "2.1.0",
147 | "style-loader": "1.1.3",
148 | "stylefmt": "6.0.3",
149 | "stylelint": "13.0.0",
150 | "stylelint-config-recommended": "3.0.0",
151 | "stylelint-config-standard": "19.0.0",
152 | "terser-webpack-plugin": "2.3.2",
153 | "vuepress": "1.4.1",
154 | "webpack": "4.41.5",
155 | "webpack-cli": "3.3.10",
156 | "webpack-manifest-plugin": "2.2.0"
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/examples/custom-posts/custom-posts.js:
--------------------------------------------------------------------------------
1 | const tumblr = new window.TumblrBuilder({
2 | element: document.querySelector('#tumblr-app'),
3 | host: 'tmblr-builder.tumblr.com',
4 | apiKey: 'X3Hk9uvqCx5OJJVKm9AZX8uh6wf1OhLPtKK5vCJcmQUyngWabO',
5 | limitData: 250,
6 | cache: true,
7 | cacheMethod: 'sessionStorage',
8 | nearBottom: 350,
9 | elementsPerPage: 2,
10 | templatesPosts: {
11 | audio: datas => {
12 | /* prettier-ignore */
13 | return `
14 |
15 |
16 | ${datas.player}
17 |
18 |
28 |
29 | `
30 | },
31 | chat: datas => {
32 | /* prettier-ignore */
33 | return `
34 |
35 |
36 |
${datas.title}
37 |
38 | ${datas.dialogue.map(line => `
39 |
40 | ${line.label}
41 | ${line.phrase}
42 |
43 | `).join('')}
44 |
45 |
52 |
53 |
54 | `
55 | },
56 | link: datas => {
57 | /* prettier-ignore */
58 | return `
59 |
72 | `
73 | },
74 | photo: datas => {
75 | const isPhotoset = datas.photos.length > 1
76 | /* prettier-ignore */
77 | return `
78 |
79 |
80 |
81 | ${datas.photos.map(photo => `
82 |
83 |
84 |
85 | `).join('')}
86 |
87 | ${datas.summary
88 | ? `
${datas.summary} `
89 | : ''}
90 |
97 |
98 |
99 | `
100 | },
101 | quote: datas => {
102 | /* prettier-ignore */
103 | return `
104 |
105 |
106 |
107 | ${datas.text}
108 |
109 |
110 |
117 |
118 |
119 | `
120 | },
121 | text: datas => {
122 | /* prettier-ignore */
123 | return `
124 |
125 |
126 |
${datas.title}
127 | ${datas.body}
128 |
135 |
136 |
137 | `
138 | },
139 | video: datas => {
140 | /* prettier-ignore */
141 | return `
142 |
143 |
144 | ${datas.player[2].embed_code}
145 |
146 |
147 |
${datas.summary}
148 |
155 |
156 |
157 | `
158 | }
159 | }
160 | })
161 |
162 | // Initialize the Tumblr from the instance
163 | tumblr.init().then(response => {
164 | console.log(response)
165 | })
166 |
--------------------------------------------------------------------------------
/examples/dist/custom-posts.js:
--------------------------------------------------------------------------------
1 | !function(t){var n={};function a(s){if(n[s])return n[s].exports;var e=n[s]={i:s,l:!1,exports:{}};return t[s].call(e.exports,e,e.exports,a),e.l=!0,e.exports}a.m=t,a.c=n,a.d=function(t,n,s){a.o(t,n)||Object.defineProperty(t,n,{enumerable:!0,get:s})},a.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},a.t=function(t,n){if(1&n&&(t=a(t)),8&n)return t;if(4&n&&"object"==typeof t&&t&&t.__esModule)return t;var s=Object.create(null);if(a.r(s),Object.defineProperty(s,"default",{enumerable:!0,value:t}),2&n&&"string"!=typeof t)for(var e in t)a.d(s,e,function(n){return t[n]}.bind(null,e));return s},a.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return a.d(n,"a",n),n},a.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},a.p="",a(a.s="./examples/custom-posts/custom-posts.js")}({"./examples/custom-posts/custom-posts.js":function(t,n){new window.TumblrBuilder({element:document.querySelector("#tumblr-app"),host:"tmblr-builder.tumblr.com",apiKey:"X3Hk9uvqCx5OJJVKm9AZX8uh6wf1OhLPtKK5vCJcmQUyngWabO",limitData:250,cache:!0,cacheMethod:"sessionStorage",nearBottom:350,elementsPerPage:2,templatesPosts:{audio:t=>`\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t${t.player}\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
${t.summary} \n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t`,chat:t=>`\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
${t.title} \n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t${t.dialogue.map(t=>`\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t${t.label} \n\t\t\t\t\t\t\t\t\t\t${t.phrase}\n\t\t\t\t\t\t\t\t\t \n\t\t\t\t\t\t\t`).join("")}\n\t\t\t\t\t\t \n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t`,link:t=>`\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
${t.title} \n\t\t\t\t\t\t${t.description}\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t`,photo:t=>`\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t${t.photos.map(n=>`\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t \n\t\t\t\t\t\t\t\t \n\t\t\t\t\t\t`).join("")}\n\t\t\t\t\t\t \n\t\t\t\t\t\t${t.summary?`
${t.summary} `:""}\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t`,quote:t=>`\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t${t.text}
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t \n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t`,text:t=>`\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
${t.title} \n\t\t\t\t\t\t${t.body}\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t`,video:t=>`\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t${t.player[2].embed_code}\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t
${t.summary} \n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t`}}).init().then(t=>{console.log(t)})}});
--------------------------------------------------------------------------------
/docs/guide/custom-pages.md:
--------------------------------------------------------------------------------
1 | # Custom pages
2 |
3 | Need to customize the HTML for the home page, tagged page or post page? The `templatesPages` option is for you :rocket:
4 |
5 | On app initialize, add the `templatesPages` parameter with pages you need to individually customized. The others pages will automatically used their default template.
6 |
7 | ::: tip Override default templates
8 | To simply override the default template for a specific page use `templatesPages.` parameter. The `` placeholder must be replaced by the following values: `home` `tagged` `post`.
9 | :::
10 |
11 | ::: details Prettier ignore comment
12 | To prevent issue with Prettier and template literals indents, following examples are preceded by `/* prettier-ignore */`. [More information on Prettier](https://prettier.io/docs/en/ignore.html).
13 | :::
14 |
15 | ## Home page
16 |
17 | ### Default home page
18 |
19 | The default template used for the home page is described below. The function can be replaced with the `templatesPages.home` parameter. The `data-infinite-scroll` attribute is used for the infinite scroll feature.
20 |
21 | ```javascript
22 | function home ({ templates, tags, posts }) => {
23 | /* prettier-ignore */
24 | return `
25 |
26 |
31 |
32 |
33 |
43 |
44 | ${posts.map(post => templates[post.type](post)).join('')}
45 |
46 | `;
47 | }
48 | ```
49 |
50 | The function exposes following parameters:
51 |
52 | #### home ({ templates })
53 |
54 | - Type: `Object`
55 | - Default: `{}`
56 |
57 | List of all template pages functions (audio, chat, link, photo, quote, text, video). All defaults functions are automatically used if `templatesPages` object is not overrides them.
58 |
59 | #### home ({ tags })
60 |
61 | - Type: `Array`
62 | - Default: `[]`
63 |
64 | List of all hashtags from all available article, according to the [limitData](how-it-works.html#limitdata) option.
65 |
66 | #### home ({ posts })
67 |
68 | - Type: `Array`
69 | - Default: `[]`
70 |
71 | List of all articles to display on the first page (before the first infinite scroll), according to the [elementsPerPage](how-it-works.html#elementsperpage) option.
72 |
73 | ### Custom home page
74 |
75 | To customize the home page template, write your own `customHomePage` function inspired by the `home` function above.
76 |
77 | ```javascript
78 | function customHomePage({ templates, tags, posts }) {
79 | return `
80 |
81 | `;
82 | }
83 | ```
84 |
85 | Next, use the `templatesPages.home` parameter to passed the new home page template function.
86 |
87 | ```javascript
88 | new TumblrBuilder({
89 | templatesPages: {
90 | home: customHomePage
91 | }
92 | });
93 | ```
94 |
95 | ## Tagged page
96 |
97 | ### Default tagged page
98 |
99 | The default template used for the tagged page is described below. The function can be replaced with the `templatesPages.tagged` parameter. The `data-infinite-scroll` attribute is used for the infinite scroll feature.
100 |
101 | ```javascript
102 | function tagged ({ templates, tag, posts }) => {
103 | /* prettier-ignore */
104 | return `
105 |
106 |
107 |
108 | Home
109 |
110 |
111 |
112 |
113 | Tagged: ${tag}
114 |
115 | ${posts.map(post => templates[post.type](post)).join('')}
116 |
117 | `;
118 | }
119 | ```
120 |
121 | The function exposes following parameters:
122 |
123 | #### tagged ({ templates })
124 |
125 | - Type: `Object`
126 | - Default: `{}`
127 |
128 | List of all template pages functions (audio, chat, link, photo, quote, text, video). All defaults functions are automatically used if `templatesPages` object is not overrides them.
129 |
130 | #### tagged ({ tag })
131 |
132 | - Type: `String`
133 | - Default: `''`
134 |
135 | The current hashtag from the route.
136 |
137 | #### tagged ({ posts })
138 |
139 | - Type: `Array`
140 | - Default: `[]`
141 |
142 | List of all articles filtered by the current hashtag.
143 |
144 | ### Custom tagged page
145 |
146 | To customize the tagged page template, write your own `customTaggedPage` function inspired by the `tagged` function above.
147 |
148 | ```javascript
149 | function customTaggedPage({ templates, tag, posts }) {
150 | return `
151 |
152 | `;
153 | }
154 | ```
155 |
156 | Next, use the `templatesPages.tagged` option to passed the new tagged page function.
157 |
158 | ```javascript
159 | new TumblrBuilder({
160 | templatesPages: {
161 | tagged: customTaggedPage
162 | }
163 | });
164 | ```
165 |
166 | ## Post page
167 |
168 | ### Default post page
169 |
170 | The default template used for the post page is described below. The function can be replaced with the `templatesPages.post` parameter.
171 |
172 | ```javascript
173 | function post ({ templates, posts, relatedPosts }) => {
174 | /* prettier-ignore */
175 | return `
176 |
177 |
178 |
179 | Home
180 |
181 |
182 |
183 |
184 |
185 | ${posts.map(post => templates[post.type](post)).join('')}
186 |
187 | ${relatedPosts.length ? `
188 |
189 |
My related posts
190 | ${relatedPosts.map(post => templates[post.type](post)).join('')}
191 |
192 | ` : ''}
193 | `;
194 | }
195 | ```
196 |
197 | The function exposes following parameters:
198 |
199 | #### post ({ templates })
200 |
201 | - Type: `Object`
202 | - Default: `{}`
203 |
204 | List of all template pages functions (audio, chat, link, photo, quote, text, video). All defaults functions are automatically used if `templatesPages` object is not overrides them.
205 |
206 | #### post ({ posts })
207 |
208 | - Type: `Array`
209 | - Default: `[]`
210 |
211 | Datas for the current article.
212 |
213 | #### post ({ relatedPosts })
214 |
215 | - Type: `Array`
216 | - Default: `[]`
217 |
218 | List of related posts for the associated article.
219 |
220 | ### Custom post page
221 |
222 | To customize the post page template, write your own `customPostPage` function inspired by the `post` function above.
223 |
224 | ```javascript
225 | function customPostPage({ templates, posts, relatedPosts }) {
226 | return `
227 |
228 | `;
229 | }
230 | ```
231 |
232 | Next, use the `templatesPages.post` option to passed the new post page function.
233 |
234 | ```javascript
235 | new TumblrBuilder({
236 | templatesPages: {
237 | post: customPostPage
238 | }
239 | });
240 | ```
241 |
242 | ::: tip Custom related posts
243 | The `relatedPosts` parameter can be replaced by a new call to the [getRelatedPosts](available-methods.html#getrelatedposts) function with custom parameters.
244 | :::
245 |
--------------------------------------------------------------------------------
/docs/guide/custom-posts.md:
--------------------------------------------------------------------------------
1 | # Custom posts
2 |
3 | Need to customize the HTML for article types (audio, chat, link, photo, quote, text, video)? The `templatesPosts` option is for you :rocket:
4 |
5 | On app initialize, add the `templatesPosts` parameter with post types you need to customized. The others article types will automatically used their default template.
6 |
7 | ::: tip Override default templates
8 | To simply override the default template for a specific article type use the `templatesPosts.` parameter. The `` placeholder must be replaced by the following values: `audio` `chat` `link` `photo` `quote` `text` `video`.
9 | :::
10 |
11 | ::: details Prettier ignore comment
12 | To prevent issue with Prettier and template literals indents, following examples are preceded by `/* prettier-ignore */`. [More information on Prettier](https://prettier.io/docs/en/ignore.html).
13 | :::
14 |
15 | ## Function parameters
16 |
17 | All functions exposes following parameter.
18 |
19 | ### datas
20 |
21 | - Type: `Object`
22 | - Default: `{}`
23 |
24 | All datas available from the Tumblr API for the current article type. More informations about available fields on the [Tumblr API documentation](https://www.tumblr.com/docs/en/api/v2#posts--retrieve-published-posts).
25 |
26 | ## Audio post
27 |
28 | ### Default audio post
29 |
30 | The default template used for the audio post is described below. The function can be replaced with the `templatesPosts.audio` parameter.
31 |
32 | ```javascript
33 | function audio (datas) => {
34 | /* prettier-ignore */
35 | return `
36 |
37 |
38 | ${datas.player}
39 |
40 |
50 |
51 | `;
52 | }
53 | ```
54 |
55 | ### Custom audio post
56 |
57 | To customize the audio post template, write your own `customAudioPost` function inspired by the `audio` function above.
58 |
59 | ```javascript
60 | function customAudioPost(datas) {
61 | return `
62 |
63 | `;
64 | }
65 | ```
66 |
67 | Next, use the `templatesPosts.audio` parameter to passed the new audio post template function.
68 |
69 | ```javascript
70 | const tumblr = new TumblrBuilder({
71 | templatesPosts: {
72 | audio: customAudioPost
73 | }
74 | });
75 | ```
76 |
77 | ## Chat post
78 |
79 | ### Default chat post
80 |
81 | The default template used for the chat post is described below. The function can be replaced with the `templatesPosts.chat` parameter.
82 |
83 | ```javascript
84 | function chat (datas) => {
85 | /* prettier-ignore */
86 | return `
87 |
102 | `;
103 | }
104 | ```
105 |
106 | ### Custom chat post
107 |
108 | To customize the chat post template, write your own `customChatPost` function inspired by the `chat` function above.
109 |
110 | ```javascript
111 | function customChatPost(datas) {
112 | return `
113 |
114 | `;
115 | }
116 | ```
117 |
118 | Next, use the `templatesPosts.chat` parameter to passed the new chat post template function.
119 |
120 | ```javascript
121 | const tumblr = new TumblrBuilder({
122 | templatesPosts: {
123 | chat: customChatPost
124 | }
125 | });
126 | ```
127 |
128 | ## Link post
129 |
130 | ### Default link post
131 |
132 | The default template used for the link post is described below. The function can be replaced with the `templatesPosts.link` parameter.
133 |
134 | ```javascript
135 | function link (datas) => {
136 | /* prettier-ignore */
137 | return `
138 |
139 |
140 |
${datas.title}
141 | ${datas.description}
142 |
149 |
150 |
151 | `;
152 | }
153 | ```
154 |
155 | ### Custom link post
156 |
157 | To customize the link post template, write your own `customLinkPost` function inspired by the `link` function above.
158 |
159 | ```javascript
160 | function customLinkPost(datas) {
161 | return `
162 |
163 | `;
164 | }
165 | ```
166 |
167 | Next, use the `templatesPosts.link` parameter to passed the new link post template function.
168 |
169 | ```javascript
170 | const tumblr = new TumblrBuilder({
171 | templatesPosts: {
172 | link: customLinkPost
173 | }
174 | });
175 | ```
176 |
177 | ## Photo post
178 |
179 | ### Default photo post
180 |
181 | The default template used for the photo post is described below. The function can be replaced with the `templatesPosts.photo` parameter.
182 |
183 | ```javascript
184 | function photo (datas) => {
185 | const isPhotoset = datas.photos.length > 1
186 | /* prettier-ignore */
187 | return `
188 |
189 |
190 |
191 | ${datas.photos.map(photo => `
192 |
193 |
194 |
195 | `).join('')}
196 |
197 | ${datas.summary
198 | ? `
${datas.summary} `
199 | : ''}
200 |
207 |
208 |
209 | `;
210 | }
211 | ```
212 |
213 | ### Custom photo post
214 |
215 | To customize the photo post template, write your own `customPhotoPost` function inspired by the `photo` function above.
216 |
217 | ```javascript
218 | function customPhotoPost(datas) {
219 | return `
220 |
221 | `;
222 | }
223 | ```
224 |
225 | Next, use the `templatesPosts.photo` parameter to passed the new photo post template function.
226 |
227 | ```javascript
228 | const tumblr = new TumblrBuilder({
229 | templatesPosts: {
230 | photo: customPhotoPost
231 | }
232 | });
233 | ```
234 |
235 | ## Quote post
236 |
237 | ### Default quote post
238 |
239 | The default template used for the quote post is described below. The function can be replaced with the `templatesPosts.quote` parameter.
240 |
241 | ```javascript
242 | function quote (datas) => {
243 | /* prettier-ignore */
244 | return `
245 |
246 |
247 |
248 | ${datas.text}
249 |
250 |
251 |
258 |
259 |
260 | `;
261 | }
262 | ```
263 |
264 | ### Custom quote post
265 |
266 | To customize the quote post template, write your own `customQuotePost` function inspired by the `quote` function above.
267 |
268 | ```javascript
269 | function customQuotePost(datas) {
270 | return `
271 |
272 | `;
273 | }
274 | ```
275 |
276 | Next, use the `templatesPosts.quote` parameter to passed the new quote post parameter function.
277 |
278 | ```javascript
279 | const tumblr = new TumblrBuilder({
280 | templatesPosts: {
281 | quote: customQuotePost
282 | }
283 | });
284 | ```
285 |
286 | ## Text post
287 |
288 | ### Default text post
289 |
290 | The default template used for the text post is described below. The function can be replaced with the `templatesPosts.text` parameter.
291 |
292 | ```javascript
293 | function text (datas) => {
294 | /* prettier-ignore */
295 | return `
296 |
297 |
298 |
${datas.title}
299 | ${datas.body}
300 |
307 |
308 |
309 | `;
310 | }
311 | ```
312 |
313 | ### Custom text post
314 |
315 | To customize the text post template, write your own `customTextPost` function inspired by the `text` function above.
316 |
317 | ```javascript
318 | function customTextPost(datas) {
319 | return `
320 |
321 | `;
322 | }
323 | ```
324 |
325 | Next, use the `templatesPosts.text` parameter to passed the new text post template function.
326 |
327 | ```javascript
328 | const tumblr = new TumblrBuilder({
329 | templatesPosts: {
330 | text: customTextPost
331 | }
332 | });
333 | ```
334 |
335 | ## Video post
336 |
337 | ### Default video post
338 |
339 | The default template used for the video post is described below. The function can be replaced with the `templatesPosts.video` parameter.
340 |
341 | ```javascript
342 | function video (datas) => {
343 | /* prettier-ignore */
344 | return `
345 |
346 |
347 | ${datas.player[2].embed_code}
348 |
349 |
350 |
${datas.summary}
351 |
358 |
359 |
360 | `;
361 | }
362 | ```
363 |
364 | ### Custom video post
365 |
366 | To customize the video post template, write your own `customVideoPost` function inspired by the `video` function above.
367 |
368 | ```javascript
369 | function customVideoPost(datas) {
370 | return `
371 |
372 | `;
373 | }
374 | ```
375 |
376 | Next, use the `templatesPosts.video` parameter to passed the new video post template function.
377 |
378 | ```javascript
379 | const tumblr = new TumblrBuilder({
380 | templatesPosts: {
381 | video: customVideoPost
382 | }
383 | });
384 | ```
385 |
--------------------------------------------------------------------------------
/src/scripts/tumblr-builder.js:
--------------------------------------------------------------------------------
1 | import { getScrollTop, getRandoms } from './utils'
2 |
3 | import './styles'
4 |
5 | /*!
6 | * TumblrBuilder v2.0.0
7 | * (c) 2013-2020 Yoriiis
8 | * Released under the MIT License.
9 | */
10 | export default class TumblrBuilder {
11 | /**
12 | * @param {options}
13 | */
14 | constructor (options) {
15 | const userOptions = options || {}
16 | const defaultOptions = {
17 | element: null,
18 | host: '',
19 | apiKey: '',
20 | limitData: 250,
21 | cache: false,
22 | cacheMethod: 'sessionStorage',
23 | nearBottom: 350,
24 | elementsPerPage: 20,
25 | templatesPages: {},
26 | templatesPosts: {}
27 | }
28 |
29 | // Merge default options with user options
30 | this.options = Object.assign(defaultOptions, userOptions)
31 |
32 | // No configurables params
33 | this.browserStorageKey = 'TumblrBuilderJsonData'
34 | this.endPage = false
35 | this.isLoading = false
36 | this.jsonComplete = false
37 | this.currentPage = 1
38 | this.nbPostPerRequest = 50
39 | this.datas = {}
40 |
41 | this.keysTemplatesPage = ['home', 'post', 'tagged']
42 | this.keysTemplatePosts = ['audio', 'chat', 'link', 'photo', 'quote', 'text', 'video']
43 |
44 | this.onScroll = this.onScroll.bind(this)
45 | this.hashChanged = this.hashChanged.bind(this)
46 | }
47 |
48 | /**
49 | * Function to initialize the app
50 | */
51 | init = async () => {
52 | // Get JSON and push it in cache if option is active and if it's possible
53 | this.datas = await this.getAllDatas()
54 |
55 | if (this.datas.posts.length === 0) {
56 | window[this.options.cacheMethod].removeItem('TumblrBuilderJsonData')
57 | return false
58 | }
59 | this.jsonComplete = true
60 | this.datas.tags = await this.getAllTags()
61 | this.totalPages = Math.ceil(this.datas.totalPosts / this.options.elementsPerPage)
62 |
63 | this.addEvents()
64 | this.templates = await this.getTemplates()
65 |
66 | // Get current route
67 | this.currentRoute = this.getRoute()
68 |
69 | this.hashChanged()
70 |
71 | return this.datas
72 | }
73 |
74 | /**
75 | * Create event listeners
76 | */
77 | addEvents () {
78 | window.addEventListener('scroll', this.onScroll, false)
79 | window.addEventListener('hashchange', this.hashChanged, false)
80 | }
81 |
82 | /**
83 | * On hash changed event listener
84 | *
85 | * @param {Object} e Event listener datas
86 | */
87 | hashChanged = async e => {
88 | const currentTag = this.getHashTagFromRoute()
89 | const currentPostId = this.getPostIdFromRoute()
90 | const pageType = this.getPageType()
91 | let posts
92 |
93 | if (pageType === 'tagged' && currentTag) {
94 | posts = await this.getDatasForTaggedPage(currentTag)
95 | this.buildPage(
96 | this.templates.pages.tagged({
97 | templates: this.templates.posts,
98 | tag: currentTag,
99 | posts
100 | })
101 | )
102 | } else if (pageType === 'post' && currentPostId) {
103 | posts = await this.getDatasForPostPage(currentPostId)
104 | // Back to home page if post page returns more than one article
105 | if (posts.length > 1) {
106 | this.setRoute('')
107 | return
108 | }
109 | const relatedPosts = this.getRelatedPosts({
110 | postId: currentPostId,
111 | tags: posts[0].tags,
112 | limit: 3
113 | })
114 | this.buildPage(
115 | this.templates.pages.post({
116 | templates: this.templates.posts,
117 | posts,
118 | relatedPosts
119 | })
120 | )
121 | } else if (pageType === 'home') {
122 | posts = await this.getDatasForHomePage()
123 | this.buildPage(
124 | this.templates.pages.home({
125 | templates: this.templates.posts,
126 | tags: this.datas.tags,
127 | posts
128 | })
129 | )
130 | }
131 |
132 | // Reset class properties on page changes
133 | this.endPage = false
134 | this.currentPage = 1
135 | window.scrollTo(0, 0)
136 | }
137 |
138 | /**
139 | * On scroll event listener
140 | *
141 | * @param {Object} e Event listener datas
142 | */
143 | onScroll = async e => {
144 | const page = this.getPageType()
145 | if (!this.isLoading && !this.endPage && (page === 'home' || page === 'tagged')) {
146 | if (
147 | getScrollTop() >=
148 | document.body.clientHeight - window.innerHeight - this.options.nearBottom
149 | ) {
150 | this.isLoading = true
151 | if (this.currentPage >= this.totalPages) {
152 | this.endPage = true
153 | } else {
154 | await this.loadNewPage()
155 | this.currentPage++
156 | }
157 | this.isLoading = false
158 | }
159 | }
160 | }
161 |
162 | /**
163 | * Build the page
164 | *
165 | * @param {Array} posts List of posts for the current page
166 | * @param {Array} relatedPosts List of related posts if available
167 | */
168 | buildPage (html) {
169 | this.options.element.innerHTML = html
170 | }
171 |
172 | /**
173 | * Load new page on infinite scroll
174 | * Get next posts from the API
175 | * Inject new content after current posts on the [data-infinite-scroll] element
176 | */
177 | loadNewPage = async () => {
178 | const posts = await this.getPostsByPageNumber(this.currentPage + 1)
179 | const infiniteScrollElement = this.options.element.querySelector('[data-infinite-scroll]')
180 |
181 | if (infiniteScrollElement !== null) {
182 | infiniteScrollElement.insertAdjacentHTML(
183 | 'beforeend',
184 | `${posts.map(post => this.templates.posts[post.type](post)).join('')}`
185 | )
186 | }
187 | }
188 |
189 | /**
190 | * Set the route
191 | *
192 | * @param {String} route New value for the route
193 | */
194 | setRoute (route) {
195 | window.location.hash = route
196 | }
197 |
198 | /**
199 | * Request datas from the API
200 | *
201 | * @param {String} url API url
202 | *
203 | * @returns {Promise} Promise results from the API
204 | */
205 | requestAPI (url) {
206 | return fetch(url).then(response => response.json())
207 | }
208 |
209 | /**
210 | * Check if the Response from the API is valid
211 | *
212 | * @param {Object} datas Response datas
213 | *
214 | * @returns {Boolean} Is a valid Response
215 | */
216 | isValidResponse (datas) {
217 | return datas.meta.status === 200
218 | }
219 |
220 | /**
221 | * Extract a range of posts from browser storage if available
222 | *
223 | * @param {Object} range Start and end index for the range
224 | *
225 | * @returns {Array} List of corresponding posts
226 | */
227 | extractDatasFromLocalDatas (range) {
228 | return this.datas.posts.slice(range.start, range.end + 1)
229 | }
230 |
231 | /**
232 | * Get templates from constructor options templatesPages and templatesPosts
233 | * Else, dynamic import default template for each types
234 | *
235 | * @returns {Object} List of template functions by type
236 | */
237 | getTemplates = async () => {
238 | return {
239 | pages: await this.getTemplatesByType({
240 | keys: this.keysTemplatesPage,
241 | path: './templates/pages/',
242 | custom: this.options.templatesPages
243 | }),
244 | posts: await this.getTemplatesByType({
245 | keys: this.keysTemplatePosts,
246 | path: './templates/posts/',
247 | custom: this.options.templatesPosts
248 | })
249 | }
250 | }
251 |
252 | /**
253 | * Get templates between default and custom templates
254 | * Priority to custom templates
255 | *
256 | * @param {Object}
257 | * @param {String} keys Keys for template pages (home, tagged, post)
258 | * @param {String} path Path to dynamic import default templates
259 | * @param {String} custom Custom templates for the current type
260 | *
261 | * @returns {Object} List of templates for the current type (pages, posts)
262 | */
263 | getTemplatesByType = async ({ keys, path, custom }) => {
264 | const templates = {}
265 |
266 | const customTemplates = keys.filter(name => this.hasProperty(custom, name))
267 | customTemplates.forEach(name => {
268 | templates[name] = custom[name]
269 | })
270 | const defaultTemplates = keys.filter(name => !this.hasProperty(custom, name))
271 |
272 | const requestsDefaultTemplates = defaultTemplates.map(name => import(path + name))
273 |
274 | const responses = await Promise.all(requestsDefaultTemplates)
275 |
276 | responses.forEach((response, index) => {
277 | templates[defaultTemplates[index]] = response.default
278 | })
279 |
280 | return templates
281 | }
282 |
283 | /**
284 | * Build the Tumblr API url
285 | *
286 | * @param {Object}
287 | * @param {(String|Boolean)} id Post id
288 | * @param {Integer} offset Offset for the query
289 | * @param {Integer} limit Limit of results for the query
290 | * @param {String} tag Filter by a unique post tag
291 | *
292 | * @returns {String} API url for the request
293 | */
294 | getAPIUrl ({
295 | id = false,
296 | offset = 0,
297 | limit = this.options.limitData < this.nbPostPerRequest
298 | ? this.options.limitData
299 | : this.nbPostPerRequest,
300 | tag = false
301 | } = {}) {
302 | return `//api.tumblr.com/v2/blog/${this.options.host}/posts/?api_key=${
303 | this.options.apiKey
304 | }&limit=${limit}¬es_info=false&offset=${offset}${tag ? `&tag=${tag}` : ''}${
305 | id ? `&id=${id}` : ''
306 | }`
307 | }
308 |
309 | /**
310 | * Get all datas from the API
311 | * Store datas in browser storage is option is enabled
312 | *
313 | * @returns {Object} Datas from the API
314 | */
315 | getAllDatas = async e => {
316 | let datas
317 |
318 | if (this.options.cache) {
319 | var getDataFromCache = window[this.options.cacheMethod].getItem(this.browserStorageKey)
320 |
321 | if (getDataFromCache === null) {
322 | datas = await this.getDataFromAPI()
323 |
324 | window[this.options.cacheMethod].setItem(
325 | this.browserStorageKey,
326 | JSON.stringify(datas)
327 | )
328 | } else {
329 | datas = JSON.parse(window[this.options.cacheMethod].getItem(this.browserStorageKey))
330 | }
331 | } else {
332 | datas = await this.getDataFromAPI()
333 | window[this.options.cacheMethod].removeItem(this.browserStorageKey)
334 | }
335 |
336 | return datas
337 | }
338 |
339 | /**
340 | * Get datas from the API
341 | *
342 | * @returns {Object} Datas from the API
343 | */
344 | getDataFromAPI = async () => {
345 | // Trigger the first request to get infos about totalPosts and calculate next requests
346 | const datasFirstRequest = await this.requestAPI(this.getAPIUrl())
347 |
348 | if (this.isValidResponse(datasFirstRequest)) {
349 | const totalPosts = datasFirstRequest.response.total_posts
350 | const nbLoop = this.getNumberOfRequiredRequests(totalPosts)
351 | let posts = datasFirstRequest.response.posts
352 |
353 | if (datasFirstRequest.response.posts.length && nbLoop) {
354 | const requests = []
355 |
356 | // Do multiple loop according to nbLoop and options.limitData
357 | for (var i = 0; i < nbLoop; i++) {
358 | requests.push(
359 | this.requestAPI(
360 | this.getAPIUrl({
361 | offset: this.nbPostPerRequest + this.nbPostPerRequest * i
362 | })
363 | )
364 | )
365 | }
366 |
367 | await Promise.all(requests).then(responses => {
368 | responses.forEach(response => {
369 | posts = posts.concat(response.response.posts)
370 | })
371 | })
372 | }
373 | return { totalPosts, posts }
374 | } else {
375 | // If the request failed, return empty results to prevent break the application
376 | return { totalPosts: 0, posts: [] }
377 | }
378 | }
379 |
380 | /**
381 | * Get number of required requests to reach options.limitDatas
382 | * without the first request datas
383 | *
384 | * @param {*} totalPosts Total of posts from the API
385 | *
386 | * @returns {Integer} Number of request to reach options.limitDatas
387 | */
388 | getNumberOfRequiredRequests (totalPosts) {
389 | return totalPosts <= this.options.limitData
390 | ? Math.ceil((totalPosts - this.nbPostPerRequest) / this.nbPostPerRequest)
391 | : Math.ceil((this.options.limitData - this.nbPostPerRequest) / this.nbPostPerRequest)
392 | }
393 |
394 | /**
395 | * Get datas for the homepage (first page only)
396 | * Datas are available in the class property
397 | *
398 | * @returns {Object} Posts datas for the homepage
399 | */
400 | getDatasForHomePage () {
401 | const maxPosts =
402 | this.datas.totalPosts < this.options.elementsPerPage
403 | ? this.datas.totalPosts
404 | : this.options.elementsPerPage
405 |
406 | return this.datas.posts.slice(0, maxPosts)
407 | }
408 |
409 | /**
410 | * Get datas for the tagged page
411 | * Request the API to get posts
412 | *
413 | * @param {String} tag Tag to filter posts
414 | *
415 | * @returns {Object} Posts datas for the tagged page filter by tag
416 | */
417 | getDatasForTaggedPage = async tag => {
418 | const datas = await this.requestAPI(
419 | this.getAPIUrl({
420 | tag,
421 | limit: this.options.elementsPerPage
422 | })
423 | )
424 |
425 | return datas && datas.response ? datas.response.posts : []
426 | }
427 |
428 | /**
429 | * Get datas for the post page
430 | * Request the API to get posts
431 | *
432 | * @param {String} id Id of the current post
433 | *
434 | * @returns {Object} Posts datas for the post page filter by post id
435 | */
436 | getDatasForPostPage = async id => {
437 | const datas = await this.requestAPI(
438 | this.getAPIUrl({
439 | id: id
440 | })
441 | )
442 |
443 | return datas && datas.response ? datas.response.posts : []
444 | }
445 |
446 | /**
447 | * Get datas by page number, use by the infinite scroll
448 | * Request the local datas in priority
449 | * If no datas are available, request the API to get posts
450 | *
451 | * @param {Integer} pageNumber Page number
452 | *
453 | * @returns {Object} Posts datas for the homepage filter by page number
454 | */
455 | getPostsByPageNumber = async pageNumber => {
456 | const range = this.getRange(pageNumber)
457 | const datas = this.extractDatasFromLocalDatas(range)
458 |
459 | // Only home page can used local datas, tagged page need to filter (request directly)
460 | if (datas.length && this.getPageType() === 'home') {
461 | return datas
462 | } else {
463 | const datas = await this.requestAPI(
464 | this.getAPIUrl({
465 | offset: range.start,
466 | limit: this.options.elementsPerPage,
467 | tag: this.getPageType() === 'tagged' ? this.getHashTagFromRoute() : false
468 | })
469 | )
470 | return datas.response.posts
471 | }
472 | }
473 |
474 | /**
475 | * Get the type of the current page according to the hash
476 | *
477 | * @returns {(String|null)} Page type (tagged, post, home)
478 | */
479 | getPageType () {
480 | const hash = this.getRoute()
481 | const defaultRoute = ['', '#', '#_']
482 |
483 | if (hash.indexOf('/tagged/') !== -1) {
484 | return 'tagged'
485 | } else if (hash.indexOf('/post/') !== -1) {
486 | return 'post'
487 | } else if (defaultRoute.includes(hash)) {
488 | return 'home'
489 | } else {
490 | return null
491 | }
492 | }
493 |
494 | /**
495 | * Get range index (start, end) to extract posts from local datas
496 | * according to the current page position and elements per page
497 | *
498 | * @param {Integer} pageNumber Pagz number
499 | *
500 | * @returns {Object} Start and end index for the range
501 | */
502 | getRange (pageNumber) {
503 | const previousPage = pageNumber > 1 ? pageNumber - 1 : pageNumber
504 | return {
505 | start: previousPage * this.options.elementsPerPage,
506 | end: pageNumber * this.options.elementsPerPage - 1
507 | }
508 | }
509 |
510 | /**
511 | * Get hashtag from the route
512 | *
513 | * @returns {String} Current tag
514 | */
515 | getHashTagFromRoute () {
516 | return this.getRoute().split('/tagged/')[1]
517 | }
518 |
519 | /**
520 | * Get post id from the route
521 | *
522 | * @returns {String} Current post id
523 | */
524 | getPostIdFromRoute () {
525 | return this.getRoute().split('/post/')[1]
526 | }
527 |
528 | /**
529 | * Get the current route (hash)
530 | *
531 | * @returns {String} Current hash
532 | */
533 | getRoute () {
534 | return window.location.hash.substr(1)
535 | }
536 |
537 | /**
538 | * Get related posts
539 | * Research is executed is local datas collection only
540 | *
541 | * @param {Object}
542 | * @param {String} postId Pot id
543 | * @param {Integer} limit Limit of related posts results
544 | * @param {Array} tags Tags list of the current post
545 | * @param {Array} ignoreTags Ignore specific list of tags
546 | *
547 | * @returns {Array} List of related posts associated to the current post
548 | */
549 | getRelatedPosts ({ postId, limit = 3, tags = [], ignoreTags = [] }) {
550 | // Get all tags from current post without ignore tags
551 | const tagsSource = tags.filter(tag => !ignoreTags.includes(tag))
552 |
553 | // Get related posts
554 | const relatedPosts = this.datas.posts.filter(post =>
555 | post.tags.some(tag => tagsSource.includes(tag) && post.id_string !== postId)
556 | )
557 |
558 | // Get random keys
559 | const randomKeys = getRandoms(limit, 0, relatedPosts.length - 1) || []
560 |
561 | if (relatedPosts.length < limit) {
562 | return relatedPosts
563 | } else {
564 | // Extract random related posts
565 | return randomKeys.map(key => relatedPosts[key])
566 | }
567 | }
568 |
569 | /**
570 | * Get all tags from local datas
571 | * Tags are sorted alphabetical
572 | * Tags are search only from local datas according to options.limitData posts
573 | *
574 | * @returns {Array} List of tags
575 | */
576 | getAllTags () {
577 | return this.jsonComplete
578 | ? this.datas.posts
579 | .filter(post => post.tags.length)
580 | .flatMap(post => post.tags)
581 | .map(tag => tag.toLowerCase())
582 | .filter((elem, pos, arr) => {
583 | return arr.indexOf(elem) === pos
584 | })
585 | .sort()
586 | : []
587 | }
588 |
589 | /**
590 | * Custom hasOwnProperty to prevent security issue (ESLint no-prototype-builtins)
591 | *
592 | * @param {Object} objectSource Object source to search the key
593 | * @param {String} key The key to search inside the object
594 | *
595 | * @returns {Boolean} Is the key present inside the object
596 | */
597 | hasProperty (objectSource, key) {
598 | return Object.prototype.hasOwnProperty.call(objectSource, key)
599 | }
600 | }
601 |
--------------------------------------------------------------------------------