logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://anongit.hacktivis.me/git/pleroma-fe.git/

after_store.js (14404B)


  1. import { createApp } from 'vue'
  2. import { createRouter, createWebHistory } from 'vue-router'
  3. import vClickOutside from 'click-outside-vue3'
  4. import VueVirtualScroller from 'vue-virtual-scroller'
  5. import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
  6. import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
  7. import App from '../App.vue'
  8. import routes from './routes'
  9. import VBodyScrollLock from 'src/directives/body_scroll_lock'
  10. import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
  11. import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
  12. import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
  13. import { applyConfig } from '../services/style_setter/style_setter.js'
  14. import FaviconService from '../services/favicon_service/favicon_service.js'
  15. import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
  16. let staticInitialResults = null
  17. const parsedInitialResults = () => {
  18. if (!document.getElementById('initial-results')) {
  19. return null
  20. }
  21. if (!staticInitialResults) {
  22. staticInitialResults = JSON.parse(document.getElementById('initial-results').textContent)
  23. }
  24. return staticInitialResults
  25. }
  26. const decodeUTF8Base64 = (data) => {
  27. const rawData = atob(data)
  28. const array = Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)))
  29. const text = new TextDecoder().decode(array)
  30. return text
  31. }
  32. const preloadFetch = async (request) => {
  33. const data = parsedInitialResults()
  34. if (!data || !data[request]) {
  35. return window.fetch(request)
  36. }
  37. const decoded = decodeUTF8Base64(data[request])
  38. const requestData = JSON.parse(decoded)
  39. return {
  40. ok: true,
  41. json: () => requestData,
  42. text: () => requestData
  43. }
  44. }
  45. const getInstanceConfig = async ({ store }) => {
  46. try {
  47. const res = await preloadFetch('/api/v1/instance')
  48. if (res.ok) {
  49. const data = await res.json()
  50. const textlimit = data.max_toot_chars
  51. const vapidPublicKey = data.pleroma.vapid_public_key
  52. store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
  53. store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
  54. store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma.metadata.birthday_required })
  55. store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma.metadata.birthday_min_age || 0 })
  56. if (vapidPublicKey) {
  57. store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
  58. }
  59. } else {
  60. throw (res)
  61. }
  62. } catch (error) {
  63. console.error('Could not load instance config, potentially fatal')
  64. console.error(error)
  65. }
  66. }
  67. const getBackendProvidedConfig = async ({ store }) => {
  68. try {
  69. const res = await window.fetch('/api/pleroma/frontend_configurations')
  70. if (res.ok) {
  71. const data = await res.json()
  72. return data.pleroma_fe
  73. } else {
  74. throw (res)
  75. }
  76. } catch (error) {
  77. console.error('Could not load backend-provided frontend config, potentially fatal')
  78. console.error(error)
  79. }
  80. }
  81. const getStaticConfig = async () => {
  82. try {
  83. const res = await window.fetch('/static/config.json')
  84. if (res.ok) {
  85. return res.json()
  86. } else {
  87. throw (res)
  88. }
  89. } catch (error) {
  90. console.warn('Failed to load static/config.json, continuing without it.')
  91. console.warn(error)
  92. return {}
  93. }
  94. }
  95. const setSettings = async ({ apiConfig, staticConfig, store }) => {
  96. const overrides = window.___pleromafe_dev_overrides || {}
  97. const env = window.___pleromafe_mode.NODE_ENV
  98. // This takes static config and overrides properties that are present in apiConfig
  99. let config = {}
  100. if (overrides.staticConfigPreference && env === 'development') {
  101. console.warn('OVERRIDING API CONFIG WITH STATIC CONFIG')
  102. config = Object.assign({}, apiConfig, staticConfig)
  103. } else {
  104. config = Object.assign({}, staticConfig, apiConfig)
  105. }
  106. const copyInstanceOption = (name) => {
  107. store.dispatch('setInstanceOption', { name, value: config[name] })
  108. }
  109. copyInstanceOption('theme')
  110. copyInstanceOption('nsfwCensorImage')
  111. copyInstanceOption('background')
  112. copyInstanceOption('hidePostStats')
  113. copyInstanceOption('hideBotIndication')
  114. copyInstanceOption('hideUserStats')
  115. copyInstanceOption('hideFilteredStatuses')
  116. copyInstanceOption('logo')
  117. store.dispatch('setInstanceOption', {
  118. name: 'logoMask',
  119. value: typeof config.logoMask === 'undefined'
  120. ? true
  121. : config.logoMask
  122. })
  123. store.dispatch('setInstanceOption', {
  124. name: 'logoMargin',
  125. value: typeof config.logoMargin === 'undefined'
  126. ? 0
  127. : config.logoMargin
  128. })
  129. copyInstanceOption('logoLeft')
  130. store.commit('authFlow/setInitialStrategy', config.loginMethod)
  131. copyInstanceOption('redirectRootNoLogin')
  132. copyInstanceOption('redirectRootLogin')
  133. copyInstanceOption('showInstanceSpecificPanel')
  134. copyInstanceOption('minimalScopesMode')
  135. copyInstanceOption('hideMutedPosts')
  136. copyInstanceOption('collapseMessageWithSubject')
  137. copyInstanceOption('scopeCopy')
  138. copyInstanceOption('subjectLineBehavior')
  139. copyInstanceOption('postContentType')
  140. copyInstanceOption('alwaysShowSubjectInput')
  141. copyInstanceOption('showFeaturesPanel')
  142. copyInstanceOption('hideSitename')
  143. copyInstanceOption('sidebarRight')
  144. }
  145. const getTOS = async ({ store }) => {
  146. try {
  147. const res = await window.fetch('/static/terms-of-service.html')
  148. if (res.ok) {
  149. const html = await res.text()
  150. store.dispatch('setInstanceOption', { name: 'tos', value: html })
  151. } else {
  152. throw (res)
  153. }
  154. } catch (e) {
  155. console.warn("Can't load TOS")
  156. console.warn(e)
  157. }
  158. }
  159. const getInstancePanel = async ({ store }) => {
  160. try {
  161. const res = await preloadFetch('/instance/panel.html')
  162. if (res.ok) {
  163. const html = await res.text()
  164. store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html })
  165. } else {
  166. throw (res)
  167. }
  168. } catch (e) {
  169. console.warn("Can't load instance panel")
  170. console.warn(e)
  171. }
  172. }
  173. const getStickers = async ({ store }) => {
  174. try {
  175. const res = await window.fetch('/static/stickers.json')
  176. if (res.ok) {
  177. const values = await res.json()
  178. const stickers = (await Promise.all(
  179. Object.entries(values).map(async ([name, path]) => {
  180. const resPack = await window.fetch(path + 'pack.json')
  181. let meta = {}
  182. if (resPack.ok) {
  183. meta = await resPack.json()
  184. }
  185. return {
  186. pack: name,
  187. path,
  188. meta
  189. }
  190. })
  191. )).sort((a, b) => {
  192. return a.meta.title.localeCompare(b.meta.title)
  193. })
  194. store.dispatch('setInstanceOption', { name: 'stickers', value: stickers })
  195. } else {
  196. throw (res)
  197. }
  198. } catch (e) {
  199. console.warn("Can't load stickers")
  200. console.warn(e)
  201. }
  202. }
  203. const getAppSecret = async ({ store }) => {
  204. const { state, commit } = store
  205. const { oauth, instance } = state
  206. return getOrCreateApp({ ...oauth, instance: instance.server, commit })
  207. .then((app) => getClientToken({ ...app, instance: instance.server }))
  208. .then((token) => {
  209. commit('setAppToken', token.access_token)
  210. commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
  211. })
  212. }
  213. const resolveStaffAccounts = ({ store, accounts }) => {
  214. const nicknames = accounts.map(uri => uri.split('/').pop())
  215. store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
  216. }
  217. const getNodeInfo = async ({ store }) => {
  218. try {
  219. const res = await preloadFetch('/nodeinfo/2.1.json')
  220. if (res.ok) {
  221. const data = await res.json()
  222. const metadata = data.metadata
  223. const features = metadata.features
  224. store.dispatch('setInstanceOption', { name: 'name', value: metadata.nodeName })
  225. store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
  226. store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
  227. store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
  228. store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
  229. store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
  230. store.dispatch('setInstanceOption', { name: 'pleromaCustomEmojiReactionsAvailable', value: features.includes('pleroma_custom_emoji_reactions') })
  231. store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
  232. store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
  233. store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
  234. store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
  235. store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
  236. store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
  237. store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') })
  238. const uploadLimits = metadata.uploadLimits
  239. store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
  240. store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadLimits.avatar) })
  241. store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadLimits.background) })
  242. store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadLimits.banner) })
  243. store.dispatch('setInstanceOption', { name: 'fieldsLimits', value: metadata.fieldsLimits })
  244. store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
  245. store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
  246. const suggestions = metadata.suggestions
  247. store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled })
  248. store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web })
  249. const software = data.software
  250. store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
  251. store.dispatch('setInstanceOption', { name: 'backendRepository', value: software.repository })
  252. store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' })
  253. const priv = metadata.private
  254. store.dispatch('setInstanceOption', { name: 'private', value: priv })
  255. const frontendVersion = window.___pleromafe_commit_hash
  256. store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
  257. const federation = metadata.federation
  258. store.dispatch('setInstanceOption', {
  259. name: 'tagPolicyAvailable',
  260. value: typeof federation.mrf_policies === 'undefined'
  261. ? false
  262. : metadata.federation.mrf_policies.includes('TagPolicy')
  263. })
  264. store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
  265. store.dispatch('setInstanceOption', {
  266. name: 'federating',
  267. value: typeof federation.enabled === 'undefined'
  268. ? true
  269. : federation.enabled
  270. })
  271. const accountActivationRequired = metadata.accountActivationRequired
  272. store.dispatch('setInstanceOption', { name: 'accountActivationRequired', value: accountActivationRequired })
  273. const accounts = metadata.staffAccounts
  274. resolveStaffAccounts({ store, accounts })
  275. } else {
  276. throw (res)
  277. }
  278. } catch (e) {
  279. console.warn('Could not load nodeinfo')
  280. console.warn(e)
  281. }
  282. }
  283. const setConfig = async ({ store }) => {
  284. // apiConfig, staticConfig
  285. const configInfos = await Promise.all([getBackendProvidedConfig({ store }), getStaticConfig()])
  286. const apiConfig = configInfos[0]
  287. const staticConfig = configInfos[1]
  288. await setSettings({ store, apiConfig, staticConfig }).then(getAppSecret({ store }))
  289. }
  290. const checkOAuthToken = async ({ store }) => {
  291. if (store.getters.getUserToken()) {
  292. try {
  293. await store.dispatch('loginUser', store.getters.getUserToken())
  294. } catch (e) {
  295. console.error(e)
  296. }
  297. }
  298. return Promise.resolve()
  299. }
  300. const afterStoreSetup = async ({ store, i18n }) => {
  301. store.dispatch('setLayoutWidth', windowWidth())
  302. store.dispatch('setLayoutHeight', windowHeight())
  303. FaviconService.initFaviconService()
  304. initServiceWorker(store)
  305. window.addEventListener('focus', () => updateFocus())
  306. const overrides = window.___pleromafe_dev_overrides || {}
  307. const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
  308. store.dispatch('setInstanceOption', { name: 'server', value: server })
  309. await setConfig({ store })
  310. await store.dispatch('setTheme')
  311. applyConfig(store.state.config)
  312. // Now we can try getting the server settings and logging in
  313. // Most of these are preloaded into the index.html so blocking is minimized
  314. await Promise.all([
  315. checkOAuthToken({ store }),
  316. getInstancePanel({ store }),
  317. getNodeInfo({ store }),
  318. getInstanceConfig({ store })
  319. ])
  320. // Start fetching things that don't need to block the UI
  321. store.dispatch('fetchMutes')
  322. store.dispatch('startFetchingAnnouncements')
  323. getTOS({ store })
  324. getStickers({ store })
  325. const router = createRouter({
  326. history: createWebHistory(),
  327. routes: routes(store),
  328. scrollBehavior: (to, _from, savedPosition) => {
  329. if (to.matched.some(m => m.meta.dontScroll)) {
  330. return false
  331. }
  332. return savedPosition || { left: 0, top: 0 }
  333. }
  334. })
  335. const app = createApp(App)
  336. app.use(router)
  337. app.use(store)
  338. app.use(i18n)
  339. app.use(vClickOutside)
  340. app.use(VBodyScrollLock)
  341. app.use(VueVirtualScroller)
  342. app.component('FAIcon', FontAwesomeIcon)
  343. app.component('FALayers', FontAwesomeLayers)
  344. // remove after vue 3.3
  345. app.config.unwrapInjectedRef = true
  346. app.mount('#app')
  347. return app
  348. }
  349. export default afterStoreSetup