feat: well known files
This commit is contained in:
parent
6b51cc5e53
commit
068632b117
5
.gitignore
vendored
5
.gitignore
vendored
@ -35,5 +35,6 @@ test.log
|
||||
/.stack-work.lock
|
||||
/.npmrc
|
||||
/config/webpack.yml
|
||||
static/wp-*/
|
||||
/config/favicon.json
|
||||
/static
|
||||
/well-known
|
||||
/**/tmp-*
|
||||
|
||||
@ -33,7 +33,7 @@ npm install:
|
||||
- n stable
|
||||
- npm install -g npm
|
||||
- hash -r
|
||||
- apt-get install openssh-client -y
|
||||
- apt-get -y install openssh-client exiftool
|
||||
- install -v -m 0700 -d ~/.ssh
|
||||
- install -v -T -m 0644 ${SSH_KNOWN_HOSTS} ~/.ssh/known_hosts
|
||||
- install -v -T -m 0400 ${SSH_DEPLOY_KEY} ~/.ssh/deploy && echo "IdentityFile ~/.ssh/deploy" >> ~/.ssh/config;
|
||||
|
||||
77
config/favicon.json
Normal file
77
config/favicon.json
Normal file
@ -0,0 +1,77 @@
|
||||
{
|
||||
"masterPicture": "assets/favicon.svg",
|
||||
"design": {
|
||||
"desktop_browser": {},
|
||||
"ios": {
|
||||
"picture_aspect": "background_and_margin",
|
||||
"margin": "5%",
|
||||
"background_color": "#ffffff",
|
||||
"startup_image": {
|
||||
"background_color": "#ffffff"
|
||||
},
|
||||
"app_name": "Uni2work",
|
||||
"assets": {
|
||||
"ios6_and_prior_icons": false,
|
||||
"ios7_and_later_icons": true,
|
||||
"precomposed_icons": true,
|
||||
"declare_only_default_icon": true
|
||||
}
|
||||
},
|
||||
"windows": {
|
||||
"picture_aspect": "white_silhouette",
|
||||
"background_color": "#0a9342",
|
||||
"app_name": "Uni2work"
|
||||
},
|
||||
"firefox_app": {
|
||||
"picture_aspect": "circle",
|
||||
"keep_picture_in_circle": false,
|
||||
"circle_inner_margin": "5%",
|
||||
"background_color": "#ffffff",
|
||||
"overlay": false,
|
||||
"manifest": {
|
||||
"app_name": "Uni2work",
|
||||
"app_description": {
|
||||
"_i18n": true,
|
||||
"de-de-formal": "Ein webbasiertes Lehrverwaltungssystem der LMU München",
|
||||
"en-eu": "A web based teaching management system at LMU Munich"
|
||||
},
|
||||
"developer_name": "Uni2work-Team",
|
||||
"developer_url": "https://uni2work.ifi.lmu.de/info",
|
||||
"display": "browser",
|
||||
"start_url": "/"
|
||||
}
|
||||
},
|
||||
"android_chrome": {
|
||||
"picture_aspect": "shadow",
|
||||
"manifest": {
|
||||
"name": "Uni2work",
|
||||
"display": "browser",
|
||||
"orientation": "portrait",
|
||||
"start_url": "/"
|
||||
},
|
||||
"assets": {
|
||||
"legacy_icon": true,
|
||||
"low_resolution_icons": false
|
||||
}
|
||||
},
|
||||
"safari_pinned_tab": {
|
||||
"picture_aspect": "silhouette",
|
||||
"theme_color": "#0a9342"
|
||||
},
|
||||
"coast": {
|
||||
"picture_aspect": "background_and_margin",
|
||||
"background_color": "#ffffff",
|
||||
"margin": "10%"
|
||||
},
|
||||
"open_graph": {
|
||||
"picture_aspect": "background_and_margin",
|
||||
"background_color": "#ffffff",
|
||||
"margin": "10%",
|
||||
"ratio": "square"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"html_code_file": true
|
||||
},
|
||||
"versioning": false
|
||||
}
|
||||
@ -43,6 +43,8 @@ application/java-vm class
|
||||
application/javascript js
|
||||
application/json json
|
||||
application/jsonml+json jsonml
|
||||
application/manifest+json webmanifest
|
||||
application/x-web-app-manifest+json webapp
|
||||
application/lost+xml lostxml
|
||||
application/mac-binhex40 hqx
|
||||
application/mac-compactpro cpt
|
||||
|
||||
@ -4,8 +4,10 @@
|
||||
# See https://github.com/yesodweb/yesod/wiki/Configuration#parsing-numeric-values-as-strings
|
||||
|
||||
static-dir: "_env:STATIC_DIR:static"
|
||||
well-known-dir: "_env:WELL_KNOWN_DIR:well-known"
|
||||
well-known-link-file: "html_code.html"
|
||||
|
||||
webpack-manifest: "_env:WEBPACK_MANIFEST:config/webpack.yml"
|
||||
favicon-stats: "_env:FAVICON_STATS:config/favicon.json"
|
||||
host: "_env:HOST:*4" # any IPv4 host
|
||||
port: "_env:PORT:3000"
|
||||
ip-from-header: "_env:IP_FROM_HEADER:false"
|
||||
|
||||
@ -1208,8 +1208,7 @@ BreadcrumbTerm: Semester
|
||||
BreadcrumbSchool: Institut
|
||||
BreadcrumbUser: Benutzer
|
||||
BreadcrumbStatic: Statische Resource
|
||||
BreadcrumbFavicon: Favicon
|
||||
BreadcrumbRobots: robots.txt
|
||||
BreadcrumbWellKnown: Benannte statische Resource
|
||||
BreadcrumbMetrics: Metriken
|
||||
BreadcrumbLecturerInvite: Einladung zum Kursverwalter
|
||||
BreadcrumbExamOfficeUserInvite: Einladung bzgl. Prüfungsleistungen
|
||||
|
||||
@ -1207,8 +1207,7 @@ BreadcrumbTerm: Semester
|
||||
BreadcrumbSchool: Department
|
||||
BreadcrumbUser: User
|
||||
BreadcrumbStatic: Static resource
|
||||
BreadcrumbFavicon: Favicon
|
||||
BreadcrumbRobots: robots.txt
|
||||
BreadcrumbWellKnown: Named static resource
|
||||
BreadcrumbMetrics: Metrics
|
||||
BreadcrumbLecturerInvite: Invitation to be a course administrator
|
||||
BreadcrumbExamOfficeUserInvite: Invitation regarding exam achievements
|
||||
|
||||
1964
package-lock.json
generated
1964
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -60,7 +60,6 @@
|
||||
"@commitlint/cli": "^8.2.0",
|
||||
"@commitlint/config-conventional": "^8.2.0",
|
||||
"@fortawesome/fontawesome-pro": "^5.12.0",
|
||||
"app-manifest-webpack-plugin": "^1.2.0",
|
||||
"autoprefixer": "^9.7.3",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^10.0.3",
|
||||
@ -73,6 +72,7 @@
|
||||
"css-loader": "^2.1.1",
|
||||
"eslint": "^5.16.0",
|
||||
"file-loader": "^5.0.2",
|
||||
"glob": "^7.1.6",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"husky": "^2.7.0",
|
||||
"jasmine-core": "^3.5.0",
|
||||
@ -92,6 +92,7 @@
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"real-favicon-webpack-plugin": "^0.2.3",
|
||||
"remove-files-webpack-plugin": "^1.1.3",
|
||||
"resolve-url-loader": "^3.1.1",
|
||||
"sass": "^1.23.7",
|
||||
@ -100,6 +101,7 @@
|
||||
"standard-version": "^6.0.1",
|
||||
"style-loader": "^0.23.1",
|
||||
"terser-webpack-plugin": "^2.2.3",
|
||||
"tmp": "^0.1.0",
|
||||
"typeface-roboto": "0.0.75",
|
||||
"typeface-source-sans-pro": "0.0.75",
|
||||
"webpack": "^4.41.2",
|
||||
|
||||
5
routes
5
routes
@ -39,8 +39,6 @@
|
||||
/static StaticR EmbeddedStatic appStatic !free
|
||||
/auth AuthR Auth getAuth !free
|
||||
|
||||
/favicon.ico FaviconR GET !free
|
||||
/robots.txt RobotsR GET !free
|
||||
/metrics MetricsR GET
|
||||
|
||||
/ HomeR GET !free
|
||||
@ -204,6 +202,7 @@
|
||||
/msgs MessageListR GET POST
|
||||
/msg/#{CryptoUUIDSystemMessage} MessageR GET POST !timeANDreadANDauthentication
|
||||
|
||||
|
||||
!/#UUID CryptoUUIDDispatchR GET !free -- just redirect
|
||||
-- !/*{CI FilePath} CryptoFileNameDispatchR GET !free -- Disabled until preliminary check for valid cID exists
|
||||
|
||||
!/*WellKnownFileName WellKnownR GET !free
|
||||
@ -19,7 +19,7 @@ let
|
||||
'';
|
||||
|
||||
override = oldAttrs: {
|
||||
nativeBuildInputs = oldAttrs.nativeBuildInputs ++ (with pkgs; [ nodejs-12_x postgresql openldap google-chrome ]) ++ (with haskellPackages; [ stack yesod-bin hlint cabal-install weeder ]);
|
||||
nativeBuildInputs = oldAttrs.nativeBuildInputs ++ (with pkgs; [ nodejs-12_x postgresql openldap google-chrome exiftool ]) ++ (with haskellPackages; [ stack yesod-bin hlint cabal-install weeder ]);
|
||||
shellHook = ''
|
||||
export PROMPT_INFO="${oldAttrs.name}"
|
||||
|
||||
|
||||
@ -101,7 +101,6 @@ import Data.List (cycle)
|
||||
|
||||
-- Import all relevant handler modules here.
|
||||
-- (HPack takes care to add new modules to our cabal file nowadays.)
|
||||
import Handler.Common
|
||||
import Handler.Home
|
||||
import Handler.Info
|
||||
import Handler.Help
|
||||
|
||||
@ -1575,8 +1575,8 @@ siteLayout' headingOverride widget = do
|
||||
|
||||
pc <- widgetToPageContent $ do
|
||||
webpackLinks_main StaticR
|
||||
faviconLinks
|
||||
toWidget $(juliusFile "templates/i18n.julius")
|
||||
wellKnownHtmlLinks
|
||||
|
||||
$(widgetFile "default-layout")
|
||||
withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
|
||||
@ -1627,8 +1627,7 @@ i18nCrumb msg mbR = do
|
||||
instance YesodBreadcrumbs UniWorX where
|
||||
breadcrumb (AuthR _) = i18nCrumb MsgMenuLogin $ Just HomeR
|
||||
breadcrumb (StaticR _) = i18nCrumb MsgBreadcrumbStatic Nothing
|
||||
breadcrumb FaviconR = i18nCrumb MsgBreadcrumbFavicon Nothing
|
||||
breadcrumb RobotsR = i18nCrumb MsgBreadcrumbRobots Nothing
|
||||
breadcrumb (WellKnownR _) = i18nCrumb MsgBreadcrumbWellKnown Nothing
|
||||
breadcrumb MetricsR = i18nCrumb MsgBreadcrumbMetrics Nothing
|
||||
|
||||
breadcrumb HomeR = i18nCrumb MsgMenuHome Nothing
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
-- | Common handler functions.
|
||||
module Handler.Common
|
||||
( getFaviconR
|
||||
, getRobotsR
|
||||
) where
|
||||
|
||||
import Data.FileEmbed (embedFile)
|
||||
import Import hiding (embedFile)
|
||||
|
||||
-- These handlers embed files in the executable at compile time to avoid a
|
||||
-- runtime dependency, and for efficiency.
|
||||
|
||||
getFaviconR :: Handler TypedContent
|
||||
getFaviconR = do
|
||||
let content = $(embedFile "static/favicon.ico")
|
||||
|
||||
setEtagHashable content
|
||||
|
||||
return $ TypedContent "image/x-icon"
|
||||
$ toContent content
|
||||
|
||||
getRobotsR :: Handler TypedContent
|
||||
getRobotsR = do
|
||||
let content = $(embedFile "static/robots.txt")
|
||||
|
||||
setEtagHashable content
|
||||
|
||||
return $ TypedContent typePlain
|
||||
$ toContent content
|
||||
@ -12,8 +12,9 @@ import Utils.Tokens as Import
|
||||
import Utils.Frontend.Modal as Import
|
||||
import Utils.Lens as Import
|
||||
|
||||
import Settings as Import
|
||||
import Settings.StaticFiles as Import
|
||||
import Settings as Import
|
||||
import Settings.StaticFiles as Import
|
||||
import Settings.WellKnownFiles as Import
|
||||
|
||||
import CryptoID as Import
|
||||
import Audit as Import
|
||||
|
||||
@ -78,7 +78,8 @@ data AppSettings = AppSettings
|
||||
{ appStaticDir :: FilePath
|
||||
-- ^ Directory from which to serve static files.
|
||||
, appWebpackEntrypoints :: FilePath
|
||||
, appFaviconStats :: FilePath
|
||||
, appWellKnownDir :: FilePath
|
||||
, appWellKnownLinkFile :: FilePath
|
||||
, appDatabaseConf :: PostgresConf
|
||||
-- ^ Configuration settings for accessing the database.
|
||||
, appAutoDbMigrate :: Bool
|
||||
@ -370,8 +371,9 @@ instance FromJSON AppSettings where
|
||||
False
|
||||
#endif
|
||||
appStaticDir <- o .: "static-dir"
|
||||
appWellKnownDir <- o .: "well-known-dir"
|
||||
appWellKnownLinkFile <- o .: "well-known-link-file"
|
||||
appWebpackEntrypoints <- o .: "webpack-manifest"
|
||||
appFaviconStats <- o .: "favicon-stats"
|
||||
appDatabaseConf <- o .: "database"
|
||||
appAutoDbMigrate <- o .: "auto-db-migrate"
|
||||
let nonEmptyHost LdapConf{..} = case ldapHost of
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
{-# OPTIONS_GHC -fno-warn-unused-top-binds #-}
|
||||
-- Listing only files directly used by consumers of this module
|
||||
-- prevents rebuilds if files change, that are not directly used (like
|
||||
-- webpack bundles)
|
||||
module Settings.StaticFiles
|
||||
( module Settings.StaticFiles
|
||||
( img_lmu_sigillum_svg
|
||||
, webpackLinks_main
|
||||
, embeddedStatic
|
||||
, module Yesod.EmbeddedStatic
|
||||
) where
|
||||
|
||||
import ClassyPrelude.Yesod
|
||||
|
||||
import Settings (appStaticDir, appWebpackEntrypoints, appFaviconStats, compileTimeAppSettings)
|
||||
import Settings (appStaticDir, appWebpackEntrypoints, compileTimeAppSettings)
|
||||
import Settings.StaticFiles.Generator
|
||||
import Settings.StaticFiles.Webpack
|
||||
import Settings.StaticFiles.Favicon
|
||||
import Yesod.EmbeddedStatic
|
||||
|
||||
-- This generates easy references to files in the static directory at compile time,
|
||||
@ -28,6 +33,3 @@ import Yesod.EmbeddedStatic
|
||||
|
||||
mkEmbeddedStatic DEV_BOOL "embeddedStatic" . pure . staticGenerator $ appStaticDir compileTimeAppSettings
|
||||
mkWebpackEntrypoints (appWebpackEntrypoints compileTimeAppSettings) (pure staticGenerator) $ appStaticDir compileTimeAppSettings
|
||||
|
||||
faviconLinks :: MonadWidget m => m ()
|
||||
faviconLinks = $(mkFaviconLinks $ appFaviconStats compileTimeAppSettings)
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
module Settings.StaticFiles.Favicon
|
||||
( mkFaviconLinks
|
||||
) where
|
||||
|
||||
import ClassyPrelude.Yesod
|
||||
|
||||
import Language.Haskell.TH
|
||||
import Language.Haskell.TH.Syntax hiding (Lift(..))
|
||||
import qualified Language.Haskell.TH.Syntax as TH (Lift(..))
|
||||
|
||||
import qualified Data.Aeson as JSON
|
||||
|
||||
import qualified Data.Map as Map
|
||||
|
||||
import Text.Blaze.Html (preEscapedToHtml)
|
||||
|
||||
|
||||
mkFaviconLinks :: FilePath -- ^ Path to JSON-manifest
|
||||
-> ExpQ
|
||||
mkFaviconLinks manifest = do
|
||||
addDependentFile manifest
|
||||
htmlFragments <- decodeManifest manifest
|
||||
|
||||
doE $ map (\frag -> noBindS [e|toWidgetHead $ preEscapedToHtml $(TH.lift frag)|]) htmlFragments
|
||||
where
|
||||
decodeManifest :: FilePath -> Q [Text]
|
||||
decodeManifest manifest' = do
|
||||
res <- liftIO $ JSON.eitherDecodeFileStrict' manifest'
|
||||
case res of
|
||||
Left err -> fail err
|
||||
Right res'
|
||||
| Just frags' <- res' Map.!? ("html" :: Text)
|
||||
, JSON.Success frags <- JSON.fromJSON frags'
|
||||
-> return frags
|
||||
| otherwise
|
||||
-> fail "Could not parse favicon stats"
|
||||
|
||||
|
||||
11
src/Settings/WellKnownFiles.hs
Normal file
11
src/Settings/WellKnownFiles.hs
Normal file
@ -0,0 +1,11 @@
|
||||
module Settings.WellKnownFiles
|
||||
( WellKnownFileName
|
||||
, getWellKnownR
|
||||
, wellKnownHtmlLinks
|
||||
) where
|
||||
|
||||
import Settings.WellKnownFiles.TH
|
||||
|
||||
import Settings (appWellKnownDir, appWellKnownLinkFile, compileTimeAppSettings)
|
||||
|
||||
mkWellKnown "de-de-formal" (appWellKnownDir compileTimeAppSettings) (appWellKnownLinkFile compileTimeAppSettings)
|
||||
192
src/Settings/WellKnownFiles/TH.hs
Normal file
192
src/Settings/WellKnownFiles/TH.hs
Normal file
@ -0,0 +1,192 @@
|
||||
module Settings.WellKnownFiles.TH
|
||||
( mkWellKnown
|
||||
) where
|
||||
|
||||
import ClassyPrelude.Yesod
|
||||
import Utils
|
||||
|
||||
import Language.Haskell.TH
|
||||
import Language.Haskell.TH.Syntax hiding (Lift(..))
|
||||
import qualified Language.Haskell.TH.Syntax as TH (Lift(..))
|
||||
|
||||
import System.Directory.Tree
|
||||
|
||||
import qualified Data.ByteString as BS
|
||||
|
||||
import Utils.Lens.TH
|
||||
import Control.Lens
|
||||
import Data.Set.Lens
|
||||
|
||||
import qualified Data.Text as Text
|
||||
import qualified Data.Text.Encoding as Text
|
||||
import Data.Char as Char (isAlphaNum, toUpper)
|
||||
|
||||
import qualified Data.Map as Map
|
||||
import qualified Data.Set as Set
|
||||
|
||||
import Data.List.NonEmpty (NonEmpty(..))
|
||||
|
||||
import Data.HashMap.Strict (HashMap)
|
||||
import qualified Data.HashMap.Strict as HashMap
|
||||
|
||||
import qualified Data.HashSet as HashSet
|
||||
|
||||
import System.FilePath ((</>), splitDirectories, makeRelative)
|
||||
|
||||
import Settings.Mime
|
||||
|
||||
import Text.Blaze.Html (preEscapedToHtml)
|
||||
|
||||
nWellKnownFileName :: Name
|
||||
nWellKnownFileName = mkName "WellKnownFileName"
|
||||
|
||||
nwellKnownFileNames :: Name
|
||||
nwellKnownFileNames = mkName "wellKnownFileNames"
|
||||
|
||||
ngetWellKnownR :: Name
|
||||
ngetWellKnownR = mkName "getWellKnownR"
|
||||
|
||||
nwellKnownHtmlLinks :: Name
|
||||
nwellKnownHtmlLinks = mkName "wellKnownHtmlLinks"
|
||||
|
||||
|
||||
|
||||
mkWellKnown :: Lang -- ^ Default language
|
||||
-> FilePath -- ^ Base directory
|
||||
-> FilePath -- ^ Link file (@html_code.html@)
|
||||
-> DecsQ
|
||||
mkWellKnown defLang wellKnownBase wellKnownLinks = do
|
||||
inputFiles <- fmap dirTree . liftIO $ readDirectoryWith (\f -> (f, ) <$> BS.readFile f) wellKnownBase
|
||||
|
||||
mapM_ qAddDependentFile $ inputFiles ^.. folded . _1
|
||||
|
||||
-- languageFiles :: Map Lang [(FilePath, ByteString)]
|
||||
languageFiles <- if
|
||||
| Dir{contents} <- inputFiles
|
||||
-> return . Map.fromList $ do
|
||||
(language, lContents) <- contents ^.. folded . $(multifocusL 2) _name id
|
||||
Dir{} <- pure lContents
|
||||
let
|
||||
lContents' :: [(FilePath, ByteString)]
|
||||
lContents' = flip mapMaybe (flattenDir lContents) $ \pFile -> do
|
||||
File{..} <- pure pFile
|
||||
guard $ name /= wellKnownLinks
|
||||
return $ file & _1 %~ makeRelative (wellKnownBase </> language)
|
||||
return (Text.pack language, lContents')
|
||||
| otherwise
|
||||
-> fail "wellKnownBase is not a directory"
|
||||
|
||||
fLanguages <- if
|
||||
| defLang `Set.member` Map.keysSet languageFiles
|
||||
, let languages' = Set.delete defLang $ Map.keysSet languageFiles
|
||||
-> return $ defLang :| Set.toList languages'
|
||||
| otherwise
|
||||
-> fail "default language is missing in wellKnownBase"
|
||||
|
||||
-- languageLinks :: Map Lang ByteString
|
||||
languageLinks <- if
|
||||
| Dir{contents} <- inputFiles
|
||||
-> return . Map.fromList $ do
|
||||
(language, lContents) <- contents ^.. folded . $(multifocusL 2) _name id
|
||||
Dir{} <- pure lContents
|
||||
let
|
||||
lContents' :: [ByteString]
|
||||
lContents' = flip mapMaybe (flattenDir lContents) $ \pFile -> do
|
||||
File{..} <- pure pFile
|
||||
guard $ name == wellKnownLinks
|
||||
return $ file ^. _2
|
||||
c <- lContents'
|
||||
return (Text.pack language, c)
|
||||
| otherwise
|
||||
-> fail "wellKnownBase is not a directory"
|
||||
|
||||
lLanguages <- if
|
||||
| defLang `Set.member` Map.keysSet languageLinks
|
||||
, let languages' = Set.delete defLang $ Map.keysSet languageLinks
|
||||
-> return $ defLang :| Set.toList languages'
|
||||
| otherwise
|
||||
-> fail "default language is missing in wellKnownBase"
|
||||
|
||||
|
||||
fVar <- newName "f"
|
||||
hVar <- newName "h"
|
||||
lVar <- newName "l"
|
||||
|
||||
let fileNames = setOf (folded . folded . _1) languageFiles
|
||||
fileContents = Map.fromListWith (<>) $ do
|
||||
(lang, fs) <- Map.toList languageFiles
|
||||
(fName, fContent) <- fs
|
||||
return ((fContent, mimeLookup $ Text.pack fName), Set.singleton (lang, fName))
|
||||
|
||||
wellKnownFileName = dataD
|
||||
(cxt [])
|
||||
nWellKnownFileName
|
||||
[]
|
||||
Nothing
|
||||
[ normalC (mkName $ fNameManip fName) []
|
||||
| fName <- Set.toList fileNames
|
||||
]
|
||||
(pure $ derivClause Nothing [[t|Eq|], [t|Ord|], [t|Bounded|], [t|Enum|], [t|Read|], [t|Show|], [t|Generic|], [t|Typeable|]])
|
||||
wellKnownFileNameMapSig = sigD
|
||||
nwellKnownFileNames
|
||||
[t|HashMap [Text] $(conT nWellKnownFileName)|]
|
||||
wellKnownFileNameMap = funD
|
||||
nwellKnownFileNames
|
||||
[ clause [] (normalB $ [e|HashMap.fromList|] `appE` listE [ [e|($(TH.lift . map Text.pack $ splitDirectories fName), $(conE . mkName $ fNameManip fName))|] | fName <- Set.toList fileNames ]) []
|
||||
]
|
||||
wellKnownFileNamePathMultiPiece = instanceD
|
||||
(cxt [])
|
||||
(conT ''PathMultiPiece `appT` conT nWellKnownFileName)
|
||||
[ funD 'toPathMultiPiece
|
||||
[ clause [conP (mkName $ fNameManip fName) []] (normalB . TH.lift . map Text.pack $ splitDirectories fName) []
|
||||
| fName <- Set.toList fileNames
|
||||
]
|
||||
, funD 'fromPathMultiPiece $
|
||||
[ clause [] (normalB [e|flip HashMap.lookup $(varE nwellKnownFileNames)|]) []
|
||||
]
|
||||
]
|
||||
wellKnownFileNameHashable = instanceD
|
||||
(cxt [])
|
||||
(conT ''Hashable `appT` conT nWellKnownFileName)
|
||||
[]
|
||||
|
||||
getWellKnownRSig = sigD
|
||||
ngetWellKnownR
|
||||
[t|forall m. MonadHandler m => $(conT nWellKnownFileName) -> m TypedContent|]
|
||||
getWellKnownR = funD
|
||||
ngetWellKnownR
|
||||
[ clause [varP fVar] (normalB [e|$(varE hVar) =<< selectLanguage fLanguages|])
|
||||
[ funD hVar $
|
||||
[ clause [varP lVar] (guardedB
|
||||
[ (,) <$> normalG [e|HashSet.member ($(varE lVar), $(varE fVar)) $ HashSet.fromList $(listE [ tupE [TH.lift l, conE . mkName $ fNameManip fName] | (l, fName) <- Set.toList xs ])|]
|
||||
<*> [e|TypedContent mime (toContent fContent) <$ setEtag $(TH.lift $ hashToText (mime, fContent))|]
|
||||
]) []
|
||||
| ((fContent, mime), xs) <- Map.toList fileContents
|
||||
] ++ pure (clause [wildP] (normalB [e|notFound|]) [])
|
||||
]
|
||||
]
|
||||
|
||||
wellKnownHtmlLinksSig = sigD
|
||||
nwellKnownHtmlLinks
|
||||
[t|forall m. MonadWidget m => m ()|]
|
||||
wellKnownHtmlLinks = funD
|
||||
nwellKnownHtmlLinks
|
||||
[ clause [] (normalB [e|toWidgetHead . preEscapedToHtml . $(varE hVar) =<< selectLanguage lLanguages|])
|
||||
[ sigD hVar [t|Text -> Text|]
|
||||
, funD hVar $
|
||||
[ clause [varP lVar] (guardedB
|
||||
[ (,) <$> normalG [|$(varE lVar) == lang|]
|
||||
<*> TH.lift (Text.filter (`notElem` ['\r', '\n']) $ Text.decodeUtf8 c)
|
||||
]) []
|
||||
| (lang, c) <- Map.toList languageLinks
|
||||
] ++ pure (clause [wildP] (normalB [e|mempty|]) [])
|
||||
]
|
||||
]
|
||||
|
||||
sequence
|
||||
[ wellKnownFileName, wellKnownFileNameMapSig, wellKnownFileNameMap, wellKnownFileNamePathMultiPiece, wellKnownFileNameHashable
|
||||
, getWellKnownRSig, getWellKnownR
|
||||
, wellKnownHtmlLinksSig, wellKnownHtmlLinks
|
||||
]
|
||||
where
|
||||
fNameManip = Text.unpack . mconcat . over (traverse . _head) Char.toUpper . filter (not . null) . Text.split (not . isAlphaNum) . Text.pack
|
||||
@ -913,11 +913,11 @@ cachedHereBinary = do
|
||||
[e| \k -> cachedByBinary (loc, k) |]
|
||||
|
||||
hashToText :: Hashable a => a -> Text
|
||||
hashToText = decodeUtf8 . Base64.encode . toStrict . Binary.encode . hash
|
||||
hashToText = Text.dropWhileEnd (== '=') . decodeUtf8 . Base64.encode . toStrict . Binary.encode . hash
|
||||
|
||||
setEtagHashable, setWeakEtagHashable :: (MonadHandler m, Hashable a) => a -> m ()
|
||||
setEtagHashable = setEtag . hashToText
|
||||
setWeakEtagHashable = setEtag . hashToText
|
||||
setWeakEtagHashable = setWeakEtag . hashToText
|
||||
|
||||
setLastModified :: MonadHandler m => UTCTime -> m ()
|
||||
setLastModified lastModified = do
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 99 KiB |
@ -1,5 +1,11 @@
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const tmp = require('tmp');
|
||||
tmp.setGracefulCleanup();
|
||||
const fs = require('fs');
|
||||
const glob = require('glob');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||
@ -9,8 +15,8 @@ const TerserPlugin = require('terser-webpack-plugin');
|
||||
const yaml = require('js-yaml');
|
||||
const HashOutput = require('webpack-plugin-hash-output');
|
||||
const postcssPresetEnv = require('postcss-preset-env');
|
||||
const AppManifestWebpackPlugin = require('app-manifest-webpack-plugin');
|
||||
const RemovePlugin = require('remove-files-webpack-plugin');
|
||||
const RealFaviconPlugin = require('real-favicon-webpack-plugin');
|
||||
|
||||
const webpackVersion = require('webpack/package.json').version.split('.').slice(0, 2).join('.');
|
||||
const packageVersion = require('./package.json').version;
|
||||
@ -116,7 +122,9 @@ module.exports = {
|
||||
serialize: yaml.safeDump
|
||||
}),
|
||||
new CleanWebpackPlugin({
|
||||
cleanOnceBeforeBuildPatterns: path.resolve(__dirname, 'static', 'wp-*')
|
||||
cleanOnceBeforeBuildPatterns: [ path.resolve(__dirname, 'static'),
|
||||
path.resolve(__dirname, 'well-known'),
|
||||
]
|
||||
}),
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
new CopyPlugin([
|
||||
@ -125,38 +133,64 @@ module.exports = {
|
||||
new webpack.DefinePlugin({
|
||||
VERSION: JSON.stringify(packageVersion)
|
||||
}),
|
||||
new AppManifestWebpackPlugin({
|
||||
logo: path.resolve(__dirname, 'assets/favicon.svg'),
|
||||
output: '/[hash]-icons/',
|
||||
prefix: `/static/res/wp-${webpackVersion}/`,
|
||||
inject: false,
|
||||
emitStats: true,
|
||||
statsFilename: path.resolve(__dirname, 'config/favicon.json'),
|
||||
persistentCache: false,
|
||||
config: {
|
||||
background: '#fff',
|
||||
icons: {
|
||||
android: false,
|
||||
appleIcon: false,
|
||||
appleStartup: false,
|
||||
coast: false,
|
||||
favicons: true,
|
||||
firefox: false,
|
||||
windows: false,
|
||||
yandex: false
|
||||
...(() => {
|
||||
const faviconJson = require('./config/favicon.json');
|
||||
const langs = new Set();
|
||||
function findLangs(json) {
|
||||
if (json && json._i18n) {
|
||||
Object.keys(json).forEach(key => {
|
||||
if (key !== '_i18n') {
|
||||
langs.add(key);
|
||||
}
|
||||
})
|
||||
} else if (Array.isArray(json)) {
|
||||
json.forEach(elem => findLangs(elem));
|
||||
} else if (typeof json === 'object') {
|
||||
Object.keys(json).forEach(key => findLangs(json[key]));
|
||||
}
|
||||
}
|
||||
}),
|
||||
new RemovePlugin({
|
||||
after: {
|
||||
test: [
|
||||
{ folder: path.resolve(__dirname, `static/wp-${webpackVersion}`),
|
||||
method: (filePath) => { return new RegExp(/\/.*-icons\/.*\.(xml|json|webapp)$/, 'm').test(filePath); },
|
||||
recursive: true
|
||||
}
|
||||
]
|
||||
findLangs(faviconJson);
|
||||
|
||||
function selectLang(lang, json) {
|
||||
if (json && json._i18n) {
|
||||
return json[lang];
|
||||
} else if (Array.isArray(json)) {
|
||||
return json.map(elem => selectLang(lang, elem));
|
||||
} else if (typeof json === 'object') {
|
||||
return Object.fromEntries(Object.entries(json).map(([k, v]) => [k, selectLang(lang, v)]));
|
||||
} else {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const langJsons = {};
|
||||
Array.from(langs).forEach(lang => {
|
||||
langJsons[lang] = selectLang(lang, faviconJson);
|
||||
});
|
||||
|
||||
return Array.from(langs).map(lang => {
|
||||
const tmpobj = tmp.fileSync({ dir: ".", postfix: ".json" });
|
||||
fs.writeSync(tmpobj.fd, JSON.stringify(langJsons[lang]));
|
||||
fs.close(tmpobj.fd);
|
||||
|
||||
return [
|
||||
new RealFaviconPlugin({
|
||||
faviconJson: `./${tmpobj.name}`,
|
||||
outputPath: path.resolve(__dirname, 'well-known', lang),
|
||||
inject: false
|
||||
}),
|
||||
new CopyPlugin([
|
||||
{ from: 'config/robots.txt', to: path.resolve(__dirname, 'well-known', lang, 'robots.txt') },
|
||||
])
|
||||
];
|
||||
}).flat(1);
|
||||
})(),
|
||||
{ apply: compiler => compiler.hooks.afterEmit.tap('AfterEmitPlugin', compilation => {
|
||||
Array.from(glob.sync(path.resolve(__dirname, 'well-known', '**', '*.@(png)'))).forEach(imageFile => {
|
||||
execSync(`exiftool -overwrite_original -all= ${imageFile}`, { stdio: 'inherit' });
|
||||
});
|
||||
})
|
||||
}
|
||||
],
|
||||
|
||||
output: {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user