├── .all-contributorsrc
├── .editorconfig
├── .gitignore
├── .npmignore
├── .versionrc.json
├── CHANGELOG.md
├── CODE-OF-CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── contexts
├── DataManagerContext.d.ts
├── DataManagerContext.d.ts.map
├── DataManagerContext.js
└── DataManagerContext.js.map
├── external-types
└── strapi.d.ts
├── hooks
├── useDataManager.d.ts
├── useDataManager.d.ts.map
├── useDataManager.js
└── useDataManager.js.map
├── package.json
├── public
└── assets
│ ├── example1.png
│ ├── example2.png
│ ├── folder.png
│ └── settings.png
├── src
├── admin
│ ├── main
│ │ ├── clone
│ │ │ ├── clone-badge.js
│ │ │ └── index.js
│ │ ├── index.js
│ │ └── preview-context
│ │ │ └── index.js
│ └── src
│ │ ├── components
│ │ ├── CodeBlock
│ │ │ └── index.js
│ │ └── Text
│ │ │ └── index.js
│ │ ├── containers
│ │ ├── Initializer
│ │ │ └── index.js
│ │ └── SettingsPage
│ │ │ ├── Divider.js
│ │ │ ├── SectionTitleWrapper.js
│ │ │ ├── Wrapper.js
│ │ │ ├── index.js
│ │ │ ├── init.js
│ │ │ └── reducer.js
│ │ ├── index.js
│ │ ├── lifecycles.js
│ │ ├── pluginId.js
│ │ ├── translations
│ │ ├── ar.json
│ │ ├── cs.json
│ │ ├── de.json
│ │ ├── en.json
│ │ ├── es.json
│ │ ├── fr.json
│ │ ├── id.json
│ │ ├── index.js
│ │ ├── it.json
│ │ ├── ko.json
│ │ ├── ms.json
│ │ ├── nl.json
│ │ ├── pl.json
│ │ ├── pt-BR.json
│ │ ├── pt.json
│ │ ├── ru.json
│ │ ├── sk.json
│ │ ├── th.json
│ │ ├── tr.json
│ │ ├── uk.json
│ │ ├── vi.json
│ │ ├── zh-Hans.json
│ │ └── zh.json
│ │ └── utils
│ │ ├── getRequestUrl.js
│ │ ├── getTrad.js
│ │ └── index.js
├── config
│ ├── functions
│ │ └── bootstrap.js
│ └── routes.json
├── contexts
│ └── DataManagerContext.js
├── controllers
│ ├── preview.ts
│ └── validations
│ │ └── settings.ts
├── hooks
│ └── useDataManager.js
└── services
│ ├── preview-error.ts
│ └── preview.ts
├── strapi-files
└── v3.6.x
│ ├── README.md
│ └── extensions
│ └── content-manager
│ └── admin
│ └── src
│ ├── components
│ └── CustomTable
│ │ ├── Row
│ │ └── index.js
│ │ └── index.js
│ └── containers
│ ├── EditView
│ └── Header
│ │ ├── index.js
│ │ └── utils
│ │ └── connect.js
│ └── ListView
│ └── index.js
├── tsconfig.json
└── yarn.lock
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "README.md"
4 | ],
5 | "imageSize": 100,
6 | "commit": false,
7 | "contributors": [
8 | {
9 | "login": "polcode-dzielonka",
10 | "name": "polcode-dzielonka",
11 | "avatar_url": "https://avatars.githubusercontent.com/u/70939074?v=4",
12 | "profile": "https://github.com/polcode-dzielonka",
13 | "contributions": [
14 | "code"
15 | ]
16 | },
17 | {
18 | "login": "pr0gr8mm3r",
19 | "name": "p_0g_8mm3_",
20 | "avatar_url": "https://avatars.githubusercontent.com/u/37022952?v=4",
21 | "profile": "https://github.com/pr0gr8mm3r",
22 | "contributions": [
23 | "code"
24 | ]
25 | },
26 | {
27 | "login": "armaaar",
28 | "name": "Ahmed Rafik Ibrahim",
29 | "avatar_url": "https://avatars.githubusercontent.com/u/25823409?v=4",
30 | "profile": "https://ahmedrafik.me",
31 | "contributions": [
32 | "code"
33 | ]
34 | }
35 | ],
36 | "contributorsPerLine": 7,
37 | "projectName": "strapi-plugin-preview-content",
38 | "projectOwner": "danestves",
39 | "repoType": "github",
40 | "repoHost": "https://github.com",
41 | "skipCi": true
42 | }
43 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = false
6 | indent_style = space
7 | indent_size = 2
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ############################
2 | # OS X
3 | ############################
4 |
5 | .DS_Store
6 | .AppleDouble
7 | .LSOverride
8 | Icon
9 | .Spotlight-V100
10 | .Trashes
11 | ._*
12 |
13 |
14 | ############################
15 | # Linux
16 | ############################
17 |
18 | *~
19 |
20 |
21 | ############################
22 | # Windows
23 | ############################
24 |
25 | Thumbs.db
26 | ehthumbs.db
27 | Desktop.ini
28 | $RECYCLE.BIN/
29 | *.cab
30 | *.msi
31 | *.msm
32 | *.msp
33 |
34 |
35 | ############################
36 | # Packages
37 | ############################
38 |
39 | *.7z
40 | *.csv
41 | *.dat
42 | *.dmg
43 | *.gz
44 | *.iso
45 | *.jar
46 | *.rar
47 | *.tar
48 | *.zip
49 | *.com
50 | *.class
51 | *.dll
52 | *.exe
53 | *.o
54 | *.seed
55 | *.so
56 | *.swo
57 | *.swp
58 | *.swn
59 | *.swm
60 | *.out
61 | *.pid
62 |
63 |
64 | ############################
65 | # Logs and databases
66 | ############################
67 |
68 | .tmp
69 | *.log
70 | *.sql
71 | *.sqlite
72 | *.sqlite3
73 |
74 |
75 | ############################
76 | # Misc.
77 | ############################
78 |
79 | *#
80 | ssl
81 | .idea
82 | nbproject
83 | public/uploads/*
84 | !public/uploads/.gitkeep
85 |
86 | ############################
87 | # Node.js
88 | ############################
89 |
90 | lib-cov
91 | lcov.info
92 | pids
93 | logs
94 | results
95 | node_modules
96 | .node_history
97 | yarn.lock
98 | package-lock.json
99 |
100 |
101 | ############################
102 | # Tests
103 | ############################
104 |
105 | testApp
106 | coverage
107 |
108 | ############################
109 | # Strapi
110 | ############################
111 |
112 | .env
113 | exports
114 | .cache
115 | build
116 |
117 |
118 | ############################
119 | # Strapi PLUGIN DIST
120 | ############################
121 | /config
122 | /controllers
123 | /middlewares
124 | /models
125 | /services
126 | /admin
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .*.swp
2 | ._*
3 | .DS_Store
4 | .git
5 | .hg
6 | .npmrc
7 | .lock-wscript
8 | .svn
9 | .wafpickle-*
10 | config.gypi
11 | CVS
12 | npm-debug.log
13 | src
14 | __tests__
15 | coverage
16 | tsconfig.json
17 | dist
18 | external-types
--------------------------------------------------------------------------------
/.versionrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "types": [
3 | { "type": "feat", "section": "Features" },
4 | { "type": "fix", "section": "Bug Fixes" },
5 | { "type": "chore", "hidden": true },
6 | { "type": "docs", "hidden": true },
7 | { "type": "style", "hidden": true },
8 | { "type": "refactor", "hidden": true },
9 | { "type": "perf", "hidden": true },
10 | { "type": "test", "hidden": true }
11 | ],
12 | "commitUrlFormat": "https://github.com/danestves/strapi-plugin-preview-content/commits{{hash}}",
13 | "compareUrlFormat": "https://github.com/danestves/strapi-plugin-preview-content/compare/{{previousTag}}...{{currentTag}}"
14 | }
15 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | #### 1.3.1 (2021-06-23)
2 |
3 | ##### Documentation Changes
4 |
5 | * update .all-contributorsrc [skip ci] ([07a08ab5](https://github.com/danestves/strapi-plugin-preview-content/commit/07a08ab591e3e14bb26463ae778adc0ea1d14f99))
6 | * update README.md [skip ci] ([513d5109](https://github.com/danestves/strapi-plugin-preview-content/commit/513d510992f1236180af69c16f789afb11d96efa))
7 |
8 | ##### Bug Fixes
9 |
10 | * pass apiID to CustomTable to show preview button in list view ([9e358202](https://github.com/danestves/strapi-plugin-preview-content/commit/9e358202ed8f13fd4c1ebab443cc9a6f0ee07723))
11 | * **docs:** deleting custom code from changelog ([4136aeb5](https://github.com/danestves/strapi-plugin-preview-content/commit/4136aeb527ffab51a062b6a1bdde4c401bd6bed5))
12 |
13 | ### 1.3.0 (2021-06-02)
14 |
15 | ##### New Features
16 |
17 | - **docs:** using generate instead of standard version ([a17a3b54](https://github.com/danestves/strapi-plugin-preview-content/commit/a17a3b54e1db28036f7ca7035204db53647ff632))
18 |
19 | ### [1.2.1](https://github.com/danestves/strapi-plugin-preview-content/compare/v1.2.0...v1.2.1) (2021-06-02)
20 |
21 | ## [1.2.0](https://github.com/danestves/strapi-plugin-preview-content/compare/v1.1.1...v1.2.0) (2021-06-02)
22 |
23 | ### [1.1.1](https://github.com/danestves/strapi-plugin-preview-content/compare/v1.1.0...v1.1.1) (2021-05-05)
24 |
25 | ## [1.1.0](https://github.com/danestves/strapi-plugin-preview-content/compare/v1.1.0-alpha.0...v1.1.0) (2021-05-05)
26 |
27 | ## [1.1.0-alpha.0](https://github.com/danestves/strapi-plugin-preview-content/compare/v1.0.3-alpha.2...v1.1.0-alpha.0) (2021-05-05)
28 |
29 | ### Features
30 |
31 | - **config:** putting correct config for plugin ([ab54af5](https://github.com/danestves/strapi-plugin-preview-content/commitsab54af587f13340c64d8befcb1c0a0584365af1b))
32 |
33 | ### [1.0.3-alpha.2](https://github.com/danestves/strapi-plugin-preview-content/compare/v1.0.3-alpha.1...v1.0.3-alpha.2) (2021-05-05)
34 |
35 | ### Bug Fixes
36 |
37 | - **dependencies:** putting react dependency ([1fdf315](https://github.com/danestves/strapi-plugin-preview-content/commits1fdf31566ca87c5fb0555bb0782907fbfb819647))
38 |
39 | ### [1.0.3-alpha.1](https://github.com/danestves/strapi-plugin-preview-content/compare/v1.0.3-alpha.0...v1.0.3-alpha.1) (2021-05-05)
40 |
41 | ### [1.0.3-alpha.0](https://github.com/danestves/strapi-plugin-preview-content/compare/v1.0.2...v1.0.3-alpha.0) (2021-05-05)
42 |
43 | ### Bug Fixes
44 |
45 | - **compatibility:** files for strapi 3.6.1 ([aadb3a2](https://github.com/danestves/strapi-plugin-preview-content/commitsaadb3a2004c01a07bf5c74d6a0bb6c1a8bb24be5))
46 | - **compatibility:** files for strapi 3.6.1 ([0183d29](https://github.com/danestves/strapi-plugin-preview-content/commits0183d298f7051f1cca96a512a19df6f5e8a747eb))
47 |
48 | #### 1.0.2 (2021-05-04)
49 |
50 | ##### Documentation Changes
51 |
52 | - update .all-contributorsrc [skip ci] ([be059e71](https://github.com/danestves/strapi-plugin-preview-content/commit/be059e71d2c131c17d775f3ee7cde3cbc9eaf8c5))
53 | - update README.md [skip ci] ([9511baed](https://github.com/danestves/strapi-plugin-preview-content/commit/9511baed6ec5ac269a1a02ecdc39608fcf3a69c7))
54 | - create .all-contributorsrc [skip ci] ([b3dc5499](https://github.com/danestves/strapi-plugin-preview-content/commit/b3dc54998e2dffb61245fcdaf8aee2f67bcf8bda))
55 | - update README.md [skip ci] ([14beaa03](https://github.com/danestves/strapi-plugin-preview-content/commit/14beaa034ff848c1d7f74acd3da69d1fb804603e))
56 |
57 | #### 1.0.1 (2021-05-04)
58 |
59 | ##### New Features
60 |
61 | - **docs:** adding supported ([88ccaa5c](https://github.com/danestves/strapi-plugin-preview-content/commit/88ccaa5c35d4635d739445b765e6569ffefe812c))
62 |
63 | ## 1.0.0 (2021-05-04)
64 |
65 | ##### New Features
66 |
67 | - **docs:** adding changelog generator ([6681560b](https://github.com/danestves/strapi-plugin-preview-content/commit/6681560ba582f211f055581c37662c13492f0f24))
68 | - add inspired strapi-molecules ([4d63f6cf](https://github.com/danestves/strapi-plugin-preview-content/commit/4d63f6cff197edd69a7864869db97bf5db595786))
69 | - min width for preview button ([e07c7778](https://github.com/danestves/strapi-plugin-preview-content/commit/e07c77781fbbf348a0663e86f81d87b042262841))
70 | - updating metadata from package.json ([8112bb1a](https://github.com/danestves/strapi-plugin-preview-content/commit/8112bb1ae2cf1dc69f5ef5c059732f931298718e))
71 | - version 0.2.7 docs ([0e1d7eec](https://github.com/danestves/strapi-plugin-preview-content/commit/0e1d7eecb0becc76ace6cd04256e1b73fdd57ec7))
72 | - version 0.2.6 documetation ([87bd3f7d](https://github.com/danestves/strapi-plugin-preview-content/commit/87bd3f7da5982c50745977a5e9d85ded479f4bb0))
73 | - working with plugins settings ([272e3822](https://github.com/danestves/strapi-plugin-preview-content/commit/272e3822f100d294afc8c71845e33666b64438d5))
74 | - working with plugins settings ([04539d1f](https://github.com/danestves/strapi-plugin-preview-content/commit/04539d1fda9b13e8840be102927c9acd17e1016f))
75 | - documentation changelog ([22c2e837](https://github.com/danestves/strapi-plugin-preview-content/commit/22c2e837cceaa2bba578e22b5d54890efef13193))
76 | - working clone button ([4a081b75](https://github.com/danestves/strapi-plugin-preview-content/commit/4a081b755efbdb70d167fcf0e7292003c62c61c0))
77 | - working clone button ([6149cf54](https://github.com/danestves/strapi-plugin-preview-content/commit/6149cf54f43300b268a079f2518aff003c5ca8a5))
78 | - change notifications settings for strapi ([e066cace](https://github.com/danestves/strapi-plugin-preview-content/commit/e066cace928975850d97178676ee1fb9d29f7088))
79 | - console.log for all entity ([84dc8b05](https://github.com/danestves/strapi-plugin-preview-content/commit/84dc8b05d2c42bf79e49b70573ca71263ae0fdeb))
80 | - console log to see output data ([a833197a](https://github.com/danestves/strapi-plugin-preview-content/commit/a833197ae4bc31fa11dfd0e5dc838dc031a54f4a))
81 | - getting preview url from database ([9a2176f8](https://github.com/danestves/strapi-plugin-preview-content/commit/9a2176f8ec01384578d42818aff8f121834ad1f3))
82 | - missing translations ([c2e25e22](https://github.com/danestves/strapi-plugin-preview-content/commit/c2e25e22c6692a17cc6fd2a595313e29286005ff))
83 | - add support for models folder ([0ddb702e](https://github.com/danestves/strapi-plugin-preview-content/commit/0ddb702ef399a226f31e98e129c9bb760f49ef8b))
84 | - strapi 3.4.0 ([669f5358](https://github.com/danestves/strapi-plugin-preview-content/commit/669f5358fc0fa78e3a3494d98481c89d4de92dd6))
85 | - 1.0.2 ([a1a3a9c9](https://github.com/danestves/strapi-plugin-preview-content/commit/a1a3a9c9f6deddbd934c2f192832c884e0317c31))
86 | - all with only javascript ([8b099b45](https://github.com/danestves/strapi-plugin-preview-content/commit/8b099b45a2754437a6f9bcd1c0ccb7ec45afafb1))
87 | - first commit ([9a1ce22b](https://github.com/danestves/strapi-plugin-preview-content/commit/9a1ce22ba26a8ae2526cf2c64b8fb1f8f6e2cc2d))
88 |
89 | ##### Bug Fixes
90 |
91 | - images not showing in npmjs ([90e0d696](https://github.com/danestves/strapi-plugin-preview-content/commit/90e0d69685cc202baa0b890f2b45df1aef2a38ca))
92 | - links in docs ([f336b35b](https://github.com/danestves/strapi-plugin-preview-content/commit/f336b35b9005cd42c290631d09c595711983fd16))
93 | - diff inside README ([b0861967](https://github.com/danestves/strapi-plugin-preview-content/commit/b08619678d0fc3ce9247fc3b1573875e0aac7f31))
94 | - update or create settings ([be947f5d](https://github.com/danestves/strapi-plugin-preview-content/commit/be947f5d2cdebe4ba1fd36e42b61c0179a59372d))
95 | - lodash dependency in service ([4bf38ef6](https://github.com/danestves/strapi-plugin-preview-content/commit/4bf38ef68a6fbcd23dce51175012ab8b935ea3ec))
96 | - packages not needed ([1200b81e](https://github.com/danestves/strapi-plugin-preview-content/commit/1200b81ec46ca5563326dcb508ad39ca00fe6af4))
97 | - scripts and gitignore ([aaa5bed2](https://github.com/danestves/strapi-plugin-preview-content/commit/aaa5bed212c73c9f6d1998771e8918a93157a222))
98 | - typescript routes ([d51b9f0a](https://github.com/danestves/strapi-plugin-preview-content/commit/d51b9f0a1fd34bb122e9630a5270f2b06f68d451))
99 | - empty strapi package ([9ff05cf4](https://github.com/danestves/strapi-plugin-preview-content/commit/9ff05cf454b2ba4a28aacc537210e00c2b52bb5d))
100 |
--------------------------------------------------------------------------------
/CODE-OF-CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | estevesd8@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Setting up your dev environment
2 |
3 | ## Step 1: Make a clean strapi installation
4 |
5 | More details on this can be found in [strapi's documentation](https://strapi.io/documentation/developer-docs/latest/getting-started/quick-start.html)
6 |
7 | Add the overriding files to your `extensions` folder, as described in the README. Because the plugin will not be loaded as a package, you will have to replace the following imports:
8 |
9 | | File | Original line | New line |
10 | |---------------------------------------------|--------------------------------------------------------------------|---------------------------------------------------------------------------------------------|
11 | | containers/EditView/Header/index.js | `import { usePreview } from "strapi-plugin-preview-content";` | `import { usePreview } from "/plugins/preview-content/admin/main/preview-context";` |
12 | | containers/EditView/Header/utils/connect.js | `import { PreviewProvider } from "strapi-plugin-preview-content";` | `import { PreviewProvider } from "/plugins/preview-content/admin/main/preview-context";` |
13 |
14 | ## Step 2: Fork this repo
15 |
16 | Hit fork on GitHub so have your own copy of this repository.
17 |
18 | ## Step 3: Clone your fork of the repository into the project
19 |
20 | Git clone your fork into `plugins/preview-content` of your development strapi project. By default, git names the folder `strapi-plugin-preview-content`, so rename it to `preview-content` (or specify the folder name while cloning).
21 |
22 | ## Step 4: Set up the plugin
23 |
24 | To install its dependencies, head into the folder:
25 |
26 | ```
27 | cd plugins/preview-content
28 | ```
29 |
30 | and install:
31 |
32 | ```
33 | yarn install
34 | ```
35 |
36 | ## Step 5: Run your project with hot reload
37 |
38 | In the `plugins/preview-content` folder run:
39 |
40 | ```
41 | yarn build -w
42 | ```
43 |
44 | This will start the typescript compilation with automatic relaoding.
45 |
46 | After the first successful compilation, run strapi in the root of your project:
47 |
48 | ```
49 | yarn develop --watch-admin
50 | ```
51 |
52 | # Development
53 |
54 | Be sure to only edit the files in `/plugins/preview-content/src`, as the other files are auto-generated and therefore not tracked.
55 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Daniel Esteves & Strapi Solutions.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so,
8 | subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Strapi - Preview Content Plugin
2 |
3 | [](#contributors-)
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | > **This plugin has been inspired by [VirtusLab](https://github.com/VirtusLab/) - [strapi-molecules/strapi-plugin-preview](https://github.com/VirtusLab/strapi-molecules/tree/master/packages/strapi-plugin-preview)**
16 |
17 | A plugin for [Strapi Headless CMS](https://github.com/strapi/strapi) that provides content preview to integrate with any frontend:
18 |
19 | This is what the plugin looks like when editing content:
20 |
21 |
22 |
23 | This is what the plugin looks like when we are in list view:
24 |
25 |
26 |
27 | ### 🖐 Requirements
28 |
29 | Complete installation requirements are exact same as for Strapi itself and can be found in the documentation under Installation Requirements.
30 |
31 | **Supported Strapi versions**:
32 |
33 | - Strapi v3.6.x only version v1.0.0
34 | - Strapi v3.4.x only version v0.2.76
35 |
36 | **We recommend always using the latest version of Strapi to start your new projects**.
37 |
38 | ### ⏳ Installation
39 |
40 | ```bash
41 | # npm
42 | npm install strapi-plugin-preview-content
43 |
44 | # yarn
45 | yarn add strapi-plugin-preview-content
46 | ```
47 |
48 | ### 📁 Copy required files
49 |
50 | Inside `strapi-files` we have a list of folders with the Strapi version, enter to the version that correspond with your installation, and you will see this files
51 |
52 |
53 |
54 | Copy the folder named `content-manager` inside your `/extensions` folder
55 |
56 | ### 👍 Active content type as previewable
57 |
58 | To enable content type to be previewable and see preview, or clone entry, you've to add option previewable to true in a configuration json file (`*.settings.json`):
59 |
60 | ```diff
61 | {
62 | "pluginOptions": {
63 | + "preview-content": {
64 | + "previewable": true
65 | + }
66 | }
67 | }
68 | ```
69 |
70 | ### 🚀 Run your project
71 |
72 | After successful installation you've to build a fresh package that includes plugin UI. To archive that simply use:
73 |
74 | ```bash
75 | # npm
76 | npm run build && npm run develop
77 |
78 | # yarn
79 | yarn build && yarn develop
80 | ```
81 |
82 | ### ✏️ Usage
83 |
84 | Go to Settings > Preview Content
85 |
86 |
87 |
88 | #### Base url
89 |
90 | Here you can configure the base url of your frontend. This is a seperate field because it will be different depending on whether your project is running locally (e.g. `http://localhost:3000`) oder in production (e.g. `https://your-site.com`).
91 |
92 | #### Default preview url
93 |
94 | This is the default preview url the plugin uses for when your content type doesn't have its own url defined. For the default preview url there are three parameters provided by this plugin:
95 |
96 | | Parameter | Description |
97 | |-----------------|-----------------------------------|
98 | | `:baseUrl` | See section above for explanation |
99 | | `:contentType` | The content type to query |
100 | | `:id` | The id of content to query |
101 |
102 | For example in NextJS you can make use of [serverless functions](https://nextjs.org/docs/api-routes/introduction) to make an URL like this:
103 |
104 | `:baseUrl/api/preview/:contentType/:id`
105 |
106 | With your base url being `http://localhost:3000`, for example.
107 |
108 | And put the logic there to render content.
109 |
110 | #### Custom preview url
111 |
112 | You can also provide a custom url per content type in its `*.settings.json`. To do so, add this line to its options:
113 |
114 | ```diff
115 | {
116 | "pluginOptions": {
117 | "preview-content": {
118 | "previewable": true,
119 | + "url": ":baseUrl/your-path/:contentType/:id?a-custom-param=true"
120 | }
121 | }
122 | }
123 | ```
124 |
125 | Here you can see how the base url comes in handy: You cannot change the model in production, but you can change the base url in the settings.
126 |
127 | #### Adding data to the url
128 |
129 | To tell the plugin to allow injection of an entry's data add the following to your model's `*.settings.json`:
130 |
131 | ```diff
132 | {
133 | "pluginOptions": {
134 | "preview-content": {
135 | "previewable": true,
136 | + "url": ":baseUrl/your-path/:contentType/<%= slug %>?a-custom-param=<%= title %>",
137 | + "usesValuesInUrl": true
138 | }
139 | }
140 | }
141 | ```
142 |
143 | The plugin will now replace `<%= slug %>` and `<%= title %>` with the correct values. What you put in the brackets (`<%=` `%>`) has to be a name of the an attribute.
144 | The syntax used here is [lodash's template syntax](https://lodash.com/docs/4.17.15#template).
145 |
146 | ### ✨ Features
147 |
148 | ### 🛠 API
149 |
150 | There are some functions that make all of this posible
151 |
152 | | function | route | method | description | notes |
153 | | --------------- | ------------------------------- | ------ | ------------------------------------------------------------- | ------------------------------------------------------------------------------ |
154 | | `isPreviewable` | `/is-previewable/:contentType` | `GET` | Get if preview services is active in the current current type | |
155 | | `findOne` | `/:contentType/:id` | `GET` | Find a content type by id | You may want to active this route as public to make request from your frontend |
156 | | `getPreviewUrl` | `/preview-url/:contentType/:id` | `GET` | Get preview url of content type | |
157 |
158 | ## Contributing
159 |
160 | Feel free to fork and make a Pull Request to this plugin project. All the input is warmly welcome! To learn how, head [here](/CONTRIBUTING.md).
161 |
162 | ## Community support
163 |
164 | For general help using Strapi, please refer to [the official Strapi documentation](https://strapi.io/documentation/). For additional help, you can use one of these channels to ask a question:
165 |
166 | - [Slack](http://slack.strapi.io) We're present on official Strapi slack workspace. Look for @danestves and DM.
167 | - [GitHub](https://github.com/danestves/strapi-plugin-preview-content/issues) (Bug reports, Contributions, Questions and Discussions)
168 |
169 | ## License
170 |
171 | [MIT License](LICENSE.md) Copyright (c) 2020 [Daniel Esteves](https://danestves.com/) & [Strapi Solutions](https://strapi.io/).
172 |
173 | ## Contributors ✨
174 |
175 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
176 |
177 |
178 |
179 |
180 |
187 |
188 |
189 |
190 |
191 |
192 |
193 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
194 |
--------------------------------------------------------------------------------
/contexts/DataManagerContext.d.ts:
--------------------------------------------------------------------------------
1 | export default DataManagerContext;
2 | declare const DataManagerContext: import("react").Context;
3 | //# sourceMappingURL=DataManagerContext.d.ts.map
--------------------------------------------------------------------------------
/contexts/DataManagerContext.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"DataManagerContext.d.ts","sourceRoot":"","sources":["../src/contexts/DataManagerContext.js"],"names":[],"mappings":";AAEA,+DAA2C"}
--------------------------------------------------------------------------------
/contexts/DataManagerContext.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | Object.defineProperty(exports, "__esModule", { value: true });
3 | var react_1 = require("react");
4 | var DataManagerContext = react_1.createContext();
5 | exports.default = DataManagerContext;
6 | //# sourceMappingURL=DataManagerContext.js.map
--------------------------------------------------------------------------------
/contexts/DataManagerContext.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"DataManagerContext.js","sourceRoot":"","sources":["../src/contexts/DataManagerContext.js"],"names":[],"mappings":";;AAAA,+BAAsC;AAEtC,IAAM,kBAAkB,GAAG,qBAAa,EAAE,CAAC;AAE3C,kBAAe,kBAAkB,CAAC"}
--------------------------------------------------------------------------------
/external-types/strapi.d.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
3 | declare global {
4 | module NodeJS {
5 | interface Global {
6 | strapi: any;
7 | }
8 | }
9 | }
10 |
11 | declare module "strapi-utils";
12 |
--------------------------------------------------------------------------------
/hooks/useDataManager.d.ts:
--------------------------------------------------------------------------------
1 | export default useDataManager;
2 | declare function useDataManager(): any;
3 | //# sourceMappingURL=useDataManager.d.ts.map
--------------------------------------------------------------------------------
/hooks/useDataManager.d.ts.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"useDataManager.d.ts","sourceRoot":"","sources":["../src/hooks/useDataManager.js"],"names":[],"mappings":";AAGA,uCAA2D"}
--------------------------------------------------------------------------------
/hooks/useDataManager.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __importDefault = (this && this.__importDefault) || function (mod) {
3 | return (mod && mod.__esModule) ? mod : { "default": mod };
4 | };
5 | Object.defineProperty(exports, "__esModule", { value: true });
6 | var react_1 = require("react");
7 | var DataManagerContext_1 = __importDefault(require("../contexts/DataManagerContext"));
8 | var useDataManager = function () { return react_1.useContext(DataManagerContext_1.default); };
9 | exports.default = useDataManager;
10 | //# sourceMappingURL=useDataManager.js.map
--------------------------------------------------------------------------------
/hooks/useDataManager.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"useDataManager.js","sourceRoot":"","sources":["../src/hooks/useDataManager.js"],"names":[],"mappings":";;;;;AAAA,+BAAmC;AACnC,sFAAgE;AAEhE,IAAM,cAAc,GAAG,cAAM,OAAA,kBAAU,CAAC,4BAAkB,CAAC,EAA9B,CAA8B,CAAC;AAE5D,kBAAe,cAAc,CAAC"}
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "strapi-plugin-preview-content",
3 | "version": "1.3.1",
4 | "description": "Preview your content with custom URL.",
5 | "main": "admin/main/index.js",
6 | "strapi": {
7 | "name": "Preview Content",
8 | "icon": "link",
9 | "description": "Preview your content with custom URL."
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/danestves/strapi-plugin-preview-content.git"
14 | },
15 | "files": [
16 | "admin",
17 | "config",
18 | "controllers",
19 | "services"
20 | ],
21 | "scripts": {
22 | "build": "tsc",
23 | "prepublishOnly": "yarn build",
24 | "release:major": "changelog -M && git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version major && git push origin && git push origin --tags",
25 | "release:minor": "changelog -m && git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version minor && git push origin && git push origin --tags",
26 | "release:patch": "changelog -p && git add CHANGELOG.md && git commit -m 'updated CHANGELOG.md' && npm version patch && git push origin && git push origin --tags"
27 | },
28 | "dependencies": {
29 | "strapi-helper-plugin": "^3.6.3",
30 | "strapi-utils": "^3.6.3"
31 | },
32 | "author": {
33 | "name": "Daniel Esteves",
34 | "email": "estevesd8@gmail.com",
35 | "url": "https://danestves.com"
36 | },
37 | "maintainers": [
38 | {
39 | "name": "Daniel Esteves",
40 | "email": "estevesd8@gmail.com",
41 | "url": "https://danestves.com"
42 | }
43 | ],
44 | "engines": {
45 | "node": ">=10.16.0 <=14.x.x",
46 | "npm": ">=6.0.0"
47 | },
48 | "license": "MIT",
49 | "devDependencies": {
50 | "@types/koa": "^2.13.3",
51 | "@types/lodash": "^4.14.170",
52 | "@types/node": "^15.6.2",
53 | "generate-changelog": "^1.8.0",
54 | "typescript": "^4.3.2"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/public/assets/example1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danestves/strapi-plugin-preview-content/c0d5d2416452bd74a6837d097c85ee4492b89720/public/assets/example1.png
--------------------------------------------------------------------------------
/public/assets/example2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danestves/strapi-plugin-preview-content/c0d5d2416452bd74a6837d097c85ee4492b89720/public/assets/example2.png
--------------------------------------------------------------------------------
/public/assets/folder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danestves/strapi-plugin-preview-content/c0d5d2416452bd74a6837d097c85ee4492b89720/public/assets/folder.png
--------------------------------------------------------------------------------
/public/assets/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/danestves/strapi-plugin-preview-content/c0d5d2416452bd74a6837d097c85ee4492b89720/public/assets/settings.png
--------------------------------------------------------------------------------
/src/admin/main/clone/clone-badge.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import React from "react";
3 | import { useIntl } from "react-intl";
4 | import styled from "styled-components";
5 |
6 | import { Text } from "@buffetjs/core";
7 |
8 | const Wrapper = styled.div`
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | width: fit-content;
13 | padding: 1rem;
14 | border-radius: 0.2rem;
15 | height: 2.5rem;
16 | ${({ theme }) => `
17 | border: 1px solid #82b3c9;
18 | background-color: #e1f5fe;
19 | ${Text} {
20 | font-weight: ${theme.main.fontWeights.bold};
21 | }
22 | `};
23 | `;
24 |
25 | export const CloneBadge = ({ isClone }) => {
26 | const { formatMessage } = useIntl();
27 |
28 | if (!isClone) {
29 | return "-";
30 | }
31 |
32 | return (
33 |
34 |
35 | {formatMessage({
36 | id: "preview.containers.List.clone",
37 | })}
38 |
39 |
40 | );
41 | };
42 |
43 | CloneBadge.propTypes = {
44 | isClone: PropTypes.bool.isRequired,
45 | };
46 |
--------------------------------------------------------------------------------
/src/admin/main/clone/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { CloneBadge } from "./clone-badge";
3 |
4 | export const shouldAddCloneHeader = (layout) => {
5 | const { options, attributes } = layout.contentType.schema;
6 |
7 | return options.previewable && !!attributes.cloneOf;
8 | };
9 |
10 | export const getCloneHeader = (formatMessage) => ({
11 | label: formatMessage({ id: "preview.containers.List.state" }),
12 | name: "cloneOf",
13 | searchable: false,
14 | sortable: true,
15 | cellFormatter: (cellData) => ,
16 | });
17 |
--------------------------------------------------------------------------------
/src/admin/main/index.js:
--------------------------------------------------------------------------------
1 | export * from "./clone";
2 | export * from "./preview-context";
3 |
--------------------------------------------------------------------------------
/src/admin/main/preview-context/index.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import React, {
3 | useState,
4 | useEffect,
5 | useMemo,
6 | useContext,
7 | createContext,
8 | } from "react";
9 | import { useIntl } from "react-intl";
10 | import { request, PopUpWarning } from "strapi-helper-plugin";
11 |
12 | import { get, isEmpty, isEqual } from "lodash";
13 |
14 | const CONTENT_MANAGER_PLUGIN_ID = "content-manager";
15 |
16 | const PreviewContext = createContext(undefined);
17 |
18 | export const PreviewProvider = ({
19 | children,
20 | initialData,
21 | isCreatingEntry,
22 | layout,
23 | modifiedData,
24 | slug,
25 | canUpdate,
26 | canCreate,
27 | getPreviewUrlParams = () => ({}),
28 | }) => {
29 | const { formatMessage } = useIntl();
30 |
31 | const [showWarningClone, setWarningClone] = useState(false);
32 | const [showWarningPublish, setWarningPublish] = useState(false);
33 | const [previewable, setIsPreviewable] = useState(false);
34 | const [isButtonLoading, setButtonLoading] = useState(false);
35 |
36 | const toggleWarningClone = () => setWarningClone((prevState) => !prevState);
37 | const toggleWarningPublish = () =>
38 | setWarningPublish((prevState) => !prevState);
39 |
40 | useEffect(() => {
41 | request(`/preview-content/is-previewable/${layout.apiID}`, {
42 | method: "GET",
43 | }).then(({ isPreviewable }) => {
44 | setIsPreviewable(isPreviewable);
45 | });
46 | }, [layout.apiID]);
47 |
48 | const didChangeData = useMemo(() => {
49 | return (
50 | !isEqual(initialData, modifiedData) ||
51 | (isCreatingEntry && !isEmpty(modifiedData))
52 | );
53 | }, [initialData, isCreatingEntry, modifiedData]);
54 |
55 | const previewHeaderActions = useMemo(() => {
56 | const headerActions = [];
57 |
58 | if (
59 | previewable &&
60 | ((isCreatingEntry && canCreate) || (!isCreatingEntry && canUpdate))
61 | ) {
62 | const params = getPreviewUrlParams(initialData, modifiedData, layout);
63 | headerActions.push({
64 | disabled: didChangeData,
65 | label: formatMessage({
66 | id: getPreviewPluginTrad("containers.Edit.preview"),
67 | }),
68 | color: "secondary",
69 | onClick: async () => {
70 | try {
71 | const data = await request(
72 | `/preview-content/preview-url/${layout.apiID}/${initialData.id}`,
73 | {
74 | method: "GET",
75 | params,
76 | }
77 | );
78 |
79 | if (data.url) {
80 | window.open(data.url, "_blank");
81 | } else {
82 | strapi.notification.error(
83 | getPreviewPluginTrad("error.previewUrl.notFound")
84 | );
85 | }
86 | } catch (_e) {
87 | strapi.notification.error(
88 | getPreviewPluginTrad("error.previewUrl.notFound")
89 | );
90 | }
91 | },
92 | type: "button",
93 | style: {
94 | paddingLeft: 15,
95 | paddingRight: 15,
96 | fontWeight: 600,
97 | minWidth: 100,
98 | },
99 | });
100 |
101 | if (initialData.cloneOf) {
102 | headerActions.push({
103 | disabled: didChangeData,
104 | label: formatMessage({
105 | id: getPreviewPluginTrad("containers.Edit.publish"),
106 | }),
107 | color: "primary",
108 | onClick: async () => {
109 | toggleWarningPublish();
110 | },
111 | type: "button",
112 | style: {
113 | paddingLeft: 15,
114 | paddingRight: 15,
115 | fontWeight: 600,
116 | minWidth: 100,
117 | },
118 | });
119 | } else {
120 | headerActions.push({
121 | disabled: didChangeData,
122 | label: formatMessage({
123 | id: getPreviewPluginTrad("containers.Edit.clone"),
124 | }),
125 | color: "secondary",
126 | onClick: toggleWarningClone,
127 | type: "button",
128 | style: {
129 | paddingLeft: 15,
130 | paddingRight: 15,
131 | fontWeight: 600,
132 | minWidth: 75,
133 | },
134 | });
135 | }
136 | }
137 |
138 | return headerActions;
139 | }, [
140 | didChangeData,
141 | formatMessage,
142 | layout.apiID,
143 | previewable,
144 | initialData.cloneOf,
145 | initialData.id,
146 | canCreate,
147 | canUpdate,
148 | isCreatingEntry,
149 | ]);
150 |
151 | const handleConfirmPreviewClone = async () => {
152 | try {
153 | // Show the loading state
154 | setButtonLoading(true);
155 |
156 | strapi.notification.success(getPreviewPluginTrad("success.record.clone"));
157 |
158 | window.location.replace(getFrontendEntityUrl(slug, initialData.id));
159 | } catch (err) {
160 | console.log(err);
161 | const errorMessage = get(
162 | err,
163 | "response.payload.message",
164 | formatMessage({ id: getPreviewPluginTrad("error.record.clone") })
165 | );
166 |
167 | strapi.notification.error(errorMessage);
168 | } finally {
169 | setButtonLoading(false);
170 | toggleWarningClone();
171 | }
172 | };
173 |
174 | const handleConfirmPreviewPublish = async () => {
175 | try {
176 | // Show the loading state
177 | setButtonLoading(true);
178 |
179 | let targetId = initialData.cloneOf.id;
180 | const urlPart = getRequestUrl(slug);
181 | const body = prepareToPublish({
182 | ...initialData,
183 | id: targetId,
184 | cloneOf: null,
185 | });
186 |
187 | await request(`${urlPart}/${targetId}`, {
188 | method: "PUT",
189 | body,
190 | });
191 | await request(`${urlPart}/${initialData.id}`, {
192 | method: "DELETE",
193 | });
194 |
195 | strapi.notification.success(
196 | getPreviewPluginTrad("success.record.publish")
197 | );
198 |
199 | window.location.replace(getFrontendEntityUrl(slug, targetId));
200 | } catch (err) {
201 | const errorMessage = get(
202 | err,
203 | "response.payload.message",
204 | formatMessage({ id: getPreviewPluginTrad("error.record.publish") })
205 | );
206 |
207 | strapi.notification.error(errorMessage);
208 | } finally {
209 | setButtonLoading(false);
210 | toggleWarningPublish();
211 | }
212 | };
213 |
214 | const value = {
215 | previewHeaderActions,
216 | };
217 |
218 | return (
219 | <>
220 |
221 | {children}
222 |
223 | {previewable && (
224 |
237 | )}
238 | {previewable && (
239 |
252 | )}
253 | >
254 | );
255 | };
256 |
257 | export const usePreview = () => {
258 | const context = useContext(PreviewContext);
259 |
260 | if (context === undefined) {
261 | throw new Error("usePreview must be used within a PreviewProvider");
262 | }
263 |
264 | return context;
265 | };
266 |
267 | /**
268 | * Should remove ID's from components -
269 | * could modify only already attached componetns (with proper ID)
270 | * or create new one - in that case removing id will create new one
271 | * @param {object} payload
272 | */
273 | function prepareToPublish(payload) {
274 | if (Array.isArray(payload)) {
275 | payload.forEach(prepareToPublish);
276 | } else if (payload && payload.constructor === Object) {
277 | // eslint-disable-next-line no-prototype-builtins
278 | if (payload.hasOwnProperty("__component")) {
279 | delete payload.id;
280 | }
281 | Object.values(payload).forEach(prepareToPublish);
282 | }
283 |
284 | return payload;
285 | }
286 |
287 | const getRequestUrl = (path) =>
288 | `/${CONTENT_MANAGER_PLUGIN_ID}/explorer/${path}`;
289 | const getFrontendEntityUrl = (path, id) =>
290 | `/admin/plugins/${CONTENT_MANAGER_PLUGIN_ID}/collectionType/${path}/create/clone/${id}`;
291 |
292 | const getPreviewPluginTrad = (id) => `preview-content.${id}`;
293 |
294 | PreviewProvider.propTypes = {
295 | children: PropTypes.node.isRequired,
296 | canUpdate: PropTypes.bool.isRequired,
297 | canCreate: PropTypes.bool.isRequired,
298 | initialData: PropTypes.object.isRequired,
299 | isCreatingEntry: PropTypes.bool.isRequired,
300 | layout: PropTypes.object.isRequired,
301 | modifiedData: PropTypes.object.isRequired,
302 | slug: PropTypes.string.isRequired,
303 | getPreviewUrlParams: PropTypes.func,
304 | };
305 |
--------------------------------------------------------------------------------
/src/admin/src/components/CodeBlock/index.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import PropTypes from "prop-types";
3 |
4 | const Text = styled.p`
5 | background: #18202e;
6 | margin: 0;
7 | line-height: ${({ lineHeight }) => lineHeight};
8 | color: ${({ theme, color }) => theme.main.colors[color] || color};
9 | font-size: ${({ theme, fontSize }) => theme.main.sizes.fonts[fontSize]};
10 | font-weight: ${({ theme, fontWeight }) => theme.main.fontWeights[fontWeight]};
11 | text-transform: ${({ textTransform }) => textTransform};
12 | ${({ ellipsis }) =>
13 | ellipsis &&
14 | `
15 | white-space: nowrap;
16 | overflow: hidden;
17 | text-overflow: ellipsis;
18 | `}
19 | `;
20 |
21 | Text.defaultProps = {
22 | color: "greyDark",
23 | ellipsis: false,
24 | fontSize: "md",
25 | fontWeight: "regular",
26 | lineHeight: "normal",
27 | textTransform: "none",
28 | };
29 |
30 | Text.propTypes = {
31 | color: PropTypes.string,
32 | ellipsis: PropTypes.bool,
33 | fontSize: PropTypes.string,
34 | fontWeight: PropTypes.string,
35 | lineHeight: PropTypes.string,
36 | textTransform: PropTypes.string,
37 | };
38 |
39 | export default Text;
40 |
--------------------------------------------------------------------------------
/src/admin/src/components/Text/index.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 | import PropTypes from "prop-types";
3 |
4 | const Text = styled.p`
5 | margin: 0;
6 | line-height: ${({ lineHeight }) => lineHeight};
7 | color: ${({ theme, color }) => theme.main.colors[color] || color};
8 | font-size: ${({ theme, fontSize }) => theme.main.sizes.fonts[fontSize]};
9 | font-weight: ${({ theme, fontWeight }) => theme.main.fontWeights[fontWeight]};
10 | text-transform: ${({ textTransform }) => textTransform};
11 | ${({ ellipsis }) =>
12 | ellipsis &&
13 | `
14 | white-space: nowrap;
15 | overflow: hidden;
16 | text-overflow: ellipsis;
17 | `}
18 | `;
19 |
20 | Text.defaultProps = {
21 | color: "greyDark",
22 | ellipsis: false,
23 | fontSize: "md",
24 | fontWeight: "regular",
25 | lineHeight: "normal",
26 | textTransform: "none",
27 | };
28 |
29 | Text.propTypes = {
30 | color: PropTypes.string,
31 | ellipsis: PropTypes.bool,
32 | fontSize: PropTypes.string,
33 | fontWeight: PropTypes.string,
34 | lineHeight: PropTypes.string,
35 | textTransform: PropTypes.string,
36 | };
37 |
38 | export default Text;
39 |
--------------------------------------------------------------------------------
/src/admin/src/containers/Initializer/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Initializer
4 | *
5 | */
6 |
7 | import { useEffect, useRef } from "react";
8 | import PropTypes from "prop-types";
9 | import pluginId from "../../pluginId";
10 |
11 | const Initializer = ({ updatePlugin }) => {
12 | const ref = useRef();
13 | ref.current = updatePlugin;
14 |
15 | useEffect(() => {
16 | ref.current(pluginId, "isReady", true);
17 | }, []);
18 |
19 | return null;
20 | };
21 |
22 | Initializer.propTypes = {
23 | updatePlugin: PropTypes.func.isRequired,
24 | };
25 |
26 | export default Initializer;
27 |
--------------------------------------------------------------------------------
/src/admin/src/containers/SettingsPage/Divider.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const Divider = styled.hr`
4 | height: 1px;
5 | border: 0;
6 | margin-top: 17px;
7 | margin-bottom: 23px;
8 | background: #f6f6f6;
9 | `;
10 |
11 | export default Divider;
12 |
--------------------------------------------------------------------------------
/src/admin/src/containers/SettingsPage/SectionTitleWrapper.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const SectionTitleWrapper = styled.div`
4 | margin-bottom: 20px;
5 | `;
6 |
7 | export default SectionTitleWrapper;
8 |
--------------------------------------------------------------------------------
/src/admin/src/containers/SettingsPage/Wrapper.js:
--------------------------------------------------------------------------------
1 | /*
2 | *
3 | * This should be a component in the helper plugin that will be used
4 | * by the webhooks views
5 | */
6 |
7 | import styled from "styled-components";
8 |
9 | const Wrapper = styled.div`
10 | padding: 25px 10px;
11 | margin-top: 33px;
12 | border-radius: ${({ theme }) => theme.main.sizes.borderRadius};
13 | box-shadow: 0 2px 4px ${({ theme }) => theme.main.colors.darkGrey};
14 | background: ${({ theme }) => theme.main.colors.white};
15 | `;
16 |
17 | export default Wrapper;
18 |
--------------------------------------------------------------------------------
/src/admin/src/containers/SettingsPage/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useReducer, useRef } from "react";
2 | import { Header, Inputs } from "@buffetjs/custom";
3 | import {
4 | LoadingIndicatorPage,
5 | request,
6 | } from "strapi-helper-plugin";
7 | import { useIntl } from "react-intl";
8 | import { isEqual } from "lodash";
9 |
10 | import { getRequestUrl, getTrad } from "../../utils";
11 | import Text from "../../components/Text";
12 | import CodeBlock from "../../components/CodeBlock";
13 | import SectionTitleWrapper from "./SectionTitleWrapper";
14 | import Wrapper from "./Wrapper";
15 | import init from "./init";
16 | import reducer, { initialState } from "./reducer";
17 |
18 | const SettingsPage = () => {
19 | const { formatMessage } = useIntl();
20 | const [reducerState, dispatch] = useReducer(reducer, initialState, init);
21 | const { initialData, isLoading, modifiedData } = reducerState.toJS();
22 | const isMounted = useRef(true);
23 | const getDataRef = useRef();
24 | const abortController = new AbortController();
25 |
26 | getDataRef.current = async () => {
27 | try {
28 | const { signal } = abortController;
29 | const { data } = await request(
30 | getRequestUrl("settings", { method: "GET", signal })
31 | );
32 |
33 | if (isMounted.current) {
34 | dispatch({
35 | type: "GET_DATA_SUCCEEDED",
36 | data,
37 | });
38 | }
39 | } catch (err) {
40 | console.error(err);
41 | }
42 | };
43 |
44 | useEffect(() => {
45 | getDataRef.current();
46 |
47 | return () => {
48 | abortController.abort();
49 | isMounted.current = false;
50 | };
51 | // eslint-disable-next-line react-hooks/exhaustive-deps
52 | }, []);
53 |
54 | const handleChange = ({ target: { name, value } }) => {
55 | dispatch({
56 | type: "ON_CHANGE",
57 | keys: name,
58 | value,
59 | });
60 | };
61 |
62 | const handleSubmit = async () => {
63 | try {
64 | await request(getRequestUrl("settings"), {
65 | method: "PUT",
66 | body: modifiedData,
67 | });
68 |
69 | if (isMounted.current) {
70 | dispatch({
71 | type: "SUBMIT_SUCCEEDED",
72 | });
73 | }
74 |
75 | strapi.notification.toggle({
76 | type: "success",
77 | message: { id: "notification.form.success.fields" },
78 | });
79 | } catch (err) {
80 | console.error(err);
81 | }
82 | };
83 |
84 | const headerProps = {
85 | title: {
86 | label: formatMessage({ id: getTrad("settings.header.label") }),
87 | },
88 | content: formatMessage({
89 | id: getTrad("settings.sub-header.label"),
90 | }),
91 | actions: [
92 | {
93 | color: "cancel",
94 | disabled: isEqual(initialData, modifiedData),
95 | // TradId from the strapi-admin package
96 | label: formatMessage({ id: "app.components.Button.cancel" }),
97 | onClick: () => {
98 | dispatch({
99 | type: "CANCEL_CHANGES",
100 | });
101 | },
102 | type: "button",
103 | },
104 | {
105 | disabled: false,
106 | color: "success",
107 | // TradId from the strapi-admin package
108 | label: formatMessage({ id: "app.components.Button.save" }),
109 | onClick: handleSubmit,
110 | type: "button",
111 | },
112 | ],
113 | };
114 |
115 | if (isLoading) {
116 | return ;
117 | }
118 |
119 | return (
120 | <>
121 |
122 |
123 |
124 |
125 |
126 |
127 | {formatMessage({
128 | id: getTrad("settings.section.general.label"),
129 | })}
130 |
131 |
132 |
133 |
145 |
146 |
147 |
159 |
160 |
161 |
162 | {formatMessage({
163 | id: getTrad("settings.form.previewUrl.available"),
164 | })}
165 |
166 |
167 |
168 |
169 |
170 |
171 |
176 | :baseUrl
177 |
178 |
179 |
180 | {formatMessage({
181 | id: getTrad(
182 | "settings.form.previewUrl.available.baseUrl"
183 | ),
184 | })}
185 |
186 |
187 |
188 |
193 | :contentType
194 |
195 |
196 |
197 | {formatMessage({
198 | id: getTrad(
199 | "settings.form.previewUrl.available.contentType"
200 | ),
201 | })}
202 |
203 |
204 |
205 |
210 | :id
211 |
212 |
213 |
214 | {formatMessage({
215 | id: getTrad("settings.form.previewUrl.available.id"),
216 | })}
217 |
218 |
219 |
220 |
221 |
222 |
223 | {formatMessage({
224 | id: getTrad("settings.form.previewUrl.example"),
225 | })}
226 |
227 |
228 |
229 |
230 |
231 |
236 | NextJS: {"<"}YOUR_URL{">"}
237 | /api/preview?contentType=:contentType&id=:id
238 |
239 |
240 |
241 |
242 |
243 |
244 | >
245 | );
246 | };
247 |
248 | export default SettingsPage;
249 |
--------------------------------------------------------------------------------
/src/admin/src/containers/SettingsPage/init.js:
--------------------------------------------------------------------------------
1 | const init = (initialState) => {
2 | return initialState;
3 | };
4 |
5 | export default init;
6 |
--------------------------------------------------------------------------------
/src/admin/src/containers/SettingsPage/reducer.js:
--------------------------------------------------------------------------------
1 | import { fromJS } from "immutable";
2 |
3 | const initialState = fromJS({
4 | isLoading: true,
5 | initialData: {
6 | previewUrl: "",
7 | baseUrl: "",
8 | },
9 | modifiedData: {
10 | previewUrl: "",
11 | baseUrl: "",
12 | },
13 | });
14 |
15 | const reducer = (state, action) => {
16 | switch (action.type) {
17 | case "CANCEL_CHANGES":
18 | return state.update("modifiedData", () => state.get("initialData"));
19 | case "GET_DATA_SUCCEEDED":
20 | return state
21 | .update("isLoading", () => false)
22 | .update("initialData", () => fromJS(action.data))
23 | .update("modifiedData", () => fromJS(action.data));
24 | case "ON_CHANGE":
25 | return state.updateIn(
26 | ["modifiedData", ...action.keys.split(".")],
27 | () => action.value
28 | );
29 | case "SUBMIT_SUCCEEDED":
30 | return state.update("initialData", () => state.get("modifiedData"));
31 | default:
32 | return state;
33 | }
34 | };
35 |
36 | export default reducer;
37 | export { initialState };
38 |
--------------------------------------------------------------------------------
/src/admin/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import pluginPkg from "../../package.json";
3 | import pluginId from "./pluginId";
4 | import Initializer from "./containers/Initializer";
5 | import lifecycles from "./lifecycles";
6 | import trads from "./translations";
7 | import SettingsPage from "./containers/SettingsPage";
8 |
9 | import getTrad from "./utils/getTrad";
10 |
11 | export default (strapi) => {
12 | const pluginDescription =
13 | pluginPkg.strapi.description || pluginPkg.description;
14 | const icon = pluginPkg.strapi.icon;
15 | const name = pluginPkg.strapi.name;
16 |
17 | const plugin = {
18 | description: pluginDescription,
19 | icon,
20 | id: pluginId,
21 | initializer: Initializer,
22 | isReady: false,
23 | isRequired: pluginPkg.strapi.required || false,
24 | mainComponent: null,
25 | name,
26 | preventComponentRendering: false,
27 | settings: {
28 | global: {
29 | links: [
30 | {
31 | title: {
32 | id: getTrad("plugin.name"),
33 | defaultMessage: "Preview Content",
34 | },
35 | name: "preview-content",
36 | to: `${strapi.settingsBaseURL}/preview-content`,
37 | Component: () => ,
38 | exact: false,
39 | permissions: [
40 | { action: "plugins::preview-content.read", subject: null },
41 | ],
42 | },
43 | ],
44 | },
45 | },
46 | trads,
47 | };
48 |
49 | return strapi.registerPlugin(plugin);
50 | };
51 |
--------------------------------------------------------------------------------
/src/admin/src/lifecycles.js:
--------------------------------------------------------------------------------
1 | function lifecycles() {}
2 |
3 | export default lifecycles;
4 |
--------------------------------------------------------------------------------
/src/admin/src/pluginId.js:
--------------------------------------------------------------------------------
1 | const pluginPkg = require("../../package.json");
2 | const pluginId = pluginPkg.name.replace(/^strapi-plugin-/i, "");
3 |
4 | module.exports = pluginId;
5 |
--------------------------------------------------------------------------------
/src/admin/src/translations/ar.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "معاينة المحتوى",
3 | "settings.form.previewUrl.description": "أنشئ عنوان url مخصصًا للمعاينة لاستخدامه في الواجهة الأمامية",
4 | "settings.form.previewUrl.label": "معاينة عنوان URL",
5 | "settings.form.previewUrl.available": "المعلمات المتوفرة:",
6 | "settings.form.previewUrl.available.contentType": "نوع المحتوى المطلوب الاستعلام عنه (مطلوب في عنوان url)",
7 | "settings.form.previewUrl.available.id": "معرف الاستعلام (مطلوب في عنوان url)",
8 | "settings.form.previewUrl.example": "مثال:",
9 | "settings.header.label": "معاينة المحتوى - الإعدادات",
10 | "settings.section.general.label": "عام",
11 | "settings.sub-header.label": "تكوين الإعدادات لوظيفة إعدادات المعاينة"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/cs.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "Náhled obsahu",
3 | "settings.form.previewUrl.description": "Vytvořit vlastní adresu URL náhledu, která bude použita ve vašem rozhraní",
4 | "settings.form.previewUrl.label": "Náhled adresy URL",
5 | "settings.form.previewUrl.available": "Dostupné parametry:",
6 | "settings.form.previewUrl.available.contentType": "Typ obsahu, který má být dotazován (požadovaný v adrese URL)",
7 | "settings.form.previewUrl.available.id": "ID pro dotaz (vyžadováno v adrese URL)",
8 | "settings.form.previewUrl.example": "Příklad:",
9 | "settings.header.label": "Náhled obsahu - nastavení",
10 | "settings.section.general.label": "OBECNÉ",
11 | "settings.sub-header.label": "Konfigurovat nastavení funkce náhledu nastavení"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "containers.Edit.clone": "Duplizieren",
3 | "containers.Edit.preview": "Vorschau",
4 | "containers.Edit.publish": "Veröffentlichen",
5 |
6 | "success.record.clone": "Dupliziert",
7 | "error.record.clone": "Während dem Duplizieren des Eintrags ist ein Fehler aufgetreten.",
8 | "success.record.publish": "Veröffentlicht",
9 | "error.record.publish": "Während dem Veröffentlichen des Eintrags ist ein Fehler aufgetreten.",
10 |
11 | "popUpWarning.warning.clone": "Das Duplizieren legt einen neuen Eintrag mit den selben Feldern an.",
12 | "popUpWarning.warning.clone-question": "Willst du diesen Eintrag duplizieren?",
13 | "popUpWarning.warning.publish": "Diesen Eintrag zu veröffentlichen wird den vorher duplizierten ersetzen (Änderungen werden automatisch auf dem Original-Eintrag angewendet)",
14 | "popUpWarning.warning.publish-question": "Willst du diesen Eintrag wirklich veröffentlichen?",
15 |
16 | "plugin.name": "Vorschau von Inhalten",
17 | "settings.form.previewUrl.description": "Gib eine benutzerdefinierte Vorschau-URL an, die in Ihrem Frontend verwendet werden soll",
18 | "settings.form.previewUrl.label": "Vorschau-URL",
19 | "settings.form.previewUrl.available": "Verfügbare Parameter:",
20 | "settings.form.previewUrl.available.contentType": "Der abzufragende Inhaltstyp (in der URL erforderlich)",
21 | "settings.form.previewUrl.available.id": "Die abzufragende ID (in der URL erforderlich)",
22 | "settings.form.previewUrl.example": "Beispiel:",
23 | "settings.header.label": "Vorschau von Inhalten - Einstellungen",
24 | "settings.section.general.label": "GENERELL",
25 | "settings.sub-header.label": "Konfigurieren Sie die Einstellungen für die Funktion der Vorschaueinstellungen",
26 |
27 | "error.previewUrl.notFound": "Beim Abrufen der URL ist ein Fehler aufgetreten."
28 | }
29 |
--------------------------------------------------------------------------------
/src/admin/src/translations/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "containers.Edit.clone": "Clone",
3 | "containers.Edit.preview": "Preview",
4 | "containers.Edit.publish": "Publish",
5 |
6 | "success.record.clone": "Cloned",
7 | "error.record.clone": "An error occurred during record clone.",
8 | "success.record.publish": "Published",
9 | "error.record.publish": "An error occurred during record publish.",
10 |
11 | "popUpWarning.warning.clone": "Cloning this entry will create a new copy with the same fields.",
12 | "popUpWarning.warning.clone-question": "Are you sure you want to clone this entry?",
13 | "popUpWarning.warning.publish": "Publishing this entry will replace previously cloned entry (changes will automatically apply to original entry)",
14 | "popUpWarning.warning.publish-question": "Are you sure you want to publish this entry?",
15 |
16 | "plugin.name": "Preview Content",
17 | "settings.form.baseUrl.description": "The root url of your frontend. This can be used by custom and default preview urls.",
18 | "settings.form.baseUrl.label": "Base url",
19 | "settings.form.previewUrl.description": "Create custom preview url to be used in your frontend",
20 | "settings.form.previewUrl.label": "Default Preview url",
21 | "settings.form.previewUrl.available": "Available parameters:",
22 | "settings.form.previewUrl.available.baseUrl": "The root url as defined above",
23 | "settings.form.previewUrl.available.contentType": "The content type to query (required in the url)",
24 | "settings.form.previewUrl.available.id": "The id to query (required in the url)",
25 | "settings.form.previewUrl.example": "Example:",
26 | "settings.header.label": "Preview Content - Settings",
27 | "settings.section.general.label": "GENERAL",
28 | "settings.sub-header.label": "Configure the settings for the preview settings funcionality",
29 |
30 | "error.previewUrl.notFound": "An error occurred during preview url fetch."
31 | }
32 |
--------------------------------------------------------------------------------
/src/admin/src/translations/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "Vista Previa del Contenido",
3 | "settings.form.previewUrl.description": "Cree una URL de vista previa personalizada para usar en su interfaz",
4 | "settings.form.previewUrl.label": "URL de vista previa",
5 | "settings.form.previewUrl.available": "Parámetros disponibles:",
6 | "settings.form.previewUrl.available.contentType": "El tipo de contenido a consultar (requerido en la URL)",
7 | "settings.form.previewUrl.available.id": "El ID a consultar (requerido en la url)",
8 | "settings.form.previewUrl.example": "Ejemplo:",
9 | "settings.header.label": "Vista Previa del Contenido - Configuración",
10 | "settings.section.general.label": "GENERAL",
11 | "settings.sub-header.label": "Configure los ajustes para la funcionalidad de configuración de vista previa"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "Aperçu du contenu",
3 | "settings.form.previewUrl.description": "Créer une URL de prévisualisation personnalisée à utiliser dans votre frontend",
4 | "settings.form.previewUrl.label": "Aperçu de l'url",
5 | "settings.form.previewUrl.available": "Paramètres disponibles:",
6 | "settings.form.previewUrl.available.contentType": "Le type de contenu à interroger (obligatoire dans l'url)",
7 | "settings.form.previewUrl.available.id": "L'identifiant à interroger (obligatoire dans l'url)",
8 | "settings.form.previewUrl.example": "Exemple:",
9 | "settings.header.label": "Aperçu du contenu - Paramètres",
10 | "settings.section.general.label": "GENERAL",
11 | "settings.sub-header.label": "Configurer les paramètres de la fonctionnalité des paramètres de prévisualisation"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/id.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "Pratinjau Konten",
3 | "settings.form.previewUrl.description": "Buat url pratinjau kustom untuk digunakan di frontend Anda",
4 | "settings.form.previewUrl.label": "Pratinjau url",
5 | "settings.form.previewUrl.available": "Parameter yang tersedia:",
6 | "settings.form.previewUrl.available.contentType": "Jenis konten untuk kueri (diperlukan dalam url)",
7 | "settings.form.previewUrl.available.id": "ID yang akan ditanyakan (harus ada di url)",
8 | "settings.form.previewUrl.example": "Contoh:",
9 | "settings.header.label": "Pratinjau Konten - Pengaturan",
10 | "settings.section.general.label": "GENERAL",
11 | "settings.sub-header.label": "Konfigurasi pengaturan untuk fungsi pengaturan pratinjau"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/index.js:
--------------------------------------------------------------------------------
1 | import ar from './ar.json';
2 | import cs from './cs.json';
3 | import de from './de.json';
4 | import en from './en.json';
5 | import es from './es.json';
6 | import fr from './fr.json';
7 | import id from './id.json';
8 | import it from './it.json';
9 | import ko from './ko.json';
10 | import ms from './ms.json';
11 | import nl from './nl.json';
12 | import pl from './pl.json';
13 | import ptBR from './pt-BR.json';
14 | import pt from './pt.json';
15 | import ru from './ru.json';
16 | import th from './th.json';
17 | import tr from './tr.json';
18 | import uk from './uk.json';
19 | import vi from './vi.json';
20 | import zhHans from './zh-Hans.json';
21 | import zh from './zh.json';
22 | import sk from './sk.json';
23 |
24 | const trads = {
25 | ar,
26 | cs,
27 | de,
28 | en,
29 | es,
30 | fr,
31 | id,
32 | it,
33 | ko,
34 | ms,
35 | nl,
36 | pl,
37 | 'pt-BR': ptBR,
38 | pt,
39 | ru,
40 | th,
41 | tr,
42 | uk,
43 | vi,
44 | 'zh-Hans': zhHans,
45 | zh,
46 | sk,
47 | };
48 |
49 | export default trads;
50 |
--------------------------------------------------------------------------------
/src/admin/src/translations/it.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "Anteprima contenuto",
3 | "settings.form.previewUrl.description": "Crea un URL di anteprima personalizzato da utilizzare nel tuo frontend",
4 | "settings.form.previewUrl.label": "Anteprima URL",
5 | "settings.form.previewUrl.available": "Parametri disponibili:",
6 | "settings.form.previewUrl.available.contentType": "Il tipo di contenuto da interrogare (obbligatorio nell'URL)",
7 | "settings.form.previewUrl.available.id": "L'ID da interrogare (obbligatorio nell'URL)",
8 | "settings.form.previewUrl.example": "Esempio:",
9 | "settings.header.label": "Anteprima contenuto - Impostazioni",
10 | "settings.section.general.label": "GENERAL",
11 | "settings.sub-header.label": "Configura le impostazioni per la funzionalità delle impostazioni di anteprima"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/ko.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "콘텐츠 미리보기",
3 | "settings.form.previewUrl.description": "프런트 엔드에서 사용할 맞춤 미리보기 URL 만들기",
4 | "settings.form.previewUrl.label": "URL 미리보기",
5 | "settings.form.previewUrl.available": "사용 가능한 매개 변수 :",
6 | "settings.form.previewUrl.available.contentType": "검색 할 콘텐츠 유형 (URL에 필요)",
7 | "settings.form.previewUrl.available.id": "검색 할 ID (URL에 필요)",
8 | "settings.form.previewUrl.example": "예 :",
9 | "settings.header.label": "콘텐츠 미리보기-설정",
10 | "settings.section.general.label": "일반",
11 | "settings.sub-header.label": "미리보기 설정 기능에 대한 설정 구성"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/ms.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "Pratonton Kandungan",
3 | "settings.form.previewUrl.description": "Buat url pratonton khusus untuk digunakan di frontend anda",
4 | "settings.form.previewUrl.label": "Pratonton url",
5 | "settings.form.previewUrl.available": "Parameter yang tersedia:",
6 | "settings.form.previewUrl.available.contentType": "Jenis kandungan untuk pertanyaan (diperlukan dalam url)",
7 | "settings.form.previewUrl.available.id": "Id untuk pertanyaan (diperlukan dalam url)",
8 | "settings.form.previewUrl.example": "Contoh:",
9 | "settings.header.label": "Pratonton Kandungan - Tetapan",
10 | "settings.section.general.label": "UMUM",
11 | "settings.sub-header.label": "Konfigurasikan tetapan untuk fungsi tetapan pratonton"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/nl.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "Voorbeeld van inhoud",
3 | "settings.form.previewUrl.description": "Maak een aangepaste voorbeeld-URL voor gebruik in uw frontend",
4 | "settings.form.previewUrl.label": "Preview url",
5 | "settings.form.previewUrl.available": "Beschikbare parameters:",
6 | "settings.form.previewUrl.available.contentType": "Het inhoudstype dat moet worden opgevraagd (vereist in de url)",
7 | "settings.form.previewUrl.available.id": "De id die moet worden opgevraagd (vereist in de url)",
8 | "settings.form.previewUrl.example": "Voorbeeld:",
9 | "settings.header.label": "Voorbeeld van inhoud - Instellingen",
10 | "settings.section.general.label": "ALGEMEEN",
11 | "settings.sub-header.label": "Configureer de instellingen voor de functionaliteit van de voorbeeldinstellingen"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/pl.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "Podgląd treści",
3 | "settings.form.previewUrl.description": "Utwórz niestandardowy URL podglądu do użycia w interfejsie użytkownika",
4 | "settings.form.previewUrl.label": "URL podglądu",
5 | "settings.form.previewUrl.available": "Dostępne parametry:",
6 | "settings.form.previewUrl.available.contentType": "Typ treści do zapytania (wymagany w url)",
7 | "settings.form.previewUrl.available.id": "Identyfikator do zapytania (wymagany w url)",
8 | "settings.form.previewUrl.example": "Przykład:",
9 | "settings.header.label": "Podgląd treści - ustawienia",
10 | "settings.section.general.label": "OGÓLNE",
11 | "settings.sub-header.label": "Skonfiguruj ustawienia funkcji podglądu"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/pt-BR.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "Visualizar conteúdo",
3 | "settings.form.previewUrl.description": "Crie um URL de visualização personalizado para ser usado em seu frontend",
4 | "settings.form.previewUrl.label": "Visualizar url",
5 | "settings.form.previewUrl.available": "Parâmetros disponíveis:",
6 | "settings.form.previewUrl.available.contentType": "O tipo de conteúdo a ser consultado (obrigatório no url)",
7 | "settings.form.previewUrl.available.id": "O id a consultar (obrigatório no url)",
8 | "settings.form.previewUrl.example": "Exemplo:",
9 | "settings.header.label": "Visualizar conteúdo - Configurações",
10 | "settings.section.general.label": "GERAL",
11 | "settings.sub-header.label": "Definir as configurações para a funcionalidade de configurações de visualização"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/pt.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "Visualizar conteúdo",
3 | "settings.form.previewUrl.description": "Crie um URL de visualização personalizado para ser usado em seu frontend",
4 | "settings.form.previewUrl.label": "Visualizar url",
5 | "settings.form.previewUrl.available": "Parâmetros disponíveis:",
6 | "settings.form.previewUrl.available.contentType": "O tipo de conteúdo a ser consultado (obrigatório no url)",
7 | "settings.form.previewUrl.available.id": "O id a consultar (obrigatório no url)",
8 | "settings.form.previewUrl.example": "Exemplo:",
9 | "settings.header.label": "Visualizar conteúdo - Configurações",
10 | "settings.section.general.label": "GERAL",
11 | "settings.sub-header.label": "Definir as configurações para a funcionalidade de configurações de visualização"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/ru.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "Предварительный просмотр содержимого",
3 | "settings.form.previewUrl.description": "Создайте настраиваемый URL-адрес предварительного просмотра, который будет использоваться в вашем интерфейсе",
4 | "settings.form.previewUrl.label": "URL предварительного просмотра",
5 | "settings.form.previewUrl.available": "Доступные параметры:",
6 | "settings.form.previewUrl.available.contentType": "Тип контента для запроса (требуется в URL-адресе)",
7 | "settings.form.previewUrl.available.id": "Идентификатор запроса (требуется в URL-адресе)",
8 | "settings.form.previewUrl.example": "Пример:",
9 | "settings.header.label": "Предварительный просмотр содержимого - Настройки",
10 | "settings.section.general.label": "GENERAL",
11 | "settings.sub-header.label": "Настроить параметры для функциональности параметров предварительного просмотра"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/sk.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "Zobraziť ukážku obsahu",
3 | "settings.form.previewUrl.description": "Vytvoriť vlastnú adresu URL ukážky, ktorá sa použije vo vašom klientskom rozhraní",
4 | "settings.form.previewUrl.label": "Ukážka ukážky",
5 | "settings.form.previewUrl.available": "Dostupné parametre:",
6 | "settings.form.previewUrl.available.contentType": "Typ obsahu, ktorý sa má vyhľadať (požadovaný v adrese URL)",
7 | "settings.form.previewUrl.available.id": "ID na dopyt (požadované v adrese URL)",
8 | "settings.form.previewUrl.example": "Príklad:",
9 | "settings.header.label": "Zobraziť ukážku obsahu - Nastavenia",
10 | "settings.section.general.label": "VŠEOBECNÉ",
11 | "settings.sub-header.label": "Konfigurovať nastavenia funkčnosti nastavenia ukážky"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/th.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "ดูตัวอย่างเนื้อหา",
3 | "settings.form.previewUrl.description": "สร้าง URL ตัวอย่างที่กำหนดเองเพื่อใช้ในส่วนหน้าของคุณ",
4 | "settings.form.previewUrl.label": "แสดงตัวอย่าง url",
5 | "settings.form.previewUrl.available": "พารามิเตอร์ที่มี:",
6 | "settings.form.previewUrl.available.contentType": "ประเภทเนื้อหาที่จะสืบค้น (จำเป็นใน url)",
7 | "settings.form.previewUrl.available.id": "รหัสที่จะค้นหา (จำเป็นใน url)",
8 | "settings.form.previewUrl.example": "ตัวอย่าง:",
9 | "settings.header.label": "ดูตัวอย่างเนื้อหา - การตั้งค่า",
10 | "settings.section.general.label": "GENERAL",
11 | "settings.sub-header.label": "กำหนดการตั้งค่าสำหรับ funcionality การตั้งค่าการแสดงตัวอย่าง"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/tr.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "İçeriği Önizle",
3 | "settings.form.previewUrl.description": "Ön ucunuzda kullanılacak özel önizleme url'si oluşturun",
4 | "settings.form.previewUrl.label": "Önizleme url'si",
5 | "settings.form.previewUrl.available": "Kullanılabilir parametreler:",
6 | "settings.form.previewUrl.available.contentType": "Sorgulanacak içerik türü (url'de gerekli)",
7 | "settings.form.previewUrl.available.id": "Sorgulanacak kimlik (url'de gereklidir)",
8 | "settings.form.previewUrl.example": "Örnek:",
9 | "settings.header.label": "İçeriği Önizle - Ayarlar",
10 | "settings.section.general.label": "GENEL",
11 | "settings.sub-header.label": "Önizleme ayarları işlevselliği için ayarları yapılandırın"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/uk.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "Попередній перегляд вмісту",
3 | "settings.form.previewUrl.description": "Створити користувацьку URL-адресу попереднього перегляду для використання у вашому інтерфейсі",
4 | "settings.form.previewUrl.label": "Попередній перегляд URL-адреси",
5 | "settings.form.previewUrl.available": "Доступні параметри:",
6 | "settings.form.previewUrl.available.contentType": "Тип вмісту для запиту (обов'язково в URL-адресі)",
7 | "settings.form.previewUrl.available.id": "Ідентифікатор для запиту (обов'язковий в URL-адресі)",
8 | "settings.form.previewUrl.example": "Приклад:",
9 | "settings.header.label": "Попередній перегляд вмісту - Налаштування",
10 | "settings.section.general.label": "ЗАГАЛЬНЕ",
11 | "settings.sub-header.label": "Налаштування параметрів функціональності налаштувань попереднього перегляду"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/vi.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "Xem trước Nội dung",
3 | "settings.form.previewUrl.description": "Tạo url xem trước tùy chỉnh để sử dụng trong giao diện người dùng của bạn",
4 | "settings.form.previewUrl.label": "Url xem trước",
5 | "settings.form.previewUrl.available": "Các thông số có sẵn:",
6 | "settings.form.previewUrl.available.contentType": "Loại nội dung để truy vấn (bắt buộc trong url)",
7 | "settings.form.previewUrl.available.id": "Id để truy vấn (bắt buộc trong url)",
8 | "settings.form.previewUrl.example": "Ví dụ:",
9 | "settings.header.label": "Xem trước Nội dung - Cài đặt",
10 | "settings.section.general.label": "CHUNG",
11 | "settings.sub-header.label": "Định cấu hình cài đặt cho tính năng cá tính của cài đặt xem trước"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/zh-Hans.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "预览内容",
3 | " settings.form.previewUrl.description": "创建要在前端使用的自定义预览网址",
4 | " settings.form.previewUrl.label": "预览网址",
5 | " settings.form.previewUrl.available": "可用参数:",
6 | " settings.form.previewUrl.available.contentType": "要查询的内容类型(在url中要求)",
7 | " settings.form.previewUrl.available.id": "要查询的ID(在URL中必需)",
8 | " settings.form.previewUrl.example": "示例:",
9 | " settings.header.label": "预览内容-设置",
10 | " settings.section.general.label": "一般",
11 | " settings.sub-header.label": "配置预览设置功能的设置"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/translations/zh.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugin.name": "預覽內容",
3 | "settings.form.previewUrl.description": "創建要在前端使用的自定義預覽網址",
4 | "settings.form.previewUrl.label": "預覽網址",
5 | "settings.form.previewUrl.available": "可用參數:",
6 | "settings.form.previewUrl.available.contentType": "要查詢的內容類型(在url中要求)",
7 | "settings.form.previewUrl.available.id": "要查詢的ID(在URL中必需)",
8 | "settings.form.previewUrl.example": "示例:",
9 | "settings.header.label": "預覽內容-設置",
10 | "settings.section.general.label": "一般",
11 | "settings.sub-header.label": "配置預覽設置功能的設置"
12 | }
13 |
--------------------------------------------------------------------------------
/src/admin/src/utils/getRequestUrl.js:
--------------------------------------------------------------------------------
1 | import pluginId from "../pluginId";
2 |
3 | const getRequestUrl = (path) => `/${pluginId}/${path}`;
4 |
5 | export default getRequestUrl;
6 |
--------------------------------------------------------------------------------
/src/admin/src/utils/getTrad.js:
--------------------------------------------------------------------------------
1 | import pluginId from '../pluginId';
2 |
3 | const getTrad = id => `${pluginId}.${id}`;
4 |
5 | export default getTrad;
6 |
--------------------------------------------------------------------------------
/src/admin/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export { default as getRequestUrl } from "./getRequestUrl";
2 | export { default as getTrad } from "./getTrad";
3 |
--------------------------------------------------------------------------------
/src/config/functions/bootstrap.js:
--------------------------------------------------------------------------------
1 | module.exports = async () => {
2 | // Add permissions
3 | const actions = [
4 | {
5 | section: "plugins",
6 | displayName: "Access the Preview",
7 | uid: "read",
8 | pluginName: "preview-content",
9 | },
10 | ];
11 |
12 | // set plugin store
13 | const configurator = strapi.store({
14 | type: "plugin",
15 | name: "preview-content",
16 | key: "settings",
17 | });
18 |
19 | // if provider config does not exist set one by default
20 | const config = await configurator.get();
21 |
22 | if (!config) {
23 | await configurator.set({
24 | value: {
25 | baseUrl: "https://.com",
26 | previewUrl: ":baseUrl/api/preview?contentType=:contentType&id=:id",
27 | },
28 | });
29 | }
30 |
31 | const { actionProvider } = global.strapi.admin.services.permission;
32 |
33 | await actionProvider.registerMany(actions);
34 | };
35 |
--------------------------------------------------------------------------------
/src/config/routes.json:
--------------------------------------------------------------------------------
1 | {
2 | "routes": [
3 | {
4 | "method": "GET",
5 | "path": "/settings",
6 | "handler": "preview.getSettings",
7 | "config": {
8 | "policies": []
9 | }
10 | },
11 | {
12 | "method": "PUT",
13 | "path": "/settings",
14 | "handler": "preview.updateSettings",
15 | "config": {
16 | "policies": []
17 | }
18 | },
19 | {
20 | "method": "GET",
21 | "path": "/is-previewable/:contentType",
22 | "handler": "preview.isPreviewable",
23 | "config": {
24 | "policies": []
25 | }
26 | },
27 | {
28 | "method": "GET",
29 | "path": "/:contentType/:id",
30 | "handler": "preview.findOne",
31 | "config": {
32 | "policies": []
33 | }
34 | },
35 | {
36 | "method": "GET",
37 | "path": "/preview-url/:contentType/:id",
38 | "handler": "preview.getPreviewUrl",
39 | "config": {
40 | "policies": []
41 | }
42 | }
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/src/contexts/DataManagerContext.js:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | const DataManagerContext = createContext();
4 |
5 | export default DataManagerContext;
6 |
--------------------------------------------------------------------------------
/src/controllers/preview.ts:
--------------------------------------------------------------------------------
1 | "use strict";
2 | import { Context } from "koa";
3 | import _ from "lodash";
4 | const validateSettings = require("./validations/settings");
5 |
6 | /**
7 | * preview.js controller
8 | *
9 | * @description: A set of functions called "actions" of the `preview` plugin.
10 | */
11 | module.exports = {
12 | /**
13 | * Get if preview services is active
14 | *
15 | * @param ctx
16 | *
17 | * @return Returns true or false for preview
18 | */
19 | async isPreviewable(ctx: Context) {
20 | const service = global.strapi.plugins["preview-content"].services.preview;
21 | const isPreviewable = await service.isPreviewable(ctx.params.contentType);
22 |
23 | ctx.send({ isPreviewable });
24 | },
25 | /**
26 | * Find a content type by id
27 | *
28 | * @param ctx
29 | *
30 | * @returns Returns the content type by id, otherwise null.
31 | */
32 | async findOne(ctx: Context) {
33 | const service = global.strapi.plugins["preview-content"].services.preview;
34 | const contentPreview = await service.findOne(
35 | ctx.params.contentType,
36 | ctx.params.id,
37 | ctx.query
38 | );
39 |
40 | ctx.send(contentPreview);
41 | },
42 | /**
43 | * Get preview url of content type
44 | *
45 | * @param ctx
46 | *
47 | * @returns eturns the object containing the preview url, otherwise null.
48 | */
49 | async getPreviewUrl(ctx: Context) {
50 | const {
51 | params: { contentType, id },
52 | query,
53 | } = ctx;
54 | const service = global.strapi.plugins["preview-content"].services.preview;
55 | const url = await service.getPreviewUrl(contentType, id, query);
56 |
57 | ctx.send({ url: url || "" });
58 | },
59 | /**
60 | * Get settings of the plugin
61 | */
62 | async getSettings(ctx: Context) {
63 | // @ts-ignore
64 | const data = await strapi.plugins[
65 | "preview-content"
66 | ].services.preview.getSettings();
67 |
68 | ctx.body = { data };
69 | },
70 | /**
71 | * Update settings of the plugin
72 | */
73 | async updateSettings(ctx: Context) {
74 | const {
75 | // @ts-ignore
76 | request: { body },
77 | } = ctx;
78 |
79 | const data = await validateSettings(body);
80 |
81 | // @ts-ignore
82 | await strapi.plugins["preview-content"].services.preview.setSettings(data);
83 |
84 | ctx.body = { data };
85 | },
86 | };
87 |
--------------------------------------------------------------------------------
/src/controllers/validations/settings.ts:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const { yup, formatYupErrors } = require("strapi-utils");
4 |
5 | const settingsSchema = yup.object({
6 | previewUrl: yup.string().required(),
7 | });
8 |
9 | const validateSettings = (data: any) => {
10 | return settingsSchema
11 | .validate(data, {
12 | abortEarly: false,
13 | })
14 | .catch((error: any) => {
15 | // @ts-ignore
16 | throw strapi.errors.badRequest("ValidationError", {
17 | errors: formatYupErrors(error),
18 | });
19 | });
20 | };
21 |
22 | module.exports = validateSettings;
23 |
--------------------------------------------------------------------------------
/src/hooks/useDataManager.js:
--------------------------------------------------------------------------------
1 | import { useContext } from "react";
2 | import DataManagerContext from "../contexts/DataManagerContext";
3 |
4 | const useDataManager = () => useContext(DataManagerContext);
5 |
6 | export default useDataManager;
7 |
--------------------------------------------------------------------------------
/src/services/preview-error.ts:
--------------------------------------------------------------------------------
1 | module.exports = class PreviewError extends (
2 | Error
3 | ) {
4 | status: number;
5 | payload: any;
6 | constructor(status: number, message: string, payload: any = undefined) {
7 | super();
8 | this.name = "Strapi:Plugin:PreviewContent";
9 | this.status = status || 500;
10 | this.message = message || "Internal error";
11 | this.payload = payload;
12 | }
13 |
14 | toString(e = this) {
15 | return `${e.name} - ${e.message}`;
16 | }
17 |
18 | getData() {
19 | if (this.payload) {
20 | return JSON.stringify({
21 | name: this.name,
22 | message: this.message,
23 | ...(this.payload || {}),
24 | });
25 | }
26 | return this.toString();
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/src/services/preview.ts:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | import _ from "lodash";
4 |
5 | const { sanitizeEntity } = require("strapi-utils");
6 |
7 | const PreviewError = require("./preview-error");
8 |
9 | /**
10 | * Get components from a givne template
11 | *
12 | * @param {{ __component: string }[]} template
13 | *
14 | * @returns Returns the component, otherwise a error 400.
15 | */
16 | // const getTemplateComponentFromTemplate = (template) => {
17 | // if (template && template[0] && template[0].__component) {
18 | // const componentName = template[0].__component;
19 | // return global.strapi.components[componentName];
20 | // }
21 |
22 | // throw new PreviewError(400, "Template field is incompatible");
23 | // };
24 |
25 | /**
26 | * preview.js service
27 | *
28 | * @description: A set of functions similar to controller's actions to avoid code duplication.
29 | */
30 | module.exports = {
31 | /**
32 | * Get if content type is previewable
33 | *
34 | * @param contentType - The content type to check
35 | *
36 | * @returns - Returns inf content type is previewable
37 | */
38 | async isPreviewable(contentType: string) {
39 | const model = await global.strapi.query(contentType)?.model;
40 |
41 | if (model) {
42 | return model.pluginOptions?.['preview-content']?.previewable || model.options?.previewable;
43 | }
44 | throw new PreviewError(400, "Wrong contentType");
45 | },
46 | /**
47 | * Find a content type by id previewable
48 | *
49 | * @param - The content type to query
50 | * @param - The ID of the content to query
51 | * @param - The query string params from URL
52 | *
53 | * @returns Returns an object with the template name, content type and data; otherwise error 400.
54 | */
55 | async findOne(
56 | contentType: string,
57 | id: string,
58 | query: Record
59 | ) {
60 | const service = global.strapi.services[contentType];
61 | const model = global.strapi.models[contentType];
62 |
63 | if (!service) {
64 | throw new PreviewError(400, "Wrong contentType");
65 | }
66 |
67 | if (!model.options.previewable) {
68 | throw new PreviewError(400, "This content type is not previewable");
69 | }
70 |
71 | let contentPreview: any;
72 |
73 | const contentPreviewPublished = await service.findOne({
74 | ...query,
75 | id,
76 | });
77 |
78 | if (contentPreviewPublished) {
79 | contentPreview = contentPreviewPublished;
80 | }
81 |
82 | const contentPreviewDraft = await service.findOne({
83 | ...query,
84 | id,
85 | });
86 |
87 | if (contentPreviewDraft) {
88 | contentPreview = contentPreviewDraft;
89 | }
90 |
91 | if (!contentPreview) {
92 | throw new PreviewError(
93 | 404,
94 | "Preview not found for given content type and Id"
95 | );
96 | }
97 |
98 | const data = sanitizeEntity(contentPreview, { model });
99 | // const templateComponent = getTemplateComponentFromTemplate(data.template);
100 |
101 | return {
102 | // templateName: templateComponent.options.templateName,
103 | contentType,
104 | data,
105 | };
106 | },
107 | /**
108 | * Get the preview url from a content type by id
109 | *
110 | * @param - The content type to query
111 | * @param - The content type id to query
112 | * @param - The query strings from URL
113 | *
114 | * @returns The preview URL parsed with `replacePreviewParams()`
115 | */
116 | async getPreviewUrl(
117 | contentType: string,
118 | contentId: string,
119 | _query: Record
120 | ) {
121 | // @ts-ignore
122 | const contentTypeModel = strapi.models[contentType]
123 | const contentTypeConfig = contentTypeModel?.pluginOptions?.['preview-content'];
124 |
125 | const entity = await this.getSettings();
126 |
127 | const previewUrl = contentTypeConfig?.url || entity.previewUrl || "";
128 | const baseUrl = entity.baseUrl || "";
129 |
130 | // Fetch data that needs to be put into the url (if enabled)
131 | let additionalValues = {}
132 | if (contentTypeConfig?.usesValuesInUrl) {
133 | // Fetch the data
134 | // @ts-ignore
135 | additionalValues = await strapi.query(contentType).findOne({ id: contentId })
136 | }
137 |
138 | return this.replacePreviewParams(baseUrl, contentType, contentId, previewUrl, additionalValues);
139 | },
140 | /**
141 | * Replace URL from string params
142 | *
143 | * @param - The root url of the project's frontend
144 | * @param - The content type to query
145 | * @param - The content type id to query
146 | * @param - The url string to replace
147 | * @param - Additional data of the specific content type that needs to be injected into the url
148 | *
149 | * @returns The replaced URL
150 | */
151 | replacePreviewParams(baseUrl: string, contentType: string, contentId: string, url: string, additionalValues: object) {
152 | return _.template(
153 | url
154 | .replace(":baseUrl", baseUrl)
155 | .replace(":contentType", contentType)
156 | .replace(":id", contentId)
157 | )(additionalValues);
158 | },
159 | /**
160 | * Get settings of the plugin
161 | */
162 | async getSettings() {
163 | // @ts-ignore
164 | return strapi
165 | .store({
166 | type: "plugin",
167 | name: "preview-content",
168 | key: "settings",
169 | })
170 | .get();
171 | },
172 | /**
173 | * Update settings of the plugin
174 | */
175 | async setSettings(value: any) {
176 | // @ts-ignore
177 | return strapi
178 | .store({
179 | type: "plugin",
180 | name: "preview-content",
181 | key: "settings",
182 | })
183 | .set({ value });
184 | },
185 | };
186 |
--------------------------------------------------------------------------------
/strapi-files/v3.6.x/README.md:
--------------------------------------------------------------------------------
1 | ## This files work for Strapi >=3.4.x
2 |
--------------------------------------------------------------------------------
/strapi-files/v3.6.x/extensions/content-manager/admin/src/components/CustomTable/Row/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo, useCallback, useRef } from "react";
2 | import PropTypes from "prop-types";
3 | import { toString } from "lodash";
4 | import { useGlobalContext, request } from "strapi-helper-plugin";
5 | import { IconLinks } from "@buffetjs/core";
6 | import { Duplicate } from "@buffetjs/icons";
7 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
8 | import { useListView } from "../../../hooks";
9 | import { getDisplayedValue } from "../../../utils";
10 | import CustomInputCheckbox from "../../CustomInputCheckbox";
11 | import ActionContainer from "./ActionContainer";
12 | import Cell from "./Cell";
13 |
14 | /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
15 |
16 | function Row({
17 | canCreate,
18 | canDelete,
19 | canUpdate,
20 | isBulkable,
21 | row,
22 | headers,
23 | goTo,
24 | apiID,
25 | previewable,
26 | }) {
27 | const { entriesToDelete, onChangeBulk, onClickDelete } = useListView();
28 | const { emitEvent } = useGlobalContext();
29 | const emitEventRef = useRef(emitEvent);
30 |
31 | const memoizedDisplayedValue = useCallback(
32 | (name, type) => {
33 | return getDisplayedValue(type, row[name], name);
34 | },
35 | [row]
36 | );
37 |
38 | const links = [
39 | previewable && {
40 | icon: ,
41 | onClick: async (e) => {
42 | e.stopPropagation();
43 |
44 | console.log({ apiID });
45 |
46 | try {
47 | const data = await request(
48 | `/preview-content/preview-url/${apiID}/${row.id}`,
49 | {
50 | method: "GET",
51 | }
52 | );
53 |
54 | if (data.url) {
55 | window.open(data.url, "_blank");
56 | } else {
57 | strapi.notification.error("URL not found");
58 | }
59 | } catch (_e) {
60 | strapi.notification.error("URL not found");
61 | }
62 | },
63 | },
64 | {
65 | icon: canCreate ? : null,
66 | onClick: (e) => {
67 | e.stopPropagation();
68 | goTo(`create/clone/${row.id}`);
69 | },
70 | },
71 | {
72 | icon: canUpdate ? : null,
73 | onClick: (e) => {
74 | e.stopPropagation();
75 | emitEventRef.current("willDeleteEntryFromList");
76 | goTo(row.id);
77 | },
78 | },
79 | {
80 | icon: canDelete ? : null,
81 | onClick: (e) => {
82 | e.stopPropagation();
83 | emitEventRef.current("willDeleteEntryFromList");
84 | onClickDelete(row.id);
85 | },
86 | },
87 | ].filter((icon) => icon);
88 |
89 | return (
90 | <>
91 | {isBulkable && (
92 | // eslint-disable-next-line jsx-a11y/click-events-have-key-events
93 | e.stopPropagation()}>
94 | toString(id) === toString(row.id))
99 | .length > 0
100 | }
101 | />
102 | |
103 | )}
104 | {headers.map(
105 | ({
106 | key,
107 | name,
108 | fieldSchema: { type, relationType },
109 | cellFormatter,
110 | metadatas,
111 | queryInfos,
112 | }) => (
113 |
114 | {cellFormatter ? (
115 | cellFormatter(row)
116 | ) : (
117 | |
129 | )}
130 | |
131 | )
132 | )}
133 |
134 |
135 |
136 | >
137 | );
138 | }
139 |
140 | Row.propTypes = {
141 | canCreate: PropTypes.bool.isRequired,
142 | canDelete: PropTypes.bool.isRequired,
143 | canUpdate: PropTypes.bool.isRequired,
144 | headers: PropTypes.array.isRequired,
145 | isBulkable: PropTypes.bool.isRequired,
146 | row: PropTypes.object.isRequired,
147 | goTo: PropTypes.func.isRequired,
148 | apiID: PropTypes.string,
149 | previewable: PropTypes.bool.isRequired,
150 | };
151 |
152 | export default memo(Row);
153 |
--------------------------------------------------------------------------------
/strapi-files/v3.6.x/extensions/content-manager/admin/src/components/CustomTable/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo, useMemo, useState, useEffect } from "react";
2 | import PropTypes from "prop-types";
3 | import { useLocation, useHistory } from "react-router-dom";
4 | import { FormattedMessage, useIntl } from "react-intl";
5 | import { upperFirst, isEmpty } from "lodash";
6 | import {
7 | LoadingIndicator,
8 | useGlobalContext,
9 | request,
10 | } from "strapi-helper-plugin";
11 | import useListView from "../../hooks/useListView";
12 | import { getTrad } from "../../utils";
13 | import State from "../State";
14 | import {
15 | LoadingContainer,
16 | LoadingWrapper,
17 | Table,
18 | TableEmpty,
19 | TableRow,
20 | } from "./styledComponents";
21 | import ActionCollapse from "./ActionCollapse";
22 | import Headers from "./Headers";
23 | import Row from "./Row";
24 |
25 | const CustomTable = ({
26 | canCreate,
27 | canUpdate,
28 | canDelete,
29 | data,
30 | displayedHeaders,
31 | hasDraftAndPublish,
32 | isBulkable,
33 | showLoader,
34 | apiID,
35 | }) => {
36 | const { formatMessage } = useIntl();
37 | const { entriesToDelete, label, filters, _q } = useListView();
38 | const { emitEvent } = useGlobalContext();
39 | const [previewable, setIsPreviewable] = useState(false);
40 |
41 | const { pathname } = useLocation();
42 | const { push } = useHistory();
43 | const headers = useMemo(() => {
44 | if (hasDraftAndPublish) {
45 | return [
46 | ...displayedHeaders,
47 | {
48 | key: "__published_at_temp_key__",
49 | name: "published_at",
50 | fieldSchema: {
51 | type: "custom",
52 | },
53 | metadatas: {
54 | label: formatMessage({
55 | id: getTrad("containers.ListPage.table-headers.published_at"),
56 | }),
57 | searchable: false,
58 | sortable: true,
59 | },
60 | cellFormatter: (cellData) => {
61 | const isPublished = !isEmpty(cellData.published_at);
62 |
63 | return ;
64 | },
65 | },
66 | ];
67 | }
68 |
69 | return displayedHeaders;
70 | }, [formatMessage, hasDraftAndPublish, displayedHeaders]);
71 |
72 | const colSpanLength =
73 | isBulkable && canDelete ? headers.length + 2 : headers.length + 1;
74 |
75 | useEffect(() => {
76 | if (apiID) {
77 | request(`/preview-content/is-previewable/${apiID}`, {
78 | method: "GET",
79 | }).then(({ isPreviewable }) => {
80 | setIsPreviewable(isPreviewable);
81 | });
82 | }
83 | }, [apiID]);
84 |
85 | const handleRowGoTo = (id) => {
86 | emitEvent("willEditEntryFromList");
87 | push({
88 | pathname: `${pathname}/${id}`,
89 | state: { from: pathname },
90 | });
91 | };
92 | const handleEditGoTo = (id) => {
93 | emitEvent("willEditEntryFromButton");
94 | push({
95 | pathname: `${pathname}/${id}`,
96 | state: { from: pathname },
97 | });
98 | };
99 |
100 | const values = { contentType: upperFirst(label), search: _q };
101 | let tableEmptyMsgId = filters.length > 0 ? "withFilters" : "withoutFilter";
102 |
103 | if (_q !== "") {
104 | tableEmptyMsgId = "withSearch";
105 | }
106 |
107 | const content =
108 | data.length === 0 ? (
109 |
110 |
111 |
115 | |
116 |
117 | ) : (
118 | data.map((row) => {
119 | return (
120 | {
123 | e.preventDefault();
124 | e.stopPropagation();
125 |
126 | handleRowGoTo(row.id);
127 | }}
128 | >
129 |
140 |
141 | );
142 | })
143 | );
144 |
145 | if (showLoader) {
146 | return (
147 | <>
148 |
151 |
152 |
153 |
154 |
155 |
156 | >
157 | );
158 | }
159 |
160 | return (
161 |
162 |
163 |
164 | {entriesToDelete.length > 0 && (
165 |
166 | )}
167 | {content}
168 |
169 |
170 | );
171 | };
172 |
173 | CustomTable.propTypes = {
174 | canCreate: PropTypes.bool.isRequired,
175 | canDelete: PropTypes.bool.isRequired,
176 | canUpdate: PropTypes.bool.isRequired,
177 | data: PropTypes.array.isRequired,
178 | displayedHeaders: PropTypes.array.isRequired,
179 | hasDraftAndPublish: PropTypes.bool.isRequired,
180 | isBulkable: PropTypes.bool.isRequired,
181 | showLoader: PropTypes.bool.isRequired,
182 | apiID: PropTypes.string,
183 | };
184 |
185 | export default memo(CustomTable);
186 |
--------------------------------------------------------------------------------
/strapi-files/v3.6.x/extensions/content-manager/admin/src/containers/EditView/Header/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo, useCallback, useMemo, useRef, useState } from "react";
2 | import { useIntl } from "react-intl";
3 | import { Header as PluginHeader } from "@buffetjs/custom";
4 | import { get, isEqual, isEmpty, toString } from "lodash";
5 | import PropTypes from "prop-types";
6 | import isEqualFastCompare from "react-fast-compare";
7 | import { Text } from "@buffetjs/core";
8 | import { templateObject, ModalConfirm } from "strapi-helper-plugin";
9 | import { usePreview } from "strapi-plugin-preview-content";
10 | import { getTrad } from "../../../utils";
11 | import { connect, getDraftRelations, select } from "./utils";
12 |
13 | const primaryButtonObject = {
14 | color: "primary",
15 | type: "button",
16 | style: {
17 | minWidth: 150,
18 | fontWeight: 600,
19 | },
20 | };
21 |
22 | const Header = ({
23 | allowedActions: { canUpdate, canCreate, canPublish },
24 | componentLayouts,
25 | initialData,
26 | isCreatingEntry,
27 | isSingleType,
28 | hasDraftAndPublish,
29 | layout,
30 | modifiedData,
31 | onPublish,
32 | onUnpublish,
33 | status,
34 | }) => {
35 | const { previewHeaderActions } = usePreview();
36 | const [showWarningUnpublish, setWarningUnpublish] = useState(false);
37 | const { formatMessage } = useIntl();
38 | const formatMessageRef = useRef(formatMessage);
39 | const [draftRelationsCount, setDraftRelationsCount] = useState(0);
40 | const [showWarningDraftRelation, setShowWarningDraftRelation] = useState(
41 | false
42 | );
43 | const [shouldUnpublish, setShouldUnpublish] = useState(false);
44 | const [shouldPublish, setShouldPublish] = useState(false);
45 |
46 | const currentContentTypeMainField = useMemo(
47 | () => get(layout, ["settings", "mainField"], "id"),
48 | [layout]
49 | );
50 |
51 | const currentContentTypeName = useMemo(() => get(layout, ["info", "name"]), [
52 | layout,
53 | ]);
54 |
55 | const didChangeData = useMemo(() => {
56 | return (
57 | !isEqual(initialData, modifiedData) ||
58 | (isCreatingEntry && !isEmpty(modifiedData))
59 | );
60 | }, [initialData, isCreatingEntry, modifiedData]);
61 | const apiID = useMemo(() => layout.apiID, [layout.apiID]);
62 |
63 | /* eslint-disable indent */
64 | const entryHeaderTitle = isCreatingEntry
65 | ? formatMessage({
66 | id: getTrad("containers.Edit.pluginHeader.title.new"),
67 | })
68 | : templateObject({ mainField: currentContentTypeMainField }, initialData)
69 | .mainField;
70 | /* eslint-enable indent */
71 |
72 | const headerTitle = useMemo(() => {
73 | const title = isSingleType ? currentContentTypeName : entryHeaderTitle;
74 |
75 | return title || currentContentTypeName;
76 | }, [currentContentTypeName, entryHeaderTitle, isSingleType]);
77 |
78 | const checkIfHasDraftRelations = useCallback(() => {
79 | const count = getDraftRelations(modifiedData, layout, componentLayouts);
80 |
81 | setDraftRelationsCount(count);
82 |
83 | return count > 0;
84 | }, [modifiedData, layout, componentLayouts]);
85 |
86 | const headerActions = useMemo(() => {
87 | let headerActions = [];
88 |
89 | if ((isCreatingEntry && canCreate) || (!isCreatingEntry && canUpdate)) {
90 | headerActions = [
91 | {
92 | disabled: !didChangeData,
93 | color: "success",
94 | label: formatMessage({
95 | id: getTrad("containers.Edit.submit"),
96 | }),
97 | isLoading: status === "submit-pending",
98 | type: "submit",
99 | style: {
100 | minWidth: 150,
101 | fontWeight: 600,
102 | },
103 | },
104 | ];
105 | }
106 |
107 | if (hasDraftAndPublish && canPublish) {
108 | const isPublished = !isEmpty(initialData.published_at);
109 | const isLoading = isPublished
110 | ? status === "unpublish-pending"
111 | : status === "publish-pending";
112 | const labelID = isPublished ? "app.utils.unpublish" : "app.utils.publish";
113 | /* eslint-disable indent */
114 | const onClick = isPublished
115 | ? () => setWarningUnpublish(true)
116 | : (e) => {
117 | if (!checkIfHasDraftRelations()) {
118 | onPublish(e);
119 | } else {
120 | setShowWarningDraftRelation(true);
121 | }
122 | };
123 | /* eslint-enable indent */
124 |
125 | const action = {
126 | ...primaryButtonObject,
127 | disabled: isCreatingEntry || didChangeData,
128 | isLoading,
129 | label: formatMessage({ id: labelID }),
130 | onClick,
131 | };
132 |
133 | headerActions.unshift(action);
134 | }
135 |
136 | return [...previewHeaderActions, ...headerActions];
137 | }, [
138 | isCreatingEntry,
139 | canCreate,
140 | canUpdate,
141 | hasDraftAndPublish,
142 | canPublish,
143 | didChangeData,
144 | formatMessage,
145 | status,
146 | initialData,
147 | onPublish,
148 | checkIfHasDraftRelations,
149 | previewHeaderActions,
150 | ]);
151 |
152 | const headerProps = useMemo(() => {
153 | return {
154 | title: {
155 | label: toString(headerTitle),
156 | },
157 | content: `${formatMessageRef.current({
158 | id: getTrad("api.id"),
159 | })} : ${apiID}`,
160 | actions: headerActions,
161 | };
162 | }, [headerActions, headerTitle, apiID]);
163 |
164 | const toggleWarningPublish = () =>
165 | setWarningUnpublish((prevState) => !prevState);
166 |
167 | const toggleWarningDraftRelation = useCallback(() => {
168 | setShowWarningDraftRelation((prev) => !prev);
169 | }, []);
170 |
171 | const handleConfirmPublish = useCallback(() => {
172 | setShouldPublish(true);
173 | setShowWarningDraftRelation(false);
174 | }, []);
175 |
176 | const handleConfirmUnpublish = useCallback(() => {
177 | setShouldUnpublish(true);
178 | setWarningUnpublish(false);
179 | }, []);
180 |
181 | const handleCloseModalPublish = useCallback(
182 | (e) => {
183 | if (shouldPublish) {
184 | onPublish(e);
185 | }
186 |
187 | setShouldUnpublish(false);
188 | },
189 | [onPublish, shouldPublish]
190 | );
191 |
192 | const handleCloseModalUnpublish = useCallback(
193 | (e) => {
194 | if (shouldUnpublish) {
195 | onUnpublish(e);
196 | }
197 |
198 | setShouldUnpublish(false);
199 | },
200 | [onUnpublish, shouldUnpublish]
201 | );
202 |
203 | const contentIdSuffix = draftRelationsCount > 1 ? "plural" : "singular";
204 |
205 | return (
206 | <>
207 |
208 | {hasDraftAndPublish && (
209 | <>
210 |
,
217 | },
218 | }}
219 | type="xwarning"
220 | onConfirm={handleConfirmUnpublish}
221 | onClosed={handleCloseModalUnpublish}
222 | >
223 |
224 | {formatMessage({
225 | id: getTrad("popUpWarning.warning.unpublish-question"),
226 | })}
227 |
228 |
229 | (
247 |
248 | {chunks}
249 |
250 | ),
251 | br: () =>
,
252 | },
253 | }}
254 | >
255 |
256 | {formatMessage({
257 | id: getTrad("popUpWarning.warning.publish-question"),
258 | })}
259 |
260 |
261 | >
262 | )}
263 | >
264 | );
265 | };
266 |
267 | Header.propTypes = {
268 | allowedActions: PropTypes.shape({
269 | canUpdate: PropTypes.bool.isRequired,
270 | canCreate: PropTypes.bool.isRequired,
271 | canPublish: PropTypes.bool.isRequired,
272 | }).isRequired,
273 | componentLayouts: PropTypes.object.isRequired,
274 | initialData: PropTypes.object.isRequired,
275 | isCreatingEntry: PropTypes.bool.isRequired,
276 | isSingleType: PropTypes.bool.isRequired,
277 | status: PropTypes.string.isRequired,
278 | layout: PropTypes.object.isRequired,
279 | hasDraftAndPublish: PropTypes.bool.isRequired,
280 | modifiedData: PropTypes.object.isRequired,
281 | onPublish: PropTypes.func.isRequired,
282 | onUnpublish: PropTypes.func.isRequired,
283 | };
284 |
285 | const Memoized = memo(Header, isEqualFastCompare);
286 |
287 | export default connect(Memoized, select);
288 |
--------------------------------------------------------------------------------
/strapi-files/v3.6.x/extensions/content-manager/admin/src/containers/EditView/Header/utils/connect.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { PreviewProvider } from "strapi-plugin-preview-content";
3 |
4 |
5 | import { useContentManagerEditViewDataManager } from 'strapi-helper-plugin';
6 |
7 |
8 | function connect(WrappedComponent, select) {
9 | return function (props) {
10 | // eslint-disable-next-line react/prop-types
11 | const selectors = select();
12 | console.log(useContentManagerEditViewDataManager());
13 | const { slug } = useContentManagerEditViewDataManager();
14 |
15 | return (
16 |
17 |
18 |
19 | );
20 | };
21 | }
22 |
23 | export default connect;
24 |
--------------------------------------------------------------------------------
/strapi-files/v3.6.x/extensions/content-manager/admin/src/containers/ListView/index.js:
--------------------------------------------------------------------------------
1 | import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import { bindActionCreators, compose } from 'redux';
5 | import { get, isEmpty } from 'lodash';
6 | import { FormattedMessage, useIntl } from 'react-intl';
7 | import { useHistory, useLocation } from 'react-router-dom';
8 | import { Header } from '@buffetjs/custom';
9 | import { Flex, Padded } from '@buffetjs/core';
10 | import isEqual from 'react-fast-compare';
11 | import { stringify } from 'qs';
12 | import {
13 | CheckPermissions,
14 | InjectionZone,
15 | InjectionZoneList,
16 | PopUpWarning,
17 | useGlobalContext,
18 | useQueryParams,
19 | useUser,
20 | request,
21 | } from 'strapi-helper-plugin';
22 | import pluginId from '../../pluginId';
23 | import pluginPermissions from '../../permissions';
24 | import { formatFiltersFromQuery, getRequestUrl, getTrad } from '../../utils';
25 | import Container from '../../components/Container';
26 | import CustomTable from '../../components/CustomTable';
27 | import FilterPicker from '../../components/FilterPicker';
28 | import Search from '../../components/Search';
29 | import ListViewProvider from '../ListViewProvider';
30 | import { AddFilterCta, FilterIcon, Wrapper } from './components';
31 | import FieldPicker from './FieldPicker';
32 | import Filter from './Filter';
33 | import Footer from './Footer';
34 | import {
35 | getData,
36 | getDataSucceeded,
37 | onChangeBulk,
38 | onChangeBulkSelectall,
39 | onDeleteDataError,
40 | onDeleteDataSucceeded,
41 | onDeleteSeveralDataSucceeded,
42 | setModalLoadingState,
43 | toggleModalDelete,
44 | toggleModalDeleteAll,
45 | setLayout,
46 | onChangeListHeaders,
47 | onResetListHeaders,
48 | } from './actions';
49 | import makeSelectListView from './selectors';
50 | import { getAllAllowedHeaders, getFirstSortableHeader, buildQueryString } from './utils';
51 |
52 | /* eslint-disable react/no-array-index-key */
53 | function ListView({
54 | canCreate,
55 | canDelete,
56 | canRead,
57 | canUpdate,
58 | didDeleteData,
59 | entriesToDelete,
60 | onChangeBulk,
61 | onChangeBulkSelectall,
62 | onDeleteDataError,
63 | onDeleteDataSucceeded,
64 | onDeleteSeveralDataSucceeded,
65 | setModalLoadingState,
66 | showWarningDelete,
67 | showModalConfirmButtonLoading,
68 | showWarningDeleteAll,
69 | toggleModalDelete,
70 | toggleModalDeleteAll,
71 | data,
72 | displayedHeaders,
73 | getData,
74 | getDataSucceeded,
75 | isLoading,
76 | layout,
77 | onChangeListHeaders,
78 | onResetListHeaders,
79 | pagination: { total },
80 | slug,
81 | }) {
82 | const {
83 | contentType: {
84 | attributes,
85 | metadatas,
86 | settings: { bulkable: isBulkable, filterable: isFilterable, searchable: isSearchable },
87 | },
88 | } = layout;
89 |
90 | const { emitEvent } = useGlobalContext();
91 | const { fetchUserPermissions } = useUser();
92 | const emitEventRef = useRef(emitEvent);
93 | const fetchPermissionsRef = useRef(fetchUserPermissions);
94 |
95 | const [{ query }, setQuery] = useQueryParams();
96 | const params = buildQueryString(query);
97 |
98 | const { pathname } = useLocation();
99 | const { push } = useHistory();
100 | const { formatMessage } = useIntl();
101 |
102 | const [isFilterPickerOpen, setFilterPickerState] = useState(false);
103 | const [idToDelete, setIdToDelete] = useState(null);
104 | const contentType = layout.contentType;
105 | const hasDraftAndPublish = get(contentType, 'options.draftAndPublish', false);
106 | const allAllowedHeaders = useMemo(() => getAllAllowedHeaders(attributes), [attributes]);
107 |
108 | const filters = useMemo(() => {
109 | return formatFiltersFromQuery(query);
110 | }, [query]);
111 |
112 | const _sort = query._sort;
113 | const _q = query._q || '';
114 |
115 | const label = contentType.info.label;
116 |
117 | const firstSortableHeader = useMemo(() => getFirstSortableHeader(displayedHeaders), [
118 | displayedHeaders,
119 | ]);
120 |
121 | useEffect(() => {
122 | setFilterPickerState(false);
123 | }, []);
124 |
125 | // Using a ref to avoid requests being fired multiple times on slug on change
126 | // We need it because the hook as mulitple dependencies so it may run before the permissions have checked
127 | const requestUrlRef = useRef('');
128 |
129 | const fetchData = useCallback(
130 | async (endPoint, abortSignal = false) => {
131 | getData();
132 | const signal = abortSignal || new AbortController().signal;
133 |
134 | try {
135 | const { results, pagination } = await request(endPoint, { method: 'GET', signal });
136 |
137 | getDataSucceeded(pagination, results);
138 | } catch (err) {
139 | const resStatus = get(err, 'response.status', null);
140 | console.log(err);
141 |
142 | if (resStatus === 403) {
143 | await fetchPermissionsRef.current();
144 |
145 | strapi.notification.info(getTrad('permissions.not-allowed.update'));
146 |
147 | push('/');
148 |
149 | return;
150 | }
151 |
152 | if (err.name !== 'AbortError') {
153 | console.error(err);
154 | strapi.notification.error(getTrad('error.model.fetch'));
155 | }
156 | }
157 | },
158 | [getData, getDataSucceeded, push]
159 | );
160 |
161 | const handleChangeListLabels = useCallback(
162 | ({ name, value }) => {
163 | // Display a notification if trying to remove the last displayed field
164 |
165 | if (value && displayedHeaders.length === 1) {
166 | strapi.notification.toggle({
167 | type: 'warning',
168 | message: { id: 'content-manager.notification.error.displayedFields' },
169 | });
170 | } else {
171 | emitEventRef.current('didChangeDisplayedFields');
172 |
173 | onChangeListHeaders({ name, value });
174 | }
175 | },
176 | [displayedHeaders, onChangeListHeaders]
177 | );
178 |
179 | const handleConfirmDeleteAllData = useCallback(async () => {
180 | try {
181 | setModalLoadingState();
182 |
183 | await request(getRequestUrl(`collection-types/${slug}/actions/bulkDelete`), {
184 | method: 'POST',
185 | body: { ids: entriesToDelete },
186 | });
187 |
188 | onDeleteSeveralDataSucceeded();
189 | emitEventRef.current('didBulkDeleteEntries');
190 | } catch (err) {
191 | strapi.notification.error(`${pluginId}.error.record.delete`);
192 | }
193 | }, [entriesToDelete, onDeleteSeveralDataSucceeded, slug, setModalLoadingState]);
194 |
195 | const handleConfirmDeleteData = useCallback(async () => {
196 | try {
197 | let trackerProperty = {};
198 |
199 | if (hasDraftAndPublish) {
200 | const dataToDelete = data.find(obj => obj.id.toString() === idToDelete.toString());
201 | const isDraftEntry = isEmpty(dataToDelete.published_at);
202 | const status = isDraftEntry ? 'draft' : 'published';
203 |
204 | trackerProperty = { status };
205 | }
206 |
207 | emitEventRef.current('willDeleteEntry', trackerProperty);
208 | setModalLoadingState();
209 |
210 | await request(getRequestUrl(`collection-types/${slug}/${idToDelete}`), {
211 | method: 'DELETE',
212 | });
213 |
214 | strapi.notification.toggle({
215 | type: 'success',
216 | message: { id: `${pluginId}.success.record.delete` },
217 | });
218 |
219 | // Close the modal and refetch data
220 | onDeleteDataSucceeded();
221 | emitEventRef.current('didDeleteEntry', trackerProperty);
222 | } catch (err) {
223 | const errorMessage = get(
224 | err,
225 | 'response.payload.message',
226 | formatMessage({ id: `${pluginId}.error.record.delete` })
227 | );
228 |
229 | strapi.notification.toggle({
230 | type: 'warning',
231 | message: errorMessage,
232 | });
233 | // Close the modal
234 | onDeleteDataError();
235 | }
236 | }, [
237 | hasDraftAndPublish,
238 | setModalLoadingState,
239 | slug,
240 | idToDelete,
241 | onDeleteDataSucceeded,
242 | data,
243 | formatMessage,
244 | onDeleteDataError,
245 | ]);
246 |
247 | useEffect(() => {
248 | const abortController = new AbortController();
249 | const { signal } = abortController;
250 |
251 | const shouldSendRequest = canRead;
252 | const requestUrl = `/${pluginId}/collection-types/${slug}${params}`;
253 |
254 | if (shouldSendRequest && requestUrl.includes(requestUrlRef.current)) {
255 | fetchData(requestUrl, signal);
256 | }
257 |
258 | return () => {
259 | requestUrlRef.current = slug;
260 | abortController.abort();
261 | };
262 | }, [canRead, getData, slug, params, getDataSucceeded, fetchData]);
263 |
264 | const handleClickDelete = id => {
265 | setIdToDelete(id);
266 | toggleModalDelete();
267 | };
268 |
269 | const handleModalClose = useCallback(() => {
270 | if (didDeleteData) {
271 | const requestUrl = `/${pluginId}/collection-types/${slug}${params}`;
272 |
273 | fetchData(requestUrl);
274 | }
275 | }, [fetchData, didDeleteData, slug, params]);
276 |
277 | const toggleFilterPickerState = useCallback(() => {
278 | setFilterPickerState(prevState => {
279 | if (!prevState) {
280 | emitEventRef.current('willFilterEntries');
281 | }
282 |
283 | return !prevState;
284 | });
285 | }, []);
286 |
287 | const headerAction = useMemo(() => {
288 | if (!canCreate) {
289 | return [];
290 | }
291 |
292 | return [
293 | {
294 | label: formatMessage(
295 | {
296 | id: 'content-manager.containers.List.addAnEntry',
297 | },
298 | {
299 | entity: label || 'Content Manager',
300 | }
301 | ),
302 | onClick: () => {
303 | const trackerProperty = hasDraftAndPublish ? { status: 'draft' } : {};
304 |
305 | emitEventRef.current('willCreateEntry', trackerProperty);
306 | push({
307 | pathname: `${pathname}/create`,
308 | search: query.plugins ? stringify({ plugins: query.plugins }, { encode: false }) : '',
309 | });
310 | },
311 | color: 'primary',
312 | type: 'button',
313 | icon: true,
314 | style: {
315 | paddingLeft: 15,
316 | paddingRight: 15,
317 | fontWeight: 600,
318 | },
319 | },
320 | ];
321 | }, [label, pathname, canCreate, formatMessage, hasDraftAndPublish, push, query]);
322 |
323 | const headerProps = useMemo(() => {
324 | /* eslint-disable indent */
325 | return {
326 | title: {
327 | label: label || 'Content Manager',
328 | },
329 | content: canRead
330 | ? formatMessage(
331 | {
332 | id:
333 | total > 1
334 | ? `${pluginId}.containers.List.pluginHeaderDescription`
335 | : `${pluginId}.containers.List.pluginHeaderDescription.singular`,
336 | },
337 | { label: total }
338 | )
339 | : null,
340 | actions: headerAction,
341 | };
342 | }, [total, headerAction, label, canRead, formatMessage]);
343 |
344 | const handleToggleModalDeleteAll = e => {
345 | emitEventRef.current('willBulkDeleteEntries');
346 | toggleModalDeleteAll(e);
347 | };
348 |
349 | return (
350 | <>
351 |
366 |
376 |
377 | {!isFilterPickerOpen && }
378 | {isSearchable && canRead && (
379 |
380 | )}
381 |
382 | {!canRead && (
383 |
384 |
385 |
386 |
387 |
388 | )}
389 |
390 | {canRead && (
391 |
392 |
393 |
394 |
395 | {isFilterable && (
396 | <>
397 |
398 |
399 |
400 |
401 | {filters.map(({ filter: filterName, name, value }, key) => (
402 |
415 | ))}
416 | >
417 | )}
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
453 |
454 |
455 |
456 |
457 | )}
458 |
459 |
470 |
471 |
472 | 1 ? '.all' : ''
478 | }`
479 | ),
480 | }}
481 | popUpWarningType="danger"
482 | onConfirm={handleConfirmDeleteAllData}
483 | onClosed={handleModalClose}
484 | isConfirmButtonLoading={showModalConfirmButtonLoading}
485 | >
486 |
487 |
488 |
489 | >
490 | );
491 | }
492 |
493 | ListView.defaultProps = {
494 | permissions: [],
495 | };
496 |
497 | ListView.propTypes = {
498 | canCreate: PropTypes.bool.isRequired,
499 | canDelete: PropTypes.bool.isRequired,
500 | canRead: PropTypes.bool.isRequired,
501 | canUpdate: PropTypes.bool.isRequired,
502 | displayedHeaders: PropTypes.array.isRequired,
503 | data: PropTypes.array.isRequired,
504 | didDeleteData: PropTypes.bool.isRequired,
505 | entriesToDelete: PropTypes.array.isRequired,
506 | layout: PropTypes.exact({
507 | components: PropTypes.object.isRequired,
508 | contentType: PropTypes.shape({
509 | attributes: PropTypes.object.isRequired,
510 | metadatas: PropTypes.object.isRequired,
511 | info: PropTypes.shape({ label: PropTypes.string.isRequired }).isRequired,
512 | layouts: PropTypes.shape({
513 | list: PropTypes.array.isRequired,
514 | editRelations: PropTypes.array,
515 | }).isRequired,
516 | options: PropTypes.object.isRequired,
517 | settings: PropTypes.object.isRequired,
518 | }).isRequired,
519 | }).isRequired,
520 | isLoading: PropTypes.bool.isRequired,
521 | getData: PropTypes.func.isRequired,
522 | getDataSucceeded: PropTypes.func.isRequired,
523 | onChangeBulk: PropTypes.func.isRequired,
524 | onChangeBulkSelectall: PropTypes.func.isRequired,
525 | onChangeListHeaders: PropTypes.func.isRequired,
526 | onDeleteDataError: PropTypes.func.isRequired,
527 | onDeleteDataSucceeded: PropTypes.func.isRequired,
528 | onDeleteSeveralDataSucceeded: PropTypes.func.isRequired,
529 | onResetListHeaders: PropTypes.func.isRequired,
530 | pagination: PropTypes.shape({ total: PropTypes.number.isRequired }).isRequired,
531 | setModalLoadingState: PropTypes.func.isRequired,
532 | showModalConfirmButtonLoading: PropTypes.bool.isRequired,
533 | showWarningDelete: PropTypes.bool.isRequired,
534 | showWarningDeleteAll: PropTypes.bool.isRequired,
535 | slug: PropTypes.string.isRequired,
536 | toggleModalDelete: PropTypes.func.isRequired,
537 | toggleModalDeleteAll: PropTypes.func.isRequired,
538 | setLayout: PropTypes.func.isRequired,
539 | permissions: PropTypes.arrayOf(
540 | PropTypes.shape({
541 | action: PropTypes.string.isRequired,
542 | subject: PropTypes.string.isRequired,
543 | properties: PropTypes.object,
544 | conditions: PropTypes.arrayOf(PropTypes.string),
545 | })
546 | ),
547 | };
548 |
549 | const mapStateToProps = makeSelectListView();
550 |
551 | export function mapDispatchToProps(dispatch) {
552 | return bindActionCreators(
553 | {
554 | getData,
555 | getDataSucceeded,
556 | onChangeBulk,
557 | onChangeBulkSelectall,
558 | onChangeListHeaders,
559 | onDeleteDataError,
560 | onDeleteDataSucceeded,
561 | onDeleteSeveralDataSucceeded,
562 | onResetListHeaders,
563 | setModalLoadingState,
564 | toggleModalDelete,
565 | toggleModalDeleteAll,
566 | setLayout,
567 | },
568 | dispatch
569 | );
570 | }
571 | const withConnect = connect(mapStateToProps, mapDispatchToProps);
572 |
573 | export default compose(withConnect)(memo(ListView, isEqual));
574 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": ".",
4 | "lib": ["esnext", "dom"],
5 | "target": "es5",
6 | "module": "commonjs",
7 | "jsx": "react",
8 | "moduleResolution": "node",
9 | "allowSyntheticDefaultImports": true,
10 | "esModuleInterop": true,
11 | "strict": true,
12 | "noUnusedLocals": true,
13 | "noUnusedParameters": true,
14 | "removeComments": false,
15 | "sourceMap": true,
16 | "declaration": true,
17 | "declarationMap": true,
18 | "downlevelIteration": true,
19 | "allowJs": true,
20 | "skipLibCheck": true,
21 | "resolveJsonModule": true
22 | },
23 | "include": ["external-types/*", "src/**/*", "src/**/*.json"],
24 | "exclude": ["**/node_modules/**/*", "**/dist/**/*"]
25 | }
26 |
--------------------------------------------------------------------------------