diff --git a/.babelrc b/.babelrc index 8335bb5..36ed323 100644 --- a/.babelrc +++ b/.babelrc @@ -7,6 +7,7 @@ "plugins": [ "@babel/plugin-syntax-dynamic-import", "@babel/plugin-proposal-class-properties", - "@babel/plugin-proposal-object-rest-spread" + "@babel/plugin-proposal-object-rest-spread", + "@babel/plugin-transform-runtime" ] } diff --git a/package-lock.json b/package-lock.json index 22f2cb4..06c5b8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,12 +16,9 @@ "react-ace": "^5.9.0", "react-custom-scrollbars": "^4.2.1", "react-dom": "^16.8.6", - "react-redux": "^5.0.6", "react-router-dom": "^4.2.2", "react-tag-input": "^5.2.3", - "redux": "^3.7.2", - "redux-immutable": "^4.0.0", - "redux-thunk": "^2.2.0" + "recoil": "^0.1.2" }, "devDependencies": { "@babel/core": "^7.0.0", @@ -29,12 +26,14 @@ "@babel/plugin-proposal-class-properties": "^7.0.0", "@babel/plugin-proposal-object-rest-spread": "^7.0.0", "@babel/plugin-syntax-dynamic-import": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.12.10", "@babel/preset-env": "^7.0.0", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.12.7", + "@babel/runtime": "^7.12.5", + "@types/parse-link-header": "^1.0.0", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", - "@types/react-redux": "^7.1.15", "babel-core": "^7.0.0-bridge.0", "babel-jest": "^23.4.2", "babel-loader": "^8.0.0", @@ -2492,6 +2491,20 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.10.tgz", + "integrity": "sha512-xOrUfzPxw7+WDm9igMgQCbO3cJKymX7dFdsgRr1eu9n3KjjyU4pptIXbXPseQDquw+W+RuJEJMHKHNsPNNm3CA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.12.5", + "@babel/helper-plugin-utils": "^7.10.4", + "semver": "^5.5.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-shorthand-properties": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", @@ -2958,25 +2971,6 @@ "@types/node": "*" } }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "dev": true, - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "node_modules/@types/hoist-non-react-statics/node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dev": true, - "dependencies": { - "react-is": "^16.7.0" - } - }, "node_modules/@types/invariant": { "version": "2.2.34", "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.34.tgz", @@ -3011,6 +3005,12 @@ "integrity": "sha512-bjk1RIeZBCe/WukrFToIVegOf91Pebr8cXYBwLBIsfiGWVQ+ifwWsT59H3RxrWzWrzd1l/Amk1/ioY5Fq3/bpA==", "dev": true }, + "node_modules/@types/parse-link-header": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-link-header/-/parse-link-header-1.0.0.tgz", + "integrity": "sha512-fCA3btjE7QFeRLfcD0Sjg+6/CnmC66HpMBoRfRzd2raTaWMJV21CCZ0LO8MOqf8onl5n0EPfjq4zDhbyX8SVwA==", + "dev": true + }, "node_modules/@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", @@ -3043,37 +3043,6 @@ "@types/react": "*" } }, - "node_modules/@types/react-redux": { - "version": "7.1.15", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.15.tgz", - "integrity": "sha512-+piY42tUflPfI7y9Vy3UkG6MEMuJlrxfdtgeUcWmd5Z0qB57NXAPG6smkqu1DNXluo/KDyXPeRYhcFzMwt1BEA==", - "dev": true, - "dependencies": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" - } - }, - "node_modules/@types/react-redux/node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dev": true, - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/@types/react-redux/node_modules/redux": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz", - "integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==", - "dev": true, - "dependencies": { - "loose-envify": "^1.4.0", - "symbol-observable": "^1.2.0" - } - }, "node_modules/@types/redux": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@types/redux/-/redux-3.6.0.tgz", @@ -4761,16 +4730,6 @@ "node": ">=6" } }, - "node_modules/bin-version-check/node_modules/semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true, - "optional": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/bin-version/node_modules/cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -18322,14 +18281,13 @@ } }, "node_modules/react": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react/-/react-16.8.6.tgz", - "integrity": "sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==", + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.13.6" + "prop-types": "^15.6.2" }, "engines": { "node": ">=0.10.0" @@ -18458,19 +18416,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-redux": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz", - "integrity": "sha512-5VI8EV5hdgNgyjfmWzBbdrqUkrVRKlyTKk1sGH3jzM2M2Mhj/seQgPXaz6gVAj2lz/nz688AdTqMO18Lr24Zhg==", - "dependencies": { - "hoist-non-react-statics": "^2.5.0", - "invariant": "^2.0.0", - "lodash": "^4.17.5", - "lodash-es": "^4.17.5", - "loose-envify": "^1.1.0", - "prop-types": "^15.6.0" - } - }, "node_modules/react-router": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.2.0.tgz", @@ -18646,6 +18591,22 @@ "node": ">=4" } }, + "node_modules/recoil": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.1.2.tgz", + "integrity": "sha512-hIRrHlkmW4yITlBFprVYjVPhzPKYrJKoaDrrJtAtbkMeXfXaa/XE5OlyR10n+rNfnKWNToCKb3Z4fo86IGjkzg==", + "peerDependencies": { + "react": "^16.13.1" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/recompose": { "version": "0.27.1", "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.27.1.tgz", @@ -18719,16 +18680,6 @@ "symbol-observable": "^1.0.3" } }, - "node_modules/redux-immutable": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-4.0.0.tgz", - "integrity": "sha1-Ohoy32Y2ZGK2NpHw4dw15HK7yfM=" - }, - "node_modules/redux-thunk": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.2.0.tgz", - "integrity": "sha1-5hWhbha0ehmlFXZhM9Hj6Zt4UuU=" - }, "node_modules/regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", @@ -19685,9 +19636,9 @@ } }, "node_modules/semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true, "bin": { "semver": "bin/semver" @@ -21230,15 +21181,6 @@ "node": ">= 4" } }, - "node_modules/terser-webpack-plugin/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/terser-webpack-plugin/node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -27381,6 +27323,17 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, + "@babel/plugin-transform-runtime": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.10.tgz", + "integrity": "sha512-xOrUfzPxw7+WDm9igMgQCbO3cJKymX7dFdsgRr1eu9n3KjjyU4pptIXbXPseQDquw+W+RuJEJMHKHNsPNNm3CA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.12.5", + "@babel/helper-plugin-utils": "^7.10.4", + "semver": "^5.5.1" + } + }, "@babel/plugin-transform-shorthand-properties": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", @@ -27772,27 +27725,6 @@ "@types/node": "*" } }, - "@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "dev": true, - "requires": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - }, - "dependencies": { - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dev": true, - "requires": { - "react-is": "^16.7.0" - } - } - } - }, "@types/invariant": { "version": "2.2.34", "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.34.tgz", @@ -27827,6 +27759,12 @@ "integrity": "sha512-bjk1RIeZBCe/WukrFToIVegOf91Pebr8cXYBwLBIsfiGWVQ+ifwWsT59H3RxrWzWrzd1l/Amk1/ioY5Fq3/bpA==", "dev": true }, + "@types/parse-link-header": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-link-header/-/parse-link-header-1.0.0.tgz", + "integrity": "sha512-fCA3btjE7QFeRLfcD0Sjg+6/CnmC66HpMBoRfRzd2raTaWMJV21CCZ0LO8MOqf8onl5n0EPfjq4zDhbyX8SVwA==", + "dev": true + }, "@types/prop-types": { "version": "15.7.3", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", @@ -27859,39 +27797,6 @@ "@types/react": "*" } }, - "@types/react-redux": { - "version": "7.1.15", - "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.15.tgz", - "integrity": "sha512-+piY42tUflPfI7y9Vy3UkG6MEMuJlrxfdtgeUcWmd5Z0qB57NXAPG6smkqu1DNXluo/KDyXPeRYhcFzMwt1BEA==", - "dev": true, - "requires": { - "@types/hoist-non-react-statics": "^3.3.0", - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0", - "redux": "^4.0.0" - }, - "dependencies": { - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dev": true, - "requires": { - "react-is": "^16.7.0" - } - }, - "redux": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz", - "integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==", - "dev": true, - "requires": { - "loose-envify": "^1.4.0", - "symbol-observable": "^1.2.0" - } - } - } - }, "@types/redux": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@types/redux/-/redux-3.6.0.tgz", @@ -29385,15 +29290,6 @@ "bin-version": "^3.0.0", "semver": "^5.6.0", "semver-truncate": "^1.1.2" - }, - "dependencies": { - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true, - "optional": true - } } }, "bin-wrapper": { @@ -40535,14 +40431,13 @@ } }, "react": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react/-/react-16.8.6.tgz", - "integrity": "sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==", + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.13.6" + "prop-types": "^15.6.2" }, "dependencies": { "prop-types": { @@ -40678,19 +40573,6 @@ "prop-types": "^15.6.0" } }, - "react-redux": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-5.0.7.tgz", - "integrity": "sha512-5VI8EV5hdgNgyjfmWzBbdrqUkrVRKlyTKk1sGH3jzM2M2Mhj/seQgPXaz6gVAj2lz/nz688AdTqMO18Lr24Zhg==", - "requires": { - "hoist-non-react-statics": "^2.5.0", - "invariant": "^2.0.0", - "lodash": "^4.17.5", - "lodash-es": "^4.17.5", - "loose-envify": "^1.1.0", - "prop-types": "^15.6.0" - } - }, "react-router": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-4.2.0.tgz", @@ -40836,6 +40718,12 @@ "util.promisify": "^1.0.0" } }, + "recoil": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.1.2.tgz", + "integrity": "sha512-hIRrHlkmW4yITlBFprVYjVPhzPKYrJKoaDrrJtAtbkMeXfXaa/XE5OlyR10n+rNfnKWNToCKb3Z4fo86IGjkzg==", + "requires": {} + }, "recompose": { "version": "0.27.1", "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.27.1.tgz", @@ -40907,16 +40795,6 @@ "symbol-observable": "^1.0.3" } }, - "redux-immutable": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-4.0.0.tgz", - "integrity": "sha1-Ohoy32Y2ZGK2NpHw4dw15HK7yfM=" - }, - "redux-thunk": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.2.0.tgz", - "integrity": "sha1-5hWhbha0ehmlFXZhM9Hj6Zt4UuU=" - }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", @@ -41720,9 +41598,9 @@ } }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, "semver-regex": { @@ -43019,12 +42897,6 @@ "ajv-keywords": "^3.1.0" } }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/package.json b/package.json index 0bfa620..bc1aa7c 100644 --- a/package.json +++ b/package.json @@ -37,12 +37,9 @@ "react-ace": "^5.9.0", "react-custom-scrollbars": "^4.2.1", "react-dom": "^16.8.6", - "react-redux": "^5.0.6", "react-router-dom": "^4.2.2", "react-tag-input": "^5.2.3", - "redux": "^3.7.2", - "redux-immutable": "^4.0.0", - "redux-thunk": "^2.2.0" + "recoil": "^0.1.2" }, "devDependencies": { "@babel/core": "^7.0.0", @@ -50,12 +47,14 @@ "@babel/plugin-proposal-class-properties": "^7.0.0", "@babel/plugin-proposal-object-rest-spread": "^7.0.0", "@babel/plugin-syntax-dynamic-import": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.12.10", "@babel/preset-env": "^7.0.0", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.12.7", + "@babel/runtime": "^7.12.5", + "@types/parse-link-header": "^1.0.0", "@types/react": "^17.0.0", "@types/react-dom": "^17.0.0", - "@types/react-redux": "^7.1.15", "babel-core": "^7.0.0-bridge.0", "babel-jest": "^23.4.2", "babel-loader": "^8.0.0", diff --git a/src/actions/index.js b/src/actions/index.js deleted file mode 100644 index 45a6c10..0000000 --- a/src/actions/index.js +++ /dev/null @@ -1,71 +0,0 @@ -import parseLinkHeader from 'parse-link-header' -import { getApiUri } from '../misc/url' -import { - SET_RECENT_SNIPPETS, - SET_PAGINATION_LINKS, - SET_SNIPPET, - SET_SYNTAXES, -} from './types' - -export const setRecentSnippets = snippets => ({ - type: SET_RECENT_SNIPPETS, - snippets, -}) - -export const setPaginationLinks = links => ({ - type: SET_PAGINATION_LINKS, - links, -}) - -export const setSnippet = snippet => ({ - type: SET_SNIPPET, - snippet, -}) - -export const setSyntaxes = syntaxes => ({ - type: SET_SYNTAXES, - syntaxes, -}) - -export const fetchRecentSnippets = marker => (dispatch) => { - let qs = '' - if (marker) { qs = `&marker=${marker}` } - - return fetch(getApiUri(`snippets?limit=20${qs}`)) - .then((response) => { - const links = parseLinkHeader(response.headers.get('Link')) - - dispatch(setPaginationLinks(links)) - return response.json() - }) - .then(json => dispatch(setRecentSnippets(json))) -} - -export const fetchSnippet = id => dispatch => ( - fetch(getApiUri(`snippets/${id}`)) - .then(response => response.json()) - .then(json => dispatch(setSnippet(json))) -) - -export const fetchSyntaxes = dispatch => ( - fetch(getApiUri('syntaxes')) - .then(response => response.json()) - .then(json => dispatch(setSyntaxes(json))) -) - -export const postSnippet = (snippet, onSuccess, onError = () => {}) => dispatch => ( - fetch(getApiUri('snippets'), { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(snippet), - }) - .then(response => response.json()) - .then((json) => { - dispatch(setSnippet(json)) - onSuccess(json) - }) - .catch(onError) -) diff --git a/src/actions/types.js b/src/actions/types.js deleted file mode 100644 index 30db527..0000000 --- a/src/actions/types.js +++ /dev/null @@ -1,4 +0,0 @@ -export const SET_PAGINATION_LINKS = 'SET_PAGINATION_LINKS' -export const SET_RECENT_SNIPPETS = 'SET_RECENT_SNIPPETS' -export const SET_SNIPPET = 'SET_SNIPPET' -export const SET_SYNTAXES = 'SET_SYNTAXES' diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..85d4d08 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,41 @@ +import parseLinkHeader from 'parse-link-header' + +import { getApiUri } from '../misc/url' +import { Snippet, RawSnippet } from '../store' + +export const fetchSnippet = (id: number): Promise => { + return fetch(getApiUri(`snippets/${id}`)) + .then(response => response.json()) +} + +export const fetchSyntaxes = (): Promise => { + return fetch(getApiUri('syntaxes')) + .then(response => response.json()) +} + +export const fetchRecentSnippets = (marker: number): Promise<{ snippets: Snippet[], pagination: any}> => { + let qs = '' + if (marker) { qs = `&marker=${marker}` } + let pagination = null + + return fetch(getApiUri(`snippets?limit=20${qs}`)) + .then(response => { + pagination = parseLinkHeader(response.headers.get('Link')) + return response.json() + }) + .then(snippets => ({ snippets, pagination })) +} + +export const postSnippet = (snippet: RawSnippet, onSuccess: (snippet: Snippet) => void, onError = () => {}): Promise => { + return fetch(getApiUri('snippets'), { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(snippet), + }) + .then(response => response.json()) + .then(onSuccess) + .catch(onError) +} diff --git a/src/components/ListBox.jsx b/src/components/ListBox.jsx index de1a1a4..861b11e 100644 --- a/src/components/ListBox.jsx +++ b/src/components/ListBox.jsx @@ -11,7 +11,7 @@ const ListBox = (props) => { // extend behaviour (e.g. some ListBoxWithSearchbar), while the latter - // when you want a standalone ListBox component. let select = props.selected || selected - const def = props.items.get(0) + const def = props.items[0] // If selected item is not a part of new items, aggressively fallback to // first item from the list. We're doing it to be protected from cases @@ -32,7 +32,7 @@ const ListBox = (props) => { } const renderItems = () => { - if (!items.size) { + if (!items.length) { return
  • No results found
  • } diff --git a/src/components/NewSnippet.jsx b/src/components/NewSnippet.jsx index 2e39af8..17f3e6c 100644 --- a/src/components/NewSnippet.jsx +++ b/src/components/NewSnippet.jsx @@ -1,17 +1,15 @@ -import React, { useEffect, useRef, useMemo } from 'react' -import { connect } from 'react-redux' +import React, { useRef } from 'react' import AceEditor from 'react-ace' import { WithContext as Tags } from 'react-tag-input' +import { useRecoilValueLoadable } from 'recoil' import 'brace/theme/textmate' import Notification from './common/Notification' import ListBox from './ListBox' -import { fetchSyntaxes, postSnippet } from '../actions' - import { onEditorLoad } from '../misc/editor' -import { getCurrentModeName, getModesByName } from '../misc/modes' +import { getCurrentModeName } from '../misc/modes' import { validateSnippet } from '../entries/snippetValidation' import { delimeterKeys } from '../entries/keyboardKeys' @@ -19,12 +17,14 @@ import { defaultOptions } from '../entries/aceEditorOptions' import withSearch from '../hoc/withSearch' import useForm from '../hooks/useForm' +import { syntaxesQuery } from '../store' +import { postSnippet } from '../api' import '../styles/NewSnippet.styl' const ListBoxWithSearch = withSearch(ListBox) -const NewSnippet = ({ fetchSyntaxes, postSnippet, history, syntaxes }) => { +const NewSnippet = ({ history }) => { const snippetHeader = useRef() const { values: { title = '', syntax = '', content = '', tags = [] }, @@ -32,17 +32,16 @@ const NewSnippet = ({ fetchSyntaxes, postSnippet, history, syntaxes }) => { handleChange, handleSubmit, } = useForm(post, validate) - - useEffect(() => { - fetchSyntaxes() - }, []) + const syntaxes = useRecoilValueLoadable(syntaxesQuery) function validate() { return validateSnippet({ content: content.trim() }) } function post() { - postSnippet({ content, title, tags: tags.map(tag => tag.text), syntax, cb: json => history.push(`/${json.id}`) }) + postSnippet({ content, title, tags: tags.map(tag => tag.text), syntax }, json => { + history.push(`/${json.id}`) + }) } const onTagBlur = tag => onTagAdded({ id: tag, text: tag }) @@ -60,15 +59,6 @@ const NewSnippet = ({ fetchSyntaxes, postSnippet, history, syntaxes }) => { const handleSyntax = syntax => ({ syntax }) const handleContent = content => ({ content }) - const memoizedSyntaxes = useMemo(() => { - const { modesByName } = getModesByName() - - return syntaxes.map(item => ({ - name: modesByName[item].caption, - value: item, - })) - }, [syntaxes]) - const renderValidationError = () => (error && ) return ( @@ -119,7 +109,7 @@ const NewSnippet = ({ fetchSyntaxes, postSnippet, history, syntaxes }) => {
    handleChange(syntax, handleSyntax)} />
    @@ -127,15 +117,4 @@ const NewSnippet = ({ fetchSyntaxes, postSnippet, history, syntaxes }) => { ) } -const mapStateToProps = state => ({ - syntaxes: state.get('syntaxes'), -}) - -const mapDispatchToProps = dispatch => ({ - fetchSyntaxes: () => dispatch(fetchSyntaxes), - postSnippet: ({ content, title, tags, syntax, cb }) => dispatch(postSnippet({ - content, title, tags, syntax, - }, cb)), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(NewSnippet) +export default NewSnippet diff --git a/src/components/RecentSnippetItem.jsx b/src/components/RecentSnippetItem.jsx index bfbbce3..a2b0dd3 100644 --- a/src/components/RecentSnippetItem.jsx +++ b/src/components/RecentSnippetItem.jsx @@ -4,14 +4,10 @@ import { Link } from 'react-router-dom' import SnippetTags from './common/SnippetTags' import { downloadSnippet } from '../misc/download' -import { getCurrentModeCaption } from '../misc/modes' -import { getSnippetTitle, formatDate } from '../misc/snippet' -import { getRawUrl } from '../misc/url' +import { getSnippetMetadata } from '../misc/snippet' const RecentSnippetItem = ({ snippet }) => { - const syntax = getCurrentModeCaption(snippet.get('syntax')) - const title = getSnippetTitle(snippet) - const rawUrl = getRawUrl(snippet.get('id')) + const { syntax, title, rawUrl, createdAt } = getSnippetMetadata(snippet) const download = () => downloadSnippet(snippet) @@ -19,17 +15,17 @@ const RecentSnippetItem = ({ snippet }) => {
  • - {title} - + {title} +
    - {formatDate(snippet.get('created_at'))}, by Guest + {createdAt}, by Guest
    {syntax}
    Raw - Show + Show
  • diff --git a/src/components/RecentSnippets.jsx b/src/components/RecentSnippets.jsx index cef1b2e..f778227 100644 --- a/src/components/RecentSnippets.jsx +++ b/src/components/RecentSnippets.jsx @@ -1,70 +1,47 @@ -import React, { Fragment, useEffect } from 'react' -import { connect } from 'react-redux' +import React, { Fragment, useState } from 'react' +import { useSetRecoilState, useRecoilValueLoadable } from 'recoil' +import { recentSnippetsQuery, recentSnippetsState } from '../store' +import Spinner from './common/Spinner' import RecentSnippetItem from './RecentSnippetItem' - -import { fetchRecentSnippets } from '../actions' +import { scrollTop } from '../misc/snippet' import '../styles/RecentSnippets.styl' -const scrollTop = () => { - window.scroll({ top: 0, behavior: 'smooth' }) -} - -const RecentSnippets = ({ fetchRecentSnippets, pagination, snippets, recent }) => { - const older = pagination.get('next') - const newer = pagination.get('prev') - - useEffect(() => { - let marker = null - - if (pagination.get('prev')) { - marker = recent.get(0) + 1 - } - - fetchRecentSnippets(marker) - }, []) - - const newerSetOfSnippets = () => { - const prev = pagination.get('prev') +const RecentSnippets = () => { + const [marker, setMarker] = useState(null) + const setSnippets = useSetRecoilState(recentSnippetsState) + const values = useRecoilValueLoadable(recentSnippetsQuery(marker)) - if (prev) { - const marker = Number(prev.marker) - - fetchRecentSnippets(marker) - } - - scrollTop() + if (values.state !== 'hasValue') { + return } - const olderSetOfSnippets = () => { - const marker = Number(pagination.get('next').marker) + const { pagination, recentIds, snippets } = values.contents - fetchRecentSnippets(marker) + setSnippets(snippets) + const getSetOfSnippets = (direction) => { + setMarker(Number(pagination[direction].marker)) scrollTop() } - const renderRecentSnippets = () => ( -
      - {recent.map(id => )} -
    - ) - return ( - {renderRecentSnippets()} +
      + {recentIds.map(id => )} +
    getSetOfSnippets('prev')} role="presentation" > ‹ Newer getSetOfSnippets('next')} role="presentation" > Older › @@ -74,14 +51,4 @@ const RecentSnippets = ({ fetchRecentSnippets, pagination, snippets, recent }) = ) } -const mapStateToProps = state => ({ - snippets: state.get('snippets'), - recent: state.get('recent'), - pagination: state.get('pagination'), -}) - -const mapDispatchToProps = dispatch => ({ - fetchRecentSnippets: marker => dispatch(fetchRecentSnippets(marker)), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(RecentSnippets) +export default RecentSnippets diff --git a/src/components/Snippet.jsx b/src/components/Snippet.jsx index aea12e5..2fbd48c 100644 --- a/src/components/Snippet.jsx +++ b/src/components/Snippet.jsx @@ -1,5 +1,5 @@ -import React, { useEffect, useState, useRef } from 'react' -import { connect } from 'react-redux' +import React, { useState, useRef } from 'react' +import { useRecoilValueLoadable } from 'recoil' import AceEditor from 'react-ace' import 'brace/theme/textmate' @@ -7,29 +7,23 @@ import 'brace/theme/textmate' import SnippetTags from './common/SnippetTags' import Spinner from './common/Spinner' -import { fetchSnippet } from '../actions' - import { downloadSnippet } from '../misc/download' import { onEditorLoad } from '../misc/editor' -import { getCurrentModeCaption } from '../misc/modes' -import { getSnippetTitle, formatDate } from '../misc/snippet' -import { getRawUrl } from '../misc/url' +import { getSnippetMetadata } from '../misc/snippet' +import { snippetById } from '../store' import { existingSnippetOptions } from '../entries/aceEditorOptions' import '../styles/Snippet.styl' -const Snippet = ({ snippet, fetchSnippet, match }) => { +const Snippet = ({ match }) => { const [isShowEmbed, setIsShowEmbed] = useState(false) + const fetchedSnippet = useRecoilValueLoadable(snippetById(Number(match.params.id))) const embeddedRef = useRef() - useEffect(() => { - const { id } = match.params - - fetchSnippet(Number(id)) - }, []) + if (fetchedSnippet.state !== 'hasValue') return - if (!snippet) return + const snippet = fetchedSnippet.contents const copyToClipboard = () => { embeddedRef.current.select() @@ -44,9 +38,7 @@ const Snippet = ({ snippet, fetchSnippet, match }) => { setIsShowEmbed(!isShowEmbed) } - const title = getSnippetTitle(snippet) - const syntax = getCurrentModeCaption(snippet.get('syntax')) - const rawUrl = getRawUrl(snippet.get('id')) + const { syntax, title, rawUrl, createdAt } = getSnippetMetadata(snippet) const renderEmbed = () => (
    @@ -60,22 +52,20 @@ const Snippet = ({ snippet, fetchSnippet, match }) => { ref={embeddedRef} className="input" type="text" - defaultValue={``} + defaultValue={``} />
    ) - const renderMetadata = () => ({formatDate(snippet.get('created_at'))}, by Guest) - return (
    {title} - - {renderMetadata()} + + {createdAt}, by Guest
    {syntax} @@ -98,26 +88,18 @@ const Snippet = ({ snippet, fetchSnippet, match }) => { {renderEmbed()}
    ) } -const mapStateToProps = (state, ownProps) => ({ - snippet: state.getIn(['snippets', Number(ownProps.match.params.id)]), -}) - -const mapDispatchToProps = dispatch => ({ - fetchSnippet: id => dispatch(fetchSnippet(Number(id))), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(Snippet) +export default Snippet diff --git a/src/components/common/SnippetTags.jsx b/src/components/common/SnippetTags.jsx index 68c27c0..92644fd 100644 --- a/src/components/common/SnippetTags.jsx +++ b/src/components/common/SnippetTags.jsx @@ -1,9 +1,13 @@ import React from 'react' -const SnippetTags = ({ className, snippet }) => { +const SnippetTags = ({ className, tags, id }) => { + if (!tags.length) { + return false + } + return (
    - {snippet.get('tags').map(item => {item})} + {tags.map(item => {item})}
    ) } diff --git a/src/index.tsx b/src/index.tsx index cad1ddc..ccf99a4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,16 +1,11 @@ import React from 'react' import ReactDOM from 'react-dom' -import { Provider } from 'react-redux' - +import { RecoilRoot } from 'recoil' import App from './components/App' -import createStore from './store' - -const store:any = createStore(); ReactDOM.render( - ( - - - - ), document.getElementById('root') as HTMLElement, + + + , + document.getElementById('root') as HTMLElement, ) diff --git a/src/misc/download.js b/src/misc/download.js index 9b0bdda..de8e2e0 100644 --- a/src/misc/download.js +++ b/src/misc/download.js @@ -20,10 +20,10 @@ export const downloadSnippet = snippet => { // Despite using AceEditor's modes as syntaxes, we can imagine other setup // when more or even other syntaxes can be used on API side. Hence, we better // be prepared and fallback to "Text" mode if unknown syntaxes it is. - const mode = getCurrentMode(snippet.get('syntax')) + const mode = getCurrentMode(snippet.syntax) const ext = mode.extensions.split('|')[0] || 'txt' - const content = snippet.get('content') - const name = `${snippet.get('id')}.${ext}` + const content = snippet.content + const name = `${snippet.id}.${ext}` // Unfortunately, AceEditor doesn't maintain MIME type map so we don't know // for sure which mode corresponds to which MIME type. Hence, let's use diff --git a/src/misc/modes.js b/src/misc/modes.js deleted file mode 100644 index 3b138ac..0000000 --- a/src/misc/modes.js +++ /dev/null @@ -1,19 +0,0 @@ -import brace from 'brace' -import 'brace/ext/modelist' - -export const getModesByName = () => brace.acequire('ace/ext/modelist') - -export const getCurrentMode = syntax => { - const { modesByName } = getModesByName() - return modesByName[syntax] || modesByName.text -} - -export const getCurrentModeName = syntax => { - const mode = getCurrentMode(syntax) - return mode.name -} - -export const getCurrentModeCaption = syntax => { - const mode = getCurrentMode(syntax) - return mode.caption -} diff --git a/src/misc/modes.ts b/src/misc/modes.ts new file mode 100644 index 0000000..72f47fb --- /dev/null +++ b/src/misc/modes.ts @@ -0,0 +1,41 @@ +import brace from 'brace' +import 'brace/ext/modelist' + +interface Mode { + caption: string; + extRe: RegExp; + extensions: string; + mode: string; + name: string; +} + +interface NormalizedSyntax { + name: string; + value: string; +} + +export const getModesByName = () => brace.acequire('ace/ext/modelist') + +export const getCurrentMode = (syntax: string): Mode => { + const { modesByName } = getModesByName() + return modesByName[syntax] || modesByName.text +} + +export const getCurrentModeName = (syntax: string): string => { + const mode = getCurrentMode(syntax) + return mode.name +} + +export const getCurrentModeCaption = (syntax: string): string => { + const mode = getCurrentMode(syntax) + return mode.caption +} + +export const normalizedSyntaxes = (syntaxes: string[]): NormalizedSyntax[] => { + const { modesByName } = getModesByName() + + return syntaxes.map(item => ({ + name: modesByName[item].caption, + value: item, + })) +} diff --git a/src/misc/snippet.js b/src/misc/snippet.js deleted file mode 100644 index b532fb0..0000000 --- a/src/misc/snippet.js +++ /dev/null @@ -1,9 +0,0 @@ -export const getSnippetTitle = snippet => snippet.get('title') || `#${snippet.get('id')}, Untitled` - -// This function is here just because I don't want to pull the whole moment.js -// only for one tiny date -export function formatDate(d) { - const ISOdate = d.split('T')[0] - - return ISOdate.split('-').reverse().join('.') -} diff --git a/src/misc/snippet.ts b/src/misc/snippet.ts new file mode 100644 index 0000000..46663c1 --- /dev/null +++ b/src/misc/snippet.ts @@ -0,0 +1,22 @@ +import { Snippet } from '../store' +import { getCurrentModeCaption } from './modes' +import { getRawUrl } from './url' + +export const getSnippetTitle = (snippet: Snippet): string => snippet.title || `#${snippet.id}, Untitled` + +export const formatDate = (date: string): string => { + const ISOdate = date.split('T')[0] + + return ISOdate.split('-').reverse().join('.') +} + +export const scrollTop = (): void => { + window.scroll({ top: 0, behavior: 'smooth' }) +} + +export const getSnippetMetadata = (snippet: Snippet) => ({ + syntax: getCurrentModeCaption(snippet.syntax), + title: getSnippetTitle(snippet), + rawUrl: getRawUrl(snippet.id), + createdAt: formatDate(snippet.created_at), +}) diff --git a/src/misc/url.js b/src/misc/url.js deleted file mode 100644 index 7130631..0000000 --- a/src/misc/url.js +++ /dev/null @@ -1,5 +0,0 @@ -import conf from '../conf' - -export const getRawUrl = id => conf.RAW_SNIPPET_URI_FORMAT.replace('%s', id) - -export const getApiUri = (endpoint, version = 'v1') => `${conf.API_BASE_URI}/${version}/${endpoint}` diff --git a/src/misc/url.ts b/src/misc/url.ts new file mode 100644 index 0000000..84a6a59 --- /dev/null +++ b/src/misc/url.ts @@ -0,0 +1,7 @@ +import conf from '../conf' + +export const getRawUrl = (id: number): string => conf.RAW_SNIPPET_URI_FORMAT.replace('%s', id) + +export const getApiUri = (endpoint: string, version = 'v1'): string => ( + `${conf.API_BASE_URI}/${version}/${endpoint}` +) diff --git a/src/reducers/index.js b/src/reducers/index.js deleted file mode 100644 index e319fc5..0000000 --- a/src/reducers/index.js +++ /dev/null @@ -1,58 +0,0 @@ -import { List, Map, fromJS } from 'immutable' -import { combineReducers } from 'redux-immutable' -import { - SET_RECENT_SNIPPETS, - SET_SNIPPET, - SET_PAGINATION_LINKS, - SET_SYNTAXES, -} from '../actions/types' - -const snippets = (state = Map(), action) => { - switch (action.type) { - case SET_RECENT_SNIPPETS: - return state.merge(action.snippets.map(snippet => [snippet.id, snippet])) - - case SET_SNIPPET: - return state.set(action.snippet.id, fromJS(action.snippet)) - - default: - return state - } -} - -const recent = (state = List(), action) => { - switch (action.type) { - case SET_RECENT_SNIPPETS: - return List(action.snippets.map(snippet => snippet.id)) - - default: - return state - } -} - -const pagination = (state = Map(), action) => { - switch (action.type) { - case SET_PAGINATION_LINKS: - return Map(action.links) - - default: - return state - } -} - -const syntaxes = (state = List(), action) => { - switch (action.type) { - case SET_SYNTAXES: - return List(action.syntaxes) - - default: - return state - } -} - -export default combineReducers({ - snippets, - recent, - syntaxes, - pagination, -}) diff --git a/src/store/index.js b/src/store/index.js deleted file mode 100644 index 32e251b..0000000 --- a/src/store/index.js +++ /dev/null @@ -1,15 +0,0 @@ -import * as redux from 'redux' -import thunk from 'redux-thunk' - -import reducer from '../reducers' - -function createStore() { - // Redux Thunk middleware allows you to write action creators that return a - // function instead of an action. The thunk can be used to delay the dispatch - // of an action, or to dispatch only if a certain condition is met. The - // former is the case for XSnippet Web since we need to fetch data via HTTP - // API first and then dispatch it to store. - return redux.createStore(reducer, redux.applyMiddleware(thunk)) -} - -export default createStore diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..5a0a373 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,60 @@ +import { atom, selector, selectorFamily } from 'recoil' + +import { fetchSnippet, fetchSyntaxes, fetchRecentSnippets } from '../api' +import { normalizedSyntaxes } from '../misc/modes' + +export interface Snippet { + content: string; + created_at: string; + id: number; + syntax: string; + tags: string[]; + title: string; + updated_at: string; +} + +export interface RawSnippet { + content: string; + syntax: string; + tags: string[]; + title: string; +} + +export const recentSnippetsState = atom({ + key: 'recentSnippetsState', + default: null, +}) + +export const syntaxesQuery = selector({ + key: 'syntaxesQuery', + get: async () => { + const syntaxes = await fetchSyntaxes() + return normalizedSyntaxes(syntaxes) + } +}) + +export const recentSnippetsQuery = selectorFamily({ + key: 'recentSnippetsQuery', + get: (marker: number) => async () => { + const { snippets, pagination } = await fetchRecentSnippets(marker) + + return { + pagination, + recentIds: snippets.map((snippet: Snippet) => snippet.id), + snippets: Object.assign({}, ...snippets.map((snippet: Snippet) => ({ [snippet.id]: snippet }))), + } + }, +}) + +export const snippetById = selectorFamily({ + key: 'snippetById', + get: (id: number) => async ({ get }) => { + const snippets = get(recentSnippetsState) + + if (snippets) { + return snippets[id] + } + + return await fetchSnippet(id) + }, +}) diff --git a/tests/helpers/index.test.js b/tests/helpers/index.test.ts similarity index 89% rename from tests/helpers/index.test.js rename to tests/helpers/index.test.ts index a56ce7b..b389c3d 100644 --- a/tests/helpers/index.test.js +++ b/tests/helpers/index.test.ts @@ -1,4 +1,3 @@ -import { fromJS } from 'immutable' import { getApiUri } from '../../src/misc/url' import { formatDate, getSnippetTitle } from '../../src/misc/snippet' @@ -23,14 +22,14 @@ describe('misc', () => { }) it('should return propper title', () => { - expect(getSnippetTitle(fromJS(snippet))).toEqual(snippet.title) + expect(getSnippetTitle(snippet)).toEqual(snippet.title) }) it('should return propper title', () => { const untitled = { ...snippet, title: undefined } const expected = `#${untitled.id}, Untitled` - expect(getSnippetTitle(fromJS(untitled))).toEqual(expected) + expect(getSnippetTitle(untitled)).toEqual(expected) }) }) diff --git a/tests/store.test.js b/tests/store.test.js deleted file mode 100644 index 089d6ff..0000000 --- a/tests/store.test.js +++ /dev/null @@ -1,304 +0,0 @@ -import fetchMock from 'fetch-mock' -import createStore from '../src/store' - -import { - setRecentSnippets, - setPaginationLinks, - fetchRecentSnippets, - setSnippet, - setSyntaxes, - fetchSnippet, - fetchSyntaxes, - postSnippet, -} from '../src/actions' - -describe('actions', () => { - it('should create an action to set recent snippets', () => { - const snippets = [ - { - id: 1, - content: 'test', - syntax: 'JavaScript', - }, - { - id: 2, - content: 'batman', - syntax: 'Python', - }, - ] - const store = createStore() - store.dispatch(setRecentSnippets(snippets)) - - expect(store.getState().toJS()).toEqual({ - recent: [1, 2], - snippets: { - 1: { - id: 1, - content: 'test', - syntax: 'JavaScript', - }, - 2: { - id: 2, - content: 'batman', - syntax: 'Python', - }, - }, - syntaxes: [], - pagination: {}, - }) - }) - - it('should create an action to set pagination links', () => { - const links = { - first: { - limit: '20', - rel: 'first', - url: '//api.xsnippet.org/v1/snippets?limit=20', - }, - next: { - limit: '20', - marker: 28, - rel: 'next', - url: '//api.xsnippet.org/v1/snippets?limit=20&marker=28', - }, - prev: { - limit: '20', - rel: 'prev', - url: '//api.xsnippet.org/v1/snippets?limit=20', - }, - } - const store = createStore() - store.dispatch(setPaginationLinks(links)) - - expect(store.getState().toJS()).toEqual({ - recent: [], - snippets: {}, - syntaxes: [], - pagination: links, - }) - }) - - it('should create an action to fetch recent snippets with marker', async () => { - const snippets = [ - { - id: 1, - content: 'test', - syntax: 'JavaScript', - }, - { - id: 2, - content: 'batman', - syntax: 'Python', - }, - ] - const links = '; rel="first", ; rel="next", ; rel="prev"' - - fetchMock.getOnce( - '//api.xsnippet.org/v1/snippets?limit=20&marker=39', - { - headers: { Link: links }, - body: snippets, - }, - ) - - const store = createStore() - await store.dispatch(fetchRecentSnippets(39)) - - expect(store.getState().toJS()).toEqual({ - recent: [1, 2], - snippets: { - 1: { - id: 1, - content: 'test', - syntax: 'JavaScript', - }, - 2: { - id: 2, - content: 'batman', - syntax: 'Python', - }, - }, - syntaxes: [], - pagination: { - first: { - limit: '20', - rel: 'first', - url: '//api.xsnippet.org/v1/snippets?limit=20', - }, - next: { - limit: '20', - marker: '19', - rel: 'next', - url: '//api.xsnippet.org/v1/snippets?limit=20&marker=19', - }, - prev: { - limit: '20', - marker: '59', - rel: 'prev', - url: '//api.xsnippet.org/v1/snippets?limit=20&marker=59', - }, - }, - }) - }) - - it('should create an action to fetch recent snippets without marker', async () => { - const snippets = [ - { - id: 1, - content: 'test', - syntax: 'JavaScript', - }, - { - id: 2, - content: 'batman', - syntax: 'Python', - }, - ] - const links = '; rel="first", ; rel="next"' - - fetchMock.getOnce( - '//api.xsnippet.org/v1/snippets?limit=20', - { - headers: { Link: links }, - body: snippets, - }, - ) - - const store = createStore() - await store.dispatch(fetchRecentSnippets()) - - expect(store.getState().toJS()).toEqual({ - recent: [1, 2], - snippets: { - 1: { - id: 1, - content: 'test', - syntax: 'JavaScript', - }, - 2: { - id: 2, - content: 'batman', - syntax: 'Python', - }, - }, - syntaxes: [], - pagination: { - first: { - limit: '20', - rel: 'first', - url: '//api.xsnippet.org/v1/snippets?limit=20', - }, - next: { - limit: '20', - marker: '39', - rel: 'next', - url: '//api.xsnippet.org/v1/snippets?limit=20&marker=39', - }, - }, - }) - }) - - it('should create an action to set snippet', () => { - const snippet = { - id: 3, - content: 'not batman', - syntax: 'Go', - } - const store = createStore() - store.dispatch(setSnippet(snippet)) - - expect(store.getState().toJS()).toEqual({ - recent: [], - snippets: { - 3: { - id: 3, - content: 'not batman', - syntax: 'Go', - }, - }, - syntaxes: [], - pagination: {}, - }) - }) - - it('should create an action to fetch snippet', async () => { - const snippet = { - id: 3, - content: 'not batman', - syntax: 'Go', - } - - fetchMock.getOnce(`//api.xsnippet.org/v1/snippets/${snippet.id}`, JSON.stringify(snippet)) - - const store = createStore() - await store.dispatch(fetchSnippet(snippet.id)) - - expect(store.getState().toJS()).toEqual({ - recent: [], - snippets: { - 3: { - id: 3, - content: 'not batman', - syntax: 'Go', - }, - }, - syntaxes: [], - pagination: {}, - }) - }) - - it('should create an action to set syntaxes', () => { - const syntaxes = ['JavaScript', 'Python', 'Java', 'Go', 'Plain Text'] - const store = createStore() - store.dispatch(setSyntaxes(syntaxes)) - - expect(store.getState().toJS()).toEqual({ - recent: [], - snippets: {}, - pagination: {}, - syntaxes, - }) - }) - - it('should create an action to fetch syntaxes', async () => { - const syntaxes = ['JavaScript', 'Python', 'Java', 'Go', 'Plain Text'] - - fetchMock.getOnce('//api.xsnippet.org/v1/syntaxes', JSON.stringify(syntaxes)) - - const store = createStore() - await store.dispatch(fetchSyntaxes) - - expect(store.getState().toJS()).toEqual({ - recent: [], - snippets: {}, - pagination: {}, - syntaxes, - }) - }) - - it('should create an action to post snippet', async () => { - const snippet = { - id: 4, - content: 'Batman', - syntax: 'JavaScript', - } - - fetchMock.postOnce('//api.xsnippet.org/v1/snippets', JSON.stringify(snippet)) - - const store = createStore() - await store.dispatch(postSnippet(snippet, () => {})) - - expect(store.getState().toJS()).toEqual({ - recent: [], - snippets: { - 4: { - id: 4, - content: 'Batman', - syntax: 'JavaScript', - }, - }, - syntaxes: [], - pagination: {}, - }) - }) -})