mirror of
https://github.com/edufeed-org/edufeed-web.git
synced 2025-12-07 23:34:34 +00:00
some things are working
This commit is contained in:
parent
83ff1e86ac
commit
31aceb49c7
21 changed files with 2767 additions and 371 deletions
27
karma.conf.js
Normal file
27
karma.conf.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
module.exports = function (config) {
|
||||
var junitOutputDir = process.env.CIRCLE_TEST_REPORTS || "target/junit"
|
||||
|
||||
config.set({
|
||||
browsers: ['ChromeHeadless'],
|
||||
basePath: 'target',
|
||||
files: ['karma-test.js'],
|
||||
frameworks: ['cljs-test'],
|
||||
plugins: [
|
||||
'karma-cljs-test',
|
||||
'karma-chrome-launcher',
|
||||
'karma-junit-reporter'
|
||||
],
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
client: {
|
||||
args: ['shadow.test.karma.init']
|
||||
},
|
||||
|
||||
// the default configuration
|
||||
junitReporter: {
|
||||
outputDir: junitOutputDir + '/karma', // results will be saved as outputDir/browserName.xml
|
||||
outputFile: undefined, // if included, results will be saved as outputDir/browserName/outputFile
|
||||
suite: '' // suite will become the package name attribute in xml testsuite element
|
||||
}
|
||||
})
|
||||
}
|
||||
208
package-lock.json
generated
208
package-lock.json
generated
|
|
@ -1,16 +1,17 @@
|
|||
{
|
||||
"name": "ied",
|
||||
"name": "edufeed",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ied",
|
||||
"name": "edufeed",
|
||||
"dependencies": {
|
||||
"@noble/secp256k1": "^2.1.0",
|
||||
"bech32": "^2.0.0",
|
||||
"js-confetti": "^0.12.0",
|
||||
"nostr-tools": "^2.7.2",
|
||||
"react": "17.0.2",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-dom": "17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -31,6 +32,18 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz",
|
||||
"integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
|
|
@ -237,6 +250,44 @@
|
|||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
|
||||
"integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
|
||||
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.7.tgz",
|
||||
"integrity": "sha512-KUnDCJF5+AiZd8owLIeVHqmW9yM4sqmDVf2JRJiBMFkGvkoZ4/WyV2lL4zVsoinmRS/W3FeEdZLEWFRofnT2FQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-redux": {
|
||||
"version": "7.1.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz",
|
||||
"integrity": "sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"redux": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
|
|
@ -717,6 +768,15 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/css-box-model": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
|
||||
"integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tiny-invariant": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/css-selector-tokenizer": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz",
|
||||
|
|
@ -739,6 +799,12 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/culori": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz",
|
||||
|
|
@ -857,10 +923,11 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/elliptic": {
|
||||
"version": "6.5.5",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.5.tgz",
|
||||
"integrity": "sha512-7EjbcmUm17NQFu4Pmgmq2olYMj8nwMnpcddByChSUjArp8F5DQWcIcpriwO4ZToLNAJig0yiyjswfyGNje/ixw==",
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.0.tgz",
|
||||
"integrity": "sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bn.js": "^4.11.9",
|
||||
"brorand": "^1.1.0",
|
||||
|
|
@ -1162,6 +1229,21 @@
|
|||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hoist-non-react-statics/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/https-browserify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
|
||||
|
|
@ -1349,6 +1431,12 @@
|
|||
"safe-buffer": "^5.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
|
|
@ -1359,10 +1447,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
|
||||
"integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
|
|
@ -1872,6 +1961,23 @@
|
|||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/public-encrypt": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
|
||||
|
|
@ -1942,6 +2048,12 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"node_modules/raf-schd": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
|
||||
"integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
|
|
@ -1973,6 +2085,25 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-beautiful-dnd": {
|
||||
"version": "13.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz",
|
||||
"integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.9.2",
|
||||
"css-box-model": "^1.2.0",
|
||||
"memoize-one": "^5.1.1",
|
||||
"raf-schd": "^4.0.2",
|
||||
"react-redux": "^7.2.0",
|
||||
"redux": "^4.0.4",
|
||||
"use-memo-one": "^1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.5 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.5 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
|
||||
|
|
@ -1986,6 +2117,37 @@
|
|||
"react": "17.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "7.2.9",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
|
||||
"integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.15.4",
|
||||
"@types/react-redux": "^7.1.20",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^17.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.3 || ^17 || ^18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
|
|
@ -2046,6 +2208,21 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/redux": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
|
|
@ -2507,6 +2684,12 @@
|
|||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/to-arraybuffer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
|
||||
|
|
@ -2547,6 +2730,15 @@
|
|||
"qs": "^6.11.2"
|
||||
}
|
||||
},
|
||||
"node_modules/use-memo-one": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz",
|
||||
"integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "ied",
|
||||
"name": "edufeed",
|
||||
"scripts": {
|
||||
"ancient": "clojure -Sdeps '{:deps {com.github.liquidz/antq {:mvn/version \"RELEASE\"}}}' -m antq.core",
|
||||
"watch": "npx shadow-cljs watch app browser-test karma-test",
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
"js-confetti": "^0.12.0",
|
||||
"nostr-tools": "^2.7.2",
|
||||
"react": "17.0.2",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-dom": "17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -10,10 +10,11 @@
|
|||
[clj-commons/pushy "0.3.10"]
|
||||
[binaryage/devtools "1.0.6"]
|
||||
[day8.re-frame/re-frame-10x "1.9.3"]
|
||||
|
||||
[day8.re-frame/http-fx "0.2.4"]
|
||||
[funcool/promesa "11.0.678"]
|
||||
[nilenso/wscljs "0.2.0"]
|
||||
]
|
||||
[markdown-to-hiccup "0.6.2"]
|
||||
[superstructor/re-frame-fetch-fx "0.4.0"]
|
||||
[nilenso/wscljs "0.2.0"]]
|
||||
|
||||
:dev-http
|
||||
{8280 "resources/public"
|
||||
|
|
|
|||
63
src/ied/components/icons.cljs
Normal file
63
src/ied/components/icons.cljs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
(ns ied.components.icons)
|
||||
|
||||
(defn close-icon []
|
||||
[:svg
|
||||
{:class "h-6 w-6"
|
||||
:fill ""
|
||||
:xmlns "http://www.w3.org/2000/svg"
|
||||
:viewBox "0 0 512 512"}
|
||||
(comment
|
||||
"!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.")
|
||||
[:path
|
||||
{:d
|
||||
"M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c-9.4 9.4-9.4 24.6 0 33.9l47 47-47 47c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l47-47 47 47c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-47-47 47-47c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-47 47-47-47c-9.4-9.4-24.6-9.4-33.9 0z"}]])
|
||||
|
||||
(defn add
|
||||
([]
|
||||
(add ""))
|
||||
([fill-color]
|
||||
[:svg
|
||||
|
||||
{:class "h-6 w-6"
|
||||
:fill fill-color
|
||||
:xmlns "http://www.w3.org/2000/svg", :viewBox "0 0 448 512"}
|
||||
(comment
|
||||
"!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.")
|
||||
[:path
|
||||
{:d
|
||||
"M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 144L48 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l144 0 0 144c0 17.7 14.3 32 32 32s32-14.3 32-32l0-144 144 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-144 0 0-144z"}]]))
|
||||
|
||||
(defn move []
|
||||
[:svg
|
||||
{:class "h-6 w-6"
|
||||
:fill ""
|
||||
:xmlns "http://www.w3.org/2000/svg", :viewBox "0 0 512 512"}
|
||||
(comment
|
||||
"!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.")
|
||||
[:path
|
||||
{:d
|
||||
"M278.6 9.4c-12.5-12.5-32.8-12.5-45.3 0l-64 64c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l9.4-9.4L224 224l-114.7 0 9.4-9.4c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-64 64c-12.5 12.5-12.5 32.8 0 45.3l64 64c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-9.4-9.4L224 288l0 114.7-9.4-9.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l64 64c12.5 12.5 32.8 12.5 45.3 0l64-64c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-9.4 9.4L288 288l114.7 0-9.4 9.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l64-64c12.5-12.5 12.5-32.8 0-45.3l-64-64c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l9.4 9.4L288 224l0-114.7 9.4 9.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-64-64z"}]])
|
||||
|
||||
(defn grid []
|
||||
[:svg
|
||||
{:xmlns "http://www.w3.org/2000/svg",
|
||||
:width "16",
|
||||
:height "16",
|
||||
:fill "currentColor",
|
||||
:class "bi bi-grid",
|
||||
:viewBox "0 0 16 16"}
|
||||
[:path
|
||||
{:d
|
||||
"M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5zM2.5 2a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5zm6.5.5A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5zM1 10.5A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5zm6.5.5A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5z"}]])
|
||||
|
||||
(defn pencil []
|
||||
[:svg
|
||||
{:xmlns "http://www.w3.org/2000/svg",
|
||||
:width "16",
|
||||
:height "16",
|
||||
:fill "currentColor",
|
||||
:class "bi bi-pencil",
|
||||
:viewBox "0 0 16 16"}
|
||||
[:path
|
||||
{:d
|
||||
"M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325"}]])
|
||||
108
src/ied/components/resource.cljs
Normal file
108
src/ied/components/resource.cljs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
(ns ied.components.resource
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[clojure.string :as str]
|
||||
[ied.nostr :as nostr]
|
||||
[ied.subs :as subs]
|
||||
[ied.events :as events]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Components used to display info about resources
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn about-tags [[event]]
|
||||
(doall
|
||||
(for [about (nostr/get-about-names-from-metadata-event event)]
|
||||
[:div {:class "badge badge-primary m-1 truncate "
|
||||
:key about} about])))
|
||||
|
||||
(defn grouped-about-tags [[group-key values]]
|
||||
;; group key is the identifier of the concept
|
||||
;; values is an array of events that referenced the concept
|
||||
(let [_ (println "values " values)
|
||||
pubkeys (map :pubkey values)
|
||||
profiles (re-frame/subscribe [::subs/profiles pubkeys])
|
||||
user-language (re-frame/subscribe [::subs/user-language])
|
||||
_ (js/console.log "group key and values" (clj->js values))
|
||||
_ (js/console.log (clj->js @profiles))]
|
||||
[:div {:on-click #(re-frame/dispatch [::events/handle-filter-search ["about.id" group-key]])
|
||||
:data-tip (str
|
||||
(str/join
|
||||
", "
|
||||
(remove str/blank? (map (fn [[_ p]] (get p :display_name "unbekannt")) @profiles)))
|
||||
(if (> (count @profiles) 1)
|
||||
" haben"
|
||||
" hat")
|
||||
" das zugeordnet.")
|
||||
:class " tooltip "}
|
||||
[:div {:class "badge badge-primary m-1 truncate cursor-pointer"}
|
||||
(vals (:about-label (first (filter #(= @user-language (:label-language %)) values))))]]))
|
||||
|
||||
;; Helper function to extract the information and associate it with `about`
|
||||
(defn extract-about-info [tags event]
|
||||
(let [abouts (map (fn [e] {:about-id (second e)
|
||||
:label-language (nth e 3 "")
|
||||
:about-label {(keyword (nth e 3 "")) (nth e 2)}
|
||||
:pubkey (:pubkey event)
|
||||
:id (:id event)})
|
||||
tags)
|
||||
_ (.log js/console "abouts " (clj->js abouts))]
|
||||
abouts))
|
||||
|
||||
;; Flatten and group by `about-id`
|
||||
(defn group-by-for-skos-tags
|
||||
;; TODO update this docstring
|
||||
"Expects an array of kind 30142 skos tag arrays, like
|
||||
```
|
||||
[
|
||||
[
|
||||
['about' 'id' 'val' 'lang']
|
||||
['about' 'id' 'val' 'lang']
|
||||
]
|
||||
[
|
||||
['about' 'id' 'val' 'lang']
|
||||
]
|
||||
]
|
||||
|
||||
Returns:
|
||||
- a map of grouped by the ids of the skos tags
|
||||
```
|
||||
"
|
||||
[events name]
|
||||
(println "events array" events)
|
||||
(let [tags-array (map
|
||||
(fn [e]
|
||||
(filter #(= name (first %)) e))
|
||||
(map :tags events))
|
||||
extracted-about (into [] (apply concat (map extract-about-info tags-array events)))
|
||||
grouped-about (group-by :about-id extracted-about)]
|
||||
grouped-about))
|
||||
|
||||
(defn skos-tags [[events name]]
|
||||
(let [grouped (group-by-for-skos-tags events name)]
|
||||
(doall
|
||||
(for [[k v] grouped]
|
||||
^{:key k} [grouped-about-tags [k v]]))))
|
||||
|
||||
(defn keywords-component [kw]
|
||||
^{:key kw}[:div {:on-click #(do
|
||||
(re-frame/dispatch [::events/navigate [:search-view]])
|
||||
(re-frame/dispatch [::events/handle-filter-search ["keywords" kw]]))
|
||||
:class "badge badge-secondary m-1 cursor-pointer"}
|
||||
(str "#" kw)])
|
||||
|
||||
; (defn keywords-component [kw]
|
||||
; [:div {:on-click #(re-frame/dispatch
|
||||
; [::events/navigate [:search-view]]
|
||||
; [::events/handle-filter-search ["keywords" kw]])
|
||||
; :class "badge badge-secondary m-1 cursor-pointer"}
|
||||
; (str "#" kw)])
|
||||
|
||||
(defn authors-component [event]
|
||||
(let [creators (nostr/get-creators-from-metadata-event event)]
|
||||
[:div {:class "flex flex-row"}
|
||||
(doall
|
||||
(for [creator creators]
|
||||
^{:key (:name creator)} [:p {:class "mx-1"} (:name creator)]))]))
|
||||
|
||||
(comment
|
||||
(hash "https://w3id.org/kim/hcrt/scheme"))
|
||||
|
|
@ -3,3 +3,5 @@
|
|||
(def debug?
|
||||
^boolean goog.DEBUG)
|
||||
|
||||
(def typesense-uri "http://localhost:8108/collections/amb/documents/")
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
[ied.events :as events]
|
||||
[ied.routes :as routes]
|
||||
[ied.views :as views]
|
||||
[ied.subs :as subs]
|
||||
[ied.config :as config]))
|
||||
|
||||
(defn dev-setup []
|
||||
|
|
@ -18,9 +19,10 @@
|
|||
(rdom/render [views/main-panel] root-el)))
|
||||
|
||||
(defn init []
|
||||
(let [default-relays (re-frame/subscribe [::subs/default-relays])]
|
||||
(routes/start!)
|
||||
(re-frame/dispatch-sync [::events/initialize-db])
|
||||
(re-frame/dispatch [::events/connect-to-default-relays])
|
||||
(re-frame/dispatch [::events/connect-to-default-relays @default-relays])
|
||||
(re-frame/dispatch [::events/set-visit-timestamp])
|
||||
(dev-setup)
|
||||
(mount-root))
|
||||
(mount-root)))
|
||||
|
|
|
|||
|
|
@ -1,35 +1,69 @@
|
|||
(ns ied.db)
|
||||
(ns ied.db
|
||||
(:require
|
||||
[ied.config :as config]))
|
||||
|
||||
(def default-opencard-board
|
||||
{:id 1
|
||||
:title "Default board"
|
||||
:lists [{:id 1 :items [{:id 1 :content "*Item 1*"} {:id 2 :content "Item 2"}]}
|
||||
{:id 2 :items [{:id 3 :content "Item 3"} {:id 4 :content "Item 4"}]}
|
||||
{:id 3 :items [{:id 5 :content "Item 5"} {:id 6 :content "Item 6"}]}
|
||||
{:id 4 :items [{:id 7 :content "Item 7"} {:id 8 :content "Item 8"}]}]})
|
||||
|
||||
(def default-db
|
||||
{:name "re-frame"
|
||||
:current-path nil
|
||||
:concept-schemes {}
|
||||
:confetti false
|
||||
:show-add-event false
|
||||
:events #{}
|
||||
:md-form-resource nil
|
||||
:selected-md-scheme nil
|
||||
:pk nil
|
||||
:sk nil
|
||||
:list-kinds [30001 30004]
|
||||
:default-relays [{:name "strfry-1"
|
||||
:uri "http://localhost:7777"
|
||||
:id (random-uuid)
|
||||
:status "disconnected"}
|
||||
{:name "strfry-2"
|
||||
:uri "http://localhost:7778"
|
||||
:id (random-uuid)
|
||||
:status "disconnected"}
|
||||
{:name "rust-relay"
|
||||
:uri "http://localhost:4445"
|
||||
:id (random-uuid)
|
||||
:status "disconnected"}
|
||||
; {:name "damus"
|
||||
; :uri "wss://relay.damus.io"
|
||||
; :status "disconnected"}
|
||||
{:name "SC24"
|
||||
:uri "wss://relay.sc24.steffen-roertgen.de"
|
||||
:status "disconnected"}
|
||||
]
|
||||
:opencard-kinds [30043 30044 30045]
|
||||
:opencard-boards [default-opencard-board]
|
||||
:follow-sets [30000]
|
||||
:resource-to-add nil
|
||||
:default-relays (concat
|
||||
(if config/debug?
|
||||
[{:name "strfry-1"
|
||||
:uri "http://localhost:7777"
|
||||
:id (random-uuid)
|
||||
:status "disconnected"
|
||||
:type ["outbox" "inbox"]}
|
||||
{:name "strfry-2"
|
||||
:uri "http://localhost:7778"
|
||||
:id (random-uuid)
|
||||
:status "disconnected"
|
||||
:type ["outbox" "inbox"]}
|
||||
{:name "rust-relay"
|
||||
:uri "http://localhost:4445"
|
||||
:id (random-uuid)
|
||||
:status "disconnected"
|
||||
:type ["outbox" "inbox"]}]
|
||||
[{:name "SC24"
|
||||
:uri "wss://relay.sc24.steffen-roertgen.de"
|
||||
:status "disconnected"
|
||||
:type ["outbox" "inbox"]}])
|
||||
[{:name "Purplepages"
|
||||
:uri "wss://purplepag.es"
|
||||
:status "disconnected"
|
||||
:type ["search"]}])
|
||||
:selected-events #{}
|
||||
:selected-list-ids #{}
|
||||
:show-lists-modal false
|
||||
:show-create-list-modal false
|
||||
:show-event-data-modal false
|
||||
:sockets []})
|
||||
:sockets []
|
||||
:search-results nil
|
||||
:user-language "de"})
|
||||
|
||||
(comment
|
||||
(filter
|
||||
(fn [s]
|
||||
(some
|
||||
#(= "search" %)
|
||||
(:type s)))
|
||||
(-> default-db :default-relays)))
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
(ns ied.events
|
||||
(:require
|
||||
[re-frame.core :as re-frame]
|
||||
[ied.config :as config]
|
||||
[ied.db :as db]
|
||||
[day8.re-frame.tracing :refer-macros [fn-traced]]
|
||||
[ied.subs :as subs]
|
||||
[ied.nostr :as nostr]
|
||||
|
||||
[day8.re-frame.http-fx]
|
||||
[promesa.core :as p]
|
||||
[wscljs.client :as ws]
|
||||
[wscljs.format :as fmt]
|
||||
[clojure.string :as str]
|
||||
[clojure.set :as set]
|
||||
[superstructor.re-frame.fetch-fx]
|
||||
[ajax.core :as ajax]
|
||||
|
||||
["js-confetti" :as jsConfetti]
|
||||
[js-confetti :as jsConfetti]))
|
||||
|
||||
(def list-kinds [30001 30004])
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::initialize-db
|
||||
(fn-traced [_ _]
|
||||
|
|
@ -38,15 +38,37 @@
|
|||
(fn-traced [{:keys [db]} [_ route]]
|
||||
{:db (assoc db :route route)}))
|
||||
|
||||
(def confetti-instance
|
||||
(new jsConfetti))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::add-confetti
|
||||
(fn [cofx _]
|
||||
(let [visited-at (-> cofx :db :visited-at)
|
||||
now (quot (.now js/Date) 1000)
|
||||
diff (- now visited-at)]
|
||||
(when (-> cofx :db :confetti)
|
||||
(when (>= diff 5)
|
||||
(.addConfetti confetti-instance (clj->js {:emojis ["😺" "🐈⬛" "🦄"]}))))
|
||||
{})))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::relay-list
|
||||
(fn [event]
|
||||
(when (= 30002 (:kind event))
|
||||
(.log js/console "got a relay list!"))))
|
||||
|
||||
;; Database Event?
|
||||
(re-frame/reg-event-fx
|
||||
::save-event
|
||||
;; TODO if EOSE retrieved end connection identified by uri
|
||||
(fn-traced [{:keys [db]} [_ [uri raw-event]]]
|
||||
(let [event (nth raw-event 2 raw-event)]
|
||||
|
||||
(when (and
|
||||
(= (first raw-event) "EVENT"))
|
||||
{:dispatch [::add-confetti]
|
||||
{:fx [[::add-confetti]
|
||||
[::relay-list event]]
|
||||
:db (update db :events conj event)}))))
|
||||
|
||||
(defn handlers
|
||||
|
|
@ -72,15 +94,14 @@
|
|||
(re-frame/reg-event-fx
|
||||
::load-events
|
||||
(fn-traced [cofx [_ ws-uri]]
|
||||
{::load-events-fx ws-uri}))
|
||||
{::load-events-fx [ws-uri (-> cofx :db :sockets)]}))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::load-events-fx
|
||||
(fn [ws-uri]
|
||||
(fn [[ws-uri sockets]]
|
||||
(println "loading events")
|
||||
|
||||
(let [sockets (re-frame/subscribe [::subs/sockets])
|
||||
target-ws (first (filter #(= ws-uri (:uri %)) @sockets))]
|
||||
(let [target-ws (first (filter #(= ws-uri (:uri %)) sockets))]
|
||||
(ws/send (:socket target-ws) ["REQ" "424242" {:kinds [30004 30142]
|
||||
:limit 100}] fmt/json)
|
||||
; (ws/close (:socket (first @sockets))) ;; should be handled otherwise (?)
|
||||
|
|
@ -115,13 +136,12 @@
|
|||
(re-frame/reg-event-fx
|
||||
::connect-to-websocket
|
||||
(fn-traced [{:keys [db]} [_ ws-uri]]
|
||||
{::connect-to-websocket-fx ws-uri}))
|
||||
{::connect-to-websocket-fx [ws-uri (:sockets db)]}))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::connect-to-websocket-fx
|
||||
(fn [ws-uri]
|
||||
(let [sockets (re-frame/subscribe [::subs/sockets])
|
||||
target-ws (first (filter #(= ws-uri (:uri %)) @sockets))]
|
||||
(fn [[ws-uri sockets]]
|
||||
(let [target-ws (first (filter #(= ws-uri (:uri %)) sockets))]
|
||||
(re-frame/dispatch [::create-websocket target-ws]))))
|
||||
|
||||
;; TODO use id to close socket
|
||||
|
|
@ -130,20 +150,18 @@
|
|||
(re-frame/reg-event-fx
|
||||
::close-connection-to-websocket
|
||||
(fn-traced [{:keys [db]} [_ ws-uri]]
|
||||
{::close-connection-to-websocket-fx ws-uri}))
|
||||
{::close-connection-to-websocket-fx [ws-uri (:sockets db)]}))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::close-connection-to-websocket-fx
|
||||
(fn [ws-uri]
|
||||
(let [sockets (re-frame/subscribe [::subs/sockets])]
|
||||
(ws/close (:socket (first (filter #(= ws-uri (:uri %)) @sockets))))
|
||||
(re-frame/dispatch [::update-ws-connection-status ws-uri "disconnected"]))))
|
||||
(fn [[ws-uri sockets]]
|
||||
(ws/close (:socket (first (filter #(= ws-uri (:uri %)) sockets))))
|
||||
(re-frame/dispatch [::update-ws-connection-status ws-uri "disconnected"])))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::connect-to-default-relays
|
||||
(fn-traced [cofx [_]]
|
||||
(let [default-relays (re-frame/subscribe [::subs/default-relays])]
|
||||
{::connect-to-default-relays-fx @default-relays})))
|
||||
(fn-traced [cofx [_ default-relays]]
|
||||
{::connect-to-default-relays-fx default-relays}))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::connect-to-default-relays-fx
|
||||
|
|
@ -154,9 +172,14 @@
|
|||
|
||||
;; TODO
|
||||
(re-frame/reg-event-fx
|
||||
::send-to-relays
|
||||
::publish-signed-event
|
||||
(fn-traced [cofx [_ signedEvent]]
|
||||
(let [sockets (-> cofx :db :sockets)]
|
||||
(let [sockets (filter
|
||||
(fn [s]
|
||||
(some #(= "outbox" %)
|
||||
(:type s)))
|
||||
(-> cofx :db :sockets))
|
||||
_ (.log js/console "available sockets " (clj->js sockets))]
|
||||
{::send-to-relays-fx [sockets signedEvent]})))
|
||||
|
||||
(re-frame/reg-fx
|
||||
|
|
@ -174,13 +197,12 @@
|
|||
(re-frame/reg-event-fx
|
||||
::remove-websocket
|
||||
(fn-traced [{:keys [db]} [_ socket]]
|
||||
{::remove-websocket-fx (:id socket)}))
|
||||
{::remove-websocket-fx [(:id socket) (:sockets db)]}))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::remove-websocket-fx
|
||||
(fn [id]
|
||||
(let [sockets (re-frame/subscribe [::subs/sockets])
|
||||
filtered (filter #(not= id (:id %)) @sockets)] ;; TODO maybe this can also be done using the URI
|
||||
(fn [[id sockets]]
|
||||
(let [filtered (filter #(not= id (:id %)) sockets)] ;; TODO maybe this can also be done using the URI
|
||||
(re-frame/dispatch [::update-websockets filtered]))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
|
|
@ -192,34 +214,48 @@
|
|||
(re-frame/reg-event-fx
|
||||
::publish-resource
|
||||
[(re-frame/inject-cofx :now)]
|
||||
(fn-traced [cofx [_ resource]]
|
||||
(let [event {:kind 30142
|
||||
(fn-traced [cofx _]
|
||||
;; TODO hier muss ich den ansatz ändern, es ist vmtl sinnvoller durch alle keys in md-form-resource zu iterieren und abhängig davon die tags zu bauen
|
||||
;; -> vllt kann ich einfach eine Funktion bauen, die md-form resource amb-konform verwandet und dann die funktion zur publikation von amb daten recyclen
|
||||
(let [form-data (-> cofx :db :md-form-resource)
|
||||
about (map (fn [e] ["about"
|
||||
(:id e)
|
||||
(second (first (filter (fn [l]
|
||||
(= :de (first l)))
|
||||
(:prefLabel e))))
|
||||
"de"])
|
||||
(:about form-data)) ;; TODO this should be abstracted in a nostr-make-event-kind-function or sth
|
||||
tags [["d" (:uri form-data)]
|
||||
["id" (:uri form-data)]
|
||||
["author" "" (:author form-data)]
|
||||
["name" (:name form-data)]]
|
||||
event {:kind 30142
|
||||
:created_at (:now cofx)
|
||||
:content ""
|
||||
:tags [["d" (:id resource)]
|
||||
["id" (:id resource)]
|
||||
#_["author" "" (:author resource)]
|
||||
["name" (:name resource)]]}]
|
||||
:tags (into tags about)}
|
||||
_ (.log js/console "Event to publish " (clj->js event))]
|
||||
{:navigate [:home]
|
||||
::sign-and-publish-event [event (-> cofx :db :sk)]
|
||||
}
|
||||
:db (assoc (:db cofx) :md-form-resource nil)})))
|
||||
|
||||
#_{::sign-and-publish-event [event (-> cofx :db :sk)]})))
|
||||
(defn sign-event [unsignedEvent sk]
|
||||
(if (nostr/valid-unsigned-nostr-event? unsignedEvent)
|
||||
(p/let [_ (js/console.log (clj->js unsignedEvent))
|
||||
signedEvent (if (nil? sk)
|
||||
(.nostr.signEvent js/window (clj->js unsignedEvent))
|
||||
(nostr/finalize-event unsignedEvent sk))
|
||||
_ (js/console.log "Signed event: " (clj->js signedEvent))]
|
||||
signedEvent)
|
||||
(.error js/console "Event is not a valid nostr event: " (clj->js unsignedEvent))))
|
||||
|
||||
;; TODO maybe we need some validation before publishing
|
||||
(re-frame/reg-fx
|
||||
::sign-and-publish-event
|
||||
(fn [[unsignedEvent sk]]
|
||||
(if (nostr/valid-unsigned-nostr-event? unsignedEvent)
|
||||
(p/let [_ (js/console.log (clj->js unsignedEvent))
|
||||
_ (js/console.log (nostr/sk-as-hex sk))
|
||||
signedEvent (if (nil? sk)
|
||||
(.nostr.signEvent js/window (clj->js unsignedEvent))
|
||||
(nostr/finalize-event unsignedEvent sk))
|
||||
_ (js/console.log "Signed event: " (clj->js signedEvent))]
|
||||
(re-frame/dispatch [::send-to-relays signedEvent]))
|
||||
(.error js/console "Event is not a valid nostr event: " (clj->js unsignedEvent)))))
|
||||
(p/let [signedEvent (sign-event unsignedEvent sk)]
|
||||
(re-frame/dispatch [::publish-signed-event signedEvent]))))
|
||||
|
||||
;; TODO make login a multimethod and call it with either extension or anononymslouy keyword?
|
||||
(re-frame/reg-event-fx
|
||||
::login-with-extension
|
||||
(fn-traced [cofx [_ _]]
|
||||
|
|
@ -229,7 +265,8 @@
|
|||
::login-with-extension-fx
|
||||
(fn [db _]
|
||||
(p/let [pk (.nostr.getPublicKey js/window)]
|
||||
(re-frame/dispatch [::save-pk pk]))))
|
||||
(re-frame/dispatch [::save-pk pk])
|
||||
(re-frame/dispatch [::relay-list-for-pk pk]))))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::save-pk
|
||||
|
|
@ -258,27 +295,41 @@
|
|||
(assoc cofx :pk (:pk cofx))))
|
||||
|
||||
(defn convert-amb-to-nostr-event
|
||||
[json-string created_at]
|
||||
(let [parsed-json (js->clj (js/JSON.parse json-string) :keywordize-keys true)
|
||||
tags (into [["d" (:id parsed-json)]
|
||||
[parsed-json created_at]
|
||||
(let [tags (into [["d" (:id parsed-json)]
|
||||
["r" (:id parsed-json)]
|
||||
["id" (:id parsed-json)]
|
||||
["name" (:name parsed-json)]
|
||||
["description" (:description parsed-json "")]
|
||||
(into ["keywords"] (:keywords parsed-json))
|
||||
["image" (:image parsed-json "")]]
|
||||
cat [(map (fn [e] ["about" (:id e) (-> e :prefLabel :de)]) (:about parsed-json))
|
||||
cat [(map (fn [e] ["about" (:id e) (-> e :prefLabel :de) "de"]) (:about parsed-json)) ;; TODO fix the language parsing and make it generic
|
||||
(map (fn [e] ["creator"
|
||||
(if-let [id (get e :id)]
|
||||
id
|
||||
"")
|
||||
(:name e)])
|
||||
(:creator parsed-json))
|
||||
(map (fn [e] ["inLanguage" e]) (:inLanguage parsed-json))])
|
||||
event {:kind 30142
|
||||
:created_at created_at
|
||||
:content "Added AMB Resource with d-tag"
|
||||
:content ""
|
||||
:tags tags}]
|
||||
event))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::convert-amb-and-publish-as-nostr-event
|
||||
::convert-amb-string-and-publish-as-nostr-event
|
||||
[(re-frame/inject-cofx :now)]
|
||||
(fn-traced [cofx [_ json-string]]
|
||||
(let [event (convert-amb-to-nostr-event json-string (:now cofx))]
|
||||
(let [parsed-json (js->clj (js/JSON.parse json-string) :keywordize-keys true)
|
||||
event (convert-amb-to-nostr-event parsed-json (:now cofx))]
|
||||
{::sign-and-publish-event [event (-> cofx :db :sk)]})))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::convert-amb-json-and-publish-as-nostr-event
|
||||
[(re-frame/inject-cofx :now)]
|
||||
(fn-traced [cofx [_ json]]
|
||||
(let [event (convert-amb-to-nostr-event json (:now cofx))]
|
||||
{::sign-and-publish-event [event (-> cofx :db :sk)]})))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
|
|
@ -308,7 +359,7 @@
|
|||
tags-to-add (filter #(not (contains? existing-tags-set %))
|
||||
(map (fn [e] (cond
|
||||
(= 1 (:kind e)) ["e" (:id e)]
|
||||
(= 30142 (:kind e)) (nostr/build-kind-30142-tag e)))
|
||||
(= 30142 (:kind e)) (nostr/build-tag-for-adressable-event e)))
|
||||
resources-to-add))
|
||||
new-tags (vec (concat existing-tags tags-to-add))
|
||||
event {:kind 30004
|
||||
|
|
@ -337,11 +388,32 @@
|
|||
(let [query-for-lists ["REQ"
|
||||
(make-sub-id "lists-" npub) ;; TODO maybe make this more explicit later
|
||||
{:authors [(nostr/get-pk-from-npub npub)]
|
||||
:kinds list-kinds}]
|
||||
:kinds (concat (:follow-sets db) (:list-kinds db))}]
|
||||
sockets (:sockets db)]
|
||||
{::request-from-relay [sockets query-for-lists]
|
||||
:dispatch [::get-deleted-lists-for-npub [sockets npub]]})))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::relay-list-for-pk
|
||||
(fn [{:keys [db]} [_ pk]]
|
||||
(.log js/console "get relay list for pk" pk)
|
||||
(let [query-for-relay-list ["REQ"
|
||||
(make-sub-id "relay-lists-" pk) ;; TODO maybe make this more explicit later
|
||||
{:authors [pk]
|
||||
:kinds [30002]}]
|
||||
sockets (:sockets db)]
|
||||
{::request-from-relay [sockets query-for-relay-list]})))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::get-follow-set-for-npub
|
||||
(fn [{:keys [db]} [_ npub]]
|
||||
(let [query-for-lists ["REQ"
|
||||
(make-sub-id "follow-set-" npub) ;; TODO maybe make this more explicit later
|
||||
{:authors [(nostr/get-pk-from-npub npub)]
|
||||
:kinds (:follow-sets db)}]
|
||||
sockets (:sockets db)]
|
||||
{::request-from-relay [sockets query-for-lists]})))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::get-deleted-lists-for-npub
|
||||
(fn [cofx [_ [sockets npub]]]
|
||||
|
|
@ -351,7 +423,16 @@
|
|||
:kinds [5]}]]
|
||||
{::request-from-relay [sockets query-for-deleted-lists]})))
|
||||
|
||||
(comment)
|
||||
(re-frame/reg-event-fx
|
||||
::events-from-pks-actor-follows
|
||||
(fn [{:keys [db]} [_ pks]]
|
||||
(.log js/console "requesting events from pks actor follows..")
|
||||
(let [query-for-events ["REQ"
|
||||
(make-sub-id "events-from-pks-actor-follows" (:pk db))
|
||||
{:authors pks
|
||||
:kinds [1]
|
||||
:limit 10}]] ;; TODO remember timestamps and use them in query
|
||||
{::request-from-relay [(:sockets db) query-for-events]})))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::request-from-relay
|
||||
|
|
@ -374,32 +455,97 @@
|
|||
(str/replace #"\s" "-")
|
||||
(str/replace #"[^a-zA-Z0-9]" "-")))
|
||||
|
||||
(comment
|
||||
(cleanup-list-name "this is gönna be an awesüm+ l]st"))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::create-new-list
|
||||
[(re-frame/inject-cofx :now)]
|
||||
(fn [cofx [_ name]]
|
||||
(let [tags [["d" (cleanup-list-name name)]
|
||||
["name" name]]
|
||||
["title" name]]
|
||||
create-list-event {:kind 30004
|
||||
:created_at (:now cofx)
|
||||
:content ""
|
||||
:tags tags}]
|
||||
{::sign-and-publish-event [create-list-event (-> cofx :db :sk)]})))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Opencard stuff
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::create-new-opencard-index
|
||||
[(re-frame/inject-cofx :now)]
|
||||
(fn [cofx [_ name]]
|
||||
(let [tags [["d" (cleanup-list-name name)]
|
||||
["title" name]]
|
||||
create-opencard-index-event {:kind 30043
|
||||
:created_at (:now cofx)
|
||||
:content ""
|
||||
:tags tags}]
|
||||
{::sign-and-publish-event [create-opencard-index-event (-> cofx :db :sk)]})))
|
||||
|
||||
;; create, sign, publish list event
|
||||
;; add signed list event to opencard-index
|
||||
(re-frame/reg-event-fx
|
||||
::add-opencard-list-to-index
|
||||
(fn [cofx [_ [name opencard-index-old]]]
|
||||
(let [opencard-list {:kind 30044
|
||||
:created_at (:now cofx)
|
||||
:content ""
|
||||
:tags [["d" (cleanup-list-name name)]
|
||||
["title" name]]}
|
||||
opencard-index (update opencard-index-old :created_at (:now cofx))]
|
||||
{::add-opencard-list-to-index-fx [opencard-list opencard-index (-> cofx :db :sk)]})))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::add-opencard-list-to-index-fx
|
||||
(fn [opencard-list opencard-index sk]
|
||||
(p/let [opencard-list-signed (sign-event opencard-list sk)
|
||||
opencard-index (update opencard-index :tags (fn [tags]
|
||||
(conj tags ["a" (nostr/build-tag-for-adressable-event opencard-list-signed)])))
|
||||
opencard-index-signed (sign-event opencard-index sk)]
|
||||
(re-frame/dispatch [::publish-signed-event opencard-list-signed])
|
||||
(re-frame/dispatch [::publish-signed-event opencard-index-signed]))))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::remove-opencard-list-from-index
|
||||
[(re-frame/inject-cofx :now)]
|
||||
(fn [cofx [_ opencard-list-to-delete opencard-index]]
|
||||
(let [opencard-index-new {:kind 30043
|
||||
:created_at (:now cofx)
|
||||
:content ""
|
||||
:tags (filter #(not= (:id opencard-list-to-delete) (:id %)) (:tags opencard-index))}]
|
||||
{::sign-and-publish-event [opencard-index-new (-> cofx :db :sk)]
|
||||
::delete-list [opencard-list-to-delete]})))
|
||||
|
||||
;; TODO add 30045 opencard note to opencard-list
|
||||
(re-frame/reg-event-fx
|
||||
::add-opencard-note-to-list
|
||||
[(re-frame/inject-cofx :now)]
|
||||
(fn [cofx [_ [name content] opencard-list]]
|
||||
(let [tags [["d" (cleanup-list-name name)]
|
||||
["title" name]] ;; TODO depending on the note content we might need to add more tags like references to other events and so on
|
||||
opencard-note-event {:kind 30045
|
||||
:created_at (:now cofx)
|
||||
:content content
|
||||
:tags tags}]
|
||||
{::sign-and-publish-event [opencard-note-event (-> cofx :db :sk)]})))
|
||||
|
||||
;; TODO delete-opencard-index
|
||||
;; should we just remove the index or anything associated? maybe ask the user first
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::delete-list
|
||||
[(re-frame/inject-cofx :now)]
|
||||
(fn [cofx [_ l]]
|
||||
(let [deletion-event {:kind 5
|
||||
(let [{:keys [list-kinds opencard-kinds]} (:db cofx)
|
||||
all-list-kinds (concat list-kinds opencard-kinds)
|
||||
deletion-event {:kind 5
|
||||
:created_at (:now cofx)
|
||||
:content ""
|
||||
:tags [(cond
|
||||
(= 1 (:kind l))
|
||||
["e" (:id l)]
|
||||
(some #{(:kind l)} (-> cofx :db :list-kinds))
|
||||
(some #{(:kind l)} all-list-kinds)
|
||||
["a" (str (:kind l) ":" (:pubkey l) ":" (second (first (filter
|
||||
#(= "d" (first %))
|
||||
(:tags l)))))])]}]
|
||||
|
|
@ -448,27 +594,15 @@
|
|||
(p/let [raw-html (js/fetch uri {:headers {"Access-Control-Allow-Origin" "*"}})]
|
||||
(println raw-html))))
|
||||
|
||||
(comment
|
||||
(p/->> (js/fetch "https://oersi.org/resources/aHR0cHM6Ly9lZ292LWNhbXB1cy5vcmcvY291cnNlcy9hcmJlaXRlbnVuZGZ1ZWhyZW5fdXBfMjAyMi0x")
|
||||
(println)))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::publish-amb-uri-as-nostr-event
|
||||
(fn [db [_ uri]]
|
||||
{::get-amb-json-from-uri uri}))
|
||||
|
||||
(def confetti-instance
|
||||
(new jsConfetti))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::add-confetti
|
||||
(fn [cofx _]
|
||||
(let [visited-at (-> cofx :db :visited-at)
|
||||
now (quot (.now js/Date) 1000)
|
||||
diff (- now visited-at)]
|
||||
(when (>= diff 5)
|
||||
(.addConfetti confetti-instance (clj->js {:emojis ["😺" "🐈⬛" "🦄"]})))
|
||||
{})))
|
||||
{:http-xhrio {:method :get
|
||||
:uri uri
|
||||
:timeout 8000
|
||||
:response-format (ajax/json-response-format {:keywords? true})
|
||||
:on-success [::convert-amb-json-and-publish-as-nostr-event]
|
||||
:on-failure (.log js/console "publishing amb uri as nostr did not work")}}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::set-visit-timestamp
|
||||
|
|
@ -476,4 +610,207 @@
|
|||
(fn [cofx [_]]
|
||||
{:db (assoc (:db cofx) :visited-at (:now cofx))}))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Get metadata from uri ;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn valid-uri? [s]
|
||||
(try
|
||||
(js/URL. s)
|
||||
true
|
||||
(catch :default e false)))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::try-get-metadata-from-uri
|
||||
(fn [cofx [_ uri]]
|
||||
(when (valid-uri? uri)
|
||||
(println "valid uri" (valid-uri? uri))
|
||||
{:dispatch [::get-metadata-from-json uri]})))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::try-get-metadata-from-uri-fx
|
||||
(fn [uri]
|
||||
(try
|
||||
(println "trying to get metadata from json")
|
||||
(re-frame/dispatch [::get-metadata-from-json uri])
|
||||
(catch :default e
|
||||
(println "... did not work..looking for script tag")
|
||||
(try
|
||||
(re-frame/dispatch [::get-metadata-from-script-tag uri])
|
||||
(catch :default e
|
||||
(js/console.error "all attempts to fetch something sensible failed.")))))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::prefill-metadata-form
|
||||
(fn [db [_ uri data]]
|
||||
(if (and
|
||||
(:id data)
|
||||
(:about data))
|
||||
(assoc db :resource-to-add data)
|
||||
(doall (js/console.error "Not the right kind of data")
|
||||
(re-frame/dispatch [::get-metadata-from-script-tag uri])))))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::failure
|
||||
(fn [cofx _]
|
||||
(println "failure in xhrio request")))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::parse-text-for-script-tag
|
||||
(fn [db [_ uri data]]
|
||||
(let [parser (js/DOMParser.)
|
||||
doc (.parseFromString parser data "text/html")
|
||||
script-tag (.querySelector doc "script[type='application/ld+json']")]
|
||||
(if script-tag
|
||||
(let [ld-json (js/JSON.parse (.-textContent script-tag))]
|
||||
(assoc db :resource-to-add (js->clj ld-json)))
|
||||
(println "did not get script tag")))))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::get-metadata-from-script-tag
|
||||
(fn [cofx [_ uri]]
|
||||
(println "now trying to get script tag..")
|
||||
{:http-xhrio {:method :get
|
||||
:uri uri
|
||||
:timeout 3000
|
||||
:response-format (ajax/text-response-format)
|
||||
:on-success [::parse-text-for-script-tag uri]
|
||||
:on-failure [::failure]}}))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::get-metadata-from-json
|
||||
(fn [cofx [_ uri]]
|
||||
{:http-xhrio {:method :get
|
||||
:uri (if (str/ends-with? uri "json") ;; TODO elaborate this a bit more
|
||||
uri
|
||||
(str/replace uri #".html" ".json"))
|
||||
:timeout 3000
|
||||
:response-format (ajax/json-response-format {:keywords? true})
|
||||
:on-success [::prefill-metadata-form uri]
|
||||
:on-failure [::get-metadata-from-script-tag uri]}}))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::save-concept-scheme
|
||||
(fn [db [_ cs]]
|
||||
(assoc-in db [:concept-schemes (:id cs)] cs)))
|
||||
|
||||
(comment
|
||||
(keyword "https://ww.googl-e.com/"))
|
||||
|
||||
(defn jsonize-uri
|
||||
[uri]
|
||||
(cond
|
||||
(str/ends-with? uri ".json")
|
||||
uri
|
||||
(str/ends-with? uri ".html")
|
||||
(str/replace uri #"\.\w{4}" ".json")
|
||||
:else
|
||||
(str uri ".json")))
|
||||
|
||||
(comment
|
||||
(jsonize-uri "https:/goo"))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::skos-concept-scheme-from-uri
|
||||
(fn [cofx [_ uri]]
|
||||
{:http-xhrio {:method :get
|
||||
:uri (jsonize-uri uri)
|
||||
:timeout 5000
|
||||
:response-format (ajax/json-response-format {:keywords? true})
|
||||
:on-success [::save-concept-scheme]
|
||||
:on-failure [::failure]}}))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::toggle-concept
|
||||
(fn [db [_ [concept field]]]
|
||||
(update-in db [:md-form-resource field] (fn [coll]
|
||||
(if (some #(= (:id concept) (:id %)) coll)
|
||||
(filter (fn [e] (not= (:id e) (:id concept))) coll)
|
||||
(conj coll (select-keys concept [:id :notation :prefLabel] )))))))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::handle-md-form-input
|
||||
(fn [db [_ [field-name field-value]]]
|
||||
(assoc-in db [:md-form-resource field-name] field-value)))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::handle-md-form-array-input
|
||||
(fn [db [_ [field-name field-id field-value]]]
|
||||
(assoc-in db [:md-form-resource field-name field-id] field-value)))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::handle-md-form-rm-input
|
||||
(fn [db [_ [field-name field-id]]]
|
||||
(update-in
|
||||
db
|
||||
[:md-form-resource field-name]
|
||||
dissoc
|
||||
field-id)))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::handle-md-form-add-input
|
||||
(fn [db [_ [field-name]]]
|
||||
(assoc-in db [:md-form-resource field-name (random-uuid)] nil)))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::handle-search
|
||||
(fn [cofx [_ search-term]]
|
||||
(let [uri (str config/typesense-uri
|
||||
"search?q="
|
||||
search-term
|
||||
"&query_by="
|
||||
"name,about,description,creator")]
|
||||
{:http-xhrio {:method :get
|
||||
:uri uri
|
||||
:headers {"x-typesense-api-key" "xyz"}
|
||||
:timeout 5000
|
||||
:response-format (ajax/json-response-format {:keywords? true})
|
||||
:on-success [::save-search-results]
|
||||
:on-failure [::failure]}})))
|
||||
|
||||
(defn sanitize-filter-term [term]
|
||||
(str/replace term #"[ ()]" " "))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::handle-filter-search
|
||||
(fn [cofx [_ [filter-attribute filter-term]]]
|
||||
(let [uri (str config/typesense-uri
|
||||
"search?q=*"
|
||||
"&query_by="
|
||||
"name"
|
||||
"&filter_by="
|
||||
filter-attribute
|
||||
":="
|
||||
(sanitize-filter-term filter-term ))] ;; parantetheses seem to cause error when filtering
|
||||
{:http-xhrio {:method :get
|
||||
:uri uri
|
||||
:headers {"x-typesense-api-key" "xyz"}
|
||||
:timeout 5000
|
||||
:response-format (ajax/json-response-format {:keywords? true})
|
||||
:on-success [::save-search-results]
|
||||
:on-failure [::failure]}})))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::save-search-results
|
||||
(fn [db [_ results]]
|
||||
(let [raw-result-events (map #(-> % :document :event_raw) (:hits results))]
|
||||
(-> db
|
||||
(assoc :search-results (:hits results))
|
||||
(update :events into raw-result-events)))))
|
||||
|
||||
(re-frame/reg-event-fx
|
||||
::load-profile
|
||||
(fn [cofx [_ pubkey]]
|
||||
(let [sockets (filter #(= "connected" (:status %)) (-> cofx :db :sockets))
|
||||
profile-request ["REQ"
|
||||
(make-sub-id "profile" pubkey)
|
||||
{:kinds [0]
|
||||
:authors [pubkey]
|
||||
:limit 10}]]
|
||||
{::request-from-relay [sockets profile-request]})))
|
||||
|
||||
(re-frame/reg-event-db
|
||||
::set-md-scheme
|
||||
(fn [db [_ md-scheme]]
|
||||
(assoc db :selected-md-scheme md-scheme)))
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,15 @@
|
|||
["nostr-tools/pure" :as nostr]
|
||||
[cljs.core :as c]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;
|
||||
;; Profile
|
||||
;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn profile-picture [profile pubkey]
|
||||
(if (:picture profile)
|
||||
(:picture profile)
|
||||
(str "https://robohash.org/" pubkey)))
|
||||
|
||||
(defn event-to-serialized-json [m]
|
||||
(let [event [0
|
||||
(:pubkey m)
|
||||
|
|
@ -47,10 +56,14 @@
|
|||
(defn sk-as-hex [sk]
|
||||
(byte-array-to-hex sk))
|
||||
|
||||
(defn string-to-byte-array [s]
|
||||
(let [encoder (js/TextEncoder.)]
|
||||
(.encode encoder s)))
|
||||
|
||||
(defn sk-as-nsec [sk]
|
||||
;; let nsec = nip19.nsecEncode(sk)
|
||||
;; sk should be byte-array
|
||||
(.nsecEncode nip19 sk))
|
||||
(.nsecEncode nip19 (string-to-byte-array sk)))
|
||||
|
||||
(defn nsec-as-sk [nsec]
|
||||
;; byte array is returned
|
||||
|
|
@ -84,11 +97,76 @@
|
|||
(.encode encoder s)))
|
||||
|
||||
(defn get-npub-from-pk [pk]
|
||||
(.npubEncode nip19 pk))
|
||||
(.npubEncode nip19 pk))
|
||||
|
||||
(comment
|
||||
(get-npub-from-pk "1c5ff3caacd842c01dca8f378231b16617516d214da75c7aeabbe9e1efe9c0f6"))
|
||||
|
||||
(defn get-pk-from-npub [npub]
|
||||
(.-data (.decode nip19 npub)))
|
||||
|
||||
(comment
|
||||
(get-pk-from-npub "npub1r30l8j4vmppvq8w23umcyvd3vct4zmfpfkn4c7h2h057rmlfcrmq9xt9ma"))
|
||||
|
||||
(defn get-d-id-from-event [event]
|
||||
(second (first (filter #(= "d" (first %)) (:tags event)))))
|
||||
|
||||
(defn naddr-from-event [event]
|
||||
(let [pk (:pubkey event)
|
||||
relays #_(get-relays-from-event event) ["ws://localhost:7777" "wss://relay.sc24.steffen-roertgen.de"] ;; TODO how to know what relays to use here? do i need to remember where i fetched an event from? or maybe just skip?
|
||||
kind (:kind event)
|
||||
identifier (get-d-id-from-event event)
|
||||
naddr (.naddrEncode nip19 (clj->js {:pubkey pk
|
||||
:relays relays
|
||||
:kind kind
|
||||
:identifier identifier}))]
|
||||
naddr))
|
||||
|
||||
(defn decode-naddr
|
||||
"Takes a nip-19 naddr and returns its data
|
||||
Returns:
|
||||
A map with `:identifier` `:pubkey` `:relays` `:kind`.
|
||||
"
|
||||
[naddr]
|
||||
|
||||
(:data (js->clj (.decode nip19 naddr) :keywordize-keys true)))
|
||||
|
||||
(comment
|
||||
(def addressable-event {:content "Added AMB Resource with d-tag",
|
||||
:created_at 1729616701,
|
||||
:id
|
||||
"dec183af8f1ac91dac3bb7716b8564ab515c050480721c7ee2ea308b51caf982",
|
||||
:kind 30142,
|
||||
:pubkey
|
||||
"1c5ff3caacd842c01dca8f378231b16617516d214da75c7aeabbe9e1efe9c0f6",
|
||||
:sig
|
||||
"17d54fc1a418100a41d8b9dd0eac5db0cad544bff84c5d13befa1c1791979fbf319234c9749a690d1cb166116741642deafb8b9c8f91a16590931f7eb80f3bb4",
|
||||
:tags
|
||||
[["d" "https://av.tib.eu/media/40427"]
|
||||
["r" "https://av.tib.eu/media/40427"]
|
||||
["id" "https://av.tib.eu/media/40427"]
|
||||
["name" "Digitale Identitäten: Sicher, dezentral und Europäisch?"]
|
||||
["description"
|
||||
"(de)#INFORMATIK2018 Panelgespräch: Digitale Identitäten: Sicher, dezentral und Europäisch? Moderation: Dr. Jan Sürmeli, TU Berlin • Prof. Dr. Reinhard Riedl, Präsident Schweizer InformatikGesellschaft (FH Bern) • Prof. Dr. Hannes Federrath, Präsident der Gesellschaft für Informatik (Universität Hamburg) • Prof. Dr. Kai Rannenberg, Präsidium der Gesellschaft für Informatik (Uni Frankfurt) • Prof. Dr. Stefan Jähnichen, TU Berlin / FZI Forschungszentrum Informatik • Benjamin Helfritz, DIN Deutsches Institut für Normung e.V. • Arno Fiedler, Vorstand Sichere Identität Berlin Brandenburg"]
|
||||
["keywords" "Computer Science"]
|
||||
["image" "https://av.tib.eu/thumbnail/40427"]
|
||||
["about"
|
||||
"https://w3id.org/kim/hochschulfaechersystematik/n71"
|
||||
"Studienbereich Informatik"
|
||||
"de"]
|
||||
["about"
|
||||
"https://w3id.org/kim/hochschulfaechersystematik/n8"
|
||||
"Ingenieurwissenschaften"
|
||||
"de"]
|
||||
["creator" "test-uri" "maxi muster"]
|
||||
["creator" "" "maxa muster"]
|
||||
["inLanguage" "de"]]})
|
||||
(naddr-from-event addressable-event)
|
||||
(get-creators-from-metadata-event addressable-event)
|
||||
(.log js/console (clj->js addressable-event))
|
||||
|
||||
(decode-naddr "naddr1qvzqqqr4hcpzq8zl7092ekzzcqwu4rehsgcmzesh29kjznd8t3aw4wlfu8h7ns8kqqwksar5wpen5te0v9mzuarfvghx2af0d4jkg6tp9u6rqdpjxunlx7u8"))
|
||||
|
||||
;; TODO make multimethod?
|
||||
(defn get-list-name [list]
|
||||
(or (second (first (filter #(= "title" (first %)) (:tags list))))
|
||||
|
|
@ -96,14 +174,20 @@
|
|||
(second (first (filter #(= "d" (first %)) (:tags list))))
|
||||
(str "No name found for List-ID: " (:id list))))
|
||||
|
||||
(defn get-d-id-from-event [event]
|
||||
(second (first (filter #(= "d" (first %)) (:tags event)))))
|
||||
|
||||
(defn get-name-from-metadata-event [event]
|
||||
(or (second (first (filter #(= "name" (first %)) (:tags event))))
|
||||
(second (first (filter #(= "title" (first %)) (:tags event))))
|
||||
(second (first (filter #(= "id" (first %)) (:tags event))))
|
||||
(str "No name found for Metadata-Event: " (:id event))))
|
||||
|
||||
(defn get-creators-from-metadata-event [event]
|
||||
(or (map (fn [e] {:id (second e)
|
||||
:name (nth e 2 "n/a")}) (filter #(= "creator" (first %)) (:tags event)))
|
||||
(str "No creators found for Metadata-Event: " (:id event))))
|
||||
|
||||
(defn get-keywords-from-metadata-event [event]
|
||||
(rest (first (filter (fn [e] (= "keywords" (first e))) (:tags event)))))
|
||||
|
||||
(defn get-image-from-metadata-event [event]
|
||||
(or (let [img-url (second (first (filter #(= "image" (first %)) (:tags event))))]
|
||||
(if (= "" img-url) false img-url))
|
||||
|
|
@ -182,7 +266,7 @@
|
|||
(seq metadata-event-ids-in-list)
|
||||
(contains? metadata-event-ids-in-list event-id))))
|
||||
|
||||
(defn build-kind-30142-tag [event]
|
||||
(defn build-tag-for-adressable-event [event]
|
||||
["a" (str "30142:" (:id event) ":" (second (first (filter #(= "d" (first %)) (:tags event)))))])
|
||||
|
||||
(defn get-event-ids-from-list [list]
|
||||
|
|
|
|||
13
src/ied/opencard/subs.cljs
Normal file
13
src/ied/opencard/subs.cljs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
(ns ied.opencard.subs
|
||||
(:require
|
||||
[re-frame.core :as re-frame]))
|
||||
|
||||
|
||||
(re-frame/reg-sub
|
||||
::opencard-boards
|
||||
(fn [db]
|
||||
(:opencard-boards db)))
|
||||
|
||||
(comment
|
||||
(re-frame/subscribe [::opencard-boards])
|
||||
)
|
||||
331
src/ied/opencard/views.cljs
Normal file
331
src/ied/opencard/views.cljs
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
(ns ied.opencard.views
|
||||
(:require
|
||||
[re-frame.core :as re-frame]
|
||||
[ied.opencard.subs :as subs]
|
||||
[clojure.string :as str]
|
||||
[ied.routes :as routes]
|
||||
[ied.components.icons :as icons]
|
||||
[markdown-to-hiccup.core :as m]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
;; Sample data
|
||||
(defonce state (reagent/atom {:lists [{:id 1 :items [{:id 1 :content "*Item 1*"} {:id 2 :content "Item 2"}]}
|
||||
{:id 2 :items [{:id 3 :content "Item 3"} {:id 4 :content "Item 4"}]}
|
||||
{:id 3 :items [{:id 5 :content "Item 5"} {:id 6 :content "Item 6"}]}
|
||||
{:id 4 :items [{:id 7 :content "Item 7"} {:id 8 :content "Item 8"}]}]
|
||||
:show-edit-item-modal false
|
||||
:selected-item {:id nil
|
||||
:content ""}
|
||||
:dragging nil
|
||||
:dragging-over nil
|
||||
:hover-index nil
|
||||
:hover-item nil
|
||||
:dragged-column nil
|
||||
:hover-column-id nil
|
||||
:dragging-item nil
|
||||
:dragging-over-list nil})) ;; New state to track the current column being dragged over
|
||||
|
||||
;; Helper functions
|
||||
(defn find-item [lists item-id]
|
||||
(some (fn [list]
|
||||
(some (fn [item]
|
||||
(when (= (:id item) item-id) [list item]))
|
||||
(:items list)))
|
||||
lists))
|
||||
|
||||
(defn reorder-items [items from-index to-index]
|
||||
(let [item (nth items from-index)
|
||||
items (vec (concat (subvec items 0 from-index) (subvec items (inc from-index))))
|
||||
head (subvec items 0 to-index)
|
||||
tail (subvec items to-index)]
|
||||
(vec (concat head [item] tail))))
|
||||
|
||||
(defn reorder-columns [columns from-index to-index]
|
||||
(let [column (nth columns from-index)
|
||||
columns-filtered (vec (concat (subvec (vec columns) 0 from-index) (subvec (vec columns) (inc from-index))))
|
||||
head (subvec columns-filtered 0 to-index)
|
||||
tail (subvec columns-filtered to-index)
|
||||
reordered (vec (concat head [column] tail))]
|
||||
reordered))
|
||||
|
||||
(defn handle-drag-start [item-id]
|
||||
(swap! state assoc :dragging item-id :hover-index nil :hover-item nil :dragged-column nil :dragging-item item-id))
|
||||
|
||||
(defn set-dragged-column
|
||||
"Sets `:dragged-column`. It's the id of the column"
|
||||
[column-id]
|
||||
(swap! state assoc :dragged-column column-id :hover-column-id nil :dragging nil))
|
||||
|
||||
(defn handle-drag-over [e list-id & [index item-id]]
|
||||
(.preventDefault e)
|
||||
(swap! state assoc :dragging-over list-id :dragging-over-list list-id) ;; Track the list being dragged over
|
||||
(when (and index item-id) ;; Only update hover-index and hover-item if item details are provided
|
||||
(swap! state assoc :hover-index index :hover-item item-id)))
|
||||
|
||||
(defn handle-column-drag-over [e column-id]
|
||||
(.preventDefault e)
|
||||
(swap! state assoc :hover-column-id column-id))
|
||||
|
||||
(defn handle-column-drop []
|
||||
(let [is-dragged (:dragged-column @state)
|
||||
columns (:lists @state)
|
||||
hover-column-id (:hover-column-id @state)
|
||||
hover-column-index (.indexOf (map :id columns) hover-column-id)
|
||||
from-index (.indexOf (map :id columns) is-dragged)]
|
||||
(when (and is-dragged (not= from-index hover-column-index)) ;; Only reorder if we are dragging a column
|
||||
(swap! state update :lists #(reorder-columns % from-index hover-column-index)))
|
||||
;; Clear column drag state
|
||||
(swap! state assoc :dragged-column nil :hover-column-id nil)))
|
||||
|
||||
(defn handle-drop [list-id]
|
||||
(when-not (:dragged-column @state) ;; Only handle item drop if not dragging a column
|
||||
(let [item-id (:dragging @state)
|
||||
[source-list item] (find-item (:lists @state) item-id)
|
||||
hover-index (:hover-index @state)
|
||||
is-same-list (= (:id source-list) list-id)]
|
||||
(when item
|
||||
;; Handle reorder if the item is dropped in the same list
|
||||
(if is-same-list
|
||||
(when (not= (.indexOf (:items source-list) item) hover-index) ; Only reorder if the position changed
|
||||
(swap! state update :lists
|
||||
(fn [lists]
|
||||
(map (fn [list]
|
||||
(if (= (:id list) list-id)
|
||||
(update list :items #(reorder-items % (.indexOf (:items list) item)
|
||||
(if hover-index hover-index (count (:items list)))))
|
||||
list))
|
||||
lists))))
|
||||
;; Move the item to another list
|
||||
(swap! state update :lists
|
||||
(fn [lists]
|
||||
(map (fn [list]
|
||||
(cond
|
||||
;; Remove item from the source list
|
||||
(= (:id list) (:id source-list)) (update list :items #(remove (fn [i] (= (:id i) item-id)) %))
|
||||
;; Insert item into the destination list at the hover index or at the end if not hovering over any item
|
||||
(= (:id list) list-id) (update list :items
|
||||
(fn [items]
|
||||
(if hover-index
|
||||
(let [split-items (split-at hover-index items)]
|
||||
(vec (concat (first split-items) [item] (second split-items))))
|
||||
;; Insert at the end if no hover index is provided
|
||||
(conj items item))))
|
||||
:else list))
|
||||
lists)))))
|
||||
|
||||
;; Clear dragging state
|
||||
(swap! state assoc :dragging nil :dragging-over nil :hover-index nil :hover-item nil :dragging-item nil :dragging-over-list nil))))
|
||||
|
||||
(defn handle-drag-end []
|
||||
(swap! state assoc :dragging nil :dragging-over nil :hover-index nil :hover-item nil :dragging-item nil :dragging-over-list nil))
|
||||
|
||||
(defn handle-add-item [column]
|
||||
(swap! state update :lists
|
||||
(fn [lists]
|
||||
(map (fn [list]
|
||||
(if (= (:id column) (:id list))
|
||||
(update list :items
|
||||
(fn [items] (conj items {:id (rand-int 1000) :content "huhu"})))
|
||||
list))
|
||||
lists))))
|
||||
|
||||
(defn handle-add-column []
|
||||
(let [item {:id (rand-int 1000) :content "huhu"}
|
||||
column {:id (rand-int 1000) :items [item]}]
|
||||
(swap! state update :lists
|
||||
(fn [lists]
|
||||
(conj lists column)))))
|
||||
|
||||
(defn update-item-content [data-atom target-id new-content]
|
||||
(println "update item content with : " new-content)
|
||||
(swap! data-atom
|
||||
(fn [data]
|
||||
(update data :lists
|
||||
(fn [lists]
|
||||
(mapv (fn [list]
|
||||
(update list :items
|
||||
(fn [items]
|
||||
(mapv (fn [item]
|
||||
(if (= (:id item) target-id)
|
||||
(assoc item :content new-content)
|
||||
item))
|
||||
items))))
|
||||
lists))))))
|
||||
|
||||
(defn find-item-by-id [target-id]
|
||||
(some #(when (= (:id %) target-id) %)
|
||||
(mapcat :items (:lists @state))))
|
||||
|
||||
(defn open-edit-item-modal []
|
||||
(.showModal (.getElementById js/document "my_modal_1")))
|
||||
|
||||
;; Components
|
||||
(defn draggable-item [{:keys [id content]} list-id index]
|
||||
(fn []
|
||||
(let [item (find-item-by-id id)
|
||||
is-hovered (= id (:hover-item @state))
|
||||
is-dragging (= id (:dragging-item @state))] ;; Determine if this item is being dragged
|
||||
[:div {:draggable true
|
||||
:on-drag-start #(handle-drag-start id)
|
||||
:on-drag-over (fn [e] (handle-drag-over e list-id index id))
|
||||
:on-double-click #((swap! state assoc :selected-item {:id id
|
||||
:content content})
|
||||
(open-edit-item-modal))
|
||||
:class (str/join " " ["hover:cursor-grab"
|
||||
"hover:bg-yellow-200"
|
||||
"bg-slate-100"
|
||||
"min-h-16"
|
||||
"p-2"
|
||||
"text-black"
|
||||
(when is-dragging "hidden")
|
||||
])} ;; Bring to front if dragging
|
||||
(->> (:content item)
|
||||
(m/md->hiccup)
|
||||
(m/component))])))
|
||||
|
||||
(defn droppable-list [id]
|
||||
(fn []
|
||||
(let [items (:items (some #(when (= id (:id %)) %) (:lists @state)))
|
||||
dragging-over? (= id (:dragging-over @state))
|
||||
dragging-item (:dragging-item @state)
|
||||
dragging-over-list (:dragging-over-list @state)] ;; Get the list currently being dragged over
|
||||
[:div {:on-drag-over (fn [e] (handle-drag-over e id)) ; Only list id is passed here to mark the column as hovered
|
||||
:on-drop (fn [_] (handle-drop id))
|
||||
:class (str/join " " ["relative"
|
||||
"flex flex-col gap-1"
|
||||
"rounded-md"
|
||||
"min-h-32"
|
||||
(if dragging-over? "bg-green-200" "")
|
||||
(if dragging-over? "border-dashed border-2 border-green-500" "")])}
|
||||
;; Ensure the dragged item is positioned correctly
|
||||
(doall
|
||||
(for [index (range (count items))]
|
||||
(let [item (nth items index)]
|
||||
;; Render the placeholder only in the correct column
|
||||
^{:key (:id item)} [:<>
|
||||
(when (and dragging-item (= id dragging-over-list) (= index (:hover-index @state)))
|
||||
[:div {:class (str/join " " ["min-h-16"
|
||||
"m-2"
|
||||
"bg-slate-100"
|
||||
"opacity-50"])}])
|
||||
[draggable-item item id index]])))])))
|
||||
|
||||
;; TODO update title
|
||||
(defn title-field []
|
||||
(let [edit (reagent/atom false)]
|
||||
(fn []
|
||||
[:div {:class "flex justify-between items-center"}
|
||||
(if @edit
|
||||
[:input {:class "input"}]
|
||||
[:p {} "Title of column"])
|
||||
[:label {:on-click #(reset! edit (not @edit))
|
||||
:class "btn"} [icons/pencil]]])))
|
||||
|
||||
(defn draggable-column [column index]
|
||||
(fn []
|
||||
(let [is-hovered (= (:id column) (:hover-column-id @state))
|
||||
is-dragged (:dragged-column @state)] ;; Get the currently dragged column
|
||||
[:div {:on-drag-over (fn [e] (when is-dragged (handle-column-drag-over e (:id column)))) ;; Only handle if a column is being dragged
|
||||
:on-drag-end #(handle-drag-end)
|
||||
:on-drop (fn [_] (handle-column-drop))
|
||||
:class (str/join " " [""
|
||||
"overflow-y-auto"
|
||||
"max-h-screen"
|
||||
"m-2"
|
||||
"p-4"
|
||||
"rounded-md"
|
||||
"w-96"
|
||||
(if (and is-hovered is-dragged)
|
||||
"border-dashed border-2 border-red-200 bg-red-200"
|
||||
"border-solid border-1 border-slate-100 bg-slate-200")])}
|
||||
|
||||
;; Add the handle for column dragging
|
||||
[:div {:class "flex flex-col gap-1"}
|
||||
[:span {:draggable true
|
||||
:on-drag-start (fn [_] (set-dragged-column (:id column)))
|
||||
; :on-drag-start (fn [_] (handle-column-drag-start index))
|
||||
:class "cursor-grab p-2 mr-0 ml-auto bg-slate-100 border-solid rounded-full"}
|
||||
[icons/move]]
|
||||
[:div {:class "rounded-md bg-primary p-2 text-xl text-white font-medium"}
|
||||
[title-field]]
|
||||
[:label
|
||||
{:class "btn btn-neutral my-1"
|
||||
:on-click #(handle-add-item column)}
|
||||
[icons/add "white"]]
|
||||
|
||||
[droppable-list (:id column)]]])))
|
||||
|
||||
(defn add-column []
|
||||
[:label
|
||||
{:class "btn btn-rounded fixed bottom-4 right-4 bg-blue-500 text-white p-3 shadow-lg hover:bg-blue-600"
|
||||
:on-click #(handle-add-column)}
|
||||
[icons/add]])
|
||||
|
||||
(defn drag-and-drop []
|
||||
[:div {:class "flex overflow-auto"}
|
||||
[:div {:class "flex flex-nowrap "}
|
||||
(doall
|
||||
(for [index (range (count (:lists @state)))]
|
||||
(let [hash (hash (:items (nth (:lists @state) index)))]
|
||||
^{:key hash} [draggable-column (nth (:lists @state) index) index])))]
|
||||
[add-column]])
|
||||
|
||||
(defn edit-item-modal []
|
||||
(let [content (reagent/atom "")
|
||||
selected-item-content (reagent/reaction
|
||||
(let [item (:selected-item @state)]
|
||||
(:content item)))]
|
||||
(reagent/track! (fn []
|
||||
(reset! content @selected-item-content)))
|
||||
(fn []
|
||||
(let [item (:selected-item @state)
|
||||
id (:id item)]
|
||||
[:dialog
|
||||
{:id "my_modal_1", :class "modal"}
|
||||
[:div
|
||||
{:class "modal-box"}
|
||||
[:h3 {:class "text-lg font-bold"} "Hello!"]
|
||||
[:p @content]
|
||||
[:textarea {:class "textarea textarea-bordered"
|
||||
:value @content
|
||||
:on-change (fn [e] (reset! content (-> e .-target .-value)))}]
|
||||
[:p
|
||||
{:class "py-4"}
|
||||
"Press ESC key or click the button below to close"]
|
||||
[:div
|
||||
{:class "modal-action"}
|
||||
[:form
|
||||
{:method "dialog"}
|
||||
[:button {:class "btn"
|
||||
:on-click (fn [e]
|
||||
(update-item-content state id @content))} "Save"]]]]]))))
|
||||
|
||||
(defn board-component [board]
|
||||
[:div {:class ""}
|
||||
[:button {:class "btn"} (:title board)]])
|
||||
|
||||
(defn opencard-boards []
|
||||
(let [boards (re-frame/subscribe [::subs/opencard-boards])]
|
||||
[:div {:class "grid grid-cols-4 gap-2 mt-2"}
|
||||
(doall
|
||||
(for [board @boards]
|
||||
^{:key (:id board)} [board-component board]))]))
|
||||
|
||||
(defn opencard-component []
|
||||
(let [show-board-overview (reagent/atom true)]
|
||||
(fn []
|
||||
[:div {:class "flex flex-col"}
|
||||
[:button {:class "btn btn-square ml-0 mr-auto"
|
||||
:on-click #(reset! show-board-overview (not @show-board-overview))} [icons/grid]]
|
||||
[:div {:class ""}
|
||||
(if @show-board-overview
|
||||
[opencard-boards]
|
||||
[drag-and-drop])]])))
|
||||
|
||||
(defn opencard-view-panel []
|
||||
[:div {:class ""}
|
||||
[opencard-component]
|
||||
[edit-item-modal]])
|
||||
|
||||
(defmethod routes/panels :opencard-view-panel [] [opencard-view-panel])
|
||||
|
||||
|
|
@ -12,18 +12,25 @@
|
|||
(atom
|
||||
["/"
|
||||
{"" :home
|
||||
"search" :search-view
|
||||
"add-resource" :add-resource
|
||||
"keys" :keys
|
||||
"feed" :event-feed
|
||||
"about" :about
|
||||
"settings" :settings
|
||||
["" [#"npub1[ac-hj-np-z02-9]{58}" :npub]] {"" :npub-view
|
||||
"/" :npub-view}}]))
|
||||
"/" :npub-view}
|
||||
["" [#"naddr1[ac-hj-np-z02-9]+" :naddr]] {"" :naddr-view
|
||||
"/" :naddr-view}
|
||||
"opencard" :opencard-view}]))
|
||||
|
||||
(comment
|
||||
(parse "/npub1r30l8j4vmppvq8w23umcyvd3vct4zmfpfkn4c7h2h057rmlfcrmq9xt9ma/")
|
||||
(navigate! (parse "/npub1r30l8j4vmppvq8w23umcyvd3vct4zmfpfkn4c7h2h057rmlfcrmq9xt9ma/"))
|
||||
(navigate! (parse "/naddr1r30l8j4vmppvq8w23umcyvd3vct4zmfpfkn4c7h2h057rmlfcrmq9xt9ma/"))
|
||||
(apply url-for [:npub-view :npub "npub1r30l8j4vmppvq8w23umcyvd3vct4zmfpfkn4c7h2h057rmlfcrmq9xt9ma"])
|
||||
(apply url-for [:home]))
|
||||
(apply url-for [:naddr-view :naddr "naddr1r30l8j4vmppvq8w23umcyvd3vct4zmfpfkn4c7h2h057rmlfcrmq9xt9ma"])
|
||||
(apply url-for [:home])
|
||||
(navigate! (parse "/opencard")))
|
||||
|
||||
(defn parse
|
||||
[url]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
(ns ied.subs
|
||||
(:require
|
||||
[re-frame.core :as re-frame]
|
||||
[ied.events :as events]
|
||||
[clojure.string :as str]
|
||||
[clojure.set :as set]
|
||||
[ied.nostr :as nostr]))
|
||||
|
|
@ -25,6 +26,15 @@
|
|||
(fn [db _]
|
||||
(:events db)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::event-by-id
|
||||
(fn [db [_ id]]
|
||||
(first (filter #(= (:id %) id) (:events db)))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::events-by-d-tag
|
||||
(fn [db [_ d]]
|
||||
(sort-by :created_at #(> %1 %2) (filter #(= d (nostr/get-d-id-from-event %)) (:events db)))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::metadata-events
|
||||
|
|
@ -44,7 +54,9 @@
|
|||
(re-frame/reg-sub
|
||||
::npub
|
||||
(fn [db _]
|
||||
(nostr/get-npub-from-pk (:pk db))))
|
||||
(if (:pk db)
|
||||
(nostr/get-npub-from-pk (:pk db))
|
||||
nil)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::nsec
|
||||
|
|
@ -93,7 +105,7 @@
|
|||
deleted-list-ids)
|
||||
(not (contains? deleted-list-ids d-id)))
|
||||
|
||||
(defn most-recent-by-d-tag
|
||||
(defn most-recent-event-by-d-tag
|
||||
[events]
|
||||
(->> events
|
||||
(group-by (fn [event]
|
||||
|
|
@ -112,9 +124,9 @@
|
|||
:<- [::deleted-list-ids]
|
||||
(fn [[list-kinds events deleted-lists]]
|
||||
(let [all-lists (filter #(and (some #{(:kind %)} list-kinds)
|
||||
#_(d-id-not-in-deleted-list-ids (get-d-id-from-tags (:tags %)) deleted-lists))
|
||||
(d-id-not-in-deleted-list-ids (get-d-id-from-tags (:tags %)) deleted-lists))
|
||||
events)
|
||||
most-recent-lists (most-recent-by-d-tag all-lists)]
|
||||
most-recent-lists (most-recent-event-by-d-tag all-lists)]
|
||||
#_(.log js/console "all lists: " (clj->js all-lists))
|
||||
#_(.log js/console "most recent lists: " (clj->js most-recent-lists))
|
||||
most-recent-lists)))
|
||||
|
|
@ -210,6 +222,120 @@
|
|||
(filter (fn [e] (contains? event-ids (:id e))) (:events db))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::visited-at
|
||||
::visited-at
|
||||
(fn [db _]
|
||||
(:visited-at db)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::follow-sets
|
||||
(fn [db _]
|
||||
(filter #(= 30000 (:kind %)) (:events db))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
;; returns a vector of pks of people the actor follows
|
||||
::following
|
||||
:<- [::follow-sets]
|
||||
:<- [::pk]
|
||||
(fn [[follow-sets pk]]
|
||||
(let [f (->> follow-sets
|
||||
(filter #(= pk (:pubkey %)))
|
||||
(map :tags)
|
||||
(apply concat)
|
||||
(filter #(= "p" (first %)))
|
||||
(map second))]
|
||||
f)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::posts-from-pks-actor-follows
|
||||
:<- [::following]
|
||||
:<- [::events]
|
||||
(fn [[following-pks events]]
|
||||
(->> events
|
||||
(filter (fn [e] (some #(= (:pubkey e) %) following-pks)))
|
||||
(filter #(= 1 (:kind %))))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::concept-schemes
|
||||
(fn [db [_ schemes]]
|
||||
(into {} (map (fn [cs]
|
||||
[cs (get-in db [:concept-schemes cs]) nil])
|
||||
schemes))))
|
||||
|
||||
(comment
|
||||
(get-in {:concept-schemes {"1" :yes "2" :no}} [:concept-schemes "3"] nil))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::toggled-concepts
|
||||
(fn [db]
|
||||
(apply concat (vals (:md-form-resource db)))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::toggled
|
||||
:<- [::toggled-concepts]
|
||||
(fn [[toggled-concepts] id]
|
||||
(some #(= (:id %) id) toggled-concepts)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::md-form-array-input-fields
|
||||
(fn [db [_ field]]
|
||||
(get-in db [:md-form-resource field] {(random-uuid) nil})))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::search-results
|
||||
(fn [db]
|
||||
(get db :search-results [])))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::grouped-search-results
|
||||
:<- [::search-results]
|
||||
(fn [search-results]
|
||||
(group-by #(get-in % [:document :resource_id]) search-results)))
|
||||
|
||||
(defn profile-from-db [db pubkey] ;; TODO think about if this will always fetch the latest profile
|
||||
(js->clj (js/JSON.parse (:content (first (filter #(and (= 0 (:kind %))
|
||||
(= pubkey (:pubkey %))) (:events db)))))
|
||||
:keywordize-keys true))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::profile
|
||||
(fn [db [_ pubkey]]
|
||||
(let [profile (profile-from-db db pubkey)]
|
||||
(when (nil? profile)
|
||||
(re-frame/dispatch [::events/load-profile pubkey]))
|
||||
profile)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::profiles
|
||||
(fn [db [_ pubkeys]]
|
||||
(let [profiles (map (fn [p] [p (profile-from-db db p)]) pubkeys)]
|
||||
(doseq [[pubkey profile] profiles]
|
||||
(when (nil? profile)
|
||||
(re-frame/dispatch [::events/load-profile pubkey])))
|
||||
profiles))) ;; unique profiles
|
||||
|
||||
(re-frame/reg-sub
|
||||
::md-form-image
|
||||
(fn [db _]
|
||||
(:visited-at db)))
|
||||
(-> db :md-form-resource :image)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::selected-md-scheme
|
||||
(fn [db]
|
||||
(:selected-md-scheme db)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::md-form-resource
|
||||
(fn [db]
|
||||
(:md-form-resource db)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
::user-language
|
||||
(fn [db]
|
||||
(:user-language db)))
|
||||
|
||||
(comment
|
||||
@(re-frame/subscribe [::profile "1c5ff3caacd842c01dca8f378231b16617516d214da75c7aeabbe9e1efe9c0f6"])
|
||||
|
||||
@(re-frame/subscribe [::md-form-resource])
|
||||
@(re-frame/subscribe [::md-form-image])
|
||||
)
|
||||
|
|
|
|||
113
src/ied/utils.cljs
Normal file
113
src/ied/utils.cljs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
(ns ied.utils
|
||||
(:require [re-frame.core :as re-frame]))
|
||||
|
||||
(comment
|
||||
(def test-form-data
|
||||
{:description "eine beschreibung",
|
||||
:creator
|
||||
{#uuid "36620422-1c79-4610-96e7-dc161fcc823e" "autor 1",
|
||||
#uuid "270fc20f-74ca-40a1-89ea-12e35253d714" "autor 2"},
|
||||
:publisher
|
||||
{#uuid "4b47e9cf-21d5-4e35-8afd-c7989a3c464b" "v1",
|
||||
#uuid "340af7ad-e94a-45ff-8221-94bc89d1f976" "v2"},
|
||||
:funder
|
||||
{#uuid "cd65e75f-e0e6-4b6f-994d-14ab89b5ce7c" "stil",
|
||||
#uuid "2f6cf069-8ddb-4e17-b479-d0c388931b83" "daad"},
|
||||
:learningResourceType
|
||||
({:id "https://w3id.org/kim/hcrt/application",
|
||||
:prefLabel
|
||||
{:de "Softwareanwendung",
|
||||
:en "Software Application",
|
||||
:nl "Computerprogramma",
|
||||
:uk "Програмне забезпечення",
|
||||
:cs "Počítačový program",
|
||||
:fr "Application logicielle"}}
|
||||
{:id "https://w3id.org/kim/hcrt/assessment",
|
||||
:prefLabel
|
||||
{:de "Lernkontrolle",
|
||||
:en "Assessment",
|
||||
:nl "Evaluatie",
|
||||
:uk "Оцінювання",
|
||||
:cs "Hodnocení",
|
||||
:fr "Contrôle d’apprentissage"}}),
|
||||
:name "ein name",
|
||||
:datePublished "2018-01-10",
|
||||
:keywords
|
||||
{#uuid "516abd15-67dd-49fb-b1dd-699c84e7e76d" "s1",
|
||||
#uuid "27d95b48-8cbd-4ae8-90fc-2f88d88ee96e" "s2"},
|
||||
:image "ein bild",
|
||||
:uri "eine uri",
|
||||
:about
|
||||
[{:id "https://w3id.org/kim/hochschulfaechersystematik/n136",
|
||||
:notation ["136"],
|
||||
:prefLabel
|
||||
{:de "Religionswissenschaft",
|
||||
:en "Religious Studies",
|
||||
:uk "Релігієзнавство"}}
|
||||
{:id "https://w3id.org/kim/hochschulfaechersystematik/n1",
|
||||
:notation ["1"],
|
||||
:prefLabel
|
||||
{:de "Geisteswissenschaften",
|
||||
:en "Humanities",
|
||||
:uk "Гуманітарні науки"}}
|
||||
{:id "https://w3id.org/kim/hochschulfaechersystematik/n02",
|
||||
:notation ["02"],
|
||||
:prefLabel
|
||||
{:de "Evang. Theologie, -Religionslehre",
|
||||
:en "Protestant Theology, Protestant Religious Education",
|
||||
:uk "Протестантська теологія, протестантська релігійна освіта"}}
|
||||
{:id "https://w3id.org/kim/hochschulfaechersystematik/n01",
|
||||
:notation ["01"],
|
||||
:prefLabel
|
||||
{:de "Geisteswissenschaften allgemein",
|
||||
:en "Humanities (general)",
|
||||
:uk "Гуманітарні науки загалом"}}]})
|
||||
|
||||
(println test-form-data)
|
||||
|
||||
(form-data-to-tags test-form-data :description)
|
||||
(form-data-to-tags test-form-data :creator-form)
|
||||
|
||||
(def creator-uuid
|
||||
{:creator
|
||||
{#uuid "36620422-1c79-4610-96e7-dc161fcc823e" {:id 1
|
||||
:name "autor 1"}
|
||||
#uuid "270fc20f-74ca-40a1-89ea-12e35253d714" {:id 2
|
||||
:name "autor 2"}}})
|
||||
|
||||
(form-data-to-tags creator-uuid :creator-form)
|
||||
|
||||
(def creator-array
|
||||
{:creator
|
||||
[{:id 1
|
||||
:name "autor 1"}
|
||||
{:id 2
|
||||
:name "autor 2"}]})
|
||||
|
||||
(form-data-to-tags creator-array :creator))
|
||||
|
||||
(defmulti form-data-to-tags (fn [form-data k] k))
|
||||
|
||||
(defmethod form-data-to-tags :default [form-data k]
|
||||
nil)
|
||||
|
||||
(defmethod form-data-to-tags :description [form-data _]
|
||||
["description" (:description form-data)])
|
||||
|
||||
(defmethod form-data-to-tags :creator-form [form-data _]
|
||||
(map (fn [e]
|
||||
["creator" (get (val e) :id "") (get (val e) :name "")])
|
||||
(:creator form-data)))
|
||||
|
||||
(defmethod form-data-to-tags :creator [form-data _]
|
||||
(map (fn [e]
|
||||
["creator" (get e :id "") (get e :name "")])
|
||||
(:creator form-data)))
|
||||
|
||||
(defmethod form-data-to-tags :publisher [form-data _]
|
||||
(map (fn [e] ["creator" (val e)]) (:creator form-data)))
|
||||
|
||||
(defmethod form-data-to-tags :funder [form-data _]
|
||||
(map (fn [e] ["creator" (val e)]) (:creator form-data)))
|
||||
|
||||
(defmethod form-data-to-tags :learningResourceType [form-data _])
|
||||
|
|
@ -6,6 +6,11 @@
|
|||
[ied.routes :as routes]
|
||||
[ied.subs :as subs]
|
||||
[ied.nostr :as nostr]
|
||||
[ied.opencard.views :as opencard]
|
||||
[ied.views.search :as search]
|
||||
[ied.views.resource :as resource]
|
||||
[ied.views.add :as add]
|
||||
[ied.components.resource :as resource-component]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(defn layer-icon []
|
||||
|
|
@ -30,68 +35,6 @@
|
|||
{:d
|
||||
"M6 2c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm7 7V3.5L18.5 9z"}]])
|
||||
|
||||
;; add resource form
|
||||
(defn add-resource-form
|
||||
[name uri author]
|
||||
(let [s (reagent/atom {:name name
|
||||
:uri uri
|
||||
:author author})]
|
||||
(fn []
|
||||
[:form {:class "flex flex-col space-y-4"
|
||||
:on-submit (fn [e]
|
||||
(.preventDefault e)
|
||||
;; do something with the state @s
|
||||
)}
|
||||
[:label
|
||||
{:class "input input-bordered flex items-center gap-2"}
|
||||
"Name"
|
||||
[:input {:on-change (fn [e]
|
||||
(swap! s assoc :name (-> e .-target .-value)))
|
||||
:type "text", :class "grow", :placeholder "Best Resource Ever"}]]
|
||||
[:label
|
||||
{:class "input input-bordered flex items-center gap-2"}
|
||||
"URL"
|
||||
[:input {:on-change (fn [e]
|
||||
(swap! s assoc :uri (-> e .-target .-value)))
|
||||
:type "text", :class "grow", :placeholder "https://wirlernenonline.de/r31"}]]
|
||||
#_[:label
|
||||
{:class "input input-bordered flex items-center gap-2"}
|
||||
"Author"
|
||||
[:input {:on-change (fn [e]
|
||||
(swap! s assoc :author (-> e .-target .-value)))
|
||||
:type "text", :class "grow", :placeholder "Robbi"}]]
|
||||
|
||||
[:button {:class "btn btn-warning w-1/2 mx-auto"
|
||||
:on-click #(re-frame/dispatch [::events/publish-resource {:name (:name @s) ;; TODO this should be sth like build event
|
||||
:id (:uri @s)
|
||||
:author (:author @s)}])}
|
||||
"Publish Resource"]])))
|
||||
|
||||
(defn add-resource-by-json []
|
||||
(let [s (reagent/atom {:json-string ""})]
|
||||
(fn []
|
||||
[:div
|
||||
[:p "Paste an AMB object"]
|
||||
[:form {:on-submit (fn [e] (.preventDefault e))}
|
||||
[:textarea {:on-change (fn [e]
|
||||
(swap! s assoc :json-string (-> e .-target .-value)))}]
|
||||
[:button {:class "btn btn-warning"
|
||||
:on-click #(re-frame/dispatch [::events/convert-amb-and-publish-as-nostr-event (:json-string @s)])}
|
||||
"Publish as Nostr Event"]]])))
|
||||
|
||||
;; TODO try again using xhrio
|
||||
(defn add-resosurce-by-uri []
|
||||
(let [uri (reagent/atom {:uri ""})]
|
||||
(fn []
|
||||
[:form {:on-submit (fn [e] (.preventDefault e))}
|
||||
[:label {:for "uri"} "URI: "]
|
||||
[:input {:id "uri"
|
||||
:on-change (fn [e]
|
||||
(swap! uri assoc :uri (-> e .-target .-value)))}]
|
||||
[:button {:class "btn btn-warning"
|
||||
:on-click #(re-frame/dispatch [::events/publish-amb-uri-as-nostr-event (:uri @uri)])}
|
||||
"Publish as Nostr Event"]])))
|
||||
|
||||
;; event data modal
|
||||
(defn event-data-modal []
|
||||
(let [visible? @(re-frame/subscribe [::subs/show-event-data-modal])
|
||||
|
|
@ -108,68 +51,83 @@
|
|||
[:button "close"]]])))
|
||||
|
||||
;; metadata event component
|
||||
;; TODO add author profile name and image
|
||||
(defn metadata-event-component [event]
|
||||
(let [selected-events @(re-frame/subscribe [::subs/selected-events])]
|
||||
[:div
|
||||
{:class "animate-flyIn card bg-base-100 w-96 shadow-xl min-h-[620px]"}
|
||||
[:div {:class "relative"}
|
||||
[:div {:class "absolute w-12 h-12 right-12 -bottom-4 rounded-full bg-white"}
|
||||
(cond
|
||||
(= 30004 (:kind event)) [layer-icon]
|
||||
(= 30142 (:kind event)) [file-icon])]
|
||||
[:div {:class "flex flex-row gap-2"}
|
||||
[:div {:class "w-3/4 p-2 min-h-full flex flex-col mt-2 justify-between"}
|
||||
[:div {:class ""}
|
||||
[:div {:class "flex flex-row gap-2 "}
|
||||
[:div {:class "w-6 h-6 rounded-full bg-white"}
|
||||
(cond
|
||||
(= 30004 (:kind event)) [layer-icon]
|
||||
(= 30142 (:kind event)) [file-icon])]
|
||||
[:a {:href (nostr/get-d-id-from-event event)
|
||||
:class "font-bold hover:underline text-ellipsis overflow-hidden"}
|
||||
(nostr/get-name-from-metadata-event event)]]
|
||||
[:div {:class "flex flex-wrap"}
|
||||
(resource-component/about-tags [event])]
|
||||
[:p {:class "text-wrap"}
|
||||
(nostr/get-description-from-metadata-event event)]]
|
||||
|
||||
[:div {:class "flex flex-row justify-between items-center"}
|
||||
[:button {:class "btn"
|
||||
:on-click #(re-frame/dispatch [::events/toggle-show-event-data-modal event])} "Show Event Data"]
|
||||
[:div
|
||||
[:div {:class "form-control"}
|
||||
[:label {:class "cursor-pointer label "}
|
||||
[:span {:class "label-text m-2"} "Add to List "]
|
||||
[:input
|
||||
{:type "checkbox"
|
||||
:checked (contains? (set (map #(:id %) selected-events)) (:id event))
|
||||
:class "checkbox checkbox-success"
|
||||
:on-change #(re-frame/dispatch [::events/toggle-selected-events event])}]]]]]]
|
||||
[:div {:class "w-1/4"}
|
||||
[:figure
|
||||
[:img
|
||||
{:class "h-48 object-cover"
|
||||
{:class "h-48 object-cover mx-auto m-2"
|
||||
:loading "lazy"
|
||||
:src
|
||||
(nostr/get-image-from-metadata-event event)
|
||||
:alt ""}]]]
|
||||
[:div
|
||||
{:class "card-body"}
|
||||
[:a {:href (nostr/get-d-id-from-event event)
|
||||
:class "card-title hover:underline truncate"}
|
||||
(nostr/get-name-from-metadata-event event)]
|
||||
(doall
|
||||
(for [about (nostr/get-about-names-from-metadata-event event)]
|
||||
[:div {:class "badge badge-primary m-1 truncate "
|
||||
:key about} about]))
|
||||
[:p {:class "break-all"}
|
||||
(nostr/get-description-from-metadata-event event)]
|
||||
[:button {:on-click #(re-frame/dispatch [::events/toggle-show-event-data-modal event])} "Show Event Data"]
|
||||
[:div
|
||||
{:class "card-actions justify-end"}
|
||||
[:div
|
||||
{:class "form-control"}
|
||||
[:label
|
||||
{:class "cursor-pointer label"}
|
||||
[:span {:class "label-text"} ""]
|
||||
[:input
|
||||
{:type "checkbox"
|
||||
:checked (contains? (set (map #(:id %) selected-events)) (:id event))
|
||||
:class "checkbox checkbox-success"
|
||||
:on-change #(re-frame/dispatch [::events/toggle-selected-events event])}]]]]]]))
|
||||
:alt ""}]]]]))
|
||||
|
||||
(defn activity-feed []
|
||||
(let [following @(re-frame/subscribe [::subs/following])
|
||||
npub @(re-frame/subscribe [::subs/npub])
|
||||
_ (when (nil? following)
|
||||
(when npub (re-frame/dispatch [::events/get-follow-set-for-npub npub])))
|
||||
following-events @(re-frame/subscribe [::subs/posts-from-pks-actor-follows])
|
||||
_ (.log js/console (count following-events) (< 5 (count following-events)))
|
||||
_ (when (> 5 (count following-events))
|
||||
(re-frame/dispatch [::events/events-from-pks-actor-follows following]))]
|
||||
|
||||
[:div
|
||||
(doall
|
||||
(for [event following-events]
|
||||
[:p {:key (:id event)} (:content event)]))]))
|
||||
|
||||
;; events
|
||||
(defn events-panel []
|
||||
(let [events @(re-frame/subscribe [::subs/feed-events])
|
||||
selected-events @(re-frame/subscribe [::subs/selected-events])]
|
||||
[:div {:class ""}
|
||||
[:p (str "Num of events: " (count events))]
|
||||
(if (> (count events) 0)
|
||||
[:div {:class "flex flex-wrap justify-center gap-2"}
|
||||
(doall
|
||||
(fn []
|
||||
(let [events @(re-frame/subscribe [::subs/feed-events])
|
||||
selected-events @(re-frame/subscribe [::subs/selected-events])]
|
||||
[:div {:class "flex flex-col mx-auto gap-4 "}
|
||||
[activity-feed]
|
||||
(if (> (count events) 0)
|
||||
[:div {:class "divide-y divide-slate-500"}
|
||||
(doall
|
||||
(for [event events]
|
||||
[:div {:key (:id event)
|
||||
:class ""}
|
||||
[metadata-event-component event]]))]
|
||||
|
||||
(for [event events]
|
||||
[:div {:key (:id event)}
|
||||
[metadata-event-component event]]))]
|
||||
|
||||
[:p "no events there"])
|
||||
[:button {:class "btn"
|
||||
:disabled (not (boolean (seq selected-events)))
|
||||
:on-click #(re-frame/dispatch [::events/add-metadata-event-to-list [{:d "unique-id-1"
|
||||
:name "Test List SC"}
|
||||
selected-events]])}
|
||||
"Add To Lists"]]))
|
||||
[:p "no events there"])
|
||||
[:button {:class "btn"
|
||||
:disabled (not (boolean (seq selected-events)))
|
||||
:on-click #(re-frame/dispatch [::events/add-metadata-event-to-list [{:d "unique-id-1"
|
||||
:name "Test List SC"}
|
||||
selected-events]])}
|
||||
"Add To Lists"]])))
|
||||
|
||||
;; event feed component
|
||||
(defn event-feed-component [event]
|
||||
|
|
@ -195,7 +153,7 @@
|
|||
[:h1 "Event Feed"]
|
||||
[:p (str "Num of events: " (count events))]
|
||||
(if (> (count events) 0)
|
||||
[:div {:class "flex flex-row gap-2"}
|
||||
[:div {:class "flex flex-col"}
|
||||
(doall
|
||||
(for [event events]
|
||||
[:div {:key (:id event)}
|
||||
|
|
@ -375,7 +333,8 @@
|
|||
"Add To Lists"]]]]]))
|
||||
|
||||
(defn user-menu []
|
||||
(let [pk @(re-frame/subscribe [::subs/pk])]
|
||||
(let [pk @(re-frame/subscribe [::subs/pk])
|
||||
user-profile (re-frame/subscribe [::subs/profile pk])]
|
||||
(if pk
|
||||
[:div
|
||||
{:class "dropdown dropdown-end"}
|
||||
|
|
@ -388,7 +347,7 @@
|
|||
[:img
|
||||
{:alt "user image",
|
||||
:src
|
||||
(str "https://robohash.org/" pk)}]]]
|
||||
(nostr/profile-picture @user-profile pk)}]]]
|
||||
[:ul
|
||||
{:tabIndex "0",
|
||||
:class
|
||||
|
|
@ -413,23 +372,21 @@
|
|||
"... with Extension"]]]])))
|
||||
|
||||
;; Header
|
||||
(defn new-header []
|
||||
(let [selected-events @(re-frame/subscribe [::subs/selected-events])
|
||||
pk @(re-frame/subscribe [::subs/pk])]
|
||||
(defn header []
|
||||
(let [pk @(re-frame/subscribe [::subs/pk])]
|
||||
[:div
|
||||
{:class "navbar sticky z-50 top-0 left-0 bg-base-100"}
|
||||
[:div
|
||||
{:class "flex-1"}
|
||||
[:a {:class "btn btn-ghost text-xl"
|
||||
[:a {:class "cursor-pointer text-xl font-bold"
|
||||
:on-click #(re-frame/dispatch [::events/navigate [:home]])}
|
||||
"edufeed"]
|
||||
#_[:a {:class "btn btn-circle"
|
||||
:on-click #(re-frame/dispatch [::events/navigate [:event-feed]])} "Event-Feed"]]
|
||||
[:div
|
||||
{:class "flex-none"}
|
||||
|
||||
[:a {:disabled (nil? pk)
|
||||
:class "btn btn-circle btn-primary"
|
||||
:class "btn btn-circle btn-primary text-xl"
|
||||
:on-click #(re-frame/dispatch [::events/navigate [:add-resource]])} "+"]
|
||||
(when (not (nil? pk))
|
||||
[shopping-cart])
|
||||
|
|
@ -445,24 +402,6 @@
|
|||
|
||||
(defmethod routes/panels :keys-panel [] [keys-panel])
|
||||
|
||||
;; Add Resource Panel
|
||||
(defn add-resource-panel []
|
||||
[:div
|
||||
{:class "sm:w-1/2 p-2 mx-auto flex flex-col space-y-4"}
|
||||
[add-resource-form]
|
||||
[:div
|
||||
{:tabIndex "0",
|
||||
:class "collapse collapse-arrow border-base-300 bg-base-200 border"}
|
||||
[:div
|
||||
{:class "collapse-title text-xl font-medium"}
|
||||
"Advanced Options"]
|
||||
[:div
|
||||
{:class "collapse-content"}
|
||||
[add-resource-by-json]
|
||||
#_[add-resosurce-by-uri]]]])
|
||||
|
||||
(defmethod routes/panels :add-resource-panel [] [add-resource-panel])
|
||||
|
||||
;; Settings
|
||||
(defn settings-panel []
|
||||
[:div
|
||||
|
|
@ -471,10 +410,25 @@
|
|||
|
||||
(defmethod routes/panels :settings-panel [] [settings-panel])
|
||||
|
||||
(defn sidepanel []
|
||||
(let [pk (re-frame/subscribe [::subs/pk])]
|
||||
(fn []
|
||||
[:div {:class "static flex flex-col m-2 "}
|
||||
[:ul {:class "menu rounded-box"}
|
||||
[:li
|
||||
[:a {:on-click #(re-frame/dispatch [::events/navigate [:home]])}
|
||||
"Feed"]]
|
||||
[:li [:a {:on-click #(re-frame/dispatch [::events/navigate [:search-view]])}
|
||||
"Search"]]
|
||||
[:li [:a "My Network"]]
|
||||
[:li [:a {:on-click #(re-frame/dispatch [::events/navigate [:npub-view :npub (nostr/get-npub-from-pk @pk)]])}
|
||||
"Bookmarks"]]
|
||||
[:li [:a {:on-click #(re-frame/dispatch [::events/navigate [:opencard-view]])} "Opencard"]]]])))
|
||||
|
||||
;; Home
|
||||
(defn home-panel []
|
||||
(let [events (re-frame/subscribe [::subs/events])]
|
||||
[:div
|
||||
[:div {:class "basis-5/6"}
|
||||
[events-panel]
|
||||
[:p (count @events)]]))
|
||||
|
||||
|
|
@ -542,9 +496,13 @@
|
|||
(defn main-panel []
|
||||
(let [active-panel (re-frame/subscribe [::subs/active-panel])]
|
||||
[:div {:class "relative"}
|
||||
[new-header]
|
||||
[:div {:class ""}
|
||||
(routes/panels @active-panel)
|
||||
[header]
|
||||
[:div {:class "flex flex-row"}
|
||||
[:div {:class "w-1/6"}
|
||||
[sidepanel]]
|
||||
[:div {:class "w-5/6 mt-1"}
|
||||
[:div {:class ""}
|
||||
(routes/panels @active-panel)]]
|
||||
[add-to-lists-modal]
|
||||
[create-list-modal]
|
||||
[event-data-modal]]]))
|
||||
|
|
|
|||
280
src/ied/views/add.cljs
Normal file
280
src/ied/views/add.cljs
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
(ns ied.views.add
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[reagent.core :as reagent]
|
||||
[ied.subs :as subs]
|
||||
[ied.events :as events]
|
||||
[ied.components.icons :as icons]
|
||||
[ied.routes :as routes]))
|
||||
|
||||
(def md-scheme-map
|
||||
{:amblight {:name {:title "Name"
|
||||
:type :string}
|
||||
:uri {:title "URL"
|
||||
:type :string}
|
||||
:author {:title "Author"
|
||||
:type :string}
|
||||
:keywords {:title "Schlagworte"
|
||||
:type :array
|
||||
:items {:type :string}}
|
||||
:about {:title "Worum geht es??"
|
||||
:type :skos
|
||||
:schemes ["https://w3id.org/kim/hochschulfaechersystematik/scheme"]}
|
||||
:learningResourceType {:type :skos
|
||||
:schemes ["https://w3id.org/kim/hcrt/scheme"]}}
|
||||
:genmint (array-map :name {:title "Name"
|
||||
:type :string}
|
||||
:uri {:title "URL"
|
||||
:type :string}
|
||||
:image {:title "Thumbnail"
|
||||
:type :string}
|
||||
:description {:title "Beschreibung"
|
||||
:type :string
|
||||
:x-string-type :textarea}
|
||||
:creator {:title "Autor:innen"
|
||||
:type :array
|
||||
:items {:type :string}}
|
||||
:publisher {:title "Veröffentlicher:innen"
|
||||
:type :array
|
||||
:items {:type :string}}
|
||||
:funder {:title "Gefördert durch"
|
||||
:type :array
|
||||
:items {:type :string}}
|
||||
:datePublished {:title "Publikationsdatum"
|
||||
:type :string
|
||||
:format :date}
|
||||
:learningResourceType {:type :skos
|
||||
:schemes ["https://w3id.org/kim/hcrt/scheme"]}
|
||||
:teaches {:title "Welche Kompetenzen werden adressiert?"
|
||||
:type :skos
|
||||
:schemes [""]}
|
||||
:about {:title "Fächersystematik"
|
||||
:type :skos
|
||||
:schemes ["https://w3id.org/kim/hochschulfaechersystematik/scheme"]}
|
||||
:keywords {:title "Schlagworte"
|
||||
:type :array
|
||||
:items {:type :string}}
|
||||
:inLanguage {:title "Sprache"
|
||||
:type :string
|
||||
:enum ["de" "en"]}
|
||||
:isAccessibleForFree {:title "Frei zugänglich"
|
||||
:type :string
|
||||
:enum ["ja" "nein"]}
|
||||
;; LICENSE
|
||||
:license {:title "Lizenz"
|
||||
:type :string
|
||||
:enum ["CC-0" "CC-BY"]}
|
||||
;; ConditionsOfAccess
|
||||
;; audience
|
||||
;; didaktische Methoden und Planungshilfen
|
||||
;; educationalLevel
|
||||
)
|
||||
:amb {:name {:title "Name"
|
||||
:type :string}
|
||||
:uri {:title "URL"
|
||||
:type :string}
|
||||
:author {:title "Author"
|
||||
:type :string}
|
||||
:learningResourceType {:title "Learning Resource Typ"
|
||||
:type :skos
|
||||
:schemes ["https://w3id.org/kim/hcrt/scheme"]}
|
||||
:about {:type :skos
|
||||
:schemes ["https://w3id.org/kim/hochschulfaechersystematik/scheme"]}}})
|
||||
|
||||
(defn concept-label-component
|
||||
[[concept field]]
|
||||
(fn []
|
||||
(let [toggled-concepts @(re-frame/subscribe [::subs/toggled-concepts])
|
||||
toggled (some #(= (:id %) (:id concept)) toggled-concepts)]
|
||||
[:div {:class ""}
|
||||
[:p
|
||||
{:class (str "hover:bg-base-200 cursor-pointer" (when toggled " bg-white"))
|
||||
:on-click (fn []
|
||||
(re-frame/dispatch [::events/toggle-concept [concept field]]))}
|
||||
(-> concept :prefLabel :de)]
|
||||
(when-let [narrower (:narrower concept)]
|
||||
[:div {:class "pl-2 "}
|
||||
(for [child narrower]
|
||||
^{:key (:id child)} [concept-label-component [child field]])])])))
|
||||
|
||||
(defn skos-concept-scheme-multiselect-component
|
||||
[[cs field field-title]]
|
||||
[:div
|
||||
{:class "dropdown w-full"
|
||||
:key (:id cs)}
|
||||
[:div
|
||||
{:tabIndex "0"
|
||||
:role "button"
|
||||
:class "btn m-1 grow w-full"}
|
||||
field-title]
|
||||
[:div
|
||||
{:tabIndex "0",
|
||||
:class
|
||||
"dropdown-content card card-compact bg-base-100 z-[1] w-64 p-2 shadow"}
|
||||
[:div
|
||||
{:class "card-body"}
|
||||
[:h3 {:class "card-title"}
|
||||
field-title]
|
||||
(doall
|
||||
(for [concept (:hasTopConcept cs)]
|
||||
^{:key (:id concept)} [concept-label-component [concept field]]))
|
||||
[:p "you can use any element as a dropdown."]]]])
|
||||
|
||||
(defn array-fields-component [selected-md-scheme field field-title]
|
||||
(let [array-items-type (get-in selected-md-scheme [field :items :type])
|
||||
fields (re-frame/subscribe [::subs/md-form-array-input-fields field])]
|
||||
(fn []
|
||||
[:div {:class "flex flex-col gap-1"}
|
||||
[:p field-title]
|
||||
(case array-items-type
|
||||
:string (doall (for [[k v] @fields]
|
||||
[:div {:class "flex flex-row gap-1"
|
||||
:key k}
|
||||
[:input {:type "text"
|
||||
:class "input input-bordered w-full"
|
||||
:default-value ""
|
||||
:on-blur (fn [e]
|
||||
(re-frame/dispatch
|
||||
[::events/handle-md-form-array-input
|
||||
[field
|
||||
k
|
||||
(-> e .-target .-value)]]))}]
|
||||
[:button {:class "btn btn-square"
|
||||
:on-click
|
||||
#(re-frame/dispatch
|
||||
[::events/handle-md-form-rm-input
|
||||
[field k]])} [icons/close-icon]]])))
|
||||
[:button {:class "btn"
|
||||
:on-click #(re-frame/dispatch
|
||||
[::events/handle-md-form-add-input [field]])}
|
||||
"Add field"]])))
|
||||
|
||||
;; add resource form
|
||||
(defn add-resource-form
|
||||
[]
|
||||
(let [md-scheme (re-frame/subscribe [::subs/selected-md-scheme])
|
||||
md-form-image (re-frame/subscribe [::subs/md-form-image])]
|
||||
(fn []
|
||||
[:form {:class "flex flex-col space-y-4"
|
||||
:on-submit (fn [e]
|
||||
(.preventDefault e))}
|
||||
[:select
|
||||
{:default-value @md-scheme
|
||||
:on-change (fn [e]
|
||||
(let [value (-> e .-target .-value keyword)]
|
||||
(re-frame/dispatch [::events/set-md-scheme value])))
|
||||
:class "select select-bordered w-full max-w-xs"}
|
||||
[:option {:disabled true} "Select a Metadata Scheme"]
|
||||
[:option {:value "amblight"} "AMB Light"]
|
||||
[:option {:value "genmint"} "GenMint"]]
|
||||
|
||||
(let [selected-md-scheme (get md-scheme-map @md-scheme (:amblight md-scheme-map))]
|
||||
[:div {:class "flex flex-col gap-2"}
|
||||
(when @md-form-image
|
||||
[:img {:src @md-form-image}])
|
||||
(doall
|
||||
(for [field (keys selected-md-scheme)]
|
||||
(let [field-type (-> selected-md-scheme field :type)
|
||||
field-title (get-in selected-md-scheme [field :title] (name field))]
|
||||
(cond ;; TODO the conditions need to get more logic to evaluate form fields in order
|
||||
(and (= :string field-type)
|
||||
(= :textarea (-> selected-md-scheme field :x-string-type)))
|
||||
[:textarea {:key field
|
||||
:type ""
|
||||
:class "textarea textarea-bordered grow"
|
||||
:placeholder field-title
|
||||
:on-blur #(re-frame/dispatch [::events/handle-md-form-input [field (-> % .-target .-value)]])}]
|
||||
(and (= :string field-type)
|
||||
(-> selected-md-scheme field :enum))
|
||||
(let [enum-vals (-> selected-md-scheme field :enum)]
|
||||
[:select
|
||||
{:key field
|
||||
:class "select select-bordered w-full"}
|
||||
[:option {:disabled "" :selected ""} field-title]
|
||||
(doall
|
||||
(for [val enum-vals]
|
||||
[:option {:key val} val]))])
|
||||
(and (= :string field-type)
|
||||
(= :date (-> selected-md-scheme field :format)))
|
||||
[:div {:class "my-4"}
|
||||
[:label {:for field} field-title]
|
||||
[:input
|
||||
{:type "date"
|
||||
:on-blur #(re-frame/dispatch [::events/handle-md-form-input [field (-> % .-target .-value)]])
|
||||
:id field
|
||||
:name field-title
|
||||
; :value "2018-07-22"
|
||||
:min "2018-01-01"
|
||||
:max "2018-12-31"}]]
|
||||
(= :string field-type)
|
||||
[:label {:key field
|
||||
:class "input input-bordered flex items-center gap-2"}
|
||||
(-> selected-md-scheme field :title)
|
||||
[:input {:type "text"
|
||||
:class "grow"
|
||||
:placeholder (name field)
|
||||
:on-blur #(re-frame/dispatch [::events/handle-md-form-input [field (-> % .-target .-value)]])}]] ;; TODO maybe better handle this with an atom in the component
|
||||
(= :skos field-type)
|
||||
(let [concept-schemes @(re-frame/subscribe
|
||||
[::subs/concept-schemes (-> selected-md-scheme field :schemes)])
|
||||
_ (doall (for [[cs-uri _] (filter (fn [[_ v]] (nil? v)) concept-schemes)]
|
||||
(re-frame/dispatch [::events/skos-concept-scheme-from-uri cs-uri])))]
|
||||
|
||||
(doall
|
||||
(for [cs (keys concept-schemes)]
|
||||
^{:key cs} [skos-concept-scheme-multiselect-component [(get concept-schemes cs)
|
||||
field
|
||||
field-title]])))
|
||||
(= :array field-type)
|
||||
^{:key field} [array-fields-component selected-md-scheme field field-title]
|
||||
:else
|
||||
[:p {:key field} "field type not found"]))))])
|
||||
|
||||
[:button {:class "btn btn-warning w-1/2 mx-auto"
|
||||
:on-click #(re-frame/dispatch [::events/publish-resource])}
|
||||
"Publish Resource"]])))
|
||||
|
||||
(defn add-resource-by-json []
|
||||
(let [s (reagent/atom {:json-string ""})]
|
||||
(fn []
|
||||
[:div {:class "flex flex-col"}
|
||||
[:p "Paste an AMB object"]
|
||||
[:form {:class "flex flex-col"
|
||||
:on-submit (fn [e] (.preventDefault e))}
|
||||
[:textarea {:rows 10
|
||||
:on-change (fn [e]
|
||||
(swap! s assoc :json-string (-> e .-target .-value)))}]
|
||||
[:button {:class "btn btn-warning"
|
||||
:on-click #(re-frame/dispatch [::events/convert-amb-string-and-publish-as-nostr-event (:json-string @s)])}
|
||||
"Publish as Nostr Event"]]])))
|
||||
|
||||
;; TODO try again using xhrio
|
||||
(defn add-resosurce-by-uri []
|
||||
(let [uri (reagent/atom {:uri ""})]
|
||||
(fn []
|
||||
[:form {:class "flex flex-col"
|
||||
:on-submit (fn [e] (.preventDefault e))}
|
||||
[:label {:for "uri"} "URI: "]
|
||||
[:input {:id "uri"
|
||||
:on-change (fn [e]
|
||||
(swap! uri assoc :uri (-> e .-target .-value)))}]
|
||||
[:button {:class "btn btn-warning"
|
||||
:on-click #(re-frame/dispatch [::events/publish-amb-uri-as-nostr-event (:uri @uri)])}
|
||||
"Publish as Nostr Event"]])))
|
||||
|
||||
;; Add Resource Panel
|
||||
(defn add-resource-panel []
|
||||
[:div
|
||||
{:class "sm:w-1/2 p-2 mx-auto flex flex-col space-y-4"}
|
||||
[add-resource-form]
|
||||
[:details
|
||||
{:tabIndex "0",
|
||||
:class "collapse collapse-arrow border-base-300 bg-base-200 border"}
|
||||
[:summary
|
||||
{:class "collapse-title text-xl font-medium"}
|
||||
"Advanced Options"]
|
||||
[:div
|
||||
{:class "collapse-content"}
|
||||
[add-resource-by-json]
|
||||
[add-resosurce-by-uri]]]])
|
||||
|
||||
(defmethod routes/panels :add-resource-panel [] [add-resource-panel])
|
||||
43
src/ied/views/resource.cljs
Normal file
43
src/ied/views/resource.cljs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
(ns ied.views.resource
|
||||
(:require
|
||||
[re-frame.core :as re-frame]
|
||||
[ied.subs :as subs]
|
||||
[ied.nostr :as nostr]
|
||||
[ied.routes :as routes]
|
||||
[ied.components.resource :as resource-components]
|
||||
[ied.components.resource :as resource-component]))
|
||||
|
||||
(defn naddr-view-panel []
|
||||
(let [route-params @(re-frame/subscribe [::subs/route-params])
|
||||
naddr (:naddr route-params)
|
||||
data (nostr/decode-naddr naddr)
|
||||
events-with-same-d (re-frame/subscribe [::subs/events-by-d-tag (:identifier data)])
|
||||
latest-event (first @events-with-same-d)
|
||||
_ (.log js/console "events with same d id" (clj->js @events-with-same-d))
|
||||
; _ (.log js/)
|
||||
]
|
||||
|
||||
[:div
|
||||
(case (:kind data)
|
||||
30142 [:div {:class "flex flex-col"}
|
||||
[:img {:class "w-full object-contain bg-transparent max-h-[40vh] "
|
||||
:src (nostr/get-image-from-metadata-event latest-event)}]
|
||||
[:h1 {:class "text-2xl font-bold"} (nostr/get-name-from-metadata-event latest-event)]
|
||||
[:div
|
||||
(resource-component/skos-tags [@events-with-same-d "about"])]
|
||||
;; Keywords
|
||||
[:div
|
||||
(doall
|
||||
(for [kw (nostr/get-keywords-from-metadata-event latest-event)]
|
||||
(resource-components/keywords-component kw)))]
|
||||
;; Author
|
||||
[:div {:class "ml-auto mr-0 my-2"}
|
||||
(resource-component/authors-component latest-event)]
|
||||
[:p (nostr/get-description-from-metadata-event latest-event)]]
|
||||
|
||||
[:p "Dunno how to render that, sorry 🤷"])]))
|
||||
|
||||
(comment
|
||||
(re-frame/subscribe [::subs/events-by-d-tag "https://langsci-press.org/catalog/book/406"]))
|
||||
|
||||
(defmethod routes/panels :naddr-view-panel [] [naddr-view-panel])
|
||||
123
src/ied/views/search.cljs
Normal file
123
src/ied/views/search.cljs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
(ns ied.views.search
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[ied.routes :as routes]
|
||||
[ied.subs :as subs]
|
||||
[ied.events :as events]
|
||||
[reagent.core :as reagent]
|
||||
[clojure.string :as str]
|
||||
[ied.components.resource :as resource-components]
|
||||
[ied.nostr :as nostr]
|
||||
[ied.components.resource :as resource-component]
|
||||
[cljs.core :as c]))
|
||||
|
||||
;; TODO refactor this to also work with raw event data
|
||||
(defn extract-string [result]
|
||||
(let [document (:document result)
|
||||
event-info (select-keys document [:id :event_created_at :event_kind :event_pubkey])
|
||||
extracted-string (map (fn [k]
|
||||
{:keyword k
|
||||
:event-info event-info})
|
||||
(get document :keywords))]
|
||||
extracted-string))
|
||||
|
||||
(defn group-by-string [data]
|
||||
(let [extracted-string (into [] (apply concat (map extract-string data)))
|
||||
grouped-keyword (group-by :keyword extracted-string)]
|
||||
grouped-keyword))
|
||||
|
||||
(defn result [r]
|
||||
; r is an array of matching event results
|
||||
(let [id (nostr/get-d-id-from-event (-> (first r) :document :event_raw))
|
||||
pubkeys (map #(get-in % [:document :event_pubkey]) r)
|
||||
profiles (re-frame/subscribe [::subs/profiles pubkeys])
|
||||
name (first (map #(get-in % [:document :name]) r))
|
||||
description (first (map #(get-in % [:document :description]) r))
|
||||
events (map #(-> % :document :event_raw) r)
|
||||
keywords (group-by-string r)
|
||||
latest-event (-> (last (sort-by #(-> r :document :event_created_at) r))
|
||||
:document
|
||||
:event_raw)
|
||||
naddr (nostr/naddr-from-event latest-event)]
|
||||
(fn []
|
||||
[:div {:class "flex flex-row"}
|
||||
[:div {:class "flex flex-col w-3/4"}
|
||||
[:div {:class "flex flex-row items-center mt-2"}
|
||||
[:div
|
||||
{:class "avatar-group -space-x-6 "}
|
||||
(doall
|
||||
(for [[pubkey profile] @profiles]
|
||||
^{:key pubkey} [:div
|
||||
{:class "avatar bg-neutral"}
|
||||
[:div
|
||||
{:class "w-8"}
|
||||
[:img
|
||||
{:src
|
||||
(nostr/profile-picture profile pubkey)}]]]))]
|
||||
[:p (str
|
||||
(str/join
|
||||
", "
|
||||
(remove str/blank? (map (fn [[_ p]] (get p :display_name "unbekannt")) @profiles)))
|
||||
(if (> (count @profiles) 1)
|
||||
" haben"
|
||||
" hat")
|
||||
" das geteilt.")]]
|
||||
|
||||
[:a {:href id
|
||||
:target "_blank"
|
||||
:class "text-xl hover:underline"} name]
|
||||
[:div
|
||||
(resource-components/skos-tags [events "about"])]
|
||||
;; keywords
|
||||
[:div {:class "flex flex-wrap"}
|
||||
(doall
|
||||
(for [[k v] keywords]
|
||||
^{:key k} (resource-component/keywords-component k)))]
|
||||
|
||||
[:p {:class "line-clamp-3"} description]
|
||||
|
||||
[:label {:class "btn self-end mb-0 mt-auto "
|
||||
:on-click #(re-frame/dispatch [::events/navigate [:naddr-view :naddr naddr]])}
|
||||
"Details"]]
|
||||
|
||||
[:div {:class "w-1/4"}
|
||||
[:figure
|
||||
[:img
|
||||
{:class "h-48 object-cover mx-auto m-2"
|
||||
:loading "lazy"
|
||||
:src
|
||||
(nostr/get-image-from-metadata-event latest-event)
|
||||
:alt ""}]]]])))
|
||||
|
||||
(defn search-view-panel []
|
||||
(let [search-term (reagent/atom nil)
|
||||
grouped-results (re-frame/subscribe [::subs/grouped-search-results])
|
||||
_ (.log js/console (clj->js @grouped-results))]
|
||||
[:div {:class ""}
|
||||
[:form {:on-submit (fn [e]
|
||||
(.preventDefault e)
|
||||
(.log js/console @search-term)
|
||||
(re-frame/dispatch [::events/handle-search @search-term]))}
|
||||
[:label
|
||||
{:class "input input-bordered flex items-center gap-2 w-1/2 mx-auto"}
|
||||
[:input {:type "search"
|
||||
:class "grow"
|
||||
:placeholder "Search"
|
||||
:on-change (fn [e] (reset! search-term (-> e .-target .-value)))}]
|
||||
[:svg
|
||||
{:xmlns "http://www.w3.org/2000/svg",
|
||||
:viewBox "0 0 16 16",
|
||||
:fill "currentColor",
|
||||
:class "h-4 w-4 opacity-70"}
|
||||
[:path
|
||||
{:fill-rule "evenodd",
|
||||
:d
|
||||
"M9.965 11.026a5 5 0 1 1 1.06-1.06l2.755 2.754a.75.75 0 1 1-1.06 1.06l-2.755-2.754ZM10.5 7a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z",
|
||||
:clip-rule "evenodd"}]]]]
|
||||
(when (not-empty @grouped-results)
|
||||
[:div {:class "divide-y divide-slate-700 flex flex-col gap-2"}
|
||||
(doall
|
||||
(for [[k v] @grouped-results]
|
||||
^{:key k} [result v]))])]))
|
||||
|
||||
(defmethod routes/panels :search-view-panel [] [search-view-panel])
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue