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 (15486B)


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