logo

pleroma-fe

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

after_store.js (15019B)


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