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


  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. return getOrCreateApp({ ...oauth, instance: instance.server, commit })
  210. .then((app) => getClientToken({ ...app, instance: instance.server }))
  211. .then((token) => {
  212. commit('setAppToken', token.access_token)
  213. commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
  214. })
  215. }
  216. const resolveStaffAccounts = ({ store, accounts }) => {
  217. const nicknames = accounts.map(uri => uri.split('/').pop())
  218. store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
  219. }
  220. const getNodeInfo = async ({ store }) => {
  221. try {
  222. const res = await preloadFetch('/nodeinfo/2.1.json')
  223. if (res.ok) {
  224. const data = await res.json()
  225. const metadata = data.metadata
  226. const features = metadata.features
  227. store.dispatch('setInstanceOption', { name: 'name', value: metadata.nodeName })
  228. store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
  229. store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
  230. store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
  231. store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
  232. store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
  233. store.dispatch('setInstanceOption', { name: 'pleromaCustomEmojiReactionsAvailable', value: features.includes('pleroma_custom_emoji_reactions') })
  234. store.dispatch('setInstanceOption', { name: 'pleromaBookmarkFoldersAvailable', value: features.includes('pleroma:bookmark_folders') })
  235. store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
  236. store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
  237. store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
  238. store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
  239. store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
  240. store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
  241. store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') })
  242. const uploadLimits = metadata.uploadLimits
  243. store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
  244. store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadLimits.avatar) })
  245. store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadLimits.background) })
  246. store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadLimits.banner) })
  247. store.dispatch('setInstanceOption', { name: 'fieldsLimits', value: metadata.fieldsLimits })
  248. store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
  249. store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
  250. const suggestions = metadata.suggestions
  251. store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled })
  252. store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web })
  253. const software = data.software
  254. store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
  255. store.dispatch('setInstanceOption', { name: 'backendRepository', value: software.repository })
  256. store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' })
  257. const priv = metadata.private
  258. store.dispatch('setInstanceOption', { name: 'private', value: priv })
  259. const frontendVersion = window.___pleromafe_commit_hash
  260. store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
  261. const federation = metadata.federation
  262. store.dispatch('setInstanceOption', {
  263. name: 'tagPolicyAvailable',
  264. value: typeof federation.mrf_policies === 'undefined'
  265. ? false
  266. : metadata.federation.mrf_policies.includes('TagPolicy')
  267. })
  268. store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
  269. store.dispatch('setInstanceOption', {
  270. name: 'federating',
  271. value: typeof federation.enabled === 'undefined'
  272. ? true
  273. : federation.enabled
  274. })
  275. const accountActivationRequired = metadata.accountActivationRequired
  276. store.dispatch('setInstanceOption', { name: 'accountActivationRequired', value: accountActivationRequired })
  277. const accounts = metadata.staffAccounts
  278. resolveStaffAccounts({ store, accounts })
  279. } else {
  280. throw (res)
  281. }
  282. } catch (e) {
  283. console.warn('Could not load nodeinfo')
  284. console.warn(e)
  285. }
  286. }
  287. const setConfig = async ({ store }) => {
  288. // apiConfig, staticConfig
  289. const configInfos = await Promise.all([getBackendProvidedConfig({ store }), getStaticConfig()])
  290. const apiConfig = configInfos[0]
  291. const staticConfig = configInfos[1]
  292. await setSettings({ store, apiConfig, staticConfig }).then(getAppSecret({ store }))
  293. }
  294. const checkOAuthToken = async ({ store }) => {
  295. if (store.getters.getUserToken()) {
  296. return store.dispatch('loginUser', store.getters.getUserToken())
  297. }
  298. return Promise.resolve()
  299. }
  300. const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
  301. const app = createApp(App)
  302. app.use(pinia)
  303. if (storageError) {
  304. useInterfaceStore().pushGlobalNotice({ messageKey: 'errors.storage_unavailable', level: 'error' })
  305. }
  306. useInterfaceStore().setLayoutWidth(windowWidth())
  307. useInterfaceStore().setLayoutHeight(windowHeight())
  308. FaviconService.initFaviconService()
  309. initServiceWorker(store)
  310. window.addEventListener('focus', () => updateFocus())
  311. const overrides = window.___pleromafe_dev_overrides || {}
  312. const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
  313. store.dispatch('setInstanceOption', { name: 'server', value: server })
  314. await setConfig({ store })
  315. try {
  316. await useInterfaceStore().applyTheme().catch((e) => { console.error('Error setting theme', e) })
  317. } catch (e) {
  318. window.splashError(e)
  319. return Promise.reject(e)
  320. }
  321. applyConfig(store.state.config, i18n.global)
  322. // Now we can try getting the server settings and logging in
  323. // Most of these are preloaded into the index.html so blocking is minimized
  324. await Promise.all([
  325. checkOAuthToken({ store }),
  326. getInstancePanel({ store }),
  327. getNodeInfo({ store }),
  328. getInstanceConfig({ store })
  329. ]).catch(e => Promise.reject(e))
  330. await store.dispatch('loadDrafts')
  331. // Start fetching things that don't need to block the UI
  332. store.dispatch('fetchMutes')
  333. useAnnouncementsStore().startFetchingAnnouncements()
  334. getTOS({ store })
  335. getStickers({ store })
  336. const router = createRouter({
  337. history: createWebHistory(),
  338. routes: routes(store),
  339. scrollBehavior: (to, _from, savedPosition) => {
  340. if (to.matched.some(m => m.meta.dontScroll)) {
  341. return false
  342. }
  343. return savedPosition || { left: 0, top: 0 }
  344. }
  345. })
  346. useI18nStore().setI18n(i18n)
  347. app.use(router)
  348. app.use(store)
  349. app.use(i18n)
  350. // Little thing to get out of invalid theme state
  351. window.resetThemes = () => {
  352. useInterfaceStore().resetThemeV3()
  353. useInterfaceStore().resetThemeV3Palette()
  354. useInterfaceStore().resetThemeV2()
  355. }
  356. app.use(vClickOutside)
  357. app.use(VBodyScrollLock)
  358. app.use(VueVirtualScroller)
  359. app.component('FAIcon', FontAwesomeIcon)
  360. app.component('FALayers', FontAwesomeLayers)
  361. // remove after vue 3.3
  362. app.config.unwrapInjectedRef = true
  363. app.mount('#app')
  364. return app
  365. }
  366. export default afterStoreSetup