├── .gitignore
├── CHANGELOG.md
├── README.md
├── package.json
├── src
├── gear.png
├── icon.png
├── index.html
├── index.ts
├── manifest.json
├── readwiseStuff.ts
├── refresh.png
├── snooker.png
└── style.css
├── tsconfig.json
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # vscode
2 | .vscode
3 | @updates.md
4 |
5 | # npm
6 | node_modules
7 | package-lock.json
8 |
9 | # build folder
10 | dist
11 | .vscode
12 |
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # v4
2 | - Bug fix: highlights are now exported in oldest to newest date order
3 | - Bug fix: Fixed "Last Highlight Date"
4 | - New: now exports in the date order of the highlights, and includes a header for the date, thus grouping highlights by date
5 |
6 | # v3
7 | - Image export
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Update from 2022-05-26
2 | Since Craft has slowed down or reoriented themselves with their API, I have decided for now to archive this plugin. It is still working, but I will continue development on it because of the lack of API progress. If someone is interested in taking over this repository, please let me know.
3 |
4 |
5 |
6 | # Introduction
7 | A Craft X extension to import [Readwise](https://readwise.io/) highlights into [Craft](https://www.craft.do/).
8 |
9 | [](https://www.loom.com/share/5c601a6fe43c4b06a57d9f27a5dfb945)
10 |
11 | # Installing the Extension into Craft
12 |
13 | [](https://www.loom.com/share/849a920a8cba4b199dd90a1718f8b025)
14 |
15 | ### Step by step
16 | To install this extension, first download it:
17 | 1. From this GitHub page, click on "Releases"
18 | 1. A list of releases is listed
19 | 1. Expand the first release at the top of the page and download the **craft42-readwise.craftx** file
20 |
21 | Now with the extension downloaded, let us add it to Craft.
22 |
23 | > Note: These instructions assumed you have enabled extension support in Craft.
24 |
25 | 1. Open Craft and click on the button to add an extension
26 | 1. Find the file downloaded in the previous step and select it
27 | 1. Confirm that you want to install the extension
28 | 1. The extension will now be available in the list of installed extensions. Click on the Readwise Highlights extension to open it.
29 |
30 | Now provide your private Readwise access token
31 | 1. From https://readwise.io/access_token, get your access token and copy it.
32 | 1. In the extension, copy the access token into the field provided for it and then click Go
33 |
34 | This extension is now configured for use
35 |
36 | # Features
37 | ## Insert Highlights from a Document
38 | The extension opens to listing the documents with highligths you have in Readwise. The most recently highlighted documents appear at the top.
39 |
40 | Next to each document is a small icon for importing the highlights of that document into craft. Click that icon and the highlights will from that document will be appended to the current file open in craft
41 |
42 | ## Random Highlight
43 | Along the bottom of the extension is a 8-ball button. This will grab a random highlight from your Readwise highlights and insert it into the document.
44 |
45 | ## Refresh button
46 | Along the bottom of the extension is a refresh button that refreshs the book list displayed in the plugin.
47 |
48 | ## Config Button
49 | Along the bottom of the extension is a button for configuring the extension.
50 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "typescript-extension",
3 | "version": "1.0.0",
4 | "description": "",
5 | "scripts": {
6 | "build": "webpack --mode=production",
7 | "dev": "webpack serve --mode=development"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "devDependencies": {
12 | "@craftdocs/craft-extension-api": "0.0.4",
13 | "@craftdocs/craft-extension-api-sdk": "^0.0.4",
14 | "copy-webpack-plugin": "^10.2.0",
15 | "css-loader": "^6.5.1",
16 | "html-webpack-plugin": "^5.5.0",
17 | "react-dev-utils": "^12.0.0",
18 | "style-loader": "^3.3.1",
19 | "ts-loader": "^9.2.6",
20 | "typescript": "^4.5.4",
21 | "url-loader": "^4.1.1",
22 | "webpack": "^5.64.5",
23 | "webpack-cli": "^4.9.1",
24 | "webpack-dev-server": "^4.6.0",
25 | "zip-webpack-plugin": "^4.0.1"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/gear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TfTHacker/craft42-readwise/da8ca76bc1d1071abe62064ee662e3f6e8162985/src/gear.png
--------------------------------------------------------------------------------
/src/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TfTHacker/craft42-readwise/da8ca76bc1d1071abe62064ee662e3f6e8162985/src/icon.png
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
25 |
26 |
Readwise Access Token:
27 |
28 |
Go
29 |
You can get your Readwise access token at this address
https://readwise.io/access_token .
On that page copy your token and paste it into the field above. Then click the go button to go to the primary view of this extension.
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import "./style.css"
2 | import {readwiseGetBookList, readwiseGetHighlightsByBookID, readwiseGetRandomHighlight} from "./readwiseStuff";
3 | import { CraftBlockInsert, CraftImageBlockInsert } from "@craftdocs/craft-extension-api"
4 |
5 | let divBookList: HTMLDivElement;
6 | let divToolbarLower: HTMLDivElement;
7 | let btnRandomHighlight: HTMLButtonElement;
8 | let btnRefreshHighlightList: HTMLButtonElement;
9 | let btnConfigureReadwise: HTMLImageElement;
10 | let divSettingsWrapper: HTMLDivElement;
11 | let inputReadwiseApiToken: HTMLInputElement;
12 | let btnSaveConfig: HTMLDivElement;
13 | let lastBookListQuery: any;
14 |
15 | window.addEventListener("load", async () => {
16 | await initializeUI();
17 | })
18 |
19 |
20 | /**
21 | * Generates the UI for the widget
22 | */
23 | const initializeUI = async ()=> {
24 | divBookList = document.getElementById("book-list");
25 | divToolbarLower = document.getElementById("toolbar-lower");
26 | btnRandomHighlight = document.getElementById('btn-random-highlight');
27 | btnRefreshHighlightList = document.getElementById('btn-refresh-highlights');
28 | btnConfigureReadwise = document.getElementById("btn-configure-readwise");
29 | divSettingsWrapper = document.getElementById("config-readwise-setings");
30 | inputReadwiseApiToken = document.getElementById("readwise-api-key")
31 | btnSaveConfig = document.getElementById("btn-save-config");
32 |
33 | pleaseWaitLoadingHighlights();
34 |
35 | // prepare event handlers
36 | btnRandomHighlight.addEventListener("click", async ()=> await insertRandomHighlight() );
37 |
38 | btnRefreshHighlightList.addEventListener("click", async () => {
39 | divBookList.innerHTML="";
40 | await listBooks()
41 | });
42 |
43 | inputReadwiseApiToken?.addEventListener("keyup", () => {
44 | craft.storageApi.put("readwiseToken", inputReadwiseApiToken.value)
45 | if(inputReadwiseApiToken.value.trim()==="") {
46 | btnRefreshHighlightList.style.display="";
47 | divSettingsWrapper.style.display="inline";
48 | }
49 | });
50 |
51 | btnSaveConfig.addEventListener("click", async ()=> await toggleSettingsOnOff());
52 |
53 | btnConfigureReadwise.addEventListener("click", async ()=> await toggleSettingsOnOff());
54 |
55 | // initialize UI
56 | const rwToken = await craft.storageApi.get("readwiseToken");
57 | if (rwToken.status != "error" && rwToken.data != "") {
58 | inputReadwiseApiToken.value = rwToken.data;
59 | btnRefreshHighlightList.style.display = "inline";
60 | await listBooks();
61 | } else {
62 | divToolbarLower.style.visibility="hidden";
63 | divSettingsWrapper.style.display = "inline";
64 | divBookList.style.height="0px";
65 | }
66 | }
67 |
68 | /**
69 | * Displays the settings area or hides it
70 | */
71 | const toggleSettingsOnOff = async ()=> {
72 | divSettingsWrapper.style.display = divSettingsWrapper.style.display==="" ? "inline" : "";
73 | divBookList.style.height="0px";
74 | if(divSettingsWrapper.style.display==="") {
75 | divBookList.innerHTML = "";
76 | divBookList.style.height="500px";
77 | divToolbarLower.style.visibility="visible";
78 | await listBooks();
79 | } else {
80 | divToolbarLower.style.visibility="hidden";
81 | }
82 | if(inputReadwiseApiToken.value.trim()!="") btnRefreshHighlightList.style.display="inline";
83 | }
84 |
85 | /**
86 | * Message displayed in the book list area while loading books
87 | */
88 | const pleaseWaitLoadingHighlights = ()=>{
89 | divBookList.innerHTML=`Please wait, loading highlights ...
`;
90 | }
91 |
92 | /**
93 | * Inserts into craft a random highlight from Readwise
94 | */
95 | const insertRandomHighlight = async ()=> {
96 | const rwToken = await craft.storageApi.get("readwiseToken");
97 | const highlight = await readwiseGetRandomHighlight( rwToken.data);
98 | const bookInfo = lastBookListQuery.find((b:any)=> b.id.toString() === highlight.book_id.toString() );
99 | craft.dataApi.addBlocks([{
100 | type: "textBlock",
101 | hasFocusDecoration: true,
102 | content: [
103 | { text: highlight.text },
104 | { text: " - " },
105 | { text: bookInfo.title, isItalic:true, link: {
106 | type: "url",
107 | url: (highlight.url != null ? highlight.url : (bookInfo.source_url!=null ? bookInfo.source_url : `https://readwise.io/open/${highlight.id}`) )
108 | }},
109 | { text: " by " + bookInfo.author + "", isItalic:true},
110 | ]
111 | },
112 | { type: "textBlock", content: ""}
113 | ]);
114 | }
115 |
116 | /**
117 | *
118 | * Grabs all highlights for a book and inserts them into Craft
119 | *
120 | * @param bookId ID of the document to retrieve highlights for
121 | *
122 | */
123 | const insertHighlights = async (bookId : string) => {
124 | const rwToken = await craft.storageApi.get("readwiseToken");
125 | const highlights = await readwiseGetHighlightsByBookID( rwToken.data, bookId);
126 | const bookInfo = lastBookListQuery.find((b:any)=> b.id.toString() === bookId );
127 | let output: CraftBlockInsert[] = [];
128 | if(bookInfo.title)
129 | output.push( { type: "textBlock", content: bookInfo.title, style: { textStyle: "title"} } );
130 |
131 | console.log(bookInfo)
132 | if(bookInfo.cover_image_url)
133 | output.push( { type: "imageBlock", url: bookInfo.cover_image_url } );
134 |
135 | if(bookInfo.author)
136 | output.push( { type: "textBlock", content: [ { text: "Author:", isBold: true}, {text: " " + bookInfo.author}]} );
137 |
138 | if(bookInfo.category)
139 | output.push( { type: "textBlock", content: [ { text: "Category:", isBold: true}, {text: " " + bookInfo.category}]} );
140 |
141 | if(bookInfo.source_url)
142 | output.push( { type: "textBlock", content: [ { text: "Source: ", isBold: true},
143 | { text: bookInfo.source_url, link: {type: "url", url: bookInfo.source_url} }] });
144 |
145 | if(bookInfo.tags.length>0) {
146 | const tags = bookInfo.tags.map((t:any)=> "#" + t.name).join(" ");
147 | output.push( { type: "textBlock", content: [ { text: "Tags:", isBold: true}, {text: " " + tags}]} );
148 | }
149 | output.push( { type: "textBlock", content: [ { text: "Import Date:", isBold: true}, {text: " " + (new Date()).toLocaleDateString() + " " + (new Date()).toLocaleTimeString() }]} );
150 |
151 | if(bookInfo.last_highlight_at && bookInfo.last_highlight_at != "")
152 | output.push( { type: "textBlock", content: [ { text: "Last Highlight Date:", isBold: true}, {text: " " + (new Date(bookInfo.last_highlight_at)).toLocaleDateString() + " " + (new Date(bookInfo.last_highlight_at)).toLocaleTimeString() }]} );
153 |
154 | output.push( { type: "textBlock", content: [{ text: `Total Highlights: `, isBold: true}, { text: `${bookInfo.num_highlights}`}]} );
155 |
156 | const bulletStyle = craft.blockFactory.defaultListStyle("bullet");
157 | let lastGroupDate: string;
158 | const allHighlights = highlights.results.reverse().forEach( (highlight:any) => {
159 | const highlightDate: Date = new Date(highlight.highlighted_at);
160 | if(lastGroupDate!==getFormattedDate(highlightDate)) {
161 | lastGroupDate = getFormattedDate(highlightDate);
162 | output.push( { type: "textBlock",
163 | content: [
164 | {
165 | text: getFormattedDate(highlightDate),
166 | link: {type:"dateLink", date: getFormattedDate(highlightDate) }
167 | }
168 | ]}
169 | );
170 | }
171 | output.push(
172 | craft.blockFactory.textBlock({
173 | listStyle: bulletStyle,
174 | indentationLevel: 1,
175 | content: [
176 | { text: highlight.text + " " },
177 | { text: "link", link: { type: "url",
178 | url: (highlight.url != null ? highlight.url : (bookInfo.source_url!=null ? bookInfo.source_url : `https://readwise.io/open/${highlight.id}`)) }
179 | }
180 | ]
181 | })
182 | );
183 | if(highlight.note!="")
184 | output.push( craft.blockFactory.textBlock({ listStyle: bulletStyle, indentationLevel: 2, content: [{text: `Notes: ${highlight.note}`}] }) );
185 | if(highlight.tags.length>0) {
186 | const tags = highlight.tags.map((t:any)=> "#" + t.name).join(" ");
187 | output.push( craft.blockFactory.textBlock({ listStyle: bulletStyle, indentationLevel: 2, content: [{text: `Tags: ${tags}`}] }) );
188 | }
189 | });
190 | console.log(output);
191 | craft.dataApi.addBlocks( output );
192 | }
193 |
194 | const listBooks = async () => {
195 | pleaseWaitLoadingHighlights();
196 | const rwToken = await craft.storageApi.get("readwiseToken");
197 | lastBookListQuery = await readwiseGetBookList(rwToken.data)
198 | let output = "";
199 | if(lastBookListQuery===null) {
200 | divBookList.innerHTML="Information could not be retrieved from Readwise. Please verify the Readwise Access Token."
201 | return;
202 | }
203 | lastBookListQuery.forEach((e : any) => {
204 | if(e.num_highlights===0) return;
205 | output += `
206 |
207 |
208 | ${e.title} (${e.num_highlights})
209 | ${e.author}
210 |
211 |
212 |
`;
213 | });
214 | divBookList.innerHTML = output;
215 | document.querySelectorAll(".btn-insert-highlights").forEach(async (i) => {
216 | i.addEventListener("click", async (e) => await insertHighlights(i.id) );
217 | });
218 | }
219 |
220 | craft.env.setListener((env) => {
221 | switch (env.colorScheme) {
222 | case "dark":
223 | document.body.classList.add("dark");
224 | break;
225 | case "light":
226 | document.body.classList.remove("dark");
227 | break;
228 | }
229 | })
230 |
231 | // Function borrowed from our good friends at: https://github.com/deadlyhifi/craft-x-insert-date
232 | function getFormattedDate(date: Date | null = null) {
233 | const selectedDate = date ? date : new Date();
234 | return `${selectedDate.getFullYear()}-${(
235 | "0" +
236 | (selectedDate.getMonth() + 1)
237 | ).slice(-2)}-${("0" + selectedDate.getDate()).slice(-2)}`;
238 | }
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "TfTHacker.craft42.readwise",
3 | "name": "Readwise Highlights",
4 | "fileName": "craft42-readwise",
5 | "author" : "@TfThacker",
6 | "author-email" : "@TfTHacker",
7 | "description" : "This is a prototype (v4) developed by @TfTHacker and is not affliated with the Readwise company. This extension imports highilghts into current document from Readwise.",
8 | "api": "0.0.4",
9 | "main": "index.html"
10 | }
11 |
--------------------------------------------------------------------------------
/src/readwiseStuff.ts:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * List of last 1000 books highlighted in Readwise
4 | *
5 | * @param rwToken Access token for api call
6 | * @returns list of last 1000 items from readwise
7 | */
8 | export const readwiseGetBookList = async (rwToken : string) => {
9 | try {
10 | const bookList = await fetch('https://readwise.io/api/v2/books/?page_size=1000', {
11 | headers: {
12 | "Authorization": "Token " + rwToken
13 | }
14 | })
15 | // sort by date, newest to oldest
16 | return (await bookList.json()).results.sort( (a:any,b:any)=>{
17 | const c = new Date(a.last_highlight_at).getTime();
18 | const d = new Date(b.last_highlight_at).getTime();
19 | return d - c;
20 | });
21 | } catch (error) {
22 | return null;
23 | }
24 | }
25 |
26 | /**
27 | *
28 | * Retrieves all highlights for a given document
29 | *
30 | * @param rwToken Access token for api call
31 | * @param bookId Retrieve highlights for this book ID
32 | * @returns array of highlights
33 | */
34 | export const readwiseGetHighlightsByBookID = async (rwToken : string, bookId: string) => {
35 | const highlightList = await fetch(`https://readwise.io/api/v2/highlights/?book_id=${bookId}`, {
36 | headers: {
37 | "Authorization": "Token " + rwToken,
38 | }
39 | });
40 | return await highlightList.json();
41 | }
42 |
43 |
44 | /**
45 | *
46 | * Gets detailed information on a document by its book id
47 | *
48 | * @param rwToken Access token for api call
49 | * @param bookId Retrieve highlights for this book ID
50 | * @returns details of a document
51 | */
52 | export const readwiseGetBookDetails= async (rwToken : string, bookId: string) => {
53 | const highlightList = await fetch(`https://readwise.io/api/v2/highlights/?book_id=${bookId}`, {
54 | headers: {
55 | "Authorization": "Token " + rwToken,
56 | }
57 | });
58 | return await highlightList.json();
59 | }
60 |
61 |
62 | /**
63 | *
64 | * generates a random number
65 | *
66 | * @param max highest number
67 | * @returns number
68 | */
69 | function getRandomInt(max: number) {
70 | return Math.floor(Math.random() * max);
71 | }
72 |
73 |
74 | /**
75 | *
76 | * Grabs a random highlight from readwise
77 | *
78 | * @param rwToken Access token for api call
79 | * @returns
80 | */
81 | export const readwiseGetRandomHighlight =async (rwToken : string) => {
82 | const highlights = await fetch(`https://readwise.io/api/v2/highlights/?page_size=500`, {
83 | headers: {
84 | "Authorization": "Token " + rwToken,
85 | }
86 | });
87 | const respone = await highlights.json();
88 | const randomIndex = getRandomInt(respone.results.length);
89 | return respone.results[randomIndex];
90 | }
91 |
92 |
--------------------------------------------------------------------------------
/src/refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TfTHacker/craft42-readwise/da8ca76bc1d1071abe62064ee662e3f6e8162985/src/refresh.png
--------------------------------------------------------------------------------
/src/snooker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TfTHacker/craft42-readwise/da8ca76bc1d1071abe62064ee662e3f6e8162985/src/snooker.png
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 |
2 | body {
3 | display: flex;
4 | flex-direction: column;
5 | align-items: center;
6 | overflow-y: hidden;
7 | }
8 |
9 | body.dark {
10 | background-color: #222222;
11 | color: silver;
12 | }
13 |
14 | .extension-header {
15 | padding-top: 15px;
16 | padding-bottom: 10px;
17 | font-size: 16pt;
18 | font-weight: bold;
19 | color: darkcyan;
20 | }
21 |
22 | #book-list {
23 | overflow-x:hidden;
24 | overflow-y:auto;
25 | width:255px;
26 | height:500px;
27 | border-color: darkcyan !important;
28 | border-width: 1px;
29 | border-style:solid;
30 | }
31 |
32 | .dark #book-list {
33 | border-color: darkslategray !important;
34 | }
35 |
36 | #book-list::-webkit-scrollbar {
37 | width: 8px;
38 | }
39 |
40 | #book-list::-webkit-scrollbar-track {
41 | background-color: darkcyan;
42 | }
43 |
44 | .dark #book-list::-webkit-scrollbar-track {
45 | background-color: darkslategray;
46 | }
47 |
48 | #book-list::-webkit-scrollbar-thumb {
49 | box-shadow: inset 0 0 6px lightblue;
50 | }
51 |
52 | .readwise-book-container {
53 | padding-bottom:4px;
54 | width:250px;
55 | display: flex;
56 | border-bottom-style: inset;
57 | border-color: lightblue;
58 | border-bottom-width:1px;
59 | padding-top: 3px;
60 | }
61 |
62 | .dark .readwise-book-container {
63 | border-color: darkslategray;
64 | }
65 |
66 | .book-images-wrapper {
67 | width: 50px;
68 | }
69 |
70 | .book-images {
71 | width: 45px;
72 | margin-left: 5px;
73 | border-radius: 10%;
74 | }
75 |
76 | .book-info {
77 | padding-left:5px;
78 | width: 100%;
79 | }
80 |
81 | .btn-insert-highlights {
82 | filter: invert(0.95);
83 | width: 20px !important;
84 | padding-right: 4px;
85 | position: relative;
86 | float: right;
87 | }
88 |
89 | .dark .btn-insert-highlights {
90 | filter: invert(0.2);
91 | }
92 |
93 |
94 | .btn-insert-highlights:hover {
95 | filter: invert(0.85);
96 | }
97 |
98 | #btn-save-config, .button-refresh-highlights {
99 | background-color: lightblue;
100 | border: none;
101 | text-align: center;
102 | text-decoration: none;
103 | font-size: 12px;
104 | border-radius: 6px;
105 | cursor: pointer;
106 | height: 25px;
107 | }
108 |
109 | .button-refresh-highlights {
110 | width: 145px;
111 | display: none;
112 | }
113 |
114 | .dark #btn-save-config, .dark .button-refresh-highlights {
115 | background-color: darkcyan;
116 | color: white;
117 | }
118 |
119 | #btn-save-config:hover {
120 | background-color: rgb(202, 235, 247);
121 | }
122 |
123 | .dark #btn-save-config:hover {
124 | background-color: rgb(34, 160, 160);
125 | }
126 |
127 | .toolbar-lower {
128 | visibility: hidden;
129 | }
130 |
131 | .button-toolbar-lower {
132 | padding-left: 20px;
133 | }
134 |
135 | .button-toolbar-lower-image {
136 | height: 25px;
137 | position: relative;
138 | top:-2px;
139 | filter: invert(0.6);
140 | cursor: pointer;
141 | padding-bottom: 0px;
142 | }
143 |
144 | .button-toolbar-lower-image:hover {
145 | filter: invert(0.4);
146 | }
147 |
148 | #gear-image {
149 | height: 22px;
150 | padding-top: 2px;
151 | }
152 |
153 | #config-readwise-setings {
154 | display: none;
155 | margin-bottom: 20px;
156 | }
157 |
158 |
159 | #btn-configure-readwise {
160 | filter: invert(0.8);
161 | }
162 |
163 | .dark a {
164 | color: aqua
165 | }
166 |
167 |
168 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "inlineSourceMap": true,
5 | "inlineSources": true,
6 | "module": "amd",
7 | "target": "es6",
8 | "strict": true,
9 | "moduleResolution": "node",
10 | "importHelpers": true,
11 | "lib": [
12 | "dom",
13 | "esnext"
14 | ],
15 | "typeRoots": [
16 | "./node_modules/@craftdocs/craft-extension-api"
17 | ]
18 | },
19 | "include": [
20 | "src"
21 | ],
22 | "exclude": [
23 | "node_modules"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const manifestJson = require('./src/manifest.json')
3 | const HtmlWebpackPlugin = require('html-webpack-plugin')
4 | const InlineChunkHtmlPlugin = require('react-dev-utils/InlineChunkHtmlPlugin')
5 | const ZipPlugin = require('zip-webpack-plugin')
6 | const CopyPlugin = require('copy-webpack-plugin')
7 | const { CraftExtensionApiPlugin } = require('@craftdocs/craft-extension-api-sdk')
8 |
9 | module.exports = (env, argv) => {
10 | const isProd = argv.mode === 'production'
11 |
12 | return {
13 | entry: {
14 | app: './src/index.ts'
15 | },
16 | output: {
17 | filename: '[name].js',
18 | path: path.resolve(__dirname, 'dist'),
19 | publicPath: "/"
20 | },
21 | resolve: {
22 | extensions: ['.tsx', '.ts', '.jsx', '.js', '.json']
23 | },
24 | module: {
25 | rules: [
26 | {
27 | test: /\.tsx?$/,
28 | use: 'ts-loader',
29 | exclude: /node_modules/
30 | },
31 | // Enables including CSS by doing "import './file.css'" in your TypeScript code
32 | {
33 | test: /\.css$/,
34 | use: ['style-loader', 'css-loader']
35 | },
36 | // Allows you to use import './file.png'" in your code to get a data URI
37 | {
38 | test: /\.(svg|png|bmp|jpg|jpeg|webp|gif)$/i,
39 | use: [
40 | {
41 | loader: 'url-loader',
42 | options: {
43 | limit: 100000000,
44 | },
45 | },
46 | ],
47 | }
48 | ]
49 | },
50 | plugins: [
51 | new CraftExtensionApiPlugin(),
52 | new HtmlWebpackPlugin({
53 | inject: 'body',
54 | template: path.resolve(__dirname, 'src/index.html'),
55 | chunks: ['app']
56 | }),
57 | isProd && new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/app/]),
58 | isProd && new CopyPlugin({
59 | patterns: [
60 | { from: './src/manifest.json' },
61 | { from: './src/icon.png' }
62 | ],
63 | }),
64 | isProd && new ZipPlugin({
65 | filename: manifestJson.fileName,
66 | extension: 'craftx',
67 | include: [
68 | 'app.js.LICENSE.txt',
69 | 'index.html',
70 | 'manifest.json',
71 | 'icon.png'
72 | ]
73 | })
74 | ].filter(Boolean)
75 | }
76 | }
77 |
--------------------------------------------------------------------------------